Injected Serilog does log to Debug, but not File - dependency-injection

I'm trying to use Serilog with DI in my .NET 6 application. I have Serilog configured like this:
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.WriteTo.Debug()
.WriteTo.File(#"D:\log.txt",
retainedFileCountLimit: 5,
rollingInterval: RollingInterval.Day)
.CreateLogger();
and my generic Host using
IHostBuilder host = new HostBuilder().UseSerilog(Log.Logger).Build()
(configuration and service-addidtion not shown here for brevity).
In my service-classes I require an ILogger from the Microsoft.Extensions.Logging Package.
Now, the Problem I have is that I get correct Logging to Debug from everywhere, but the File-Sink only logs when I use Serilogs Log.Debug() Method for example.
If I comment out the "UseSerilog()" on the Hostbuilder I get no logging in Debug also. So Injection of Serilog to ILogger seems to work.
Any Ideas whats happening here ?

So it turns out it was a rather stupid mistake (for anyone interested).
Somehow I thought this was a good Idea:
try {
Log.Debug("Starting Hostbuild");
BuildHost();
}
catch (Exception ex)
{
Log.Fatal(ex, "Hostbuild Failed unexpectedly");
return;
}
finally
{
Log.CloseAndFlush();
}
Which of course reset my Serilog Logger (Log) to default after the Hostbuild was done in BuildHost().
I think I read somewhere that you're supposed to call CloseAndFlush at the end, but then changed where my "end" was...

Related

Why is Serilog logging in Main() but not in Form1

Using Serilog with the MSSQL sink. I am logging OK when I write to the log after defining it in Main(). When I try to write to the log in Form1 form loading event handler it does not log.
The code in Program.cs Main() look like this and works.
var connectionString = #"Data Source=XXX\XXX; Initial Catalog=XXX; User ID=XXX; Password=XXX;";
var sinkOpts = new MSSqlServerSinkOptions();
sinkOpts.TableName = "Logs";
var columnOption = new ColumnOptions();
columnOption.Store.Remove(StandardColumn.MessageTemplate);
Log.Logger = new LoggerConfiguration()
.WriteTo.MSSqlServer(
connectionString: connectionString,
sinkOptions: sinkOpts,
columnOptions: columnOption
).CreateLogger();
Log.Error("nujcsin");
Log.CloseAndFlush();
Code in Form1 loading handler looks like this. It does not log:
private void Form1_Load(object sender, EventArgs e)
{
List<ToolLocationItem> toolList = new List<ToolLocationItem>();
fastObjectListView1.ClearObjects();
fastObjectListView1.Refresh();
FillList();
Log.Information("Testing in Form1");
Log.CloseAndFlush();
}
I have tried replacing "WriteTo" with "AuditTo". I get nothing but my log entry in Main(). My Log.Information is not throwing any errors.
Why is Serilog not logging in Form1?
Edit
Same behaviour when using the file sink. Serilog logs when the logging statement is in the Main procedure but not elsewhere. It seems to be something that Serilog doesn't like rather than the sink.
You have Log.CloseAndFlush() in the code shortly after you initialize your logger. Like the documentation points out, this should be called only once right before the application exits.
Log.CloseAndFlush() will give the sinks a chance to wrap up what they were logging (some sinks are asynchronous) and then kill the logger, so any events logged after that will be lost. It disposes of the logger. So if you call that too soon, you're killing logging in the rest of your app.
You might think that putting it in a variable and then assigning it to the static logger was the solution, but you're misunderstanding what's going on. You haven't shown exactly what you did, but let's say you did this...
var log = new LoggerConfiguration()
.WriteTo.MSSqlServer(
connectionString: connectionString,
sinkOptions: sinkOpts,
columnOptions: columnOption
).CreateLogger();
Log.CloseAndFlush();
Log.Logger = log;
Now you've created a logger, assigned it to a local variable, closed and flushed (destroy/disposed) of the original static logger (there's a default implementation there that does nothing) then assigned the logger from your local variable to be the static logger. The key point is that all of this wouldn't be necessary if you closed and flushed in the proper place in the code, which is right before the program exits.
Wow. A new day and a fresh look.
What I did wrong is the following:
I needed to define the logger like this:
var log = new LoggerConfiguration()
.WriteTo.MSSqlServer(
connectionString: connectionString,
sinkOptions: sinkOpts,
columnOptions: columnOption
).CreateLogger();
and then:
Log.Logger = log;
If I defined my logger like this it did not seem to work outside the Main().
Log.Logger = new LoggerConfiguration()
.WriteTo.MSSqlServer(
connectionString: connectionString,
sinkOptions: sinkOpts,
columnOptions: columnOption
).CreateLogger();

