ok
f1c99ce2f0f04f0257df50bfee37e5302e67ca11
3 files changed
VanDerHeijden.Logging.slnxsrc/VanDerHeijden.Logging/VanDerHeijden.Logging.csprojsrc/VanDerHeijden.Logging/WebLoggingExtensions.cs
diff --git a/VanDerHeijden.Logging.slnx b/VanDerHeijden.Logging.slnx
index bbfcc3d..e6ba402 100644
--- a/VanDerHeijden.Logging.slnx
+++ b/VanDerHeijden.Logging.slnx
@@ -4,6 +4,5 @@
<Project Path="src/VanDerHeijden.Logging.MongoDb/VanDerHeijden.Logging.MongoDb.csproj" />
<Project Path="src/VanDerHeijden.Logging.Redis/VanDerHeijden.Logging.Redis.csproj" />
<Project Path="src/VanDerHeijden.Logging.Sql/VanDerHeijden.Logging.Sql.csproj" />
- <Project Path="src/VanDerHeijden.Logging.Web/VanDerHeijden.Logging.Web.csproj" />
<Project Path="tests/VanDerHeijden.Logging.File.Benchmarks/VanDerHeijden.Logging.File.Benchmarks.csproj" />
</Solution>
diff --git a/src/VanDerHeijden.Logging/VanDerHeijden.Logging.csproj b/src/VanDerHeijden.Logging/VanDerHeijden.Logging.csproj
index 9822fdb..fda551c 100644
--- a/src/VanDerHeijden.Logging/VanDerHeijden.Logging.csproj
+++ b/src/VanDerHeijden.Logging/VanDerHeijden.Logging.csproj
@@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<PackageId>VanDerHeijden.Logging</PackageId>
- <Version>10.0.4</Version>
+ <Version>10.0.5</Version>
<Authors>VanDerHeijden</Authors>
<Description>Core batched logging abstractions for .NET: IBatchedLogWriter<T>, BatchedLogger<T> and BatchedLoggerProvider<T>.</Description>
<PackageTags>logging;batched;core</PackageTags>
@@ -24,6 +24,11 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.3" />
+
+ <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.9" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.3" />
+ <PackageReference Include="Microsoft.Net.Http.Headers" Version="10.0.3" />
+
</ItemGroup>
<ItemGroup>
diff --git a/src/VanDerHeijden.Logging/WebLoggingExtensions.cs b/src/VanDerHeijden.Logging/WebLoggingExtensions.cs
new file mode 100644
index 0000000..468c42c
--- /dev/null
+++ b/src/VanDerHeijden.Logging/WebLoggingExtensions.cs
@@ -0,0 +1,126 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace VanDerHeijden.Logging;
+
+/// <summary>
+/// Extension methods for ILogger<T> to log messages with HTTP context information as structured logging scope.
+/// </summary>
+public static class WebLoggingExtensions
+{
+ /// <summary>
+ /// Logs an information message with HTTP context properties added to the logging scope.
+ /// </summary>
+ /// <typeparam name="T">The type of the logging category.</typeparam>
+ /// <param name="logger">The logger instance.</param>
+ /// <param name="context">The current HTTP context. If null, logs without context scope.</param>
+ /// <param name="message">The log message (may contain named format items).</param>
+ /// <param name="args">Arguments for the message format.</param>
+ public static void LogInformationWithContext<T>(
+ this ILogger<T> logger,
+ HttpContext? context,
+ string message,
+ params object?[] args)
+ {
+ LogWithContext(logger, context, LogLevel.Information, message, args);
+ }
+
+ /// <summary>
+ /// Logs an error message with HTTP context properties added to the logging scope.
+ /// </summary>
+ /// <typeparam name="T">The type of the logging category.</typeparam>
+ /// <param name="logger">The logger instance.</param>
+ /// <param name="context">The current HTTP context. If null, logs without context scope.</param>
+ /// <param name="message">The log message (may contain named format items).</param>
+ /// <param name="args">Arguments for the message format.</param>
+ public static void LogErrorWithContext<T>(
+ this ILogger<T> logger,
+ HttpContext? context,
+ string message,
+ params object?[] args)
+ {
+ LogWithContext(logger, context, LogLevel.Error, message, args);
+ }
+
+ /// <summary>
+ /// Logs a warning message with HTTP context properties added to the logging scope.
+ /// </summary>
+ /// <typeparam name="T">The type of the logging category.</typeparam>
+ /// <param name="logger">The logger instance.</param>
+ /// <param name="context">The current HTTP context. If null, logs without context scope.</param>
+ /// <param name="message">The log message (may contain named format items).</param>
+ /// <param name="args">Arguments for the message format.</param>
+ public static void LogWarningWithContext<T>(
+ this ILogger<T> logger,
+ HttpContext? context,
+ string message,
+ params object?[] args)
+ {
+ LogWithContext(logger, context, LogLevel.Warning, message, args);
+ }
+
+ /// <summary>
+ /// Logs a debug message with HTTP context properties added to the logging scope.
+ /// </summary>
+ /// <typeparam name="T">The type of the logging category.</typeparam>
+ /// <param name="logger">The logger instance.</param>
+ /// <param name="context">The current HTTP context. If null, logs without context scope.</param>
+ /// <param name="message">The log message (may contain named format items).</param>
+ /// <param name="args">Arguments for the message format.</param>
+ public static void LogDebugWithContext<T>(
+ this ILogger<T> logger,
+ HttpContext? context,
+ string message,
+ params object?[] args)
+ {
+ LogWithContext(logger, context, LogLevel.Debug, message, args);
+ }
+
+ /// <summary>
+ /// Internal helper that applies HTTP context as logging scope and logs at the specified level.
+ /// </summary>
+ /// <typeparam name="T">The type of the logging category.</typeparam>
+ /// <param name="logger">The logger instance.</param>
+ /// <param name="context">The HTTP context to extract properties from. Can be null.</param>
+ /// <param name="level">The log level to use.</param>
+ /// <param name="message">The log message template.</param>
+ /// <param name="args">Arguments for the message template.</param>
+ private static void LogWithContext<T>(
+ ILogger<T> logger,
+ HttpContext? context,
+ LogLevel level,
+ string message,
+ object?[] args)
+ {
+ if (context == null)
+ {
+ logger.Log(level, message, args);
+ return;
+ }
+
+ string? ip = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
+ string? forwarded = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();
+ if (!string.IsNullOrEmpty(forwarded))
+ {
+ ip = forwarded.Split(',').First().Trim();
+ }
+
+ Dictionary<string, object?> scopeProps = new()
+ {
+ ["RequestMethod"] = context.Request.Method,
+ ["RequestUrl"] = $"{context.Request.Scheme}://{context.Request.Host}{context.Request.Path}{context.Request.QueryString}",
+ ["ClientIp"] = ip,
+ ["TraceId"] = context.TraceIdentifier,
+ ["Referer"] = context.Request.Headers[HeaderNames.Referer].FirstOrDefault() ?? string.Empty,
+ ["UserAgent"] = context.Request.Headers[HeaderNames.UserAgent].FirstOrDefault() ?? string.Empty,
+ ["SessionId"] = context.Session?.Id ?? string.Empty,
+ ["User"] = context.User?.Identity?.Name ?? "anonymous"
+ };
+
+ using (logger.BeginScope(scopeProps))
+ {
+ logger.Log(level, message, args);
+ }
+ }
+}