Trouble converting Serilog Configuration code line to json configuration - c#

I found a post where the blogger explained how to filter by LogEvent level to a separate file for Serilog configuration. I am doing all my Serilog configuration in my appsettings.json. How would this look in json configuration, I can't seem to figure how to json the lambda expression....
Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Logger(l => l.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Warning).WriteTo.RollingFile(#"Logs\Warning-{Date}.log"))
I am using configuration for Serilog from my appsettings.json and am trying to convert this
.WriteTo.Logger(l => l.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Warning).WriteTo.RollingFile(#"Logs\ApplicationName\Serilog\Warning-{Date}.log"))
to json, to include in my Serilog section of my appsettings file
EDIT:
appsettings partial shown here
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] ({SourceContext}) {Message}{NewLine}{Exception}"
}
},
{
"Name": "Seq",
"Args": { "serverUrl": "http://localhost:5341" }
},
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "RollingFile",
"Args": { "pathFormat": "C:/Logs/Serilog/%APPLICATION_NAME%/log-{Date}.log" }
}
]
}
}
],
"SubLogger": {
"Level": "Warnings",
"pathFormat": "C:/Logs/Serilog/%APPLICATION_NAME%/Warnings/log-{Date}.log"
},
The sub logger pathFormat is not producing the same folder naming as the RollingFile pathFormat

For this moment Serilog does not support configuration of sub-loggers through JSON appsettings. See this issue on github.
It's not an easy task actually because you pass Func<LogEvent, bool> to ByIncludingOnly() filter. Mapping configuration data from json file to c# code is not a trivial task.
However if you are just interested in creation of sub-logger for specific log level, you could combine configuration from JSON config with ByIncludingOnly() filter.
Define a POCO that will hold filter configuration:
public class SubLoggerConfiguration
{
public LogEventLevel Level { get; set; }
private string pathFormat;
public string PathFormat
{
get => pathFormat;
set => pathFormat = value.Replace("%APPLICATION_NAME%", Environment.GetEnvironmentVariable("APPLICATION_NAME"));
}
}
Add SubLogger section to your JSON config:
{
"Serilog": {
"Using": [
"Serilog.Sinks.RollingFile"
],
"MinimumLevel": {
"Default": "Information"
},
"WriteTo": [
{
"Name": "RollingFile",
"Args": { "pathFormat": "c:\\Logs\\log-{Date}.log" }
}
],
"SubLogger": {
"Level": "Warning",
"pathFormat": "Logs\\ApplicationName\\Serilog\\Warning-{Date}.log"
}
}
}
It's a good idea to keep it inside native Serilog section, it will not break configuration of Serilog itself.
Then load SubLogger configuration from config file:
IConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("AppSettings.json");
IConfiguration configuration = configurationBuilder.Build();
SubLoggerConfiguration subLoggerConfiguration = new SubLoggerConfiguration();
configuration.GetSection("Serilog:SubLogger").Bind(subLoggerConfiguration);
Note that you have to install Microsoft.Extensions.Configuration.Binder NuGet package for binding configuration to a POCO.
Now subLoggerConfiguration will contain desired log level and path format for the log. You can use this settings to call ByIncludingOnly() filter:
Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.Logger(l => l.Filter.ByIncludingOnly(e => e.Level == subLoggerConfiguration.Level).WriteTo.RollingFile(subLoggerConfiguration.PathFormat));

Related

Serilog not logging to Sentry levels different than Error

I want log to sentry.io Information log level using serilog.
In appsettings.json I made this config:
"Sentry": {
"Dsn": "url",
"MaxRequestBodySize": "Always",
"SendDefaultPii": true,
"IncludeActivityData": true,
"AttachStackTrace": true,
"Debug": true,
"DiagnosticLevel": "Info"
},
"Serilog": {
"Using": [
"Serilog.Sinks.Console"
],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"System": "Error",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Literate, Serilog.Sinks.Console",
"outputTemplate": "[{Timestamp:HH:mm:ss} {Properties} {SourceContext} [{Level}] {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "Sentry",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Literate, Serilog.Sinks.Console",
"outputTemplate": "[{Timestamp:HH:mm:ss} {Properties} {SourceContext} [{Level}] {Message:lj}{NewLine}{Exception}"
}
}
],
"Enrich": [
"FromLogContext",
"WithMachineName",
"WithThreadId",
"WithHtpContextData",
"WithExceptionDetails"
]
}
I registered serilog and sentry into my Program.cs class:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.UseSentry();
})
.UseSerilog((hostingContext, loggerConfig) => loggerConfig.ReadFrom.Configuration(hostingContext.Configuration));
In my class i make code like this:
using System.Threading.Tasks;
using Quartz;
using Serilog;
//
private readonly ILogger _logger;
public QueuedJob(ILogger logger)
{
_logger = logger;
}
public Task Execute(IJobExecutionContext context)
{
_logger.Information("Hello World!");
return Task.CompletedTask;
}
Why from this configuration in sentry.io portal i see only logs that i logged as Error level? Why i cant log into sentry.io Information level? All levels of logs are printed to my console but only Errors are printed into console and sentry.io
By default, the Sentry Serilog integration only sends events for log level Error or higher.
For Info logs, the SDK keeps a ring buffer so when an error happens all related logs are included with that event.
This can be configured though, you can send everything (Debug or higher for example): https://docs.sentry.io/platforms/dotnet/guides/serilog/#configuration
In fact, I use this exact setup on NuGet Trends to capture any Warning or higher as event, and include any Debug or higher as breadcrumb:
Here's the configuration:
https://github.com/dotnet/nuget-trends/blob/dac67d1bd4707a94063b843571127eb055a4cc4f/src/NuGetTrends.Scheduler/appsettings.Production.json#L33-L34

