Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NLogBufferingTargetWrapperMiddleware for AspNetBufferingTargetWrapper #900

Merged
merged 8 commits into from
Dec 28, 2022
53 changes: 53 additions & 0 deletions src/NLog.Web.AspNetCore/NLogBufferingTargetWrapperMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using NLog.Web.Targets.Wrappers;

namespace NLog.Web
{
/// <summary>
/// This class is to intercept the HTTP pipeline and to allow the AspNetBufferingTargetWrapper to function properly
///
/// Usage: app.UseMiddleware&lt;NLogBufferingTargetWrapperMiddleware&gt;(); where app is an IApplicationBuilder
/// </summary>
public class NLogBufferingTargetWrapperMiddleware
{
private readonly RequestDelegate _next;

/// <summary>
/// Initializes new instance of the <see cref="NLogBufferingTargetWrapperMiddleware"/> class
/// </summary>
/// <remarks>
/// Use the following in Startup.cs:
/// <code>
/// public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
/// {
/// app.UseMiddleware&lt;NLog.Web.NLogBufferingTargetWrapperMiddleware&gt;();
/// }
/// </code>
/// </remarks>
public NLogBufferingTargetWrapperMiddleware(RequestDelegate next)
{
_next = next;
}

/// <summary>
/// This allows interception of the HTTP pipeline for logging purposes
/// </summary>
/// <param name="context">The HttpContext</param>
/// <returns></returns>
public async Task Invoke(HttpContext context)
{
try
{
AspNetBufferingTargetWrapper.OnBeginRequest(context);

// Execute the next class in the HTTP pipeline, this can be the next middleware or the actual handler
await _next(context).ConfigureAwait(false);
}
finally
{
AspNetBufferingTargetWrapper.OnEndRequest(context);
}
}
}
}
49 changes: 23 additions & 26 deletions src/NLog.Web/NLogHttpModule.cs
Original file line number Diff line number Diff line change
@@ -1,56 +1,53 @@
using System;
using NLog.Web.Targets.Wrappers;
using System;
using System.Web;

