4
4
#if NET9_0_OR_GREATER
5
5
using System ;
6
6
using System . Collections . Concurrent ;
7
- using System . Collections . Generic ;
7
+ using System . Diagnostics . CodeAnalysis ;
8
8
using Microsoft . Extensions . Diagnostics ;
9
9
using Microsoft . Extensions . Logging ;
10
- using Microsoft . Extensions . Logging . Abstractions ;
11
10
using Microsoft . Extensions . Options ;
11
+ using Microsoft . Shared . Diagnostics ;
12
+ using static Microsoft . Extensions . Logging . ExtendedLogger ;
12
13
13
14
namespace Microsoft . AspNetCore . Diagnostics . Logging ;
14
15
15
16
internal sealed class HttpRequestBuffer : ILoggingBuffer
16
17
{
17
18
private readonly IOptionsMonitor < HttpRequestBufferOptions > _options ;
18
- private readonly ConcurrentDictionary < IBufferedLogger , ConcurrentQueue < HttpRequestBufferedLogRecord > > _buffers ;
19
+ private readonly ConcurrentQueue < SerializedLogRecord > _buffer ;
19
20
private readonly TimeProvider _timeProvider = TimeProvider . System ;
21
+ private readonly IBufferSink _bufferSink ;
22
+ private readonly object _bufferCapacityLocker = new ( ) ;
23
+ private DateTimeOffset _truncateAfter ;
20
24
private DateTimeOffset _lastFlushTimestamp ;
21
25
22
- public HttpRequestBuffer ( IOptionsMonitor < HttpRequestBufferOptions > options )
26
+ public HttpRequestBuffer ( IBufferSink bufferSink , IOptionsMonitor < HttpRequestBufferOptions > options )
23
27
{
24
28
_options = options ;
25
- _buffers = new ConcurrentDictionary < IBufferedLogger , ConcurrentQueue < HttpRequestBufferedLogRecord > > ( ) ;
26
- _lastFlushTimestamp = _timeProvider . GetUtcNow ( ) ;
27
- }
29
+ _bufferSink = bufferSink ;
30
+ _buffer = new ConcurrentQueue < SerializedLogRecord > ( ) ;
28
31
29
- internal HttpRequestBuffer ( IOptionsMonitor < HttpRequestBufferOptions > options , TimeProvider timeProvider )
30
- : this ( options )
31
- {
32
- _timeProvider = timeProvider ;
33
- _lastFlushTimestamp = _timeProvider . GetUtcNow ( ) ;
32
+ _truncateAfter = _timeProvider . GetUtcNow ( ) ;
34
33
}
35
34
36
- public bool TryEnqueue (
37
- IBufferedLogger logger ,
35
+ [ RequiresUnreferencedCode (
36
+ "Calls Microsoft.Extensions.Logging.SerializedLogRecord.SerializedLogRecord(LogLevel, EventId, DateTimeOffset, IReadOnlyList<KeyValuePair<String, Object>>, Exception, String)" ) ]
37
+ public bool TryEnqueue < TState > (
38
38
LogLevel logLevel ,
39
39
string category ,
40
40
EventId eventId ,
41
- IReadOnlyList < KeyValuePair < string , object ? > > joiner ,
41
+ TState attributes ,
42
42
Exception ? exception ,
43
- string formatter )
43
+ Func < TState , Exception ? , string > formatter )
44
44
{
45
45
if ( ! IsEnabled ( category , logLevel , eventId ) )
46
46
{
47
47
return false ;
48
48
}
49
49
50
- var record = new HttpRequestBufferedLogRecord ( logLevel , eventId , joiner , exception , formatter ) ;
51
- var queue = _buffers . GetOrAdd ( logger , _ => new ConcurrentQueue < HttpRequestBufferedLogRecord > ( ) ) ;
52
-
53
- // probably don't need to limit buffer capacity?
54
- // because buffer is disposed when the respective HttpContext is disposed
55
- // don't expect it to grow so much to cause a problem?
56
- if ( queue . Count >= _options . CurrentValue . PerRequestCapacity )
50
+ switch ( attributes )
57
51
{
58
- _ = queue . TryDequeue ( out HttpRequestBufferedLogRecord ? _ ) ;
52
+ case ModernTagJoiner modernTagJoiner :
53
+ _buffer . Enqueue ( new SerializedLogRecord ( logLevel , eventId , _timeProvider . GetUtcNow ( ) , modernTagJoiner , exception ,
54
+ ( ( Func < ModernTagJoiner , Exception ? , string > ) ( object ) formatter ) ( modernTagJoiner , exception ) ) ) ;
55
+ break ;
56
+ case LegacyTagJoiner legacyTagJoiner :
57
+ _buffer . Enqueue ( new SerializedLogRecord ( logLevel , eventId , _timeProvider . GetUtcNow ( ) , legacyTagJoiner , exception ,
58
+ ( ( Func < LegacyTagJoiner , Exception ? , string > ) ( object ) formatter ) ( legacyTagJoiner , exception ) ) ) ;
59
+ break ;
60
+ default :
61
+ Throw . ArgumentException ( nameof ( attributes ) , $ "Unsupported type of the log attributes object detected: { typeof ( TState ) } ") ;
62
+ break ;
59
63
}
60
64
61
- queue . Enqueue ( record ) ;
65
+ var now = _timeProvider . GetUtcNow ( ) ;
66
+ lock ( _bufferCapacityLocker )
67
+ {
68
+ if ( now >= _truncateAfter )
69
+ {
70
+ _truncateAfter = now . Add ( _options . CurrentValue . PerRequestDuration ) ;
71
+ TruncateOverlimit ( ) ;
72
+ }
73
+ }
62
74
63
75
return true ;
64
76
}
65
77
78
+ [ RequiresUnreferencedCode ( "Calls Microsoft.Extensions.Logging.BufferSink.LogRecords(IEnumerable<SerializedLogRecord>)" ) ]
66
79
public void Flush ( )
67
80
{
68
- foreach ( var ( logger , queue ) in _buffers )
69
- {
70
- var result = new List < BufferedLogRecord > ( ) ;
71
- while ( ! queue . IsEmpty )
72
- {
73
- if ( queue . TryDequeue ( out HttpRequestBufferedLogRecord ? item ) )
74
- {
75
- result . Add ( item ) ;
76
- }
77
- }
78
-
79
- logger . LogRecords ( result ) ;
80
- }
81
+ var result = _buffer . ToArray ( ) ;
82
+ _buffer . Clear ( ) ;
81
83
82
84
_lastFlushTimestamp = _timeProvider . GetUtcNow ( ) ;
85
+
86
+ _bufferSink . LogRecords ( result ) ;
83
87
}
84
88
85
89
public bool IsEnabled ( string category , LogLevel logLevel , EventId eventId )
@@ -89,9 +93,18 @@ public bool IsEnabled(string category, LogLevel logLevel, EventId eventId)
89
93
return false ;
90
94
}
91
95
92
- LoggerFilterRuleSelector . Select < BufferFilterRule > ( _options . CurrentValue . Rules , category , logLevel , eventId , out BufferFilterRule ? rule ) ;
96
+ LoggerFilterRuleSelector . Select ( _options . CurrentValue . Rules , category , logLevel , eventId , out BufferFilterRule ? rule ) ;
93
97
94
98
return rule is not null ;
95
99
}
100
+
101
+ public void TruncateOverlimit ( )
102
+ {
103
+ // Capacity is a soft limit, which might be exceeded, esp. in multi-threaded environments.
104
+ while ( _buffer . Count > _options . CurrentValue . PerRequestCapacity )
105
+ {
106
+ _ = _buffer . TryDequeue ( out _ ) ;
107
+ }
108
+ }
96
109
}
97
110
#endif
0 commit comments