I'm creating my own SeriLog sink implementing ILogEventSink
using the Building a Simple Sink example with the intent of logging some information from the users claims. To get access to HttpContext in Core, I'd normally inject in an instance of IHttpContextAccessor
but the example shows creating an instance of the sink in an extension method e.g.
public class MySink : ILogEventSink
{
private readonly IFormatProvider _formatProvider;
public MySink(IFormatProvider formatProvider)
{
_formatProvider = formatProvider;
}
public void Emit(LogEvent logEvent)
{
// How to get HttpContext here?
}
}
public static class MySinkExtensions
{
public static LoggerConfiguration MySink(
this LoggerSinkConfiguration loggerConfiguration,
IFormatProvider formatProvider = null)
{
return loggerConfiguration.Sink(new MySink(formatProvider));
}
}
... then to use the sink ...
var log = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.MySink()
.CreateLogger();
How can I get access to the current HttpContext in the Emit method of the sink? Or is it possible to have the sink created by the DI framework for example?!
I have an MVC site running Asp.Net Core 2 framework against .Net 4.6.2 runtime using Serilog.AspNetCore v2.1.0.
Update - Workaround
After the pointer from @tsimbalar I created middleware similar to the code below. In my StartUp.Configure
method I add it using app.UseMiddleware<ClaimsMiddleware>();
after the app authentication step has happened (otherwise there will be no claims loaded).
public class ClaimsMiddleware
{
private static readonly ILogger Log = Serilog.Log.ForContext<ClaimsMiddleware>();
private readonly RequestDelegate next;
public ClaimsMiddleware(RequestDelegate next)
{
this.next = next ?? throw new ArgumentNullException(nameof(next));
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext == null) throw new ArgumentNullException(nameof(httpContext));
// Get values from claims here
var myVal = httpContext
.User
.Claims
.Where(x => x.Type == "MyVal")
.Select(x => x.Value)
.DefaultIfEmpty(string.Empty)
.SingleOrDefault();
using (LogContext.PushProperty("MyVal", myVal))
{
try
{
await next(httpContext);
}
// Never caught, because `LogException()` returns false.
catch (Exception ex) when (LogException(httpContext, ex)) { }
}
}
private static bool LogException(HttpContext httpContext, Exception ex)
{
var logForContext = Log.ForContext("StackTrace", ex.StackTrace);
logForContext.Error(ex, ex.Message);
return false;
}
}