How to overwrite application name specified in appsettings.json when using Serilog?

My appsettings.json looks like this:
{
"Serilog": {
"Using": [ "Serilog.Sinks.File" ],
"MinimumLevel": "Debug",
"WriteTo": [
{
"Name": "File",
"Args": {
"path": "%APPDATA%\\FancyProject\\logs\\RR.log",
"formatter": "Serilog.Formatting.Json.JsonFormatter",
"rollingInterval": "Day",
"retainedFileCountLimit": 20,
"buffered": false
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId", "WithExceptionDetails" ],
"Properties": {
"Application": "SampleName"
}
}
}
Loading the settings:
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
I'd like to use the same config file for multiple projects in my solution but with a different application name in order to distinguish between services.
Is there a way to change the application name ("SampleName" in my config) in code when loading the config?
You can use the following extension method for IConfiguration interface to update the configuration instance after reading it from appsettings.json
public static class Ext
{
public static IConfiguration ApplyAppName(this IConfiguration configuration, string appName)
{
foreach (var (key, _) in configuration.AsEnumerable())
if (key.StartsWith("Serilog") && key.EndsWith("Application"))
configuration[key] = appName;
return configuration;
}
}
And use it in following way (based on
serilog-settings-configuration sample from GitHub) before configuring and creating a logger
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build()
.ApplyAppName("MyApp");
var logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
Alternatively, you can follow Serilog integration for ASP.NET Core 2+ and use UseSerilog() method with the above ApplyAppName extension during CreateHostBuilder call
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); })
.UseSerilog((hostingContext, loggerConfiguration) =>
{
var config = hostingContext.Configuration.ApplyAppName("MyApp");
loggerConfiguration.ReadFrom.Configuration(config);
});
I solved it by removing the Properties section from the config and load it this way:
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.Enrich.WithProperty("ApplicationName", "my application")
.CreateLogger();
PavelAnikhouski's (deleted) answer was actually the correct answer to my question but I think directly adding the correct application name is cleaner than modifying a placeholder.

appSetting.Development.json not injected during Debug on ASP.Net Core App

I have the following in my appSetting.json;
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"MailServiceSettings": {
"SmtpServer": "<server>",
"ToAddresses": [
"email#domain.com",
"email2#domain.com"
],
"UserName": "username",
"Password": "password"
}
}
and in appSettings.Development.json i have a subtle change;
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"MailServiceSettings": {
"SmtpServer": "<server>",
"ToAddresses": [
"development#domain.com"
],
"UserName": "username",
"Password": "password"
}
}
This is so I can text the mail sender settings in my localhost without bombarding the live mailbox.
However, when I run in debug the settings from appSettings.json are being injected insted of the appSettings.Development.json.
My Program.cs is using the default WebHostBuilder;
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args)
.Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
}
and setup the DI as following in my StartUp.cs;
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.Configure<MailServiceSettings>(Configuration.GetSection("MailServiceSettings"));
// In production, the Angular files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
}
Then when I debug and break on the configuration I can see that the appSettings.Development.json have been read (as I can drill down into the Configuration sections when debugging I can see they are added as an additional entry, and I believe WebHost.CreateDefaultbuilder adds the env.EnvironmentName files by default).
However, when I then instantiate an controller method;
public ContactController(IOptions<MailServiceSettings> mailSettings, IHostingEnvironment hostingEnvironment)
{
_mailSettings = mailSettings;
_hostingEnvironment = hostingEnvironment;
}
I am finding that the 2x email address from the appSettings.json are injected instead of the appSettings.Development.json
I have also checked env.IsDevelopment() at runtime and this is returning true.
Can anyone tell me what I am doing wrong here?
I'm having trouble tracking down and official source for this, but essentially the issue is that IConfiguration is basically a dictionary and the keys and values from configuration sources are flattened into that. In other words, at the end of the day, what you're actually getting is something like the following in pseudo-code:
["MailServiceSettings:ToAddresses[0]"] = "email#domain.com"
["MailServiceSettings:ToAddresses[1]"] = "email2#domain.com"
Then, when your appsettings.Development.json config comes in:
["MailServiceSettings:ToAddresses[0]"] = "development#domain.com"
In other words, you still have two items in the config. The only way around this is to have settings like this only in your environment-specific config. If you remove this from appsettings.json entirely and then do:
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"MailServiceSettings": {
"SmtpServer": "<server>",
"UserName": "username",
"Password": "password"
}
}
appsettings.Development.json
{
"MailServiceSettings": {
"ToAddresses": [
"development#domain.com"
]
}
}
appsettings.Production.json
{
"MailServiceSettings": {
"ToAddresses": [
"email#domain.com",
"email2#domain.com"
]
}
}
Then, you'll correctly only have the one address in development and the two in production.