namespace NLog.Web
{
/// <summary>
/// ASP.NET HttpModule that enables NLog to hook BeginRequest and EndRequest events easily.
/// ASP.NET IHttpModule that enables AspNetBufferingTargetWrapper proper functioning
/// </summary>
public class NLogHttpModule : IHttpModule
{
/// <summary>
/// Event to be raised at the end of each HTTP Request.
/// </summary>
public static event EventHandler EndRequest;
snakefoot marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Event to be raised at the beginning of each HTTP Request.
/// </summary>
public static event EventHandler BeginRequest;

/// <summary>
/// Initializes the HttpModule.
/// </summary>
/// <param name="application">
/// <param name="context">
/// ASP.NET application.
/// </param>
public void Init(HttpApplication application)
public void Init(HttpApplication context)
snakefoot marked this conversation as resolved.
Show resolved Hide resolved
{
application.BeginRequest += BeginRequestHandler;
application.EndRequest += EndRequestHandler;
Initialize(context);
snakefoot marked this conversation as resolved.
Show resolved Hide resolved
context.EndRequest += OnEndRequest;
}

/// <summary>
/// Disposes the module.
/// </summary>
public void Dispose()
{
// Method intentionally left empty.
}

private void BeginRequestHandler(object sender, EventArgs args)
internal void Initialize(HttpApplication application)
snakefoot marked this conversation as resolved.
Show resolved Hide resolved
{
if (BeginRequest != null)
{
BeginRequest(sender, args);
}
Initialize(application.Context);
}

internal void Initialize(HttpContext context)
snakefoot marked this conversation as resolved.
Show resolved Hide resolved
{
AspNetBufferingTargetWrapper.OnBeginRequest(new HttpContextWrapper(context));
}

internal void OnEndRequest(object sender, EventArgs args)
snakefoot marked this conversation as resolved.
Show resolved Hide resolved
{

Flush((sender as HttpApplication)?.Context);
}

private void EndRequestHandler(object sender, EventArgs args)
internal void Flush(HttpContext context)
snakefoot marked this conversation as resolved.
Show resolved Hide resolved
{
if (EndRequest != null)
{
EndRequest(sender, args);
}
AspNetBufferingTargetWrapper.OnEndRequest(new HttpContextWrapper(context));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
//
//
// Copyright (c) 2004-2021 Jaroslaw Kowalski <[email protected]>, Kim Christensen, Julian Verdurmen
//
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// and/or other materials provided with the distribution.
//
// * Neither the name of Jaroslaw Kowalski nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
// THE POSSIBILITY OF SUCH DAMAGE.
//
//

namespace NLog.Web.Internal
{
Expand Down Expand Up @@ -132,8 +132,11 @@ public AsyncLogEventInfo[] GetEventsAndClear()
{
int cnt = _count;
if (cnt == 0)
#if NET35
return new AsyncLogEventInfo[0];

#else
return Array.Empty<AsyncLogEventInfo>();
#endif
var returnValue = new AsyncLogEventInfo[cnt];

for (int i = 0; i < cnt; ++i)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
#if !ASP_NET_CORE
using System.Web;
#else
using HttpContextBase = Microsoft.AspNetCore.Http.HttpContext;
using Microsoft.AspNetCore.Http;
using NLog.Web.DependencyInjection;
#endif
using NLog.Common;
using NLog.Targets;
using NLog.Targets.Wrappers;
using NLog.Web.Internal;


namespace NLog.Web.Targets.Wrappers
{
Expand All @@ -21,20 +28,26 @@ namespace NLog.Web.Targets.Wrappers
/// to apply after all logs for a page have been generated.
/// </p>
/// <p>
/// To use this target, you need to add an entry in the httpModules section of
/// To use this target, for classic ASP.NET you need to add an entry in the httpModules section of
/// web.config:
/// </p>
/// <code lang="XML">
/// <![CDATA[<?xml version="1.0" ?>
/// <configuration>
/// <system.web>
/// <httpModules>
/// <add name="NLog" type="NLog.Web.NLogHttpModule, NLog.Extended"/>
/// <add name="NLog" type="NLog.Web.NLogHttpModule, NLog.Web"/>
/// </httpModules>
/// </system.web>
/// </configuration>
/// ]]>
/// </code>
/// to use this target, for ASP.NET Core, you need to add a line fo code to involve the proper middleware
/// <code>
/// <![CDATA[
/// app.UseMiddleware<NLogBufferingTargetWrapperMiddleware>();
/// ]]>
/// </code>
/// </remarks>
/// <example>
/// <p>To set up the ASP.NET Buffering target wrapper <a href="config.html">configuration file</a>, put
Expand All @@ -61,7 +74,7 @@ namespace NLog.Web.Targets.Wrappers
[Target("AspNetBufferingWrapper", IsWrapper = true)]
public class AspNetBufferingTargetWrapper : WrapperTargetBase
{
private readonly object dataSlot = new object();
private static readonly object dataSlot = new object();
private int growLimit;

/// <summary>
Expand Down Expand Up @@ -130,36 +143,20 @@ public int BufferGrowLimit
}
}

/// <summary>
/// Initializes the target by hooking up the NLogHttpModule BeginRequest and EndRequest events.
/// </summary>
protected override void InitializeTarget()
internal IHttpContextAccessor HttpContextAccessor
{
base.InitializeTarget();

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

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

if (HttpContext.Current != null)
{
// we are in the context already, it's too late for OnBeginRequest to be called, so let's
// just call it ourselves
OnBeginRequest(null, null);
}
get => _httpContextAccessor ?? (_httpContextAccessor = RetrieveHttpContextAccessor());
set => _httpContextAccessor = value;
}
private IHttpContextAccessor _httpContextAccessor;

/// <summary>
/// Closes the target by flushing pending events in the buffer (if any).
/// </summary>
protected override void CloseTarget()
private IHttpContextAccessor RetrieveHttpContextAccessor()
{
NLogHttpModule.BeginRequest -= OnBeginRequest;
NLogHttpModule.EndRequest -= OnEndRequest;
base.CloseTarget();
#if ASP_NET_CORE
return ServiceLocator.ResolveService<IHttpContextAccessor>(ResolveService<IServiceProvider>(), LoggingConfiguration);
#else
return new DefaultHttpContextAccessor();
#endif
}

/// <summary>
Expand All @@ -185,30 +182,74 @@ protected override void Write(AsyncLogEventInfo logEvent)

private NLog.Web.Internal.LogEventInfoBuffer GetRequestBuffer()
{
HttpContext context = HttpContext.Current;
var context = HttpContextAccessor.HttpContext;
if (context == null)
snakefoot marked this conversation as resolved.
Show resolved Hide resolved
{
return null;
}

return context.Items[dataSlot] as NLog.Web.Internal.LogEventInfoBuffer;
var bufferDictionary = GetBufferDictionary(context);
if (bufferDictionary == null)
{
return null;
}

lock (bufferDictionary)
{
if (!bufferDictionary.TryGetValue(this, out var buffer))
{
buffer = new Internal.LogEventInfoBuffer(BufferSize, GrowBufferAsNeeded, BufferGrowLimit);
bufferDictionary.Add(this, buffer);
}

return buffer;
}
}

private static Dictionary<AspNetBufferingTargetWrapper, Internal.LogEventInfoBuffer> GetBufferDictionary(HttpContextBase context)
{
return context?.Items?[dataSlot] as
Dictionary<AspNetBufferingTargetWrapper, Internal.LogEventInfoBuffer>;
}

private void OnBeginRequest(object sender, EventArgs args)
private static void SetBufferDictionary(HttpContextBase context)
{
InternalLogger.Trace("Setting up ASP.NET request buffer.");
HttpContext context = HttpContext.Current;
context.Items[dataSlot] = new NLog.Web.Internal.LogEventInfoBuffer(BufferSize, GrowBufferAsNeeded, BufferGrowLimit);
context.Items[dataSlot] = new Dictionary<AspNetBufferingTargetWrapper, Internal.LogEventInfoBuffer>();
}

private void OnEndRequest(object sender, EventArgs args)
internal static void OnBeginRequest(HttpContextBase context)
{
var buffer = GetRequestBuffer();
if (buffer != null)
if (context == null)
{
return;
}

var bufferDictionary = GetBufferDictionary(context);
if (bufferDictionary == null)
{
InternalLogger.Trace("Setting up ASP.NET request buffer.");
SetBufferDictionary(context);
}
}

internal static void OnEndRequest(HttpContextBase context)
{
var bufferDictionary = GetBufferDictionary(context);
if (bufferDictionary == null)
{
return;
}

foreach (var bufferKeyValuePair in bufferDictionary)
{
InternalLogger.Trace("Sending buffered events to wrapped target: {0}.", WrappedTarget);
AsyncLogEventInfo[] events = buffer.GetEventsAndClear();
WrappedTarget.WriteAsyncLogEvents(events);
var wrappedTarget = bufferKeyValuePair.Key;
var buffer = bufferKeyValuePair.Value;
if (buffer != null)
{
InternalLogger.Trace("Sending buffered events to wrapped target: {0}.", wrappedTarget);
AsyncLogEventInfo[] events = buffer.GetEventsAndClear();
wrappedTarget.WriteAsyncLogEvents(events);
}
}
}
}
Expand Down
Loading