diff --git a/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestConnectionIdLayoutRenderer.cs b/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestConnectionIdLayoutRenderer.cs
new file mode 100644
index 00000000..7ec94dea
--- /dev/null
+++ b/src/NLog.Web.AspNetCore/LayoutRenderers/AspNetRequestConnectionIdLayoutRenderer.cs
@@ -0,0 +1,36 @@
+using NLog.LayoutRenderers;
+using NLog.Web.Internal;
+using System.Text;
+
+namespace NLog.Web.LayoutRenderers
+{
+ ///
+ /// ASP.NET request connection id
+ ///
+ ///
+ /// ${aspnet-request-connection-id}
+ ///
+ [LayoutRenderer("aspnet-request-connection-id")]
+ public class AspNetRequestConnectionIdLayoutRenderer : AspNetLayoutRendererBase
+ {
+ ///
+ /// Renders the ASP.NET connection ID
+ ///
+ /// The to append the rendered data to.
+ /// Logging event.
+ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
+ {
+ var connection = HttpContextAccessor.HttpContext.TryGetConnection();
+ if (connection == null)
+ {
+ return;
+ }
+
+ var id = connection.Id;
+ if(!string.IsNullOrEmpty(id))
+ {
+ builder.Append(id);
+ }
+ }
+ }
+}
diff --git a/src/Shared/Internal/HttpContextExtensions.cs b/src/Shared/Internal/HttpContextExtensions.cs
index 234999fc..b500f6e9 100644
--- a/src/Shared/Internal/HttpContextExtensions.cs
+++ b/src/Shared/Internal/HttpContextExtensions.cs
@@ -46,6 +46,22 @@ internal static HttpResponseBase TryGetResponse(this HttpContextBase context)
}
}
#else
+ internal static WebSocketManager TryGetWebSocket(this HttpContext context)
+ {
+ var websocket = context?.WebSockets;
+ if (websocket == null)
+ InternalLogger.Debug("HttpContext WebSocket Lookup returned null");
+ return websocket;
+ }
+
+ internal static ConnectionInfo TryGetConnection(this HttpContext context)
+ {
+ var connection = context?.Connection;
+ if (connection == null)
+ InternalLogger.Debug("HttpContext Connection Lookup returned null");
+ return connection;
+ }
+
internal static HttpRequest TryGetRequest(this HttpContext context)
{
var request = context?.Request;
diff --git a/src/Shared/LayoutRenderers/AspNetRequestClientCertificateLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestClientCertificateLayoutRenderer.cs
index 52566e38..c3462e4a 100644
--- a/src/Shared/LayoutRenderers/AspNetRequestClientCertificateLayoutRenderer.cs
+++ b/src/Shared/LayoutRenderers/AspNetRequestClientCertificateLayoutRenderer.cs
@@ -13,11 +13,17 @@ namespace NLog.Web.LayoutRenderers
/// ASP.NET Client Certificate of the Connection
///
///
- /// ${aspnet-request-client certificate}
+ /// ${aspnet-request-client-certificate}
+ /// ${aspnet-request-client-certificate:Verbose=True}
///
[LayoutRenderer("aspnet-request-client-certificate")]
public class AspNetRequestClientCertificateLayoutRenderer : AspNetLayoutRendererBase
{
+ ///
+ /// This is passed to the X509Certificate2.ToString(bool) method
+ ///
+ public bool Verbose { get; set; }
+
///
/// Render Remote Port
///
@@ -30,7 +36,7 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
return;
}
- builder.Append(connection.ClientCertificate);
+ builder.Append(connection.ClientCertificate?.ToString(Verbose));
#else
var certificate = httpContext.Request.ClientCertificate;
if (certificate == null)
@@ -39,7 +45,7 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
}
// Convert to an X509Certificate2, which does have the proper overridden ToString() method.
// HttpClientCertificate class only use object.ToString() which is useless.
- builder.Append(new X509Certificate2(certificate.Certificate));
+ builder.Append(new X509Certificate2(certificate.Certificate)?.ToString(Verbose));
#endif
}
}
diff --git a/src/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRenderer.cs
index a2fd3350..b33caf45 100644
--- a/src/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRenderer.cs
+++ b/src/Shared/LayoutRenderers/AspNetRequestIsWebSocketLayoutRenderer.cs
@@ -1,5 +1,6 @@
using System.Text;
using NLog.LayoutRenderers;
+using NLog.Web.Internal;
namespace NLog.Web.LayoutRenderers
{
@@ -21,7 +22,7 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
// Not available on .NET 3.5
#if ASP_NET_CORE
- var websockets = HttpContextAccessor.HttpContext?.WebSockets;
+ var websockets = HttpContextAccessor.HttpContext.TryGetWebSocket();
builder.Append(websockets?.IsWebSocketRequest == true ? '1' : '0');
#elif NET46_OR_GREATER
var httpContext = HttpContextAccessor.HttpContext;
diff --git a/src/Shared/LayoutRenderers/AspNetRequestLocalIpLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestLocalIpLayoutRenderer.cs
index 645ccfae..d8481086 100644
--- a/src/Shared/LayoutRenderers/AspNetRequestLocalIpLayoutRenderer.cs
+++ b/src/Shared/LayoutRenderers/AspNetRequestLocalIpLayoutRenderer.cs
@@ -26,7 +26,7 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
var httpContext = HttpContextAccessor.HttpContext;
#if ASP_NET_CORE
- var connection = httpContext.Connection;
+ var connection = httpContext.TryGetConnection();
if (connection == null)
{
return;
diff --git a/src/Shared/LayoutRenderers/AspNetRequestLocalPortLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestLocalPortLayoutRenderer.cs
index e1103bf2..37ff62ea 100644
--- a/src/Shared/LayoutRenderers/AspNetRequestLocalPortLayoutRenderer.cs
+++ b/src/Shared/LayoutRenderers/AspNetRequestLocalPortLayoutRenderer.cs
@@ -25,7 +25,7 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
var httpContext = HttpContextAccessor.HttpContext;
#if ASP_NET_CORE
- var connection = httpContext.Connection;
+ var connection = httpContext.TryGetConnection();
if (connection == null)
{
return;
diff --git a/src/Shared/LayoutRenderers/AspNetRequestRemotePortLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestRemotePortLayoutRenderer.cs
index e2478a47..fa46aa63 100644
--- a/src/Shared/LayoutRenderers/AspNetRequestRemotePortLayoutRenderer.cs
+++ b/src/Shared/LayoutRenderers/AspNetRequestRemotePortLayoutRenderer.cs
@@ -26,7 +26,7 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
var httpContext = HttpContextAccessor.HttpContext;
#if ASP_NET_CORE
- var connection = httpContext.Connection;
+ var connection = httpContext.TryGetConnection();
if (connection == null)
{
return;
diff --git a/src/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRenderer.cs
index e64f0558..dd0c154f 100644
--- a/src/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRenderer.cs
+++ b/src/Shared/LayoutRenderers/AspNetRequestWebSocketRequestedProtocolsLayoutRenderer.cs
@@ -1,5 +1,6 @@
using System.Text;
using NLog.LayoutRenderers;
+using NLog.Web.Internal;
namespace NLog.Web.LayoutRenderers
{
@@ -28,7 +29,7 @@ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
{
// Not available on .NET 3.5
#if ASP_NET_CORE
- var websockets = HttpContextAccessor.HttpContext?.WebSockets;
+ var websockets = HttpContextAccessor.HttpContext.TryGetWebSocket();
if (websockets == null)
{
return;
diff --git a/src/Shared/LayoutRenderers/AspNetResponseHasStartedLayoutRenderer.cs b/src/Shared/LayoutRenderers/AspNetResponseHasStartedLayoutRenderer.cs
new file mode 100644
index 00000000..ca7a8ccd
--- /dev/null
+++ b/src/Shared/LayoutRenderers/AspNetResponseHasStartedLayoutRenderer.cs
@@ -0,0 +1,31 @@
+using NLog.LayoutRenderers;
+using NLog.Web.Internal;
+using System.Text;
+
+namespace NLog.Web.LayoutRenderers
+{
+ ///
+ /// ASP.NET response headers already sent, in other words the response has started
+ ///
+ ///
+ /// ${aspnet-response-has-started}
+ ///
+ [LayoutRenderer("aspnet-response-has-started")]
+ public class AspNetResponseHasStartedLayoutRenderer : AspNetLayoutRendererBase
+ {
+ ///
+ /// Renders the ASP.NET HttpResponse HasStarted property in ASP.NET Core and the HttpResponse HeadersWritten in .NET Framework
+ ///
+ /// The to append the rendered data to.
+ /// Logging event.
+ protected override void DoAppend(StringBuilder builder, LogEventInfo logEvent)
+ {
+ var response = HttpContextAccessor.HttpContext.TryGetResponse();
+#if ASP_NET_CORE
+ builder.Append(response?.HasStarted == true ? '1' : '0');
+#elif NET46
+ builder.Append(response?.HeadersWritten == true ? '1' : '0');
+#endif
+ }
+ }
+}
diff --git a/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestConnectionIdLayoutRendererTests.cs b/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestConnectionIdLayoutRendererTests.cs
new file mode 100644
index 00000000..12eac80f
--- /dev/null
+++ b/tests/NLog.Web.AspNetCore.Tests/LayoutRenderers/AspNetRequestConnectionIdLayoutRendererTests.cs
@@ -0,0 +1,52 @@
+using NLog.Web.LayoutRenderers;
+using NSubstitute;
+using NSubstitute.ReturnsExtensions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+
+namespace NLog.Web.Tests.LayoutRenderers
+{
+ public class AspNetRequestConnectionIdLayoutRendererTests : LayoutRenderersTestBase
+ {
+ [Fact]
+ public void SuccessTest()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+
+ httpContext.Connection.Id.Returns("My Connection Id");
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+ // Assert
+ Assert.Equal("My Connection Id", result);
+ }
+
+ [Fact]
+ public void EmptyTest()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+
+ httpContext.Connection.Id.Returns("");
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+ // Assert
+ Assert.Equal("", result);
+ }
+
+ [Fact]
+ public void NullTest()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+
+ httpContext.Connection.Id.ReturnsNull();
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+ // Assert
+ Assert.Equal("", result);
+ }
+ }
+}
diff --git a/tests/Shared/LayoutRenderers/AspNetRequestClientCertificateRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetRequestClientCertificateRendererTests.cs
index db416df8..801fc58a 100644
--- a/tests/Shared/LayoutRenderers/AspNetRequestClientCertificateRendererTests.cs
+++ b/tests/Shared/LayoutRenderers/AspNetRequestClientCertificateRendererTests.cs
@@ -28,6 +28,25 @@ public void SuccessTest()
Assert.Equal(certificate.ToString(), result);
}
+ [Fact]
+ public void SuccessVerboseTest()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+ renderer.Verbose = true;
+
+ var certificate = new X509Certificate2(
+ Convert.FromBase64String(
+"MIIC7DCCAdSgAwIBAgIQJq2oGnSgP79FVWGrezYIBDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMjIwMzExMDQyOTI4WhcNMjcwMzEwMDAwMDAwWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfSpPySBJetaQdagcgNMs6owXj6QwDv4BKB/qRG4AM5myL3T+7cTiUUOaHywIR+79k2K+LVOLKzfXLJvrzYZSj3yd02ScmM4BEJEcbauY7wCYjgFfB6K9Klh87UAP6+gUUHjIVfzI/Go3883c9D29S3PbO3z5Yz1hTVS0Hes5ZE0d7TDevVSXm2ZpUZSPz7W50+FBq2z3uI3pSBg2oZYHhUvbFIhMI0VIFAPSiyU9XIo+RCv3eN27Fq8g3Qo0z+8wnk7zSldncVEZko5WGKNL781U/TDuhBigkvEme9goaRPRW8oNjm//v+vyJwo/WDzghSwc6jCIdjTXUIqxw4THBAgMBAAGjOjA4MAsGA1UdDwQEAwIEsDATBgNVHSUEDDAKBggrBgEFBQcDATAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAEqAzHpQgeiW1abdOj4LClM+8uU813tvFhepneLL59yvtZ2NT6ruSd7Fa15CT8bIKACgxzUOaB7N/KseORqLFrNiwvu2A1vvEkhueH0TLC2KJt7SvlEKw5QooiLdOIfNBaL8h/YE/TK9hXCdTgmsPuXuc47vxSyLUUzMGFlW4olFkOEHOP0havplJvPabLY/WpPTV0+mKUe+CKKEKNduH8il9heXguak06XGufP8UQP1fE+GVDFJDqX0S2TMcaoohxL2lV4VqNnadGJ/VA97ZDTWKFteDdTNwZYyb0KvxLtUCc6cHak9ZRs1E7+SZyNx/pcB4vgpnWPXKX8WDr3VGw0="));
+
+ httpContext.Connection.ClientCertificate.Returns(certificate);
+
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+ // Assert
+ Assert.Equal(certificate.ToString(true), result);
+ }
+
[Fact]
public void NullTest()
{
@@ -77,6 +96,45 @@ public void SuccessTest()
//Assert.Equal(certificate.ToString(), result);
Assert.Equal(string.Empty, result);
}
+
+ [Fact]
+ public void SuccessVerboseTest()
+ {
+ var httpContext = Substitute.For();
+ var renderer = new AspNetRequestClientCertificateLayoutRenderer();
+ renderer.Verbose = true;
+ renderer.HttpContextAccessor = new FakeHttpContextAccessor(httpContext);
+
+ // Arrange
+ var byteArray = Convert.FromBase64String(
+ "MIIC7DCCAdSgAwIBAgIQJq2oGnSgP79FVWGrezYIBDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMjIwMzExMDQyOTI4WhcNMjcwMzEwMDAwMDAwWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfSpPySBJetaQdagcgNMs6owXj6QwDv4BKB/qRG4AM5myL3T+7cTiUUOaHywIR+79k2K+LVOLKzfXLJvrzYZSj3yd02ScmM4BEJEcbauY7wCYjgFfB6K9Klh87UAP6+gUUHjIVfzI/Go3883c9D29S3PbO3z5Yz1hTVS0Hes5ZE0d7TDevVSXm2ZpUZSPz7W50+FBq2z3uI3pSBg2oZYHhUvbFIhMI0VIFAPSiyU9XIo+RCv3eN27Fq8g3Qo0z+8wnk7zSldncVEZko5WGKNL781U/TDuhBigkvEme9goaRPRW8oNjm//v+vyJwo/WDzghSwc6jCIdjTXUIqxw4THBAgMBAAGjOjA4MAsGA1UdDwQEAwIEsDATBgNVHSUEDDAKBggrBgEFBQcDATAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggEBAEqAzHpQgeiW1abdOj4LClM+8uU813tvFhepneLL59yvtZ2NT6ruSd7Fa15CT8bIKACgxzUOaB7N/KseORqLFrNiwvu2A1vvEkhueH0TLC2KJt7SvlEKw5QooiLdOIfNBaL8h/YE/TK9hXCdTgmsPuXuc47vxSyLUUzMGFlW4olFkOEHOP0havplJvPabLY/WpPTV0+mKUe+CKKEKNduH8il9heXguak06XGufP8UQP1fE+GVDFJDqX0S2TMcaoohxL2lV4VqNnadGJ/VA97ZDTWKFteDdTNwZYyb0KvxLtUCc6cHak9ZRs1E7+SZyNx/pcB4vgpnWPXKX8WDr3VGw0=");
+
+ var certificate = new X509Certificate2(byteArray);
+
+ // We need Microsoft Fakes here, but we only have Visual Studio 2022 Community, not Enterprise
+ // and we also cannot require NLog.Web community of developer to have Enterprise edition
+ //httpContext.Request.ClientCertificate = new HttpClientCertificate(byteArray);
+
+ // This throws NullReferenceException
+ //httpContext.Request.ClientCertificate.Certificate.Returns(byteArray);
+
+ var httpRequest = Substitute.For();
+ httpContext.Request.Returns(httpRequest);
+
+ // This throws NotSupportedException due to no public constructors, from Castle.DynamicProxy.Generators
+ //var clientCertificate = Substitute.For();
+
+ //clientCertificate.Certificate.Returns(byteArray);
+ //httpRequest.ClientCertificate.Returns(clientCertificate);
+
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+
+ // Assert
+ //Assert.Equal(certificate.ToString(true), result);
+ Assert.Equal(string.Empty, result);
+ }
+
[Fact]
public void NullTest()
{
diff --git a/tests/Shared/LayoutRenderers/AspNetResponseHasStartedLayoutRendererTests.cs b/tests/Shared/LayoutRenderers/AspNetResponseHasStartedLayoutRendererTests.cs
new file mode 100644
index 00000000..5562cb5d
--- /dev/null
+++ b/tests/Shared/LayoutRenderers/AspNetResponseHasStartedLayoutRendererTests.cs
@@ -0,0 +1,106 @@
+using NLog.Web.LayoutRenderers;
+using NSubstitute;
+using NSubstitute.ReturnsExtensions;
+using Xunit;
+
+namespace NLog.Web.Tests.LayoutRenderers
+{
+ public class AspNetResponseHasStartedLayoutRendererTests : LayoutRenderersTestBase
+ {
+#if !ASP_NET_CORE && NET46_OR_GREATER
+ [Fact]
+ public void TrueCase()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+ httpContext.Response.HeadersWritten.Returns(true);
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+ // Assert
+ Assert.Equal("1", result);
+ }
+
+ [Fact]
+ public void FalseCase()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+
+ httpContext.Response.HeadersWritten.Returns(false);
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+ // Assert
+ Assert.Equal("0", result);
+ }
+
+ [Fact]
+ public void NullCase()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+
+ httpContext.Response.ReturnsNull();
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+ // Assert
+ Assert.Equal("0", result);
+ }
+
+#endif
+
+
+#if ASP_NET_CORE
+ [Fact]
+ public void TrueCase()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+ httpContext.Response.HasStarted.Returns(true);
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+ // Assert
+ Assert.Equal("1", result);
+ }
+
+ [Fact]
+ public void FalseCase()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+
+ httpContext.Response.HasStarted.Returns(false);
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+ // Assert
+ Assert.Equal("0", result);
+ }
+
+ [Fact]
+ public void NullCase()
+ {
+ // Arrange
+ var (renderer, httpContext) = CreateWithHttpContext();
+
+ httpContext.Response.ReturnsNull();
+ // Act
+ string result = renderer.Render(new LogEventInfo());
+ // Assert
+ Assert.Equal("0", result);
+ }
+#endif
+
+#if ASP_NET_CORE || NET46_OR_GREATER
+ protected override void NullRendersEmptyString()
+ {
+ // Arrange
+ var (renderer, _) = CreateWithHttpContext();
+
+ // Act
+ string result = renderer.Render(LogEventInfo.CreateNullEvent());
+
+ // Assert
+ Assert.Equal("0", result);
+ }
+#endif
+ }
+}