From f5541287af21cedb0dd8d0ac2b0173ae825601b7 Mon Sep 17 00:00:00 2001 From: Burak Akgerman Date: Wed, 10 Aug 2022 10:55:23 -0400 Subject: [PATCH] Added layoutrenderer aspnet-request-has-body and aspnet-request-stream-id (#845) --- .../AspNetRequestHasBodyLayoutRenderer.cs | 39 ++++++++++ .../AspNetRequestStreamIdLayoutRenderer.cs | 31 ++++++++ ...AspNetRequestHasBodyLayoutRendererTests.cs | 75 +++++++++++++++++++ ...spNetRequestStreamIdLayoutRendererTests.cs | 55 ++++++++++++++ 4 files changed, 200 insertions(+) create mode 100644 src/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestHasBodyLayoutRenderer.cs create mode 100644 src/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestStreamIdLayoutRenderer.cs create mode 100644 tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestHasBodyLayoutRendererTests.cs create mode 100644 tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestStreamIdLayoutRendererTests.cs diff --git a/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestHasBodyLayoutRenderer.cs b/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestHasBodyLayoutRenderer.cs new file mode 100644 index 00000000..21a997fd --- /dev/null +++ b/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestHasBodyLayoutRenderer.cs @@ -0,0 +1,39 @@ +#if NET5_0_OR_GREATER +using NLog.LayoutRenderers; +using NLog.Web.Internal; +using System.Text; +using Microsoft.AspNetCore.Http.Features; + +namespace NLog.Web.LayoutRenderers +{ + /// + /// Used to indicate if the request has a body. + /// Uses IHttpRequestBodyDetectionFeature + /// + /// This returns true when: + /// - It's an HTTP/1.x request with a non-zero Content-Length or a 'Transfer-Encoding: chunked' header. + /// - It's an HTTP/2 request that did not set the END_STREAM flag on the initial headers frame. + /// The final request body length may still be zero for the chunked or HTTP/2 scenarios. + /// + /// This returns false when: + /// - It's an HTTP/1.x request with no Content-Length or 'Transfer-Encoding: chunked' header, or the Content-Length is 0. + /// - It's an HTTP/1.x request with Connection: Upgrade(e.g.WebSockets). + /// There is no HTTP request body for these requests and no data should be received until after the upgrade. + /// - It's an HTTP/2 request that set END_STREAM on the initial headers frame. When false, the request body should never return data. + /// + /// + /// ${aspnet-request-has-body} + /// + [LayoutRenderer("aspnet-request-has-body")] + public class AspNetRequestHasBodyLayoutRenderer : AspNetLayoutRendererBase + { + /// + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + var features = HttpContextAccessor.HttpContext.TryGetFeatureCollection(); + var value = features?.Get()?.CanHaveBody ?? false; + builder.Append(value ? '1' : '0'); + } + } +} +#endif diff --git a/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestStreamIdLayoutRenderer.cs b/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestStreamIdLayoutRenderer.cs new file mode 100644 index 00000000..1a29147d --- /dev/null +++ b/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestStreamIdLayoutRenderer.cs @@ -0,0 +1,31 @@ +#if NET5_0_OR_GREATER +using System.Text; +using Microsoft.AspNetCore.Connections.Features; +using NLog.LayoutRenderers; +using NLog.Web.Internal; + +namespace NLog.Web.LayoutRenderers +{ + /// + /// Represents the long int identifier for the stream. + /// Uses IStreamIdFeature + /// + /// This will inform when the connection is being reused, or when the connection has been closed and reopened, + /// based on when the value stays or same, or changes. + /// + /// + /// ${aspnet-request-stream-id} + /// + [LayoutRenderer("aspnet-request-stream-id")] + public class AspNetRequestStreamIdLayoutRenderer : AspNetLayoutRendererBase + { + /// + protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent) + { + var features = HttpContextAccessor.HttpContext.TryGetFeatureCollection(); + var streamIdFeature = features?.Get(); + builder.Append(streamIdFeature?.StreamId); + } + } +} +#endif \ No newline at end of file diff --git a/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestHasBodyLayoutRendererTests.cs b/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestHasBodyLayoutRendererTests.cs new file mode 100644 index 00000000..606787cc --- /dev/null +++ b/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestHasBodyLayoutRendererTests.cs @@ -0,0 +1,75 @@ +#if NET5_0_OR_GREATER +using NLog.Web.LayoutRenderers; +using Microsoft.AspNetCore.Http.Features; +using NSubstitute; +using Xunit; + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class AspNetRequestHasBodyLayoutRendererTests : LayoutRenderersTestBase + { + [Fact] + public void TrueTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + var bodyDetectionFeature = Substitute.For(); + bodyDetectionFeature.CanHaveBody.Returns(true); + + var featureCollection = new FeatureCollection(); + featureCollection.Set(bodyDetectionFeature); + + httpContext.Features.Returns(featureCollection); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("1", result); + } + + [Fact] + public void FalseTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + var bodyDetectionFeature = Substitute.For(); + bodyDetectionFeature.CanHaveBody.Returns(false); + + var featureCollection = new FeatureCollection(); + featureCollection.Set(bodyDetectionFeature); + + httpContext.Features.Returns(featureCollection); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("0", result); + } + + [Fact] + public void NullTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + + httpContext.Features.Returns(new FeatureCollection()); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("0", result); + } + + protected override void NullRendersEmptyString() + { + // Arrange + var (renderer, _) = CreateWithHttpContext(); + + // Act + string result = renderer.Render(LogEventInfo.CreateNullEvent()); + + // Assert + Assert.Equal("0", result); + } + } +} +#endif diff --git a/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestStreamIdLayoutRendererTests.cs b/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestStreamIdLayoutRendererTests.cs new file mode 100644 index 00000000..7767b1b0 --- /dev/null +++ b/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestStreamIdLayoutRendererTests.cs @@ -0,0 +1,55 @@ +#if NET5_0_OR_GREATER +using NLog.Web.LayoutRenderers; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Http.Features; +using NSubstitute; +using Xunit; + +namespace NLog.Web.Tests.LayoutRenderers +{ + public class AspNetRequestStreamIdLayoutRendererTests : LayoutRenderersTestBase + { + [Fact] + public void SuccessTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + var streamIdFeature = Substitute.For(); + streamIdFeature.StreamId.Returns(257); + + var featureCollection = new FeatureCollection(); + featureCollection.Set(streamIdFeature); + + httpContext.Features.Returns(featureCollection); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal("257", result); + } + + [Fact] + public void NullTest() + { + // Arrange + var (renderer, httpContext) = CreateWithHttpContext(); + httpContext.Features.Returns(new FeatureCollection()); + // Act + var result = renderer.Render(new LogEventInfo()); + // Assert + Assert.Equal(string.Empty, result); + } + + protected override void NullRendersEmptyString() + { + // Arrange + var (renderer, _) = CreateWithHttpContext(); + + // Act + string result = renderer.Render(LogEventInfo.CreateNullEvent()); + + // Assert + Assert.Equal("0", result); + } + } +} +#endif