Serilog, Change the loglevel at runtime for a specific namespace (> MinimumLevel)

This is my default Serilog configuration
SeriLogLevelSwitch.MinimumLevel = LogEventLevel.Information;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(SeriLogLevelSwitch)
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
.Enrich.FromLogContext()
....
How can i change the loglevel to Debug for a specific namespace at runtime when the default is Information?
Each of your MinimumLevel.Override can have its own LoggingLevelSwitch, which allows you to control the log level for each particular override at run-time.
Create individual LoggingLevelSwitch for each override that you intend to modify whilst the app is running, and store these instances in a place that you can access from other parts of your application, which will allow you to change the MinimumLevel of these LoggingLevelSwitch(es).
e.g.
public class LoggingLevelSwitches
{
// Logging level switch that will be used for the "Microsoft" namespace
public static readonly LoggingLevelSwitch MicrosoftLevelSwitch
= new LoggingLevelSwitch(LogEventLevel.Warning);
// Logging level switch that will be used for the "Microsoft.Hosting.Lifetime" namespace
public static readonly LoggingLevelSwitch MicrosoftHostingLifetimeLevelSwitch
= new LoggingLevelSwitch(LogEventLevel.Information);
}
Configure your Serilog logging pipeline to use these LoggingLevelSwitch instances:
static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LoggingLevelSwitches.MicrosoftLevelSwitch)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime",
LoggingLevelSwitches.MicrosoftHostingLifetimeLevelSwitch)
.Enrich.FromLogContext()
.CreateLogger();
// ...
}
Then somewhere in your application, for example, in the code that handles your application configuration that can be changed at run-time, update the LoggingLevelSwitch instance(s) to the new LogEventLevel that you want:
public class AppSettings
{
void ChangeLoggingEventLevel()
{
LoggingLevelSwitches.MicrosoftHostingLifetimeLevelSwitch
.MinimumLevel = LogEventLevel.Error;
LoggingLevelSwitches.MicrosoftHostingLifetimeLevelSwitch
.MinimumLevel = LogEventLevel.Warning;
// ...
}
}
As you can see, the LogEventLevel is controlled by the LoggingLevelSwitch instances, so it's up to you to decide where in your application (and how) these instances will be modified, to affect the logging pipeline.
The example above I'm assuming you have a screen (or API) in your application that a user would be able to configure the logging levels.
If you don't have that, then another approach is to have a background thread that periodically checks a configuration file, an environment variable, or query a database, etc. to determine what these logging levels should be.
If you're using a .NET Core Host, you can use the Options pattern which can handle the refresh of the configuration for you, and allow you to execute code when the configuration changes (where you'd change the MinimumLevel of your LoggingLevelSwitch(es) you have.
You can use an environment variable paired with another MinimumLevel.Override to change the loglevel to Debug for a specific namespace at runtime like so:
using System;
...
SeriLogLevelSwitch.MinimumLevel = LogEventLevel.Information;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(SeriLogLevelSwitch)
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
.MinimumLevel.Override(Environment.GetEnvironmentVariable("SPECIFIC_NAMESPACE"), LogEventLevel.Debug)
.Enrich.FromLogContext()
....
Then, ensure the environment variable SPECIFIC_NAMESPACE is accessible by your application at runtime. Note that "namespace" is synonymous with "source context prefix"
Environment.GetEnvironmentVariable

Serilog separate rolling files for each SourceContext

