using Microsoft.AspNetCore.Http.Features; using Microsoft.Net.Http.Headers; using Serilog.Core; using Serilog.Events; namespace SeriLoog.Extensions; /// /// Enriches every log event with request context, but only when one actually /// exists (i.e. we're inside an HTTP request). Background work, startup logs /// and anything logged outside a request simply won't get these properties. /// public sealed class HttpContextEnricher : ILogEventEnricher { private readonly IHttpContextAccessor _httpContextAccessor; public HttpContextEnricher(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory) { var context = _httpContextAccessor.HttpContext; if (context is null) return; var request = context.Request; var ip = context.Connection.RemoteIpAddress?.ToString() ?? "unknown"; var forwarded = request.Headers["X-Forwarded-For"].FirstOrDefault(); if (!string.IsNullOrEmpty(forwarded)) { ip = forwarded.Split(',').First().Trim(); } var sessionId = context.Features.Get() is not null ? context.Session.Id : string.Empty; Set(logEvent, propertyFactory, "ClientIp", ip); Set(logEvent, propertyFactory, "RequestUrl", $"{request.Scheme}://{request.Host}{request.Path}{request.QueryString}"); Set(logEvent, propertyFactory, "TraceId", context.TraceIdentifier); Set(logEvent, propertyFactory, "Referer", request.Headers[HeaderNames.Referer].FirstOrDefault() ?? string.Empty); Set(logEvent, propertyFactory, "UserAgent", request.Headers[HeaderNames.UserAgent].FirstOrDefault() ?? string.Empty); Set(logEvent, propertyFactory, "SessionId", sessionId); Set(logEvent, propertyFactory, "User", context.User?.Identity?.Name ?? "anonymous"); } private static void Set(LogEvent logEvent, ILogEventPropertyFactory propertyFactory, string name, string value) { logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(name, value)); } }