Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions src/NLog.Web.AspNetCore/NLogRequestLoggingMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace NLog.Web
{
/// <summary>
/// Middleware that writes all requests to Logger named "RequestLogging"
/// </summary>
/// <remarks>
/// - LogLevel.Error - Request failed with exception<br/>
/// - LogLevel.Warn - Request completed with unsucessful StatusCode<br/>
/// - LogLevel.Info - Request completed standard StatusCode<br/>
/// </remarks>
public class NLogRequestLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly NLogRequestLoggingOptions _options;
private readonly Microsoft.Extensions.Logging.ILogger _logger;

/// <summary>
/// Initializes new instance of the <see cref="NLogRequestLoggingMiddleware"/> class
/// </summary>
/// <remarks>
/// Use the following in Startup.cs:
/// <code>
/// public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
/// {
/// app.UseMiddleware&lt;NLog.Web.RequestLoggingMiddleware&gt;();
/// }
/// </code>
/// </remarks>
public NLogRequestLoggingMiddleware(RequestDelegate next, NLogRequestLoggingOptions options = default, ILoggerFactory loggerFactory = default)
{
_next = next;
_options = options ?? NLogRequestLoggingOptions.Default;
_logger = loggerFactory?.CreateLogger(_options.LoggerName ?? NLogRequestLoggingOptions.Default.LoggerName) ?? Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance;
}

/// <summary>
/// Executes the middleware.
/// </summary>
public async Task Invoke(HttpContext httpContext)
{
try
{
await _next(httpContext);
LogHttpRequest(httpContext, null);
}
catch (Exception exception) when (LogHttpRequest(httpContext, exception))
Comment thread
snakefoot marked this conversation as resolved.
{
// Logging complete
}
}

/// <summary>
/// Exception Filter for better capture of thread-execution-context (Ex. AsyncLocal-state)
/// </summary>
private bool LogHttpRequest(HttpContext httpContext, Exception exception)
{
if (exception != null)
{
_logger.LogError(exception, "HttpRequest Exception");
}
else
{
var statusCode = httpContext.Response?.StatusCode ?? 0;
if (statusCode < 100 || statusCode >= 400)
{
_logger.LogWarning("HttpRequest Failed");
}
else
{
_logger.LogInformation("HttpRequest Completed");
}
}

return false; // Exception Filter should not suppress the Exception
}
}
}
15 changes: 15 additions & 0 deletions src/NLog.Web.AspNetCore/NLogRequestLoggingOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace NLog.Web
{
/// <summary>
/// Options configuration for <see cref="NLogRequestLoggingMiddleware"/>
/// </summary>
public sealed class NLogRequestLoggingOptions
{
internal static readonly NLogRequestLoggingOptions Default = new NLogRequestLoggingOptions();

/// <summary>
/// Logger-name used for logging http-requests
/// </summary>
public string LoggerName { get; set; } = "NLogRequestLogging";
}
}
48 changes: 48 additions & 0 deletions src/NLog.Web/NLogRequestLoggingModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Web;

