-
Notifications
You must be signed in to change notification settings - Fork 794
/
LogContext.cs
268 lines (231 loc) · 9.1 KB
/
LogContext.cs
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
// Copyright 2013-2015 Serilog Contributors
//
// Licensed 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.
namespace Serilog.Context;
/// <summary>
/// Holds ambient properties that can be attached to log events. To
/// configure, use the <see cref="Serilog.Configuration.LoggerEnrichmentConfiguration.FromLogContext"/> method.
/// </summary>
/// <example>
/// Configuration:
/// <code lang="C#">
/// var log = new LoggerConfiguration()
/// .Enrich.FromLogContext()
/// ...
/// </code>
/// Usage:
/// <code lang="C#">
/// using (LogContext.PushProperty("MessageId", message.Id))
/// {
/// Log.Information("The MessageId property will be attached to this event");
/// }
/// </code>
/// </example>
/// <remarks>The scope of the context is the current logical thread, using AsyncLocal
/// (and so is preserved across async/await calls).</remarks>
public static class LogContext
{
#if ASYNCLOCAL
static readonly AsyncLocal<EnricherStack?> Data = new();
#elif REMOTING
static readonly string DataSlotName = typeof(LogContext).FullName + "@" + Guid.NewGuid();
#else // DOTNET_51
[ThreadStatic]
static EnricherStack? Data;
#endif
/// <summary>
/// Push a property onto the context, returning an <see cref="IDisposable"/>
/// that must later be used to remove the property, along with any others that
/// may have been pushed on top of it and not yet popped. The property must
/// be popped from the same thread/logical call context.
/// </summary>
/// <param name="name">The name of the property.</param>
/// <param name="value">The value of the property.</param>
/// <returns>A handle to later remove the property from the context.</returns>
/// <param name="destructureObjects">If true, and the value is a non-primitive, non-array type,
/// then the value will be converted to a structure; otherwise, unknown types will
/// be converted to scalars, which are generally stored as strings.</param>
/// <returns>A token that must be disposed, in order, to pop properties back off the stack.</returns>
public static IDisposable PushProperty(string name, object value, bool destructureObjects = false)
{
return Push(new PropertyEnricher(name, value, destructureObjects));
}
/// <summary>
/// Push an enricher onto the context, returning an <see cref="IDisposable"/>
/// that must later be used to remove the property, along with any others that
/// may have been pushed on top of it and not yet popped. The property must
/// be popped from the same thread/logical call context.
/// </summary>
/// <param name="enricher">An enricher to push onto the log context</param>
/// <returns>A token that must be disposed, in order, to pop properties back off the stack.</returns>
/// <exception cref="ArgumentNullException">When <paramref name="enricher"/> is <code>null</code></exception>
public static IDisposable Push(ILogEventEnricher enricher)
{
if (enricher == null) throw new ArgumentNullException(nameof(enricher));
var stack = GetOrCreateEnricherStack();
var bookmark = new ContextStackBookmark(stack);
Enrichers = stack.Push(enricher);
return bookmark;
}
/// <summary>
/// Push multiple enrichers onto the context, returning an <see cref="IDisposable"/>
/// that must later be used to remove the property, along with any others that
/// may have been pushed on top of it and not yet popped. The property must
/// be popped from the same thread/logical call context.
/// </summary>
/// <seealso cref="PropertyEnricher"/>.
/// <param name="enrichers">Enrichers to push onto the log context</param>
/// <returns>A token that must be disposed, in order, to pop properties back off the stack.</returns>
/// <exception cref="ArgumentNullException">When <paramref name="enrichers"/> is <code>null</code></exception>
public static IDisposable Push(params ILogEventEnricher[] enrichers)
{
if (enrichers == null) throw new ArgumentNullException(nameof(enrichers));
var stack = GetOrCreateEnricherStack();
var bookmark = new ContextStackBookmark(stack);
for (var i = 0; i < enrichers.Length; ++i)
stack = stack.Push(enrichers[i]);
Enrichers = stack;
return bookmark;
}
/// <summary>
/// Push enrichers onto the log context. This method is obsolete, please
/// use <see cref="Push(Serilog.Core.ILogEventEnricher[])"/> instead.
/// </summary>
/// <param name="properties">Enrichers to push onto the log context</param>
/// <returns>A token that must be disposed, in order, to pop properties back off the stack.</returns>
/// <exception cref="ArgumentNullException"></exception>
[Obsolete("Please use `LogContext.Push(properties)` instead.")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static IDisposable PushProperties(params ILogEventEnricher[] properties)
{
return Push(properties);
}
/// <summary>
/// Obtain an enricher that represents the current contents of the <see cref="LogContext"/>. This
/// can be pushed back onto the context in a different location/thread when required.
/// </summary>
/// <returns>An enricher that represents the current contents of the <see cref="LogContext"/>.</returns>
public static ILogEventEnricher Clone()
{
var stack = GetOrCreateEnricherStack();
return new SafeAggregateEnricher(stack);
}
/// <summary>
/// Remove all enrichers from the <see cref="LogContext"/>, returning an <see cref="IDisposable"/>
/// that must later be used to restore enrichers that were on the stack before <see cref="Suspend"/> was called.
/// </summary>
/// <returns>A token that must be disposed, in order, to restore properties back to the stack.</returns>
public static IDisposable Suspend()
{
var stack = GetOrCreateEnricherStack();
var bookmark = new ContextStackBookmark(stack);
Enrichers = EnricherStack.Empty;
return bookmark;
}
/// <summary>
/// Remove all enrichers from <see cref="LogContext"/> for the current async scope.
/// </summary>
public static void Reset()
{
if (Enrichers != null && Enrichers != EnricherStack.Empty)
{
Enrichers = EnricherStack.Empty;
}
}
static EnricherStack GetOrCreateEnricherStack()
{
var enrichers = Enrichers;
if (enrichers == null)
{
enrichers = EnricherStack.Empty;
Enrichers = enrichers;
}
return enrichers;
}
internal static void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
var enrichers = Enrichers;
if (enrichers == null || enrichers == EnricherStack.Empty)
return;
foreach (var enricher in enrichers)
{
enricher.Enrich(logEvent, propertyFactory);
}
}
sealed class ContextStackBookmark : IDisposable
{
readonly EnricherStack _bookmark;
public ContextStackBookmark(EnricherStack bookmark)
{
_bookmark = bookmark;
}
public void Dispose()
{
Enrichers = _bookmark;
}
}
#if ASYNCLOCAL
static EnricherStack? Enrichers
{
get => Data.Value;
set => Data.Value = value;
}
#elif REMOTING
static EnricherStack? Enrichers
{
get
{
var objectHandle = CallContext.LogicalGetData(DataSlotName) as ObjectHandle;
return objectHandle?.Unwrap() as EnricherStack;
}
set
{
if (CallContext.LogicalGetData(DataSlotName) is IDisposable oldHandle)
{
oldHandle.Dispose();
}
if (value != null)
{
CallContext.LogicalSetData(DataSlotName, new DisposableObjectHandle(value));
}
}
}
sealed class DisposableObjectHandle : ObjectHandle, IDisposable
{
static readonly ISponsor LifeTimeSponsor = new ClientSponsor();
public DisposableObjectHandle(object o)
: base(o)
{
}
public override object? InitializeLifetimeService()
{
var lease = base.InitializeLifetimeService() as ILease;
lease?.Register(LifeTimeSponsor);
return lease;
}
public void Dispose()
{
if (GetLifetimeService() is ILease lease)
{
lease.Unregister(LifeTimeSponsor);
}
}
}
#else // DOTNET_51
static EnricherStack? Enrichers
{
get => Data;
set => Data = value;
}
#endif
}