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));
}
}