I have a React app that uses a .NET 7 API. We are using Graylog for logging. The idea is to provide an API endpoint for the react app to POST its error logs. I want to use a different log source if the errors are coming from the web app. In order to do it, I have written a middleware class based on the route of the request. If /logs
route is hit, I add a new logger configuration to the Logger factory.
Middleware class:
using Gelf.Extensions.Logging;
namespace MyProject1.Api.Extensions
{
public class LoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
private readonly ILoggerFactory _loggerFactory;
public LoggingMiddleware(
RequestDelegate next,
IConfiguration configuration,
ILoggerFactory loggerFactory)
{
_next = next;
_configuration = configuration;
_loggerFactory = loggerFactory;
}
public async Task Invoke(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/logs"))
{
var gelfOptions = new GelfLoggerOptions();
_configuration.GetSection("Logging:WebAppGELF").Bind(gelfOptions);
gelfOptions.AdditionalFields["machine_name"] = Environment.MachineName;
_loggerFactory.AddGelf(gelfOptions);
await _next(context);
}
else
{
await _next(context);
}
}
}
}
Here's the logging section in appsettings.json
:
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
},
"GELF": {
"Host": "xxx.graylog.com",
"LogSource": "API"
},
"WebAppGELF": {
"Host": "xxx.graylog.com",
"LogSource": "WebApp"
}
}
The configuration in program.cs
:
app.UseMiddleware<LoggingMiddleware>();
using (var serviceScope = app.Services.CreateScope())
{
var services = serviceScope.ServiceProvider;
var logger = services.GetRequiredService<MyProject1.Logging.IMyLogger<MyProject1.Api.Extensions.Project1Exception>>();
app.ConfigureExceptionHandler(logger);
}
With the above, two log entries are being written every time the /logs
endpoint is hit. Content is the same but one log entry contains LogSource : API
and the other contains LogSource : WebApp
. I assume this is happening because the logger factory contains two loggers and both are being invoked.
What's the right way of doing this? Basically, I want to use different loggers or logging configurations based on the controller or class the logger is being used in.
I was able to create a new logger by creating a new logger factory in the controller's constructor. This controller will be called by the web app hence controller specific configuration would do the job.
public class LogsController : Controller
{
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
public LogsController(IConfiguration configuration)
{
_configuration = configuration;
_logger = LoggerFactory
.Create(configure => configure.AddGelf(m =>
{
m.Host = "graylog.xxxxx.com";
m.LogSource = _configuration["LoggingWebApp:GELF:LogSource"] ?? "webapp_local";
m.AdditionalFields["Project"] = "my_project";
m.AdditionalFields["Environment"] = _configuration["Logging:GELF:AdditionalFields:Environment"];
m.AdditionalFields["Module"] = "webapp";
}))
.CreateLogger("webapp");
}
[HttpPost("error")]
public void Error([FromBody] WebAppLogErrorRequest model)
{
var userId = User.FindFirst("sub")?.Value;
_logger.LogError(
"Message: {message}\n"
+ "File Name: {FileName}\n"
+ "Function Name: {FunctionName}\n"
+ "Stack Trace: {StackTrace}\n"
+ "User Id: {UserId}",
model.Message, model.FileName, model.FunctionName, model.StackTrace, userId ?? "");
}
}