@@ -13,6 +13,7 @@ import { LogJsonIndent, LogLevelThreshold, ReservedKeys } from './constants.js';
13
13
import type { LogFormatter } from './formatter/LogFormatter.js' ;
14
14
import type { LogItem } from './formatter/LogItem.js' ;
15
15
import { PowertoolsLogFormatter } from './formatter/PowertoolsLogFormatter.js' ;
16
+ import { CircularMap } from './logBuffer.js' ;
16
17
import type { ConfigServiceInterface } from './types/ConfigServiceInterface.js' ;
17
18
import type {
18
19
ConstructorOptions ,
@@ -142,7 +143,7 @@ class Logger extends Utility implements LoggerInterface {
142
143
* Sometimes we need to log warnings before the logger is fully initialized, however we can't log them
143
144
* immediately because the logger is not ready yet. This buffer stores those logs until the logger is ready.
144
145
*/
145
- #buffer : [ number , Parameters < Logger [ 'createAndPopulateLogItem' ] > ] [ ] = [ ] ;
146
+ #initBuffer : [ number , Parameters < Logger [ 'createAndPopulateLogItem' ] > ] [ ] = [ ] ;
146
147
/**
147
148
* Flag used to determine if the logger is initialized.
148
149
*/
@@ -165,6 +166,29 @@ class Logger extends Utility implements LoggerInterface {
165
166
*/
166
167
#jsonReplacerFn?: CustomJsonReplacerFn ;
167
168
169
+ /**
170
+ * isBufferEnabled represents whether the buffering functionality is enabled in the logger
171
+ */
172
+ protected isBufferEnabled = false ;
173
+
174
+ /**
175
+ * bufferLogThreshold represents the log level threshold for the buffer
176
+ * Logs with a level lower than this threshold will be buffered
177
+ */
178
+ protected bufferLogThreshold : number = LogLevelThreshold . DEBUG ;
179
+ /**
180
+ * maxBufferBytesSize is the max size of the buffer. Additions to the buffer beyond this size will
181
+ * cause older logs to be evicted from the buffer
182
+ */
183
+ #maxBufferBytesSize = 1024 ;
184
+
185
+ /**
186
+ * buffer stores logs up to `maxBufferBytesSize`
187
+ */
188
+ #buffer: CircularMap < string > = new CircularMap ( {
189
+ maxBytesSize : this . #maxBufferBytesSize,
190
+ } ) ;
191
+
168
192
/**
169
193
* Log level used by the current instance of Logger.
170
194
*
@@ -182,11 +206,11 @@ class Logger extends Utility implements LoggerInterface {
182
206
// all logs are buffered until the logger is initialized
183
207
this . setOptions ( rest ) ;
184
208
this . #isInitialized = true ;
185
- for ( const [ level , log ] of this . #buffer ) {
209
+ for ( const [ level , log ] of this . #initBuffer ) {
186
210
// we call the method directly and create the log item just in time
187
211
this . printLog ( level , this . createAndPopulateLogItem ( ...log ) ) ;
188
212
}
189
- this . #buffer = [ ] ;
213
+ this . #initBuffer = [ ] ;
190
214
}
191
215
192
216
/**
@@ -937,7 +961,33 @@ class Logger extends Utility implements LoggerInterface {
937
961
this . createAndPopulateLogItem ( logLevel , input , extraInput )
938
962
) ;
939
963
} else {
940
- this . #buffer. push ( [ logLevel , [ logLevel , input , extraInput ] ] ) ;
964
+ this . #initBuffer. push ( [ logLevel , [ logLevel , input , extraInput ] ] ) ;
965
+ }
966
+ return ;
967
+ }
968
+
969
+ const trace_id = this . envVarsService . getXrayTraceId ( ) ;
970
+ if ( trace_id !== undefined && this . shouldBufferLog ( trace_id , logLevel ) ) {
971
+ try {
972
+ this . bufferLogItem (
973
+ trace_id ,
974
+ this . createAndPopulateLogItem ( logLevel , input , extraInput ) ,
975
+ logLevel
976
+ ) ;
977
+ } catch ( e ) {
978
+ this . printLog (
979
+ LogLevelThreshold . WARN ,
980
+ this . createAndPopulateLogItem (
981
+ LogLevelThreshold . WARN ,
982
+ `Unable to buffer log: ${ e } ` ,
983
+ extraInput
984
+ )
985
+ ) ;
986
+
987
+ this . printLog (
988
+ logLevel ,
989
+ this . createAndPopulateLogItem ( logLevel , input , extraInput )
990
+ ) ;
941
991
}
942
992
}
943
993
}
@@ -1169,6 +1219,67 @@ class Logger extends Utility implements LoggerInterface {
1169
1219
} ) ;
1170
1220
persistentKeys && this . appendPersistentKeys ( persistentKeys ) ;
1171
1221
}
1222
+
1223
+ /**
1224
+ * bufferLogItem adds a log to the buffer
1225
+ * @param xrayTraceId
1226
+ * @param log
1227
+ * @param logLevel
1228
+ */
1229
+ protected bufferLogItem (
1230
+ xrayTraceId : string ,
1231
+ log : LogItem ,
1232
+ logLevel : number
1233
+ ) : void {
1234
+ log . prepareForPrint ( ) ;
1235
+
1236
+ const stringified = JSON . stringify (
1237
+ log . getAttributes ( ) ,
1238
+ this . getJsonReplacer ( ) ,
1239
+ this . logIndentation
1240
+ ) ;
1241
+
1242
+ this . #buffer. setItem ( xrayTraceId , stringified , logLevel ) ;
1243
+ }
1244
+
1245
+ /**
1246
+ * flushBuffer logs the items of the respective _X_AMZN_TRACE_ID within
1247
+ * the buffer.
1248
+ * @returns
1249
+ */
1250
+ protected flushBuffer ( ) : void {
1251
+ const trace_id = this . envVarsService . getXrayTraceId ( ) ;
1252
+ if ( trace_id === undefined ) {
1253
+ return ;
1254
+ }
1255
+
1256
+ const buffer = this . #buffer. get ( trace_id ) || [ ] ;
1257
+
1258
+ for ( const item of buffer ) {
1259
+ const consoleMethod =
1260
+ item . logLevel === LogLevelThreshold . CRITICAL
1261
+ ? 'error'
1262
+ : ( this . getLogLevelNameFromNumber (
1263
+ item . logLevel
1264
+ ) . toLowerCase ( ) as keyof Omit < LogFunction , 'critical' > ) ;
1265
+ this . console [ consoleMethod ] ( item . value ) ;
1266
+ }
1267
+
1268
+ this . #buffer. delete ( trace_id ) ;
1269
+ }
1270
+ /**
1271
+ * shouldBufferLog returns true if the log meets the criteria to be buffered
1272
+ * @param trace_id _X_AMZN_TRACE_ID
1273
+ * @param logLevel The level of the log being considered
1274
+ * @returns
1275
+ */
1276
+ shouldBufferLog ( trace_id : string | undefined , logLevel : number ) : boolean {
1277
+ return (
1278
+ this . isBufferEnabled &&
1279
+ trace_id !== undefined &&
1280
+ logLevel <= this . bufferLogThreshold
1281
+ ) ;
1282
+ }
1172
1283
}
1173
1284
1174
1285
export { Logger } ;
0 commit comments