I'm working on an asp.net core 6.0 project and I'm trying to set up Serilog using the two stage initialization as described in the docs to achieve the following:
- On development environment both the boostrap and the final logger should log to console and file.
- On production both loggers should only log to file.
Bootstrap conditioned execution is working fine since all is done in code. For the final logger I want to rely on the appsettings.development.json and appsettings.json files respectively for each environment to achieve the aforementioned requirements.
However, I have the practice of using dotnet watch run when developing and had the issue where if the console sink use a custom output template, the line with the URL comes between double quotes and the browser doesn't get launched:
2022-09-14 13:44:20.076 [INF] Now listening on: "https://localhost:5001"
What I did was to simple remove the Args parameter in appsettings.development.json from "WriteTo":[{"Name":"Console"}] section, but the console output still comes with a custom format I have no idea where is it coming from. I found out the adding WriteTo.Console() on UseSerilog() method fixes the issue, but this enables the console logging on both environments.
The bootstrap logger has no custom format and the output comes as the default:
[14:10:19 INF] Starting the application executable.
The final logger outputs use the default format if I add WriteTo.Console() and remove it from configuration file but if not, it comes with different format:
2022-09-14 14:10:19.938 [INF] Launching application.
How can I achieve all of these without adding environment-checking code when creating the final logger?
Program.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Events;
using System;
using System.Threading.Tasks;
namespace CWA.Web
{
public class Program
{
public static async Task Main(string[] args)
{
// Program initialization logging
SetBootstrapLogger();
try
{
Log.Information("Starting the application executable.");
using var host = CreateHostBuilder(args).Build();
Log.Information("Launching application.");
await host.RunAsync();
// When stopping
Log.Information("Process stopped manuanlly or after idle time.");
}
catch (Exception any)
{
Log.Fatal($"Process stopped unexpectedly: {any.Message} {any.StackTrace}");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
// Use default builder
var builder = Host.CreateDefaultBuilder(args);
// Configure the Web Host
builder.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
// Add application logging
builder.UseSerilog((hostContext, services, configuration) => configuration
.ReadFrom.Configuration(hostContext.Configuration)
.ReadFrom.Services(services));
return builder;
}
private static void SetBootstrapLogger()
{
var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var configuration = new LoggerConfiguration();
string logPath;
if (environmentName is not null && environmentName.ToString().ToUpper() == "DEVELOPMENT")
{
// Development
configuration.WriteTo.Console();
logPath = @"X:\Testing\app-log-.txt";
}
else
{
// Production
configuration.MinimumLevel.Override("Microsoft", LogEventLevel.Warning);
logPath = @"E:\Logs\Web\app-log-.txt";
}
// Complete
configuration
.WriteTo.File(path: logPath, rollingInterval: RollingInterval.Day, outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message}{NewLine}")
.Enrich.FromLogContext();
Log.Logger = configuration.CreateBootstrapLogger();
}
}
}
appsettings.development.json
{
// Logging
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Information",
"System": "Information"
}
},
"WriteTo": [
{
"Name": "Console"
},
{
"Name": "File",
"Args": {
"path": "X:\\Testing\\app-log-.txt",
"rollingInterval": "Day",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message}{NewLine}"
}
}
],
"Enrich": [ "FromLogContext" ]
}
}