this is my serilog configuration :
Log.Logger = new LoggerConfiguration()
.WriteTo.RollingFile(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
#"Logs\log-{Date}.txt"))
.MinimumLevel.ControlledBy(LogLevelSwitch)
.CreateLogger();
i'm logging like this :
var log = Serilog.Log.ForContext<SomeClassName>();
log.Information("some log text");
My question :
I need a modification in my configuration to create separate logs for each SourceContext.
I need log files somthing like Logs\log-{Date}-{SourceContext}.txt
thanks pros...
One way to implement this is to create your own ILogEventSink implementation (the interface is very simple) that internally dispatches to one of several RollingFileSinks based on the SourceContext property of the LogEvent.
To configure it (let's say you call your sink SourceRollingFileSink) you can use WriteTo.Sink():
Log.Logger = new LoggerConfiguration()
.WriteTo.Sink(new SourceRollingFileSink())
.CreateLogger();
There's no out-of-the-box implementation so some work is required, but it should not be complicated. Be aware that implementations of ILogEventSink need to be thread-safe.

How do I write Serilog events to a .NET collection?

I want to write Serilog Warning events to a .NET collection so they can be included in a report to the user. How do I configure the static Log.Logger to write to something like a static List<string<> ?
I saw the Rx Observer in the list of provided sinks, this was the only one that seemed to readily make .NET objects available or is there an obvious alternative that I missed?
Alternatively, is this a dumb way to do it - is there a better way to collect just Warning events to massage into a user-facing report?
class CollectionSink : ILogEventSink {
public ICollection<LogEvent> Events { get; } = new ConcurrentBag<LogEvent>();
public void Emit(LogEvent le) {
Events.Add(le);
}
}
var col = new CollectionSink();
Log.Logger = new LoggerConfiguration()
.WriteTo.Sink(col, LogEventLevel.Warning)
.CreateLogger();
// read col.Events....
Not sure this one will typecheck but this is essentially how I've done it a few times.
I found the following implementations useful as references:
CollectionSink from the Serilog Timings tests
InMemorySink from Serilog.Sinks.InMemory

How do I stop TopShelf creating multiple log files using log4net?

One of my TopShelf-hosted Windows services is creating duplicate log files that look like this:
myapp.20140729.log
myapp.20140729.log.20140729.log
A similar problem has been described on StackOverflow before - the solutions in 10639682 didn't work for me, but 579688 suggests it could be caused by initialising the logging system twice.
I'm using the TopShelf Log4Net plugin, but I'm also doing my own log4net logging and need access to the ILog instance before the service has actually started, so my code looks like this:
public static void Main(string[] args) {
XmlConfigurator.Configure();
var log = LogManager.GetLogger(typeof(MyService));
var container = new Container();
RegisterComponents(container, log);
log.InfoFormat("Starting MyService");
RunService(container);
}
public static void RunService(Container container) {
HostFactory.Run(x => {
x.Service<PortalAdaptor>(s => {
s.ConstructUsing(name => container.Resolve<MyService>());
s.WhenStarted(f => f.Start());
s.WhenStopped(f => f.Stop());
});
x.RunAsLocalService();
x.SetDescription("My Service");
x.SetDisplayName("My Service");
x.SetServiceName("MyService");
x.UseLog4Net();
});
}
I've done a little digging, and it appears that calling x.UseLog4Net() in my RunService method is actually running XmlConfigurator.Configure() again - see Log4NetLogWriterFactory, line 62 in the TopShelf code.
So...
Am I correct in thinking that calling XmlConfigurator.Configure() twice could be the cause of my duplicate log file issues?
If so - how can I either inject an existing config into TopShelf, OR get the TopShelf initialization to run before I start my service so I can start logging before the service starts?
Call Hostfactory.New instead of HostFactory.Run to get a reference to the host, do your logging, then call the Run() method on the host:
var host = HostFactory.New(configureService);
var log = LogManager.GetLogger(typeof(MyService));
log.InfoFormat("Starting MyService");
host.Run();
An alternative approache would be that once you have done your initial logging, call LogManager.ResetConfiguration to clear the configuration and allow TopShelf to reload it:
Resets all values contained in the repository instance to their
defaults. This removes all appenders from all loggers, sets the level
of all non-root loggers to null, sets their additivity flag to true
and sets the level of the root logger to Debug. Moreover, message
disabling is set to its default "off" value.

Resources