asp.net core - Set Serilog.Exceptions from appsettings.json

I need to use Serilog.Exceptions package to catch exceptions.
Serilog is read from appsettings.json
{
"Serilog": {
"Using": [
"Serilog.Sinks.RollingFile",
"Serilog.Sinks.Seq"
],
"WriteTo": [
{
"Name": "RollingFile",
"Args": {
"restrictedToMinimumLevel": "Debug",
"pathFormat": "myPath\\log-{Date}.log"
}
},
{
"Name": "RollingFile",
"Args": {
"restrictedToMinimumLevel": "Error",
"pathFormat": "myPath\\error-{Date}.log"
}
},
{
"Name": "Seq",
"Args": {
"serverUrl": "myUrl",
"apiKey": "myApiKey"
}
}
],
"Enrich": [
"FromLogContext",
"WithMachineName",
"WithThreadId"
],
"Properties": {
"Application": "myApplicationName"
}
}
}
And in my startup.cs
var logger = new LoggerConfiguration()
.Enrich.WithExceptionDetails()
.ReadFrom.Configuration(Configuration)
.CreateLogger();
Log.Logger = logger;
But it doesn't work. Do I need to add some other properties in appsettings.json for Serilog.Exceptions package? Or is the appsettings.json configure correctly? What am I doing wrong?
Thanks
The following is how I setup Serilog in ASP.NET Core 2.1. You can take it as checklist and see what you have missed.
Install packages from Nuget: Serilog.AspNetCore, Serilog.Exceptions, Serilog.Settings.Configuration and other sinks you want to use.
Setup Serilog in your main program:
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseSerilog((hostingContext, loggerConfiguration) =>
loggerConfiguration
.ReadFrom.Configuration(hostingContext.Configuration)
);
}
Put WithExceptionDetails in your Serilog section in appsettings.json:
{
"Serilog": {
...
"Enrich": [
"FromLogContext",
"WithExceptionDetails"
],
...
}
}

Serilog not getting application events, only coded logger events

I set up my application to use Serilog as the logging mechanism. And I do in fact get log files and can view them on Seq. Initially I was getting the application events logged, but for some reason I am no longer getting them. See the images below.
In the first image I am getting application events. Later, when doing the same testing operations, I am NOT getting the application events any more, only the coded events in the files (i.e. _logger.LogWarning("Warning");)
I set up Serilog in Main method.
public static int Main(string[] args)
{
var currentEnv = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{currentEnv}.json", optional: true)
.AddEnvironmentVariables()
.Build();
//Configure logger
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration)
.CreateLogger();
Log.Information("Logger created");
try
{
Log.Information("Starting web host");
BuildWebHost(args).Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Web Host terminated unexpectedly");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
My Serilog settings
"Serilog": {
"Using": [
"Serilog.Sinks.RollingFile",
"Serilog.Sinks.Async",
"Serilog.Sinks.ApplicationInsights",
"Serilog.Sinks.Console",
"Serilog.Sinks.Seq"
],
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning"
}
},
"WriteTo": [
{
"Name": "Async",
"Args": {
"configure": [
{
"Name": "RollingFile",
"Args": { "pathFormat": "C:/Logs/Serilog/log-{Date}.log" }
},
{
"Name": "Seq",
"Args": { "serverUrl": "http://localhost:5341" }
}
]
}
}
],
"Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId" ],
"Properties": {
"Application": "WebTemplate"
}
}
Before I move on to making Serilog more functional, I would like to sort out why I am not getting application events any longer.
I expect this is down to the following section in your Serilog settings:
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning"
}
}
Nicholas Blumhardt's blog post goes into detail about how the Override setting works:
The effect of the configuration above, then, is to generate events only at or above the Warning level when the logger is owned by a type in a Microsoft.* namespace.
This quote seems to apply directly to your situation, whereby you are asking Serilog to filter out any events that belong to the Microsoft.* namespace and are lower than a severity of Warning.
The messages you show in your first screenshot are coming from either MVC or Entity Framework, which live in the Microsoft.* namespace and will be logging at a lower severity than warning.
It looks like you are missing UseSerilog() in BuildWebHost():
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseSerilog() // <- Add this line
.UseStartup<Startup>()
.Build();
This assumes you have the Serilog.AspNetCore NuGet package installed.

Categories