namespace NLog.Web
{
/// <summary>
/// HttpModule that writes all requests to Logger named "RequestLogging"
/// </summary>
public class NLogRequestLoggingModule : IHttpModule
{
private static readonly NLog.Logger Logger = NLog.LogManager.GetLogger("NLogRequestLogging");

void IHttpModule.Init(HttpApplication context)
{
context.EndRequest += LogHttpRequest;
}

private void LogHttpRequest(object sender, EventArgs e)
{
Exception exception = null;
int statusCode = 0;

try
{
exception = HttpContext.Current?.Server?.GetLastError();
statusCode = HttpContext.Current?.Response?.StatusCode ?? 0;
}
catch
{
// Nothing to do
}
finally
{
if (exception != null)
Logger.Error(exception, "HttpRequest Exception");
else if (statusCode < 100 || statusCode >= 400)
Logger.Warn("HttpRequest Failed");
else
Logger.Info("HttpRequest Completed");
}
}

void IHttpModule.Dispose()
{
// Nothing here to do
}
}
}
4 changes: 4 additions & 0 deletions src/NLog.Web/Targets/Wrappers/AspNetBufferingTargetWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ protected override void InitializeTarget()
{
base.InitializeTarget();

// Prevent double subscribe
NLogHttpModule.BeginRequest -= OnBeginRequest;
NLogHttpModule.EndRequest -= OnEndRequest;

NLogHttpModule.BeginRequest += OnBeginRequest;
NLogHttpModule.EndRequest += OnEndRequest;

Expand Down
37 changes: 37 additions & 0 deletions src/Shared/LayoutRenderers/AspNetRequestContentLength.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Text;
using NLog.LayoutRenderers;
using NLog.Web.Internal;

namespace NLog.Web.LayoutRenderers
{
/// <summary>
/// ASP.NET request contentlength of the posted body
/// </summary>
/// <remarks>
/// ${aspnet-request-contentlength}
/// </remarks>
[LayoutRenderer("aspnet-request-contentlength")]
public class AspNetRequestContentLength : AspNetLayoutRendererBase
{
/// <summary>
/// Renders the ASP.NET posted body
/// </summary>
/// <param name="builder">The <see cref="StringBuilder" /> to append the rendered data to.</param>
/// <param name="logEvent">Logging event.</param>
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
var httpRequest = HttpContextAccessor.HttpContext.TryGetRequest();
if (httpRequest == null)
{
return;
}

long? contentLength = httpRequest.ContentLength;
if (contentLength > 0L)
{
builder.Append(contentLength.Value);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class AspNetRequestDurationLayoutRenderer : AspNetLayoutRendererBase
#if !ASP_NET_CORE
private string _formatString;
#elif ASP_NET_CORE2
private Layouts.Layout _scopeTiming;
private NLog.Layouts.SimpleLayout _scopeTiming;
#endif

/// <summary>
Expand Down
44 changes: 31 additions & 13 deletions src/Shared/LayoutRenderers/AspNetRequestUrlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public class AspNetRequestUrlRenderer : AspNetLayoutRendererBase
/// </summary>
public bool IncludeScheme { get; set; } = true;

/// <summary>
/// To specify whether to exclude / include the url-path. Default is true.
/// </summary>
public bool IncludePath { get; set; } = true;

#if ASP_NET_CORE

/// <summary>
Expand Down Expand Up @@ -107,8 +112,15 @@ private void RenderUrl(HttpRequestBase httpRequest, StringBuilder builder)
builder.Append(url.Port);
}

var pathAndQuery = IncludeQueryString ? url.PathAndQuery : url.AbsolutePath;
builder.Append(pathAndQuery);
if (IncludePath)
{
var pathAndQuery = IncludeQueryString ? url.PathAndQuery : url.AbsolutePath;
builder.Append(pathAndQuery);
}
else if (IncludeQueryString)
{
builder.Append(url.Query);
}
}
#else
private void RenderUrl(HttpRequest httpRequest, StringBuilder builder)
Expand All @@ -130,20 +142,26 @@ private void RenderUrl(HttpRequest httpRequest, StringBuilder builder)
builder.Append(httpRequest.Host.Port.Value);
}

IHttpRequestFeature httpRequestFeature;
if (UseRawTarget && (httpRequestFeature = httpRequest.HttpContext.Features.Get<IHttpRequestFeature>()) != null)
if (IncludePath)
{
builder.Append(httpRequestFeature.RawTarget);
}
else
{
builder.Append(httpRequest.PathBase.ToUriComponent());
builder.Append(httpRequest.Path.ToUriComponent());

if (IncludeQueryString)
IHttpRequestFeature httpRequestFeature;
if (UseRawTarget && (httpRequestFeature = httpRequest.HttpContext.Features.Get<IHttpRequestFeature>()) != null)
{
builder.Append(httpRequest.QueryString.Value);
builder.Append(httpRequestFeature.RawTarget);
}
else
{
builder.Append((httpRequest.PathBase + httpRequest.Path).ToUriComponent());

if (IncludeQueryString)
{
builder.Append(httpRequest.QueryString.Value);
}
}
}
else if (IncludeQueryString)
{
builder.Append(httpRequest.QueryString.Value);
}
}
#endif
Expand Down
6 changes: 0 additions & 6 deletions src/Shared/LayoutRenderers/AspNetRequestUserAgent.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
using System;
using System.Text;
using NLog.Config;
using NLog.LayoutRenderers;
using NLog.Web.Internal;

#if !ASP_NET_CORE
using System.Collections.Specialized;
using System.Web;
#endif

namespace NLog.Web.LayoutRenderers
{
/// <summary>
Expand Down
40 changes: 40 additions & 0 deletions src/Shared/LayoutRenderers/AspNetResponseContentLength.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Text;
using NLog.LayoutRenderers;
using NLog.Web.Internal;

namespace NLog.Web.LayoutRenderers
{
/// <summary>
/// ASP.NET Response ContentLength
/// </summary>
/// <remarks>
/// ${aspnet-response-contentlength}
/// </remarks>
[LayoutRenderer("aspnet-response-contentlength")]
public class AspNetResponseContentLength : AspNetLayoutRendererBase
{
/// <summary>
/// ASP.NET Http Response Status Code
/// </summary>
/// <param name="builder">The <see cref="StringBuilder" /> to append the rendered data to.</param>
/// <param name="logEvent">Logging event.</param>
protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
var httpResponse = HttpContextAccessor.HttpContext.TryGetResponse();
if (httpResponse == null)
{
return;
}

#if ASP_NET_CORE
var contentLength = httpResponse.ContentLength;
#else
var contentLength = httpResponse.OutputStream?.Length;
#endif
if (contentLength > 0L)
{
builder.Append(contentLength);
}
}
}
}
55 changes: 55 additions & 0 deletions src/Shared/Layouts/W3CExtendedLogField.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NLog.Config;
using NLog.Layouts;

namespace NLog.Web.Layouts
{
/// <summary>
/// Field in W3C Extended Formatted event
/// </summary>
[NLogConfigurationItem]
public class W3CExtendedLogField
{
/// <summary>
/// Initializes a new instance of the <see cref="W3CExtendedLogField" /> class.
/// </summary>
public W3CExtendedLogField()
: this(null, null)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="W3CExtendedLogField" /> class.
/// </summary>
/// <param name="name">The name of the column.</param>
/// <param name="layout">The layout of the column.</param>
public W3CExtendedLogField(string name, Layout layout)
{
Name = name;
Layout = layout;
}

/// <summary>
/// Gets or sets the name of the field.
/// </summary>
/// <remarks>
/// Standard field prefixes:<br/>
/// * s- = server details<br/>
/// * c- = client details<br/>
/// * cs- = client to server request details<br/>
/// * sc- = server to client response details<br/>
/// </remarks>
/// <docgen category='W3C Field Options' order='10' />
public string Name { get; set; }

/// <summary>
/// Gets or sets the layout of the field.
/// </summary>
/// <docgen category='W3C Field Options' order='10' />
[RequiredParameter]
public Layout Layout { get; set; }
}
}
Loading