I have create a simple Serilog sink project that looks like this :
namespace MyApp.Cloud.Serilog.MQSink
{
public class MessageQueueSink: ILogEventSink
{
private readonly IMQProducer _MQProducerService;
public MessageQueueSink(IMQProducer mQProducerService)
{
_MQProducerService = mQProducerService;
}
public void Emit(LogEvent logEvent)
{
_MQProducerService.Produce<SendLog>(new SendLog() { LogEventJson = JsonConvert.SerializeObject(logEvent)});
}
}
}
The consuming microservice are starting up like this :
var configurationBuilder = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var appSettings = configurationBuilder.Get<AppSettings>();
configurationBuilder = new ConfigurationBuilder().AddJsonFile("ExtendedSettings.json").Build();
Host.CreateDefaultBuilder(args)
.UseMyAppCloudMQ(context => context.UseSettings(appSettings.MQSettings))
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration))
.ConfigureServices((hostContext, services) =>
{
services
.AddHostedService<ExtendedProgService>()
.Configure<MQSettings>(configurationBuilder.GetSection("MQSettings"))
})
.Build().Run();
The serilog part of appsettings.json looks like this :
"serilog": {
"Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Console", "MyApp.Cloud.Serilog.MQSink" ],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId" ],
"WriteTo": [
{
"Name": "MessageQueueSink",
"Args": {}
}
]
}
The MQSink project is added as reference to the microservice project and I can see that the MQSink dll ends up in the bin folder.
The problem is that when executing a _logger.LogInformation(...) in the microservice the Emit are never triggered, but if I add a console sink it will output data? I also suspect that the injected MQ will not work properly?
How could this be solved?
EDIT :
Turned on the Serilog internal log and could see that the method MessageQueueSink could not be found. I did not find any way to get this working with appsetings.json so I started to look on how to bind this in code.
To get it working a extension hade to be created :
public static class MySinkExtensions
{
public static LoggerConfiguration MessageQueueSink(
this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration,
MyApp.Cloud.MQ.Interface.IMQProducer mQProducer = null)
{
return loggerConfiguration.Sink(new MyApp.Cloud.Serilog.MQSink.MessageQueueSink(mQProducer));
}
}
This made it possible to add the custom sink like this :
Host.CreateDefaultBuilder(args)
.UseMyAppCloudMQ(context => context.UseSettings(appSettings.MQSettings))
.ConfigureServices((hostContext, services) =>
{
services
.Configure<MQSettings>(configurationBuilder.GetSection("MQSettings"))
})
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration).WriteTo.MessageQueueSink())
.Build().Run();
The custom sink is loaded and the Emit is triggered but I still do not know how to inject the MQ in to the sink? It would also be much better if I could do all the configuration of the Serilog and sink in the appsettings.json file.
@pfx help with how to load the Serilog custom sink and for this I gave him the bounty, Thanks! It was however not the final solution in my case.
Dependency Injections means that the constructor of the class will take necessary objects as parameters. While it’s not possible to load a custom sink with constructor parameters it has to be done through the service.
public static IHost CreateHostBuilder(string[] args)
=> Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services
.AddTransient<IMyService, MyService>()
.AddTransient<ICommunicator, Communicator>()
.AddTransient<ILogEventSink, CustomSerilogSink>();
})
.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.WriteTo.Console(LogEventLevel.Information)
.Enrich.FromLogContext())
.Build();
}
The problem was that the MQ controller class had a ILogger injected and this created a circular ref resulting in frees. The MQ controller is shared so I could not change it too much, but I could make the needed methods static. Instead of using local MQ objects they was instead sent into the method.
I could however not remove the MS ILogger requirement and I still needed to be able to log within the custom sink. So first I created a Serilog like this:
private static readonly ILogger _logger = Log.ForContext<MessageQueueSink>() as ILogger;
This logger could then be wrapped in a MS Ilogger class and sent to the static producer method :
public class CustomSerilogger : Microsoft.Extensions.Logging.ILogger
{
private readonly ILogger _logger;
public CustomSerilogger(ILogger logger)
{ _logger = logger; }
public IDisposable BeginScope<TState>(TState state) => default!;
public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) { return _logger.IsEnabled(LogLevelToLogEventLevel(logLevel)); }
public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
return;
_logger.Write(LogLevelToLogEventLevel(logLevel), exception, state.ToString());
}
private LogEventLevel LogLevelToLogEventLevel(Microsoft.Extensions.Logging.LogLevel loglevel)
{
switch(loglevel)
{
case Microsoft.Extensions.Logging.LogLevel.Debug:
return LogEventLevel.Debug;
case Microsoft.Extensions.Logging.LogLevel.Information:
return LogEventLevel.Information;
case Microsoft.Extensions.Logging.LogLevel.Warning:
return LogEventLevel.Warning;
case Microsoft.Extensions.Logging.LogLevel.Error:
return LogEventLevel.Error;
case Microsoft.Extensions.Logging.LogLevel.Critical:
return LogEventLevel.Fatal;
case Microsoft.Extensions.Logging.LogLevel.None:
return LogEventLevel.Verbose;
case Microsoft.Extensions.Logging.LogLevel.Trace:
return LogEventLevel.Verbose;
}
return LogEventLevel.Verbose;
}
}
To configurate the sink I had to create a Custom Sink settings part in the appsettings.json file and then read this into a settings object.
var mqSinkSettings = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build()
.GetSection("MessageQueueSinkSettings").Get<MessageQueueSinkSettings>();
Finally it works!