Skip to content

Commit 0a7643b

Browse files
committed
W3CExtendedLogLayout for rendering W3C Extended Logs
1 parent 5be7d6d commit 0a7643b

11 files changed

Lines changed: 561 additions & 20 deletions
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.Extensions.Logging;
5+
6+
namespace NLog.Web
7+
{
8+
/// <summary>
9+
/// Middleware that writes all requests to Logger named "RequestLogging"
10+
/// </summary>
11+
/// <remarks>
12+
/// - LogLevel.Error - Request failed with exception<br/>
13+
/// - LogLevel.Warn - Request completed with unsucessful StatusCode<br/>
14+
/// - LogLevel.Info - Request completed standard StatusCode<br/>
15+
/// </remarks>
16+
public class NLogRequestLoggingMiddleware
17+
{
18+
private readonly RequestDelegate _next;
19+
private readonly NLogRequestLoggingOptions _options;
20+
private readonly Microsoft.Extensions.Logging.ILogger _logger;
21+
22+
/// <summary>
23+
/// Initializes new instance of the <see cref="NLogRequestLoggingMiddleware"/> class
24+
/// </summary>
25+
/// <remarks>
26+
/// Use the following in Startup.cs:
27+
/// <code>
28+
/// public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
29+
/// {
30+
/// app.UseMiddleware&lt;NLog.Web.RequestLoggingMiddleware&gt;();
31+
/// }
32+
/// </code>
33+
/// </remarks>
34+
public NLogRequestLoggingMiddleware(RequestDelegate next, NLogRequestLoggingOptions options = default, ILoggerFactory loggerFactory = default)
35+
{
36+
_next = next;
37+
_options = options ?? NLogRequestLoggingOptions.Default;
38+
_logger = loggerFactory?.CreateLogger(_options.LoggerName ?? NLogRequestLoggingOptions.Default.LoggerName) ?? Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance;
39+
}
40+
41+
/// <summary>
42+
/// Executes the middleware.
43+
/// </summary>
44+
public async Task Invoke(HttpContext httpContext)
45+
{
46+
try
47+
{
48+
await _next(httpContext);
49+
}
50+
catch (Exception exception) when (LogHttpRequest(httpContext, exception))
51+
{
52+
// Logging complete
53+
}
54+
}
55+
56+
/// <summary>
57+
/// Exception Filter for better capture of thread-execution-context (Ex. AsyncLocal-state)
58+
/// </summary>
59+
private bool LogHttpRequest(HttpContext httpContext, Exception exception)
60+
{
61+
if (exception != null)
62+
{
63+
_logger.LogError(exception, "HttpRequest Exception");
64+
}
65+
else
66+
{
67+
var statusCode = httpContext.Response?.StatusCode ?? 0;
68+
if (statusCode < 100 || statusCode >= 400)
69+
{
70+
_logger.LogWarning("HttpRequest Failed");
71+
}
72+
else
73+
{
74+
_logger.LogInformation("HttpRequest Completed");
75+
}
76+
}
77+
78+
return false; // Exception Filter should not suppress the Exception
79+
}
80+
}
81+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace NLog.Web
2+
{
3+
/// <summary>
4+
/// Options configuration for <see cref="NLogRequestLoggingMiddleware"/>
5+
/// </summary>
6+
public sealed class NLogRequestLoggingOptions
7+
{
8+
internal static readonly NLogRequestLoggingOptions Default = new NLogRequestLoggingOptions();
9+
10+
/// <summary>
11+
/// Logger-name used for logging http-requests
12+
/// </summary>
13+
public string LoggerName { get; set; } = "NLogRequestLogging";
14+
}
15+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Web;
3+
4+
namespace NLog.Web
5+
{
6+
/// <summary>
7+
/// HttpModule that writes all requests to Logger named "RequestLogging"
8+
/// </summary>
9+
public class NLogRequestLoggingModule : IHttpModule
10+
{
11+
private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("NLogRequestLogging");
12+
13+
void IHttpModule.Init(HttpApplication context)
14+
{
15+
context.EndRequest += LogHttpRequest;
16+
}
17+
18+
private void LogHttpRequest(object sender, EventArgs e)
19+
{
20+
Exception exception = null;
21+
int statusCode = 0;
22+
23+
try
24+
{
25+
exception = HttpContext.Current?.Server?.GetLastError();
26+
statusCode = HttpContext.Current?.Response?.StatusCode ?? 0;
27+
}
28+
catch
29+
{
30+
// Nothing to do
31+
}
32+
finally
33+
{
34+
if (exception != null)
35+
Logger.Error(exception, "HttpRequest Exception");
36+
else if (statusCode < 100 || statusCode >= 400)
37+
Logger.Warn("HttpRequest Failed");
38+
else
39+
Logger.Info("HttpRequest Completed");
40+
}
41+
}
42+
43+
void IHttpModule.Dispose()
44+
{
45+
// Nothing here to do
46+
}
47+
}
48+
}

src/NLog.Web/Targets/Wrappers/AspNetBufferingTargetWrapper.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,10 @@ protected override void InitializeTarget()
137137
{
138138
base.InitializeTarget();
139139

140+
// Prevent double subscribe
141+
NLogHttpModule.BeginRequest -= OnBeginRequest;
142+
NLogHttpModule.EndRequest -= OnEndRequest;
143+
140144
NLogHttpModule.BeginRequest += OnBeginRequest;
141145
NLogHttpModule.EndRequest += OnEndRequest;
142146

src/Shared/LayoutRenderers/AspNetRequestDurationLayoutRenderer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class AspNetRequestDurationLayoutRenderer : AspNetLayoutRendererBase
1919
#if !ASP_NET_CORE
2020
private string _formatString;
2121
#elif ASP_NET_CORE2
22-
private Layouts.Layout _scopeTiming;
22+
private NLog.Layouts.SimpleLayout _scopeTiming;
2323
#endif
2424

2525
/// <summary>

src/Shared/LayoutRenderers/AspNetRequestUrlRenderer.cs

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ public class AspNetRequestUrlRenderer : AspNetLayoutRendererBase
5151
/// </summary>
5252
public bool IncludeScheme { get; set; } = true;
5353

54+
/// <summary>
55+
/// To specify whether to exclude / include the url-path. Default is true.
56+
/// </summary>
57+
public bool IncludePath { get; set; } = true;
58+
5459
#if ASP_NET_CORE
5560

5661
/// <summary>
@@ -107,8 +112,15 @@ private void RenderUrl(HttpRequestBase httpRequest, StringBuilder builder)
107112
builder.Append(url.Port);
108113
}
109114

110-
var pathAndQuery = IncludeQueryString ? url.PathAndQuery : url.AbsolutePath;
111-
builder.Append(pathAndQuery);
115+
if (IncludePath)
116+
{
117+
var pathAndQuery = IncludeQueryString ? url.PathAndQuery : url.AbsolutePath;
118+
builder.Append(pathAndQuery);
119+
}
120+
else if (IncludeQueryString)
121+
{
122+
builder.Append(url.Query);
123+
}
112124
}
113125
#else
114126
private void RenderUrl(HttpRequest httpRequest, StringBuilder builder)
@@ -130,20 +142,26 @@ private void RenderUrl(HttpRequest httpRequest, StringBuilder builder)
130142
builder.Append(httpRequest.Host.Port.Value);
131143
}
132144

