Serilog topshelf integration not working - c#

I'm trying to set up a simple logging configuration for my Windows service using Topshelf and Serilog (the Serilog.Extras.Topshelf package respectively).
HostLogger.UseLogger(new SerilogHostLoggerConfigurator(new LoggerConfiguration()
.WriteTo.RollingFile(AppDomain.CurrentDomain.BaseDirectory + "\\logs\\app-{Date}.log")
.WriteTo.ColoredConsole()
.MinimumLevel.Debug()
.CreateLogger()));
HostFactory.Run(x => {
x.UseSerilog();
...
The service runs fine, however no output is made, neither to the console nor the specified log file (I can see that one is being created but it remains empty). Has anyone experience using both frameworks?

The second call "x.UseSerilog()" resets TopShelf's HostLogger to use Serilog's global instance (Log.Logger), which you have not configured.
Remove the second call and the logging should start working.
Another option is to configure the global logger:
Log.Logger = new LoggerConfiguration()
.WriteTo.RollingFile(AppDomain.CurrentDomain.BaseDirectory + "\\logs\\app-{Date}.log")
.WriteTo.ColoredConsole()
.MinimumLevel.Debug()
.CreateLogger();
HostFactory.Run(x => {
// configure TopShelf to use Serilog's global instance.
x.UseSerilog();
}

Related

How to write logs to a specific sink depending on the developement environment in Serilog?

I'm using Serilog with Azure Functions v4 and .NET 7. I am trying to write logs to both the local console and Application Insights. I already spent hours trying different solutions and the situation at the time of writing seems to be that this technology stack (Azure Functions + Application Insights) is not mature or too recent, and not yet very well documented.
Setting up appsettings.{env}.json files by environment and trying to add a different Serilog config by environment does not work with Azure Functions. Depending on the solution I tried, either Microsoft logger is used or there are no logs.
The code below is working for the console but I get duplicate logs on Application Insights. And if I remove Console sink, it works well in Application Insights but I get no more local logs:
.UseSerilog((hostBuilderContext, serviceProvider, loggerConfiguration) =>
loggerConfiguration
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.ApplicationInsights(
serviceProvider.GetRequiredService<TelemetryConfiguration>(),
TelemetryConverter.Traces)
)
I also tried other ways of setting up the logger, for example in .ConfigureServices(..., injecting it differently in the function code, but no luck. Many samples talk about a Startup method that I don't have. If it helps I can post the whole Program.cs and the function code here.
So I now added .Enrich.WithEnvironmentName() to get the environment name from an environment variable, and the question is: how can I filter the logs using ByIncludingOnly in the code above (not config file) to include logs depending on the environment name (Local or Development)? Same question for ByExcluding - I guess answer will be similar.
This is the way to do it. I ended up not using the Environment enricher because it is at the time of writing not supporting the Azure environment variable for Azure Functions (AZURE_FUNCTIONS_ENVIRONMENT). See this issue:
https://github.com/serilog/serilog-enrichers-environment/issues/49
This is how I got this working:
First option enriching the log event:
// Careful: Serilog is not able to enrich with the value of this variable and will default to 'Production'
var value = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") ?? "Development";
var predicate = $"EnvironmentName = 'Local'";
Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("EnvironmentName", value)
.MinimumLevel.Debug()
.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(predicate)
.WriteTo.Console(outputTemplate: "{Properties} {Message}"))
.WriteTo.Logger(lc => lc
.WriteTo.File(path: Directory.GetCurrentDirectory() + #"\test.txt"))
.CreateLogger();
Second option without enriching the log event:
var value = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") ?? "Development";
var predicate = $"{value} = 'Local'";
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Logger(lc => lc
.Filter.ByIncludingOnly(predicate)
.WriteTo.Console(outputTemplate: "{Properties} {Message}"))
.WriteTo.Logger(lc => lc
.WriteTo.File(path: Directory.GetCurrentDirectory() + #"\test.txt"))
.CreateLogger();

Configuring Serilog before configuration is ready?

I have a web api (.NET Core 3.1) which is using Serilog for logging. Serilog is added to the IWebHostBuilder quite early:
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
return WebHost
.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseSerilog((context, configuration) =>
{
if (context.HostingEnvironment.IsDevelopment())
{
configuration.WriteTo.Console(LogEventLevel.Debug);
return;
}
configuration.WriteTo.ApplicationInsights(TelemetryConverter.Traces, LogEventLevel.Error);
});
}
This means (afaik) that I need to have already configured the logger at this point. So this is the very first thing I do in the main:
public static int Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(Configuration)
.CreateLogger();
var host = CreateWebHostBuilder(args).Build();
host.Run();
}
But the line .ReadFrom.Configuration(Configuration) requires the configuration to be set up. This is usually done in the StartUp (again, afaik) which has not yet been run at this time. Obviously I could move my LoggerConfiguration to later, but the .UseSerilog would be called before it was configured.
So how do I configure Serilog with IConfugration, when I haven't set it up yet?
#RubenBartelink pointed to a very good ressource in comment.
This is also described in the Serilog for ASP.NET Core documentation.
In particular the two-stage initialization part, which states:
Two-stage initialization
The example at the top of this page shows how to configure Serilog immediately when the application starts.
This has the benefit of catching and reporting exceptions thrown during set-up of the ASP.NET Core host.
The downside of initializing Serilog first is that services from the ASP.NET Core host, including the appsettings.json configuration and dependency injection, aren't available yet.
To address this, Serilog supports two-stage initialization. An initial "bootstrap" logger is configured immediately when the program starts, and this is replaced by the fully-configured logger once the host has loaded.
To use this technique, first replace the initial CreateLogger() call with CreateBootstrapLogger():
using Serilog;
using Serilog.Events;
public class Program
{
public static int Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateBootstrapLogger(); // <-- Change this line!
Then, pass a callback to UseSerilog() that creates the final logger:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.WriteTo.Console())
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
It's important to note that the final logger completely replaces the bootstrap logger: if you want both to log to the console, for instance, you'll need to specify WriteTo.Console() in both places, as the example shows.
Consuming appsettings.json configuration
Using two-stage initialization, insert the ReadFrom.Configuration(context.Configuration) call shown in the example above. The JSON configuration syntax is documented in the Serilog.Settings.Configuration README.

How to access Log4Net after adding it to Dependency Injection

This is my first time working with Microsoft.Extensions.Logging.Log4Net.AspNetCore.
I have a WPF application where I add the Log4Net provider within the CreateDefaultBuilder in App method of App.xaml.cs. Immediately after, I want to write to the log file using LogInformation saying "Starting Application". From what I can tell, it does not seem like I can do this because of the way it has been added to the Dependency Injection container. I must either call a method that accesses the DI container in the parameter list or add Log4Net to the ServiceProvider and then retrieve the service that way. But, that does not seem right because I will effectively have Log4Net added to DI twice.
Is there a way to immediately access the DI container after configuring Log4Net so I can write to the log file?
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
.ConfigureAppConfiguration((context, appBuilder) =>
{
// Do some stuff here
}).ConfigureLogging(logBuilder =>
{
logBuilder.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
logBuilder.AddLog4Net("log4net.config");
})
.ConfigureServices((context, services) =>
{
Configuration = context.Configuration;
ConfigureServices(Configuration, services);
})
.Build();
ServiceProvider = Host.Services;
// How to access Log4Net here?
_ISomeLogger.LogInformation("Starting Application");
Update
I am using the following example: https://www.thecodebuzz.com/logging-using-log4net-net-core-console-application/
The solution is to access the Log4Net from the ServiceProvider right after building it:
logger = (ILogger<Program>)ServiceProvider.GetService(typeof(ILogger<Program>));

Serilog stops writing to file

I'm using Serilog to log Errors in my WebApi with the following configuration.
private static readonly string LogFileName = System.Web.Hosting.HostingEnvironment.MapPath(#"~/App_Data/Logs/Service.log");
private static void ConfigureLogging(ContainerBuilder builder)
{
var loggerConfig = new LoggerConfiguration()
.ReadFrom.AppSettings()
.WriteTo.Console()
.WriteTo.File(LogFileName != null ? LogFileName : "C:/Temp/Logs/Service.log",
shared: true,
rollingInterval: RollingInterval.Day);
// configure global logger for logging in owin middleware
Log.Logger = loggerConfig.CreateLogger();
builder.Register(_ => Log.Logger).As<ILogger>(); }`
With Appsettings
<appSettings>
<add key="serilog:minimum-level" value="Warning" />
</appSettings>
The problem is, it only logs once. The Service is still alive, I can send more Successful requests.
And I also see the requests/errors logged in console. But it doesnt add up in logfile.
BUT if I edit the WebConfig - change from Warning to Verbose then back to Warning, so no real changes - it logs again.
For Debugging and diagnostic use this selflog which might show you the extact error or problem your application is facing. Check this here https://github.com/serilog/serilog/wiki/Debugging-and-Diagnostics

serilog multiple instance per functionality

While I have a general purpose logger for errors, exceptions etc that use rolling file sink to log stuff to my-{date}.log.
However, I need another instance for audit to audit-{date}.log and another instance to write perf info to perf-{date}.log.
How do I create multiple instances of serilog with different configuration or sink?
You can do this like following:
var loggerConfiguration = new LoggerConfiguration()
.MinimumLevel.Verbose()
.Enrich.FromLogContext();
var fileBasePath = "<base log path>";
loggerConfiguration
.WriteTo.Console()
.WriteTo.RollingFile(fileBasePath + "log-info-{Date}.txt")
.WriteTo.Logger(fileLogger => fileLogger
.MinimumLevel.Error()
.WriteTo.RollingFile(fileBasePath + "log-error-{Date}.txt"))
.WriteTo.Logger(fileLogger => fileLogger
.Filter.ByIncludingOnly(x =>
x.Level == LogEventLevel.Information &&
x.Properties.ContainsKey("<Audit Key>"))
.WriteTo.RollingFile(fileBasePath + "log-audit-{Date}.txt"));
Log.Logger = loggerConfiguration.CreateLogger();
Note: Audit Key is a key which used in audit log
I assume you would need different interface name to write audit and perf data (to be injected to your main business code). If that's the case just implement your IAudit, IPerfData with a new Serilog instance which you can totally customize where/how you save the log.

Categories

Resources