Configuring Serilog before configuration is ready? - c#

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.

Related

Application Insights not storing ILogger<> - messages

I have a .net 5 web application that uses Application Insights. I try to log into AI trace by using ILogger<>. However: When analyzing the "traces" - Content in AI on Azure the logs are not shown.
Part of StartUp:
services.AddLogging(loggingbuilder =>
{
loggingbuilder.AddFilter<ApplicationInsightsLoggerProvider>("", LogLevel.Trace);
loggingbuilder.AddApplicationInsights();
});
services.AddApplicationInsightsTelemetry();
The constructor of the class that should do the logging injects ILogger and AppInsights via dependency injection:
public ImportService(ILogger<ImportService> log, TelemetryClient telemetryClient)
{
_log = log;
_telemetryClient = telemetryClient;
}
Inside the method I have the following two logging attempts:
public async Task<Customer> UpdateCustomerByEmail(string email)
{
_telemetryClient.TrackTrace("From Telemetry");
_log.LogWarning("From Log");
[...]
}
While the first one ("from Telemetry") ends up correctly in AI-traces, the second one ("From Log") never shows up there.
The instrumentationkey is stored in the appsettings (and obviously correct because the telemetryClient-Track is working)
Might this documentation be relevant for you? Adding the following code to program.cs worked for me:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddApplicationInsights("<instrumentationKeyHere>");
logging.AddFilter<Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider>("", LogLevel.Information);
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
The nuget package Microsoft.Extensions.Logging.ApplicationInsights must also be installed.
In your case it might be sufficient to simply provide the Instrumentation Key as a paramter to the AddApplicationInsights function.
As stated in the documentation "This code is required only when you use a standalone logging provider. For regular Application Insights monitoring, the instrumentation key is loaded automatically from the configuration path ApplicationInsights: Instrumentationkey."
This might explain why regular monitoring works, but not for logging.

Custom Serilog config possible from Program.cs?

I'm generating a custom config in ASP.NET Core during the Startup constructor, something like this:
public Startup(IWebHostEnvironment environment, IConfiguration configuration) {
Environment = environment;
// Add extra config - configs added later override ones added earlier when key names clash
Configuration = new ConfigurationBuilder()
.AddConfiguration(configuration)
.AddXmlFile("appsettings.xml")
.Build();
}
My problem is that, while this new IConfiguration can be accessed from ConfigureServices and Configure, it can't be accessed from the .UseSerilog() call in CreateHostBuilder (Program.cs). So, I can't access my XML config at the time of my call:
webBuilder
.UseStartup<Startup>()
.UseSerilog((context, config) => {
config.ReadFrom.Configuration(context.Configuration);
});
How can I get around this? Can I make the new configuration available to the UseSerilog() lambda, or can I configure the logger later, in ConfigureServices()?
OK, I found out I can just .UseConfiguration() earlier in the IWebHostBuilder chain (instead of in the Startup constructor) to pull in my XML config settings:
webBuilder
.UseConfiguration(new ConfigurationBuilder().AddXmlFile("appsettings.xml").Build())
.UseSerilog((context, config) => {
config.ReadFrom.Configuration(context.Configuration);
})

I don't want to type directly the key of application insight to register log

