How to use Serilog in .NET Core Console app - c#

I wanted my application to have capability of logging to a file, so I started to look for something more than default .NET Core 2.2 logging framework. I see that Serilog might do the job. However, I cannot find any document on how to setup Serilog in .NET Core Console application with Dependency Injection. All I see is ASP.NET materials, which is probably not what I need.
I started doing it myself. I installed (Nuget):
Serilog
Serilog.Extensions.Logging
Serilog.Sinks.File
Serilog.Sinks.Console (to use Serilog for all my logging)
I created an extension forServiceCollection
public static void AddLogging(this IServiceCollection services, Microsoft.Extensions.Logging.LogLevel logLevel)
{
var serilogLogger = new LoggerConfiguration()
.WriteTo.Console()
.WriteTo.File("log.txt")
.CreateLogger();
services.AddLogging(builder =>
{
builder.SetMinimumLevel(logLevel);
builder.AddSerilog(logger: serilogLogger, dispose: true);
});
}
Logging works, however:
log level is not what I set it to. It seems that serilog is using INFO level, although I wanted to have DEBUG. Why isn't my setting respected? After all, I'm still using NET Core's logging framework, so I'm using it to setup the log level
am I actually doing this setup correctly? I am not really sure if dispose should be true. Generally, I want NET Core's Dependency Injection framework to take care of disposal of services.

I'm not sure about builder.SetMinimumLevel (it doesn't use the Serilog enum).
We set the logger level when creating the LoggerConfiguration object.
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console(restrictedToMinimumLevel: LogEventLevel.Debug) // restricted... is Optional
(...)
.CreateLogger();
BTW. It's worth pointing out the following section from Configuration Basics
Logger vs. sink minimums - it is important to realize that the logging level can only be raised for sinks, not lowered. So, if the logger's MinimumLevel is set to Information then a sink with Debug as its specified level will still only see Information level events. This is because the logger-level configuration controls which logging statements will result in the creation of events, while the sink-level configuration only filters these. To create a single logger with a more verbose level, use a separate LoggerConfiguration.
I'm not sure about builder.AddSerilog.
Here's what works for me.
using Serilog;
(...)
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
(...)
.CreateLogger();
(...)
return new HostBuilder()
.ConfigureHostConfiguration(...)
.ConfigureServices(...)
.UseSerilog();

Related

What is the difference between AddSerilog and UseSerilog in .NET 6 Web API?

I'm trying to configure Serilog for a Web API project in .NET 6.
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
builder.Logging.ClearProviders();
builder.Logging.AddSerilog(Log.Logger);
//builder.Host.UseSerilog(Log.Logger);
What behavior difference is there between adding Serilog to the logging pipeline and setting Serilog as the logging provider? Should I call both methods?
There is a huge difference. 🙂
The .AddSerilog() provider adds a Serilog provider as one of potentially many providers. With the following configuration, the Microsoft logger will first log to the Console provider, then to the Serilog provider:
.ConfigureLogging(logging => logging.AddConsole().AddSerilog())
The .UseSerilog() configures Serilog as the only provider. The following will send all logs to Serilog regardless of whether you've configured the logging pipeline:
.UseSerilog();
The difference really boils down to using Micosoft's pluggable model or using Serilog's pluggable model.
Typically you wouldn't use .AddSerilog() as the Serilog library is really intended to be used as the sole provider with one or more "sinks", but there may be cases where you need to log to a particular destination for which there exists a Micosoft ILogger and ILoggerProvider, but for which no Serilog sink exists (and you don't want to have to write it yourself). In such cases, you might choose to add Serilog as an additional provider.
There is no difference, only syntax varies. However you need to ensure that if you read from configuration then there is no need to mention enrichers or files in the declaration, else you might end up creating two log file output:
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext() // No need
.WriteTo.Console() // No need
.WriteTo.File("Logs/Log.txt") // No Need
.CreateLogger();
With intellisense it is easier to define the configuration in code rather than in appsetting.json. As opposed to configuration you can detect error in this method quite easily. Hence better use :
Log.Logger = new LoggerConfiguration()
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.File("Logs/Log.txt")
.CreateLogger();

.NET DependencyInjection and Serilog without using IHost (for console app)

