-
-
Notifications
You must be signed in to change notification settings - Fork 335
Expand file tree
/
Copy pathBufferingAppenderSkeleton.cs
More file actions
514 lines (483 loc) · 18.1 KB
/
BufferingAppenderSkeleton.cs
File metadata and controls
514 lines (483 loc) · 18.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System;
using log4net.Util;
using log4net.Core;
using System.Collections.Generic;
namespace log4net.Appender;
/// <summary>
/// Abstract base class implementation of <see cref="IAppender"/> that
/// buffers events in a fixed size buffer.
/// </summary>
/// <remarks>
/// <para>
/// This base class should be used by appenders that need to buffer a
/// number of events before logging them.
/// For example the <see cref="AdoNetAppender"/>
/// buffers events and then submits the entire contents of the buffer to
/// the underlying database in one go.
/// </para>
/// <para>
/// Subclasses should override the <see cref="SendBuffer(LoggingEvent[])"/>
/// method to deliver the buffered events.
/// </para>
/// <para>The BufferingAppenderSkeleton maintains a fixed size cyclic
/// buffer of events. The size of the buffer is set using
/// the <see cref="BufferSize"/> property.
/// </para>
/// <para>A <see cref="ITriggeringEventEvaluator"/> is used to inspect
/// each event as it arrives in the appender. If the <see cref="Evaluator"/>
/// triggers, then the current buffer is sent immediately
/// (see <see cref="SendBuffer(LoggingEvent[])"/>). Otherwise the event
/// is stored in the buffer. For example, an evaluator can be used to
/// deliver the events immediately when an ERROR event arrives.
/// </para>
/// <para>
/// The buffering appender can be configured in a <see cref="Lossy"/> mode.
/// By default the appender is NOT lossy. When the buffer is full all
/// the buffered events are sent with <see cref="SendBuffer(LoggingEvent[])"/>.
/// If the <see cref="Lossy"/> property is set to <see langword="true"/> then the
/// buffer will not be sent when it is full, and new events arriving
/// in the appender will overwrite the oldest event in the buffer.
/// In lossy mode the buffer will only be sent when the <see cref="Evaluator"/>
/// triggers. This can be useful behavior when you need to know about
/// ERROR events but not about events with a lower level, configure an
/// evaluator that will trigger when an ERROR event arrives, the whole
/// buffer will be sent which gives a history of events leading up to
/// the ERROR event.
/// </para>
/// </remarks>
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
public abstract class BufferingAppenderSkeleton : AppenderSkeleton
{
/// <summary>
/// Initializes a new instance of the <see cref="BufferingAppenderSkeleton" /> class.
/// </summary>
/// <remarks>
/// <para>
/// Protected default constructor to allow subclassing.
/// </para>
/// </remarks>
protected BufferingAppenderSkeleton() : this(true)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BufferingAppenderSkeleton" /> class.
/// </summary>
/// <param name="eventMustBeFixed">the events passed through this appender must be
/// fixed by the time that they arrive in the derived class' <see cref="SendBuffer"/> method.</param>
/// <remarks>
/// <para>
/// Protected constructor to allow subclassing.
/// </para>
/// <para>
/// The <paramref name="eventMustBeFixed"/> should be set if the subclass
/// expects the events delivered to be fixed even if the
/// <see cref="BufferSize"/> is set to zero, i.e. when no buffering occurs.
/// </para>
/// </remarks>
protected BufferingAppenderSkeleton(bool eventMustBeFixed)
{
this._eventMustBeFixed = eventMustBeFixed;
}
/// <summary>
/// Gets or sets a value that indicates whether the appender is lossy.
/// </summary>
/// <value>
/// <see langword="true"/> if the appender is lossy, otherwise <see langword="false"/>. The default is <see langword="false"/>.
/// </value>
/// <remarks>
/// <para>
/// This appender uses a buffer to store logging events before
/// delivering them. A triggering event causes the whole buffer
/// to be sent to the remote sink. If the buffer overruns before
/// a triggering event then logging events could be lost. Set
/// <see cref="Lossy"/> to <see langword="false"/> to prevent logging events
/// from being lost.
/// </para>
/// <para>If <see cref="Lossy"/> is set to <see langword="true"/> then an
/// <see cref="Evaluator"/> must be specified.</para>
/// </remarks>
public bool Lossy { get; set; }
/// <summary>
/// Gets or sets the size of the cyclic buffer used to hold the
/// logging events.
/// </summary>
/// <value>
/// The size of the cyclic buffer used to hold the logging events.
/// </value>
/// <remarks>
/// <para>
/// The <see cref="BufferSize"/> option takes a positive integer
/// representing the maximum number of logging events to collect in
/// a cyclic buffer. When the <see cref="BufferSize"/> is reached,
/// oldest events are deleted as new events are added to the
/// buffer. By default the size of the cyclic buffer is 512 events.
/// </para>
/// <para>
/// If the <see cref="BufferSize"/> is set to a value less than
/// or equal to 1 then no buffering will occur. The logging event
/// will be delivered synchronously (depending on the <see cref="Lossy"/>
/// and <see cref="Evaluator"/> properties). Otherwise the event will
/// be buffered.
/// </para>
/// </remarks>
public int BufferSize { get; set; } = DefaultBufferSize;
/// <summary>
/// Gets or sets the <see cref="ITriggeringEventEvaluator"/> that causes the
/// buffer to be sent immediately.
/// </summary>
/// <value>
/// The <see cref="ITriggeringEventEvaluator"/> that causes the buffer to be
/// sent immediately.
/// </value>
/// <remarks>
/// <para>
/// The evaluator will be called for each event that is appended to this
/// appender. If the evaluator triggers then the current buffer will
/// immediately be sent (see <see cref="SendBuffer(LoggingEvent[])"/>).
/// </para>
/// <para>If <see cref="Lossy"/> is set to <see langword="true"/> then an
/// <see cref="Evaluator"/> must be specified.</para>
/// </remarks>
public ITriggeringEventEvaluator? Evaluator { get; set; }
/// <summary>
/// Gets or sets the value of the <see cref="ITriggeringEventEvaluator"/> to use.
/// </summary>
/// <value>
/// The value of the <see cref="ITriggeringEventEvaluator"/> to use.
/// </value>
/// <remarks>
/// <para>
/// The evaluator will be called for each event that is discarded from this
/// appender. If the evaluator triggers then the current buffer will immediately
/// be sent (see <see cref="SendBuffer(LoggingEvent[])"/>).
/// </para>
/// </remarks>
public ITriggeringEventEvaluator? LossyEvaluator { get; set; }
/// <summary>
/// Gets or sets the fields that will be fixed in the event.
/// </summary>
/// <value>
/// The event fields that will be fixed before the event is buffered
/// </value>
/// <remarks>
/// <para>
/// The logging event needs to have certain thread specific values
/// captured before it can be buffered. See <see cref="LoggingEvent.Fix"/>
/// for details.
/// </para>
/// </remarks>
/// <seealso cref="LoggingEvent.Fix"/>
public virtual FixFlags Fix { get; set; } = FixFlags.All;
/// <summary>
/// Flushes any buffered log data.
/// </summary>
/// <param name="millisecondsTimeout">The maximum time to wait for logging events to be flushed.</param>
/// <returns><see langword="true"/> if all logging events were flushed successfully, else <see langword="false"/>.</returns>
public override bool Flush(int millisecondsTimeout)
{
Flush();
return true;
}
/// <summary>
/// Flush the currently buffered events
/// </summary>
/// <remarks>
/// <para>
/// Flushes any events that have been buffered.
/// </para>
/// <para>
/// If the appender is buffering in <see cref="Lossy"/> mode then the contents
/// of the buffer will NOT be flushed to the appender.
/// </para>
/// </remarks>
public virtual void Flush() => Flush(false);
/// <summary>
/// Flush the currently buffered events
/// </summary>
/// <param name="flushLossyBuffer">set to <see langword="true"/> to flush the buffer of lossy events</param>
/// <remarks>
/// <para>
/// Flushes events that have been buffered. If <paramref name="flushLossyBuffer" /> is
/// <see langword="false"/> then events will only be flushed if this buffer is non-lossy mode.
/// </para>
/// <para>
/// If the appender is buffering in <see cref="Lossy"/> mode then the contents
/// of the buffer will only be flushed if <paramref name="flushLossyBuffer" /> is <see langword="true"/>.
/// In this case the contents of the buffer will be tested against the
/// <see cref="LossyEvaluator"/> and if triggering will be output. All other buffered
/// events will be discarded.
/// </para>
/// <para>
/// If <paramref name="flushLossyBuffer" /> is <see langword="true"/> then the buffer will always
/// be emptied by calling this method.
/// </para>
/// </remarks>
public virtual void Flush(bool flushLossyBuffer)
{
// This method will be called outside the AppenderSkeleton DoAppend() method
// therefore it needs to be protected by its own lock. This will block any
// Appends while the buffer is flushed.
lock (LockObj)
{
if (_cyclicBuffer is not null && _cyclicBuffer.Length > 0)
{
if (Lossy)
{
// If we are allowed to eagerly flush from the lossy buffer
if (flushLossyBuffer)
{
if (LossyEvaluator is not null)
{
// Test the contents of the buffer against the lossy evaluator
LoggingEvent[] bufferedEvents = _cyclicBuffer.PopAll();
List<LoggingEvent> filteredEvents = new(bufferedEvents.Length);
foreach (LoggingEvent loggingEvent in bufferedEvents)
{
if (LossyEvaluator.IsTriggeringEvent(loggingEvent))
{
filteredEvents.Add(loggingEvent);
}
}
// Send the events that meet the lossy evaluator criteria
if (filteredEvents.Count > 0)
{
SendBuffer(filteredEvents.ToArray());
}
}
else
{
// No lossy evaluator, all buffered events are discarded
_cyclicBuffer.Clear();
}
}
}
else
{
// Not lossy, send whole buffer
SendFromBuffer(null, _cyclicBuffer);
}
}
}
}
/// <summary>
/// Initialize the appender based on the options set
/// </summary>
/// <remarks>
/// <para>
/// This is part of the <see cref="IOptionHandler"/> delayed object
/// activation scheme. The <see cref="ActivateOptions"/> method must
/// be called on this object after the configuration properties have
/// been set. Until <see cref="ActivateOptions"/> is called this
/// object is in an undefined state and must not be used.
/// </para>
/// <para>
/// If any of the configuration properties are modified then
/// <see cref="ActivateOptions"/> must be called again.
/// </para>
/// </remarks>
public override void ActivateOptions()
{
base.ActivateOptions();
// If the appender is in Lossy mode then we will
// only send the buffer when the Evaluator triggers
// therefore check we have an evaluator.
if (Lossy && Evaluator is null)
{
ErrorHandler.Error($"Appender [{Name}] is Lossy but has no Evaluator. The buffer will never be sent!");
}
if (BufferSize > 1)
{
_cyclicBuffer = new CyclicBuffer(BufferSize);
}
else
{
_cyclicBuffer = null;
}
}
/// <summary>
/// Close this appender instance.
/// </summary>
/// <remarks>
/// <para>
/// Close this appender instance. If this appender is marked
/// as not <see cref="Lossy"/> then the remaining events in
/// the buffer must be sent when the appender is closed.
/// </para>
/// </remarks>
protected override void OnClose() => Flush(true); // Flush the buffer on close
/// <summary>
/// This method is called by the <see cref="AppenderSkeleton.DoAppend(LoggingEvent)"/> method.
/// </summary>
/// <param name="loggingEvent">the event to log</param>
/// <remarks>
/// <para>
/// Stores the <paramref name="loggingEvent"/> in the cyclic buffer.
/// </para>
/// <para>
/// The buffer will be sent (i.e. passed to the <see cref="SendBuffer"/>
/// method) if one of the following conditions is met:
/// </para>
/// <list type="bullet">
/// <item>
/// <description>The cyclic buffer is full and this appender is
/// marked as not lossy (see <see cref="Lossy"/>)</description>
/// </item>
/// <item>
/// <description>An <see cref="Evaluator"/> is set and
/// it is triggered for the <paramref name="loggingEvent"/>
/// specified.</description>
/// </item>
/// </list>
/// <para>
/// Before the event is stored in the buffer it is fixed
/// (see <see cref="LoggingEvent.FixVolatileData(FixFlags)"/>) to ensure that
/// any data referenced by the event will be valid when the buffer
/// is processed.
/// </para>
/// </remarks>
protected override void Append(LoggingEvent loggingEvent)
{
loggingEvent.EnsureNotNull();
// If the buffer size is set to 1 or less then the buffer will be
// sent immediately because there is not enough space in the buffer
// to buffer up more than 1 event. Therefore as a special case
// we don't use the buffer at all.
if (_cyclicBuffer is null || BufferSize <= 1)
{
// Only send the event if we are in non-lossy mode or the event is a triggering event
if ((!Lossy) ||
(Evaluator is not null && Evaluator.IsTriggeringEvent(loggingEvent)) ||
(LossyEvaluator is not null && LossyEvaluator.IsTriggeringEvent(loggingEvent)))
{
if (_eventMustBeFixed)
{
// Derive class expects fixed events
loggingEvent.Fix = Fix;
}
// Not buffering events, send immediately
SendBuffer([loggingEvent]);
}
}
else
{
// Because we are caching the LoggingEvent beyond the
// lifetime of the Append() method we must fix any
// volatile data in the event.
loggingEvent.Fix = Fix;
// Add to the buffer, returns the event discarded from the buffer if there is no space remaining after the append
LoggingEvent? discardedLoggingEvent = _cyclicBuffer.Append(loggingEvent);
if (discardedLoggingEvent is not null)
{
// Buffer is full and has had to discard an event
if (!Lossy)
{
// Not lossy, must send all events
SendFromBuffer(discardedLoggingEvent, _cyclicBuffer);
}
else
{
// Check if the discarded event should not be logged
if (LossyEvaluator is null || !LossyEvaluator.IsTriggeringEvent(discardedLoggingEvent))
{
// Clear the discarded event as we should not forward it
discardedLoggingEvent = null;
}
// Check if the event should trigger the whole buffer to be sent
if (Evaluator is not null && Evaluator.IsTriggeringEvent(loggingEvent))
{
SendFromBuffer(discardedLoggingEvent, _cyclicBuffer);
}
else if (discardedLoggingEvent is not null)
{
// Just send the discarded event
SendBuffer([discardedLoggingEvent]);
}
}
}
else
{
// Buffer is not yet full
// Check if the event should trigger the whole buffer to be sent
if (Evaluator is not null && Evaluator.IsTriggeringEvent(loggingEvent))
{
SendFromBuffer(null, _cyclicBuffer);
}
}
}
}
/// <summary>
/// Sends the contents of the buffer.
/// </summary>
/// <param name="firstLoggingEvent">The first logging event.</param>
/// <param name="buffer">The buffer containing the events that need to be sent.</param>
/// <remarks>
/// <para>
/// The subclass must override <see cref="SendBuffer(LoggingEvent[])"/>.
/// </para>
/// </remarks>
protected virtual void SendFromBuffer(LoggingEvent? firstLoggingEvent, CyclicBuffer buffer)
{
LoggingEvent[] bufferEvents = buffer.EnsureNotNull().PopAll();
if (firstLoggingEvent is null)
{
SendBuffer(bufferEvents);
}
else if (bufferEvents.Length == 0)
{
SendBuffer([firstLoggingEvent]);
}
else
{
// Create new array with the firstLoggingEvent at the head
LoggingEvent[] events = new LoggingEvent[bufferEvents.Length + 1];
Array.Copy(bufferEvents, 0, events, 1, bufferEvents.Length);
events[0] = firstLoggingEvent;
SendBuffer(events);
}
}
/// <summary>
/// Sends the events.
/// </summary>
/// <param name="events">The events that need to be sent.</param>
/// <remarks>
/// <para>
/// The subclass must override this method to process the buffered events.
/// </para>
/// </remarks>
protected abstract void SendBuffer(LoggingEvent[] events);
/// <summary>
/// The default buffer size.
/// </summary>
/// <remarks>
/// The default size of the cyclic buffer used to store events.
/// This is set to 512 by default.
/// </remarks>
private const int DefaultBufferSize = 512;
/// <summary>
/// The cyclic buffer used to store the logging events.
/// </summary>
private CyclicBuffer? _cyclicBuffer;
/// <summary>
/// The events delivered to the subclass must be fixed.
/// </summary>
private readonly bool _eventMustBeFixed;
}