Tools for logging based on Serilog.
Serilog
stands for configuring by code using fluent API. It's good that library provides functionality to configure
logger without external sources. That's enough in simple cases. But it's not so easy in the cases where you have to use
different configurations for different environments, since there must be code for these environments. Configuring by
external sources is more flexible and useful, easy to change and extend.
KorneiDontsov.Logging
provides functionality to configure logger through
Microsoft.Extensions.Configuration.Abstractions
. Look at example.
appsettings.json
{
"$schema": "https://raw.githubusercontent.com/KorneiDontsov/KorneiDontsov.Logging/v2.0.0/schema/appsettings.json",
"logging": {
"minLevels": {
"default": "information",
"overrides": {
"KorneiDontsov.Logging.Example.ServiceExample": "debug"
}
},
"enrichments": {
"thread": true
},
"profiles": {
"console": {
"type": "console",
"output": [
"[{Level:u3}] {Timestamp} [{SourceContext}]",
"{Message:lj}",
"Properties: {Properties}",
"{Exception}"
]
},
"file": {
"type": "file",
"output": [
"Timestamp: {Timestamp}",
"Level: {Level}",
"SourceContext: {SourceContext}",
"Message: {Message:lj}",
"Properties: {Properties}",
"{Exception}"
],
"path": "logs/{app}_{launchUtc}.log",
"maxSize": 10485760
},
"errorFile": {
"sync": true,
"minLevel": "error",
"type": "file",
"output": [
"Timestamp: {Timestamp}",
"Message: {Message:lj}",
"Properties: {Properties}",
"{Exception}"
],
"path": "logs/errors/{app}_{launchUtc}.log",
"maxSize": 10485760
}
}
}
}
Note: use JSON Schema to learn all parameters that are available to configure. Schema of logging is described in /schema/logging.json.
Program.cs
Host.CreateDefaultBuilder(args)
.UseConfiguredLogger()
.ConfigureSomething()
.Build()
.Run();
UseConfiguredLogger()
registers ILogger
to services and configures it. By default you can write to file and console,
enrich thread id & name. But the functionality can be extended. To add your own components you can implement special
interfaces and register them in DI container:
ILoggingProfileApplier
applies sinks (likeLoggerConfiguration.WriteTo
);ILoggingEnrichmentApplier
applies enrichers (likeLoggerConfiguration.Enrich
);ILoggingFilterApplier
applies filters (likeLoggerConfiguration.Filter
).
You can still configure some components of logger by code:
.UseConfiguredLogger(
conf => conf.Enrich.WithProperty("EnrichedByCode", true))
Also you are available to use the other methods of class ConfiguredLoggingFunctions
to get configured logging on the
platforms where you do not
use Microsoft.Extensions.Hosting.
You can use KorneiDontsov.Logging.ILogger<TSource>
to specify the owner of injected logger by a generic argument:
using Serilog;
using KorneiDontsov.Logging;
class MyService
{
readonly ILogger logger;
public MyService (ILogger<MyService> logger) =>
this.logger = logger;
...
}
It's the equivalent for:
using Serilog;
class MyService
{
readonly ILogger logger;
public MyService (ILogger logger) =>
this.logger = logger.ForContext<MyService>();
...
}
KorneiDontsov.Logging.ILogger<TSource>
inherits from Serilog.ILogger
, so it just helps to reduce boilerplate code
and cache instance of logger in DI container.
To make it work you have to invoke UseGenericLogger()
on your host builder.
CrashLogger
handles unhandled exceptions and unobserved task exceptions, and logs them to global Serilog.Log.Logger
.
To activate CrashLogger
you have to invoke
CrashLogger.Activate();
It can also log a crash to the console or create a file, always or if global logger doesn't accept crash log. By default it:
- always creates a file with a crash log at
logs/crashes
in directory of program; - writes a crash log to the console, if global logger didn't accept it.
The behaviour can be configured by CrashLoggerOptions
. Put the options to the activate method. For example:
CrashLogger.Activate(
new CrashLoggerOptions {
writeToFile = CrashLogWriteCondition.IfGlobalLoggerMissed,
crashLogRootPath = Path.Combine(AppContext.BaseDirectory, "var/crash-logs")
});
It's recommended to activate CrashLogger
before any other code is executed.
AOT platforms (like Unity IL2CPP) usually does not support virtual generic methods where generic parameters are
structs. AotLogger
decorates ILogger
to get around this constraint. For example:
ILogger logger = GetLogger();
// This invocation doesn't work on AOT platforms.
logger.Information<Int32>("One is {One}.", 1);
var aotLogger = new AotLogger(logger);
// This one works.
aotLogger.Information<Int32>("One is {One}.", 1);
You can also use methods of static class AotLog
which repeat methods of Serilog.Log
and uses global Serilog.Log.Logger
.
-
If you develop a program, based on generic host, your
Program.cs
should look like:CrashLogger.Activate(); Host.CreateDefaultBuilder(args) .UseConfiguredLogger() .UseGenericLogger() .ConfigureSomething() .Build() .Run();
-
If you doesn't use generic host, but use some of its components, you can learn
KorneiDontsov.Logging
API to find how to get all functionality you want to get. -
If you want to use Serilog on AOT platforms, you would rather use
AotLogger
andAotLog
.