@@ -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
/**
@@ -934,7 +958,33 @@ class Logger extends Utility implements LoggerInterface {
934
958
this . createAndPopulateLogItem ( logLevel , input , extraInput )
935
959
) ;
936
960
} else {
937
- this . #buffer. push ( [ logLevel , [ logLevel , input , extraInput ] ] ) ;
961
+ this . #initBuffer. push ( [ logLevel , [ logLevel , input , extraInput ] ] ) ;
962
+ }
963
+ return ;
964
+ }
965
+
966
+ const trace_id = this . envVarsService . getXrayTraceId ( ) ;
967
+ if ( trace_id !== undefined && this . shouldBufferLog ( trace_id , logLevel ) ) {
968
+ try {
969
+ this . bufferLogItem (
970
+ trace_id ,
971
+ this . createAndPopulateLogItem ( logLevel , input , extraInput ) ,
972
+ logLevel
973
+ ) ;
974
+ } catch ( e ) {
975
+ this . printLog (
976
+ LogLevelThreshold . WARN ,
977
+ this . createAndPopulateLogItem (
978
+ LogLevelThreshold . WARN ,
979
+ `Unable to buffer log: ${ e } ` ,
980
+ extraInput
981
+ )
982
+ ) ;
983
+
984
+ this . printLog (
985
+ logLevel ,
986
+ this . createAndPopulateLogItem ( logLevel , input , extraInput )
987
+ ) ;
938
988
}
939
989
}
940
990
}
@@ -1166,6 +1216,67 @@ class Logger extends Utility implements LoggerInterface {
1166
1216
} ) ;
1167
1217
persistentKeys && this . appendPersistentKeys ( persistentKeys ) ;
1168
1218
}
1219
+
1220
+ /**
1221
+ * bufferLogItem adds a log to the buffer
1222
+ * @param xrayTraceId
1223
+ * @param log
1224
+ * @param logLevel
1225
+ */
1226
+ protected bufferLogItem (
1227
+ xrayTraceId : string ,
1228
+ log : LogItem ,
1229
+ logLevel : number
1230
+ ) : void {
1231
+ log . prepareForPrint ( ) ;
1232
+
1233
+ const stringified = JSON . stringify (
1234
+ log . getAttributes ( ) ,
1235
+ this . getJsonReplacer ( ) ,
1236
+ this . logIndentation
1237
+ ) ;
1238
+
1239
+ this . #buffer. setItem ( xrayTraceId , stringified , logLevel ) ;
1240
+ }
1241
+
1242
+ /**
1243
+ * flushBuffer logs the items of the respective _X_AMZN_TRACE_ID within
1244
+ * the buffer.
1245
+ * @returns
1246
+ */
1247
+ protected flushBuffer ( ) : void {
1248
+ const trace_id = this . envVarsService . getXrayTraceId ( ) ;
1249
+ if ( trace_id === undefined ) {
1250
+ return ;
1251
+ }
1252
+
1253
+ const buffer = this . #buffer. get ( trace_id ) || [ ] ;
1254
+
1255
+ for ( const item of buffer ) {
1256
+ const consoleMethod =
1257
+ item . logLevel === LogLevelThreshold . CRITICAL
1258
+ ? 'error'
1259
+ : ( this . getLogLevelNameFromNumber (
1260
+ item . logLevel
1261
+ ) . toLowerCase ( ) as keyof Omit < LogFunction , 'critical' > ) ;
1262
+ this . console [ consoleMethod ] ( item . value ) ;
1263
+ }
1264
+
1265
+ this . #buffer. delete ( trace_id ) ;
1266
+ }
1267
+ /**
1268
+ * shouldBufferLog returns true if the log meets the criteria to be buffered
1269
+ * @param trace_id _X_AMZN_TRACE_ID
1270
+ * @param logLevel The level of the log being considered
1271
+ * @returns
1272
+ */
1273
+ shouldBufferLog ( trace_id : string | undefined , logLevel : number ) : boolean {
1274
+ return (
1275
+ this . isBufferEnabled &&
1276
+ trace_id !== undefined &&
1277
+ logLevel <= this . bufferLogThreshold
1278
+ ) ;
1279
+ }
1169
1280
}
1170
1281
1171
1282
export { Logger } ;
0 commit comments