133-
IHttpRequestFeature httpRequestFeature;
134-
if (UseRawTarget && (httpRequestFeature = httpRequest.HttpContext.Features.Get<IHttpRequestFeature>()) != null)
145+
if (IncludePath)
135146
{
136-
builder.Append(httpRequestFeature.RawTarget);
137-
}
138-
else
139-
{
140-
builder.Append(httpRequest.PathBase.ToUriComponent());
141-
builder.Append(httpRequest.Path.ToUriComponent());
142-
143-
if (IncludeQueryString)
147+
IHttpRequestFeature httpRequestFeature;
148+
if (UseRawTarget && (httpRequestFeature = httpRequest.HttpContext.Features.Get<IHttpRequestFeature>()) != null)
144149
{
145-
builder.Append(httpRequest.QueryString.Value);
150+
builder.Append(httpRequestFeature.RawTarget);
146151
}
152+
else
153+
{
154+
builder.Append((httpRequest.PathBase + httpRequest.Path).ToUriComponent());
155+
156+
if (IncludeQueryString)
157+
{
158+
builder.Append(httpRequest.QueryString.Value);
159+
}
160+
}
161+
}
162+
else if (IncludeQueryString)
163+
{
164+
builder.Append(httpRequest.QueryString.Value);
147165
}
148166
}
149167
#endif

src/Shared/LayoutRenderers/AspNetRequestUserAgent.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
11
using System;
22
using System.Text;
3-
using NLog.Config;
43
using NLog.LayoutRenderers;
54
using NLog.Web.Internal;
65

7-
#if !ASP_NET_CORE
8-
using System.Collections.Specialized;
9-
using System.Web;
10-
#endif
11-
126
namespace NLog.Web.LayoutRenderers
137
{
148
/// <summary>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using NLog.Config;
6+
using NLog.Layouts;
7+
8+
namespace NLog.Web.Layouts
9+
{
10+
/// <summary>
11+
/// Field in W3C Extended Formatted event
12+
/// </summary>
13+
[NLogConfigurationItem]
14+
public class W3CExtendedLogField
15+
{
16+
/// <summary>
17+
/// Initializes a new instance of the <see cref="W3CExtendedLogField" /> class.
18+
/// </summary>
19+
public W3CExtendedLogField()
20+
: this(null, null)
21+
{
22+
}
23+
24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="W3CExtendedLogField" /> class.
26+
/// </summary>
27+
/// <param name="name">The name of the column.</param>
28+
/// <param name="layout">The layout of the column.</param>
29+
public W3CExtendedLogField(string name, Layout layout)
30+
{
31+
Name = name;
32+
Layout = layout;
33+
}
34+
35+
/// <summary>
36+
/// Gets or sets the name of the field.
37+
/// </summary>
38+
/// <docgen category='W3C Field Options' order='10' />
39+
public string Name { get; set; }
40+
41+
/// <summary>
42+
/// Gets or sets the layout of the field.
43+
/// </summary>
44+
/// <docgen category='W3C Field Options' order='10' />
45+
[RequiredParameter]
46+
public Layout Layout { get; set; }
47+
}
48+
}

0 commit comments

Comments
 (0)