I currently use Serilog with the Ilogger extension where I log information in my .net core api.
As part of a requirement all logs that have a user registered must include the UserId in its logging.
Currently the user Id is taken from the httpcontext which is injected into a UserContextService.
I now want to extend Serilog or create a Ilogger wrapper which would then pass in my IUserContextService and apply the UserId as part of the logger information.
Here is the code for some context on what I currently do at the moment..
public class UserContextService : IUserContextService
{
private readonly IHttpContextAccessor? _accessor;
public UserContextService(IHttpContextAccessor accessor)
{
_accessor = accessor;
}
public AuthUser GetUserDetails()
{
var userContext = new AuthUser
{
Id = _accessor?.HttpContext?.Request?.HttpContext?
.User?.Claims?.SingleOrDefault(x => x.Type == ClaimTypes.NameIdentifier)?.Value
};
return userContext;
}
calling code
private readonly ILogger<MyService> _logger;
string? userId = _userContextService.GetUserDetails().Id;
_logger.LogWarning("my warning message: {userId}", userId);
I now want to extend Serilog or create a Ilogger wrapper which would then pass in my IUserContextService and apply the UserId as part of the logger information.
You can try to use the following methods:
Using Serilog Enrichers.
Serilog provides LogContext, which allows you to add properties to log events dynamically. You can create middleware that retrieves the UserId from IUserContextService and enriches the logging context. More detail information, see Pushing properties to the ILogger and LogContext.
public class SerilogUserEnricherMiddleware
{
private readonly RequestDelegate _next;
public SerilogUserEnricherMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context, IUserContextService userContextService)
{
var userId = userContextService.GetUserDetails().Id;
if (!string.IsNullOrEmpty(userId))
{
using (LogContext.PushProperty("UserId", userId))
{
await _next(context);
}
}
else
{
await _next(context);
}
}
}
And the AuthUser class as below:
public class AuthUser
{
public string Id { get; set; }
}
Then, in the Program.cs file register the middleware as below:
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console(outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level}] (UserId: {UserId}) {Message}{NewLine}{Exception}")
.CreateLogger();
try
{
Log.Information("Starting web application");
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSerilog(); // <-- Add this line
//...
builder.Services.AddScoped<IUserContextService, UserContextService>();
var app = builder.Build();
app.UseMiddleware<SerilogUserEnricherMiddleware>();
//... other middleware
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
After login success, we can see the user id from the log:
Using a Custom ILogger Wrapper
You can create a wrapper around ILogger to inject User Id:
public interface ICustomLogger
{
void LogInformation(string message, params object[] args);
}
public class CustomLogger : ICustomLogger
{
private readonly ILogger<CustomLogger> _logger;
private readonly IUserContextService _userContextService;
public CustomLogger(ILogger<CustomLogger> logger, IUserContextService userContextService)
{
_logger = logger;
_userContextService = userContextService;
}
public void LogInformation(string message, params object[] args)
{
var userId = _userContextService.GetUserDetails().Id ?? "Unknown";
_logger.LogInformation("UserId: {UserId} - " + message, userId, args);
}
}
Register the Custom Logger:
builder.Services.AddScoped<ICustomLogger, CustomLogger>();
Finally, inject and use ICustomLogger instead of ILogger in your services/controllers.
public class HomeController : Controller
{
private readonly ICustomLogger _customlogger;
public HomeController(ICustomLogger customLogger)
{
_customlogger = customLogger;
}
public IActionResult Index()
{
_customlogger.LogInformation("Custom logger information");
return View();
}
The result as below: