new web logging

alphons <alphons@heijden.com> 2 Mar 2026, 19:26
8ff5fd19e0954bf43602e64e9955dec888bc14e5
4 files changed
  • src/VanDerHeijden.Logging.Web/README.md
  • src/VanDerHeijden.Logging.Web/VanDerHeijden.Logging.Web.csproj
  • src/VanDerHeijden.Logging.Web/WebLoggingExtensions.cs
  • src/VanDerHeijden.Logging.Web/icon.png
diff --git a/src/VanDerHeijden.Logging.Web/README.md b/src/VanDerHeijden.Logging.Web/README.md
new file mode 100644
index 0000000..3feda83
--- /dev/null
+++ b/src/VanDerHeijden.Logging.Web/README.md
@@ -0,0 +1,23 @@
+# VanDerHeijden.Logging.Web
+
+Web log writer for [VanDerHeijden.Logging](https://www.nuget.org/packages/VanDerHeijden.Logging).
+
+Writes batched Web log entries to **daily rotating text files** using async file I/O with a 64 KB write buffer. A new file is opened automatically at midnight without restarting the application.
+
+## Installation
+
+```bash
+dotnet add package VanDerHeijden.Logging.Web
+```
+
+## Usage
+
+```csharp
+builder.Logging.AddFileLogger(logDirectory: "Logs");
+```
+
+
+
+## Repository
+
+[https://github.com/alphons/VanDerHeijden.Logging](https://github.com/alphons/VanDerHeijden.Logging)
diff --git a/src/VanDerHeijden.Logging.Web/VanDerHeijden.Logging.Web.csproj b/src/VanDerHeijden.Logging.Web/VanDerHeijden.Logging.Web.csproj
new file mode 100644
index 0000000..57978ac
--- /dev/null
+++ b/src/VanDerHeijden.Logging.Web/VanDerHeijden.Logging.Web.csproj
@@ -0,0 +1,29 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net10.0</TargetFramework>
+ <ImplicitUsings>enable</ImplicitUsings>
+ <Nullable>enable</Nullable>
+ <PackageId>VanDerHeijden.Logging.Web</PackageId>
+ <Version>10.0.4</Version>
+ <Authors>VanDerHeijden</Authors>
+ <Description>Web log writer for VanDerHeijden.Logging: writes batched log entries to daily rotating text files.</Description>
+ <PackageTags>logging;web;batched</PackageTags>
+ <PackageReadmeFile>README.md</PackageReadmeFile>
+ <GenerateDocumentationFile>true</GenerateDocumentationFile>
+ <PackageProjectUrl>https://github.com/alphons/VanDerHeijden.Logging</PackageProjectUrl>
+ <RepositoryUrl>https://github.com/alphons/VanDerHeijden.Logging.git</RepositoryUrl>
+ <RepositoryType>git</RepositoryType>
+ <PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
+ <PackageIcon>icon.png</PackageIcon>
+ </PropertyGroup>
+
+
+
+ <ItemGroup>
+ <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>
+
+</Project>
diff --git a/src/VanDerHeijden.Logging.Web/WebLoggingExtensions.cs b/src/VanDerHeijden.Logging.Web/WebLoggingExtensions.cs
new file mode 100644
index 0000000..3c55540
--- /dev/null
+++ b/src/VanDerHeijden.Logging.Web/WebLoggingExtensions.cs
@@ -0,0 +1,126 @@
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace VanDerHeijden.Logging.Web;
+
+/// <summary>
+/// Extension methods for ILogger&lt;T&gt; 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);
+ }
+ }
+}
diff --git a/src/VanDerHeijden.Logging.Web/icon.png b/src/VanDerHeijden.Logging.Web/icon.png
new file mode 100644
index 0000000..a0f1fdb
Binary files /dev/null and b/src/VanDerHeijden.Logging.Web/icon.png differ