forked from aspnet/AspNetWebStack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSystemDiagnosticsTraceWriter.cs
More file actions
359 lines (310 loc) · 14.2 KB
/
SystemDiagnosticsTraceWriter.cs
File metadata and controls
359 lines (310 loc) · 14.2 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
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Net.Http;
using System.Web.Http.Tracing.Properties;
namespace System.Web.Http.Tracing
{
/// <summary>
/// Implementation of <see cref="ITraceWriter"/> that traces to <see cref="System.Diagnostics.Trace"/>
/// </summary>
public class SystemDiagnosticsTraceWriter : ITraceWriter
{
// Duplicate of internal category name traced by WebApi for start/end of request
private const string SystemWebHttpRequestCategory = "System.Web.Http.Request";
private static readonly TraceEventType[] TraceLevelToTraceEventType = new TraceEventType[]
{
// TraceLevel.Off
(TraceEventType)0,
// TraceLevel.Debug
TraceEventType.Verbose,
// TraceLevel.Info
TraceEventType.Information,
// TraceLevel.Warn
TraceEventType.Warning,
// TraceLevel.Error
TraceEventType.Error,
// TraceLevel.Fatal
TraceEventType.Critical
};
private TraceLevel _minLevel = TraceLevel.Info;
/// <summary>
/// Gets or sets the minimum trace level.
/// </summary>
/// <value>
/// Any <see cref="System.Web.Http.Tracing.TraceLevel"/> below this
/// level will be ignored. The default for this property
/// is <see cref="TraceLevel.Info"/>.
/// </value>
public TraceLevel MinimumLevel
{
get
{
return _minLevel;
}
set
{
if (value < TraceLevel.Off || value > TraceLevel.Fatal)
{
throw Error.ArgumentOutOfRange("value",
value,
SRResources.TraceLevelOutOfRange);
}
_minLevel = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether the formatted message
/// should be the verbose format, meaning it displays all fields
/// of the <see cref="TraceRecord"/>.
/// </summary>
/// <value><c>true</c> means all <see cref="TraceRecord"/> fields
/// will be traced, <c>false</c> means only minimal information
/// will be traced. The default value is <c>false</c>.</value>
public bool IsVerbose { get; set; }
/// <summary>
/// Gets or sets the <see cref="TraceSource"/> to which the
/// traces will be sent.
/// </summary>
/// <value>
/// This property allows a custom <see cref="TraceSource"/>
/// to be used when writing the traces.
/// This allows an application to configure and use its
/// own <see cref="TraceSource"/> other than the default
/// <see cref="System.Diagnostics.Trace"/>.
/// If the value is <c>null</c>, this trace writer will
/// send traces to <see cref="System.Diagnostics.Trace"/>.
/// </value>
public TraceSource TraceSource { get; set; }
/// <summary>
/// Writes a trace to <see cref="System.Diagnostics.Trace"/> if the
/// <paramref name="level"/> is greater than or equal <see cref="MinimumLevel"/>.
/// </summary>
/// <param name="request">The <see cref="HttpRequestMessage"/> associated with this trace.
/// It may be <c>null</c> but the resulting trace will contain no correlation ID.</param>
/// <param name="category">The category for the trace. This can be any user-defined
/// value. It is not interpreted by this implementation but is written to the trace.</param>
/// <param name="level">The <see cref="TraceLevel"/> of this trace. If it is less than
/// <see cref="MinimumLevel"/>, this trace request will be ignored.</param>
/// <param name="traceAction">The user callback to invoke to fill in a <see cref="TraceRecord"/>
/// with additional information to add to the trace.</param>
public virtual void Trace(HttpRequestMessage request, string category, TraceLevel level, Action<TraceRecord> traceAction)
{
if (category == null)
{
throw Error.ArgumentNull("category");
}
if (traceAction == null)
{
throw Error.ArgumentNull("traceAction");
}
if (level < TraceLevel.Off || level > TraceLevel.Fatal)
{
throw Error.ArgumentOutOfRange("level",
level,
SRResources.TraceLevelOutOfRange);
}
if (MinimumLevel == TraceLevel.Off || level < MinimumLevel)
{
return;
}
TraceRecord traceRecord = new TraceRecord(request, category, level);
traceAction(traceRecord);
TranslateHttpResponseException(traceRecord);
string message = Format(traceRecord);
if (!String.IsNullOrEmpty(message))
{
// Level may have changed in Translate above
TraceMessage(traceRecord.Level, message);
}
}
/// <summary>
/// Formats the contents of the given <see cref="TraceRecord"/> into
/// a single string containing comma-separated name-value pairs
/// for each <see cref="TraceRecord"/> property.
/// </summary>
/// <param name="traceRecord">The <see cref="TraceRecord"/> from which
/// to produce the result.</param>
/// <returns>A string containing comma-separated name-value pairs.</returns>
public virtual string Format(TraceRecord traceRecord)
{
if (traceRecord == null)
{
throw Error.ArgumentNull("traceRecord");
}
// The first and last traces are injected by the tracing system itself.
// We use these to format unique strings identifying the incoming request
// and the outgoing response.
if (String.Equals(traceRecord.Category, SystemWebHttpRequestCategory, StringComparison.Ordinal))
{
return FormatRequestEnvelope(traceRecord);
}
List<string> messages = new List<string>();
if (!IsVerbose)
{
// In short format mode, we trace only End traces because it is
// where the results of each operation will appear.
if (traceRecord.Kind == TraceKind.Begin)
{
return null;
}
}
else
{
messages.Add(Error.Format(SRResources.TimeLevelKindFormat,
FormatDateTime(traceRecord.Timestamp),
traceRecord.Level.ToString(),
traceRecord.Kind.ToString()));
if (!String.IsNullOrEmpty(traceRecord.Category))
{
messages.Add(Error.Format(SRResources.CategoryFormat, traceRecord.Category));
}
messages.Add(Error.Format(SRResources.IdFormat, traceRecord.RequestId.ToString()));
}
if (!String.IsNullOrEmpty(traceRecord.Message))
{
messages.Add(Error.Format(SRResources.MessageFormat, traceRecord.Message));
}
if (traceRecord.Operator != null || traceRecord.Operation != null)
{
messages.Add(Error.Format(SRResources.OperationFormat, traceRecord.Operator, traceRecord.Operation));
}
if (traceRecord.Status != 0)
{
messages.Add(Error.Format(SRResources.HttpStatusFormat, (int)traceRecord.Status, traceRecord.Status.ToString()));
}
if (traceRecord.Exception != null)
{
messages.Add(Error.Format(SRResources.ExceptionFormat, traceRecord.Exception.ToString()));
}
return String.Join(", ", messages);
}
/// <summary>
/// Formats the given <see cref="TraceRecord"/> into a string describing
/// either the initial receipt of the incoming request or the final send
/// of the response, depending on <see cref="TraceKind"/>.
/// </summary>
/// <param name="traceRecord">The <see cref="TraceRecord"/> from which to
/// produce the result.</param>
/// <returns>A string containing comma-separated name-value pairs.</returns>
public virtual string FormatRequestEnvelope(TraceRecord traceRecord)
{
if (traceRecord == null)
{
throw Error.ArgumentNull("traceRecord");
}
List<string> messages = new List<string>();
if (IsVerbose)
{
messages.Add(Error.Format((traceRecord.Kind == TraceKind.Begin)
? SRResources.TimeRequestFormat
: SRResources.TimeResponseFormat,
FormatDateTime(traceRecord.Timestamp)));
}
else
{
messages.Add((traceRecord.Kind == TraceKind.Begin)
? SRResources.ShortRequestFormat
: SRResources.ShortResponseFormat);
}
if (traceRecord.Status != 0)
{
messages.Add(Error.Format(SRResources.HttpStatusFormat, (int)traceRecord.Status, traceRecord.Status.ToString()));
}
if (traceRecord.Request != null)
{
messages.Add(Error.Format(SRResources.HttpMethodFormat, traceRecord.Request.Method));
if (traceRecord.Request.RequestUri != null)
{
messages.Add(Error.Format(SRResources.UrlFormat, traceRecord.Request.RequestUri.ToString()));
}
}
if (IsVerbose)
{
messages.Add(Error.Format(SRResources.IdFormat, traceRecord.RequestId.ToString()));
}
// The Message and Exception fields do not contain interesting information unless
// there is a problem, so they appear after the more informative trace information.
if (!String.IsNullOrEmpty(traceRecord.Message))
{
messages.Add(Error.Format(SRResources.MessageFormat, traceRecord.Message));
}
if (traceRecord.Exception != null)
{
messages.Add(Error.Format(SRResources.ExceptionFormat, traceRecord.Exception.ToString()));
}
return String.Join(", ", messages);
}
/// <summary>
/// Examines the given <see cref="TraceRecord"/> to determine whether it
/// contains an <see cref="HttpResponseException"/> and if so, modifies
/// the <see cref="TraceRecord"/> to capture more detailed information.
/// </summary>
/// <param name="traceRecord">The <see cref="TraceRecord"/> to examine and modify.</param>
public virtual void TranslateHttpResponseException(TraceRecord traceRecord)
{
if (traceRecord == null)
{
throw Error.ArgumentNull("traceRecord");
}
TraceWriterExceptionMapper.TranslateHttpResponseException(traceRecord);
}
/// <summary>
/// Formats a <see cref="DateTime"/> for the trace.
/// </summary>
/// <remarks>
/// The default implementation uses the ISO 8601 convention
/// for round-trippable dates so they can be parsed.
/// </remarks>
/// <param name="dateTime">The <see cref="DateTime"/></param>
/// <returns>The <see cref="DateTime"/> formatted as a string</returns>
public virtual string FormatDateTime(DateTime dateTime)
{
// The 'o' format is ISO 8601 for a round-trippable DateTime.
// It is culture-invariant and can be parsed.
return dateTime.ToString("o", CultureInfo.InvariantCulture);
}
private void TraceMessage(TraceLevel level, string message)
{
Contract.Assert(level >= TraceLevel.Off && level <= TraceLevel.Fatal);
// If the user registered a custom TraceSource, we write a trace event to it
// directly, preserving the event type.
TraceSource traceSource = TraceSource;
if (traceSource != null)
{
traceSource.TraceEvent(eventType: TraceLevelToTraceEventType[(int)level], id: 0, message: message);
return;
}
// If there is no custom TraceSource, trace to System.Diagnostics.Trace.
// But System.Diagnostics.Trace does not offer a public API to trace
// TraceEventType.Verbose or TraceEventType.Critical, meaning our
// TraceLevel.Debug and TraceLevel.Fatal cannot be report directly.
// Consequently, we translate Verbose to Trace.WriteLine and
// Critical to TraceEventType.Error.
// Windows Azure Diagnostics' TraceListener already translates
// a WriteLine to a Verbose, so on Azure the Debug trace will be
// handled properly.
switch (level)
{
case TraceLevel.Off:
return;
case TraceLevel.Debug:
System.Diagnostics.Trace.WriteLine(message);
return;
case TraceLevel.Info:
System.Diagnostics.Trace.TraceInformation(message);
return;
case TraceLevel.Warn:
System.Diagnostics.Trace.TraceWarning(message);
return;
case TraceLevel.Error:
case TraceLevel.Fatal:
System.Diagnostics.Trace.TraceError(message);
return;
}
}
}
}