I don't want to type the key of application insight in the Program.cs, Can I type it in some file of configurations or in another place? It's a ASP .Net Core
I want to include a register in application insights of my logs but with modifications. Now I have a register, but I am typing the key in the Program.cs and I have "a problem" when I change of environment. Do you know any way to type this key dynamicly on the Program.cs or can I do this declaration in another place of the program.
This is the Program.cs. It started with the Main and after it starts the BuildWebHost where I load the key of applications insights and it's what I want to change:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging(logging =>
{
logging.AddApplicationInsights("db0fe38d-c208-8ed7-23e4ef4479bb");
// Optional: Apply filters to configure LogLevel Trace or above is sent to
// ApplicationInsights for all categories.
logging.AddFilter<ApplicationInsightsLoggerProvider>("", LogLevel.Trace);
// Additional filtering For category starting in "Microsoft",
// only Warning or above will be sent to Application Insights.
logging.AddFilter<ApplicationInsightsLoggerProvider>("Microsoft", LogLevel.Warning);
}).Build();
How I said, I would avoid to type key on the Program and I would like take this param from a config file or type this declaration in another place
Since it is an ASP.NET Core application, you can use the ConfigureLogging extension method that injects the WebHostBuilderContext to retrieve your configuration:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging((hostingContext, logging) =>
{
var appInsightKey = hostingContext.Configuration["MyAppInsight"];
logging.AddApplicationInsights(appInsightKey);
// Optional: Apply filters to configure LogLevel Trace or above is sent to
// ApplicationInsights for all categories.
logging.AddFilter<ApplicationInsightsLoggerProvider>("", LogLevel.Trace);
// Additional filtering For category starting in "Microsoft",
// only Warning or above will be sent to Application Insights.
logging.AddFilter<ApplicationInsightsLoggerProvider>("Microsoft", LogLevel.Warning);
}).Build();
Just add UseApplicationInsights(), then remove the instrumentation key(assume the instrumentation key set in the appsettings.json).
Sample code as below, and works well at my side:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseApplicationInsights() // add this line of code, and it will auto-read ikey from appsettings.json.
.UseStartup<Startup>()
.ConfigureLogging(logging =>
{
//then you can remove instrumentation key from here.
logging.AddApplicationInsights();
logging.AddFilter<ApplicationInsightsLoggerProvider>("", LogLevel.Trace);
logging.AddFilter<ApplicationInsightsLoggerProvider>("Microsoft", LogLevel.Warning);
}).Build();

DbContext connection string and hosted service

I have a. NET core console app that implement IHostedService and a reference to another project with a DbContext definition.
This is the configuration of DbContext in console app:
IHost host = new HostBuilder()
.ConfigureHostConfiguration(configHost =>
{
configHost.SetBasePath(Directory.GetCurrentDirectory());
configHost.AddEnvironmentVariables(prefix: "ASPNETCORE_");
configHost.AddCommandLine(args);
})
.ConfigureAppConfiguration((hostContext, configApp) =>
{
configApp.SetBasePath(Directory.GetCurrentDirectory());
configApp.AddEnvironmentVariables(prefix: "ASPNETCORE_");
configApp.AddJsonFile($"appsettings.json", true);
configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", true);
configApp.AddCommandLine(args);
})
.ConfigureServices((hostContext, services) =>
{
services.AddDbContext<MyAppDbContext>(options => options.UseNpgsql(hostContext.Configuration.GetConnectionString("DefaultConnection")));
services.AddHostedService<ApplicationLifetimeHostedService>();
})
.Build();
Now, in the OnStarted() method of ApplicationLifetimeHostedService I have:
using (var _context = new MyAppDbContext())
{
...
_context.SaveChanges();
}
Why MyAppDbContext take the connection string value from OnConfiguring method of dbcontext definition class (hard-coded, generated from scaffolding), and not from appsettings.{ASPNETCORE_ENVIROMENT}.json ()?
Thank you in advance!
Based on your configuration, currently the IHostBuilder is for non web applications and simulates a generic configuration, eventually this will replace the IWebHostBuilder. However, you do not need those... In your instance you would be better off with CreateDefaultBuilder.
Host Configuration
App Configuration
Both are provided by default, with more granular control. The primary item is the default services provided by the builder and what they compile or build.
To directly answer your issue though, in your top line, you are missing the following:
var host = new HostBuilder()
.ConfigureHostConfiguration(configuration =>
{
// For brevity, removed some.
configuration.AddJsonFile("appsettings.json", false, true);
}
That is why your appsettings.json is not working. The ConfigureHostConfiguration will carry through to the ConfigureAppConfiguration.
Host configuration automatically flows to app configuration
(ConfigureAppConfiguration and the rest of the app).
No providers are included by default. You must explicitly specify
whatever configuration providers the app requires in
ConfigureHostConfiguration, including:
File configuration (for example, from a hostsettings.json file).
Environment variable configuration.
Command-line argument configuration.
Any other required configuration providers.

Serilog topshelf integration not working

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();
}

Categories

Resources