When you look at solutions for Dependency Injection and Serilog on .NET projects, most documentation and blogs refers to the IHost model. It does not matter much if for current .NET 6 or other versions of .NET Core.
This is my favorite way for applications providing REST APIs since .NET Core 2.1.
I first defined a BuildLogger() method to configure Serilog with min log level and add all my enrichers/sinks I need.
So my Program.Main method used to look like:
Log.Logger = LoggerBuilder.BuildLogger();
var builder = Host.CreateDefaultBuilder(args).UseSerilog(); // serilog injected in Host.
builder.ConfigureServices(services => services.AddMyServices());
using var source = new CancellationTokenSource();
await builder.Build().RunAsync(source.Token).ConfigureAwait(false);
All fine.
Question is: how to do reuse DI and Serilog without using the IHost model from .NET 6?
My today's user case if for a console app which is only running a few tests and then close, and still reusing some parts I wrote for my previous apps.
So instead of the above, I defined that
Log.Logger = LoggerBuilder.BuildLogger();
await using var serviceProvider = new ServiceCollection()
.AddMyServices()
.AddLogging(logBuilder => logBuilder.AddSerilog()) // attach serilog.
.BuildServiceProvider()
RunTests(serviceProvider);
This seems to work as expected.
The trick with AddLogging/AddSerilog() instead of UseSerilog() might be helpful for others!

Is there a way to detect if Serilog's Log.Logger has already been configured?

I am using Serilog in both a class library and a .NET console application (C#). The logger is currently configured in the console application:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.File(
"verboseLog.txt",
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Verbose,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
)
.CreateLogger();
I would like to conditionally configure it in the library if the caller has not configured it yet. (This library may be used with other applications in the future by other programmers.) Serilog's wiki on Github doesn't mention any default value for Log.Logger, and I haven't found an answer anywhere else. Is there a way to detect if the static Log.Logger has already been configured in Serilog?
Configuring the logging system in a library is not a good idea. Logging configuration is something you want to do at the application level. I'd wager that there's a good reason you can't know if the logger is configured or not: because you don't need to know! 😉
With that said, here's a (dirty) solution to figure out if the logger is configured or not:
bool isLoggerConfigured = Log.Logger.GetType().Name != "SilentLogger";
Note that this solution relies on an implementation detail (the name of the silent logger internal class) that might change in the future.

Retarget logs for a specific class

I have a .NET 3.1 app.
While I'm fine with it logging information to console in general, there is one part (API client) where it generates tons of logs and it is too much for console. How to make it so that for a particular class logs go not to console but to a file?
Create 2 loggers?
Log.Logger = new LoggerConfiguration()
.WriteTo.File(#"MyFile.txt")
.WriteTo.Logger(lc => lc
.Filter.ByExcluding(Matching.FromSource<ApiClient>())
.WriteTo.Console())
.CreateLogger();
Now everything will get written to MyFile.txt but any logs from inside ApiClient won't get output to console
Another option, in case Filter gets a little complicated, is WriteTo.Conditional, which has extra support in Serilog.Expressions:
dotnet add package serilog.expressions
Then:
Log.Logger = new LoggerConfiguration()
.WriteTo.File("MyFile.txt")
.WriteTo.Conditional(
"SourceContext <> 'MyNamespace.ApiClient'",
wt => wt.Console())
.CreateLogger();

Serilog WCF usage without constructors?

I am new to c# and I am trying to using serilog in a WCF application. This WCF application is hosted on IIS 6.0.
I need to create the logger once only when the service is initialized. However WCF does not seem to have constructors. How can I create the logger below once only and have it available to the rest of the application. Any code examples would be greatly appreciated.
ILogger logger = new LoggerConfiguration()
.ReadAppSettings()
.CreateLogger();
Log.Logger = logger;
I thought about using the AppInitialize in the App_Code folder but I cannot seem to get the program to trigger the AppInitialize method.
public class InitializeApp
{
public static void AppInitialize()
{
ILogger logger = new LoggerConfiguration()
.ReadAppSettings()
.CreateLogger();
Log.Logger = logger;
}
}
When I run the project in Visual Studio 2019 AppInitialize never gets hit.
First, you can do the constructor with WCF Service via some Dependency Inject library which supports WCF Service
You can use AutoFac.WCF then you can register type of ILogger by Autofac
builder.Register<ILogger>((c, p) =>
{
return new LoggerConfiguration()
.ReadAppSettings()
.CreateLogger();
}).SingleInstance();
Second, About the "InitializeApp" of the application. there are couple ways to do it.
Add Global.asax file and then you can write the code in Application_Start
Use Owin Middleware to write own pipeline. You can write your code in Startup.cs
Use WebActivator, you can write PreApplicationStartMethod and PostApplicationStartMethod (extension for Global.asax)

Categories

Resources