15
15
* limitations under the License.
16
16
*/
17
17
18
- import { Logger , LogLevel , LogLevelString } from '@firebase/logger' ;
18
+ import { Logger , LogHandler , LogLevel , LogLevelString } from '@firebase/logger' ;
19
19
20
20
import { SDK_VERSION } from '../core/version' ;
21
21
import { formatJSON } from '../platform/format_json' ;
22
22
23
+ import { generateUniqueDebugId } from './debug_uid' ;
24
+
23
25
export { LogLevel , LogLevelString } ;
24
26
25
27
const logClient = new Logger ( '@firebase/firestore' ) ;
28
+ const defaultLogHandler = logClient . logHandler ;
29
+ let logBuffer : LogBuffer | undefined ;
26
30
27
31
// Helper methods are needed because variables can't be exported as read/write
28
32
export function getLogLevel ( ) : LogLevel {
@@ -41,20 +45,37 @@ export function getLogLevel(): LogLevel {
41
45
* <li>`error` to log errors only.</li>
42
46
* <li><code>`silent` to turn off logging.</li>
43
47
* </ul>
48
+ * @param includeContext - If set to a positive value, the logger will buffer
49
+ * all log messages (of all log levels) and log the most recent messages
50
+ * when a message of `logLevel` is seen. This is useful if you want to get
51
+ * debug logging from the SDK leading up to a warning or error, but do not
52
+ * always want debug log verbosity. This param specifies how many messages
53
+ * to buffer.
44
54
*/
45
- export function setLogLevel ( logLevel : LogLevelString ) : void {
55
+ export function setLogLevel (
56
+ logLevel : LogLevelString ,
57
+ includeContext : number = 0
58
+ ) : void {
46
59
logClient . setLogLevel ( logLevel ) ;
60
+
61
+ if ( includeContext > 0 ) {
62
+ logBuffer = new LogBuffer ( includeContext ) ;
63
+ logClient . logHandler = bufferingLogHandler ;
64
+ } else {
65
+ logBuffer = undefined ;
66
+ logClient . logHandler = defaultLogHandler ;
67
+ }
47
68
}
48
69
49
70
export function logDebug ( msg : string , ...obj : unknown [ ] ) : void {
50
- if ( logClient . logLevel <= LogLevel . DEBUG ) {
71
+ if ( logBuffer || logClient . logLevel <= LogLevel . DEBUG ) {
51
72
const args = obj . map ( argToString ) ;
52
73
logClient . debug ( `Firestore (${ SDK_VERSION } ): ${ msg } ` , ...args ) ;
53
74
}
54
75
}
55
76
56
77
export function logError ( msg : string , ...obj : unknown [ ] ) : void {
57
- if ( logClient . logLevel <= LogLevel . ERROR ) {
78
+ if ( logBuffer || logClient . logLevel <= LogLevel . ERROR ) {
58
79
const args = obj . map ( argToString ) ;
59
80
logClient . error ( `Firestore (${ SDK_VERSION } ): ${ msg } ` , ...args ) ;
60
81
}
@@ -64,7 +85,7 @@ export function logError(msg: string, ...obj: unknown[]): void {
64
85
* @internal
65
86
*/
66
87
export function logWarn ( msg : string , ...obj : unknown [ ] ) : void {
67
- if ( logClient . logLevel <= LogLevel . WARN ) {
88
+ if ( logBuffer || logClient . logLevel <= LogLevel . WARN ) {
68
89
const args = obj . map ( argToString ) ;
69
90
logClient . warn ( `Firestore (${ SDK_VERSION } ): ${ msg } ` , ...args ) ;
70
91
}
@@ -85,3 +106,163 @@ function argToString(obj: unknown): string | unknown {
85
106
}
86
107
}
87
108
}
109
+
110
+ class LogBuffer {
111
+ private _buffer : Array < { level : LogLevel ; now : string ; args : unknown [ ] } > ;
112
+ private _numTruncated : number = 0 ;
113
+
114
+ constructor ( readonly bufferSize : number ) {
115
+ this . _buffer = [ ] ;
116
+ this . _numTruncated = 0 ;
117
+ }
118
+
119
+ /**
120
+ * Clear the log buffer
121
+ */
122
+ clear ( ) : void {
123
+ this . _buffer = [ ] ;
124
+ this . _numTruncated = 0 ;
125
+ }
126
+
127
+ /**
128
+ * Add a new log message to the buffer. If the buffer will exceed
129
+ * the allocated buffer size, then remove the oldest message from
130
+ * the buffer.
131
+ * @param level
132
+ * @param now
133
+ * @param args
134
+ */
135
+ add ( level : LogLevel , now : string , args : unknown [ ] ) : void {
136
+ this . _buffer . push ( {
137
+ level,
138
+ now,
139
+ args
140
+ } ) ;
141
+
142
+ if ( this . _buffer . length > this . bufferSize ) {
143
+ // remove the first (oldest) element
144
+ this . _buffer . shift ( ) ;
145
+ this . _numTruncated ++ ;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Returns the number of old log messages that have been
151
+ * truncated from the log to maintain buffer size.
152
+ */
153
+ get numTruncated ( ) : number {
154
+ return this . _numTruncated ;
155
+ }
156
+
157
+ get first ( ) : { level : LogLevel ; now : string ; args : unknown [ ] } | undefined {
158
+ return this . _buffer [ 0 ] ;
159
+ }
160
+
161
+ /**
162
+ * Iterate from oldest to newest.
163
+ */
164
+ [ Symbol . iterator ] ( ) : Iterator < {
165
+ level : LogLevel ;
166
+ now : string ;
167
+ args : unknown [ ] ;
168
+ } > {
169
+ let currentIndex = 0 ;
170
+ // Create a snapshot of the buffer for iteration.
171
+ // This ensures that if the buffer is modified while iterating (e.g., by adding new logs),
172
+ // the iterator will continue to iterate over the state of the buffer as it was when iteration began.
173
+ // It also means you iterate from the oldest to the newest log.
174
+ const bufferSnapshot = [ ...this . _buffer ] ;
175
+
176
+ return {
177
+ next : ( ) : IteratorResult < {
178
+ level : LogLevel ;
179
+ now : string ;
180
+ args : unknown [ ] ;
181
+ } > => {
182
+ if ( currentIndex < bufferSnapshot . length ) {
183
+ return { value : bufferSnapshot [ currentIndex ++ ] , done : false } ;
184
+ } else {
185
+ return { value : undefined , done : true } ;
186
+ }
187
+ }
188
+ } ;
189
+ }
190
+ }
191
+
192
+ /**
193
+ * By default, `console.debug` is not displayed in the developer console (in
194
+ * chrome). To avoid forcing users to have to opt-in to these logs twice
195
+ * (i.e. once for firebase, and once in the console), we are sending `DEBUG`
196
+ * logs to the `console.log` function.
197
+ */
198
+ const ConsoleMethod = {
199
+ [ LogLevel . DEBUG ] : 'log' ,
200
+ [ LogLevel . VERBOSE ] : 'log' ,
201
+ [ LogLevel . INFO ] : 'info' ,
202
+ [ LogLevel . WARN ] : 'warn' ,
203
+ [ LogLevel . ERROR ] : 'error'
204
+ } ;
205
+
206
+ /**
207
+ * The default log handler will forward DEBUG, VERBOSE, INFO, WARN, and ERROR
208
+ * messages on to their corresponding console counterparts (if the log method
209
+ * is supported by the current log level)
210
+ */
211
+ const bufferingLogHandler : LogHandler = ( instance , logType , ...args ) : void => {
212
+ const now = new Date ( ) . toISOString ( ) ;
213
+
214
+ // Fail-safe. This is never expected to be true, but if it is,
215
+ // it's not important enough to throw.
216
+ if ( ! logBuffer ) {
217
+ defaultLogHandler ( instance , logType , args ) ;
218
+ return ;
219
+ }
220
+
221
+ // Buffer any messages less than the current logLevel
222
+ if ( logType < instance . logLevel ) {
223
+ logBuffer ! . add ( logType , now , args ) ;
224
+ return ;
225
+ }
226
+
227
+ // create identifier that associates all of the associated
228
+ // context messages with the log message that caused the
229
+ // flush of the logBuffer
230
+ const id = generateUniqueDebugId ( ) ;
231
+
232
+ // Optionally write a log message stating if any log messages
233
+ // were skipped.
234
+ if ( logBuffer . first ) {
235
+ writeLog ( instance , id , LogLevel . INFO , logBuffer . first . now , [
236
+ `... ${ logBuffer . numTruncated } log messages skipped ...`
237
+ ] ) ;
238
+ }
239
+
240
+ // If here, write the log buffer contents as context
241
+ for ( const logInfo of logBuffer ) {
242
+ writeLog ( instance , id , logInfo . level , logInfo . now , logInfo . args ) ;
243
+ }
244
+ logBuffer . clear ( ) ;
245
+
246
+ // Now write the target log message.
247
+ writeLog ( instance , id , logType , now , args ) ;
248
+ } ;
249
+
250
+ function writeLog (
251
+ instance : Logger ,
252
+ id : string ,
253
+ logType : LogLevel ,
254
+ now : string ,
255
+ args : unknown [ ]
256
+ ) : void {
257
+ const method = ConsoleMethod [ logType as keyof typeof ConsoleMethod ] ;
258
+ if ( method ) {
259
+ console [ method as 'log' | 'info' | 'warn' | 'error' ] (
260
+ `[${ now } ] (context: ${ id } ) ${ instance . name } :` ,
261
+ ...args
262
+ ) ;
263
+ } else {
264
+ throw new Error (
265
+ `Attempted to log a message with an invalid logType (value: ${ logType } )`
266
+ ) ;
267
+ }
268
+ }
0 commit comments