@@ -180,3 +180,135 @@ func (w *FilteredLevelWriter) WriteLevel(level Level, p []byte) (int, error) {
180
180
}
181
181
return len (p ), nil
182
182
}
183
+
184
+ var triggerWriterPool = & sync.Pool {
185
+ New : func () interface {} {
186
+ return bytes .NewBuffer (make ([]byte , 0 , 1024 ))
187
+ },
188
+ }
189
+
190
+ // TriggerLevelWriter buffers log lines at the ConditionalLevel or below
191
+ // until a trigger level (or higher) line is emitted. Log lines with level
192
+ // higher than ConditionalLevel are always written out to the destination
193
+ // writer. If trigger never happens, buffered log lines are never written out.
194
+ //
195
+ // It can be used to configure "log level per request".
196
+ type TriggerLevelWriter struct {
197
+ // Destination writer. If LevelWriter is provided (usually), its WriteLevel is used
198
+ // instead of Write.
199
+ io.Writer
200
+
201
+ // ConditionalLevel is the level (and below) at which lines are buffered until
202
+ // a trigger level (or higher) line is emitted. Usually this is set to DebugLevel.
203
+ ConditionalLevel Level
204
+
205
+ // TriggerLevel is the lowest level that triggers the sending of the conditional
206
+ // level lines. Usually this is set to ErrorLevel.
207
+ TriggerLevel Level
208
+
209
+ buf * bytes.Buffer
210
+ triggered bool
211
+ mu sync.Mutex
212
+ }
213
+
214
+ func (w * TriggerLevelWriter ) WriteLevel (l Level , p []byte ) (n int , err error ) {
215
+ w .mu .Lock ()
216
+ defer w .mu .Unlock ()
217
+
218
+ // At first trigger level or above log line, we flush the buffer and change the
219
+ // trigger state to triggered.
220
+ if ! w .triggered && l >= w .TriggerLevel {
221
+ err := w .trigger ()
222
+ if err != nil {
223
+ return 0 , err
224
+ }
225
+ }
226
+
227
+ // Unless triggered, we buffer everything at and below ConditionalLevel.
228
+ if ! w .triggered && l <= w .ConditionalLevel {
229
+ if w .buf == nil {
230
+ w .buf = triggerWriterPool .Get ().(* bytes.Buffer )
231
+ }
232
+
233
+ // We prefix each log line with a byte with the level.
234
+ // Hopefully we will never have a level value which equals a newline
235
+ // (which could interfere with reconstruction of log lines in the trigger method).
236
+ w .buf .WriteByte (byte (l ))
237
+ w .buf .Write (p )
238
+ return len (p ), nil
239
+ }
240
+
241
+ // Anything above ConditionalLevel is always passed through.
242
+ // Once triggered, everything is passed through.
243
+ if lw , ok := w .Writer .(LevelWriter ); ok {
244
+ return lw .WriteLevel (l , p )
245
+ }
246
+ return w .Write (p )
247
+ }
248
+
249
+ // trigger expects lock to be held.
250
+ func (w * TriggerLevelWriter ) trigger () error {
251
+ if w .triggered {
252
+ return nil
253
+ }
254
+ w .triggered = true
255
+
256
+ if w .buf == nil {
257
+ return nil
258
+ }
259
+
260
+ p := w .buf .Bytes ()
261
+ for len (p ) > 0 {
262
+ // We do not use bufio.Scanner here because we already have full buffer
263
+ // in the memory and we do not want extra copying from the buffer to
264
+ // scanner's token slice, nor we want to hit scanner's token size limit,
265
+ // and we also want to preserve newlines.
266
+ i := bytes .IndexByte (p , '\n' )
267
+ line := p [0 : i + 1 ]
268
+ p = p [i + 1 :]
269
+ // We prefixed each log line with a byte with the level.
270
+ level := Level (line [0 ])
271
+ line = line [1 :]
272
+ var err error
273
+ if lw , ok := w .Writer .(LevelWriter ); ok {
274
+ _ , err = lw .WriteLevel (level , line )
275
+ } else {
276
+ _ , err = w .Write (line )
277
+ }
278
+ if err != nil {
279
+ return err
280
+ }
281
+ }
282
+
283
+ return nil
284
+ }
285
+
286
+ // Trigger forces flushing the buffer and change the trigger state to
287
+ // triggered, if the writer has not already been triggered before.
288
+ func (w * TriggerLevelWriter ) Trigger () error {
289
+ w .mu .Lock ()
290
+ defer w .mu .Unlock ()
291
+
292
+ return w .trigger ()
293
+ }
294
+
295
+ // Close closes the writer and returns the buffer to the pool.
296
+ func (w * TriggerLevelWriter ) Close () error {
297
+ w .mu .Lock ()
298
+ defer w .mu .Unlock ()
299
+
300
+ if w .buf == nil {
301
+ return nil
302
+ }
303
+
304
+ // We return the buffer only if it has not grown above the limit.
305
+ // This prevents accumulation of large buffers in the pool just
306
+ // because occasionally a large buffer might be needed.
307
+ if w .buf .Cap () <= TriggerLevelWriterBufferReuseLimit {
308
+ w .buf .Reset ()
309
+ triggerWriterPool .Put (w .buf )
310
+ }
311
+ w .buf = nil
312
+
313
+ return nil
314
+ }
0 commit comments