serilog / serilog-extensions-logging-file

Add file logging to ASP.NET Core apps in one line of code.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

No Way To Flush Logger At End Of Console App Lifetime

jwbats opened this issue · comments

There doesn't seem to be a way to flush the logger's contents to file at the end of the lifetime of a console app.

To reproduce, create a dotnet core console app and log a few lines. Then let the program run to its end. More often than not, it won't log anything.

Add a Console.ReadKey() to the end of your program, and you'll see this will make the logger flush itself, because it's granted some time to do so.

The Log.CloseAndFlush() method is not available when you are only using the file logging extension method.

Even if it were, it would need to have a logger assigned to Log.Logger. But its type is not compatible with dotnet core's logger type as used in this snippet:

serviceCollection
	.AddLogging()
	.AddSingleton(
		new LoggerFactory()
			.AddFile(@"Logs/myapp-{Date}.txt")
			.AddConsole()
			.AddDebug()
);

It would be nice if the Serilog logger could flush itself on finalize.

I believe this is how log4net does it, since I never had any problems with that.

Hi @jwbats - thanks for the post.

I don't think AddSingleton() will work here - the LoggerFactory needs to be disposed on shut-down.

(https://github.com/aspnet/DependencyInjection/pull/462/files#diff-6b915bb687ac71e08ec5097464dfb306R105)

Are you using .NET Core 1.x or 2.0? Cheers!

I'm using .NET Core 1.x.

Since singletons scoped instances won't be disposed, I've rewritten my code to use a transient scoped instance.

serviceCollection
	.AddLogging()
	.AddTransient<ILoggerFactory>(
		(x) => { return new LoggerFactory()
			.AddFile(@"Logs/myapp-{Date}.txt")
			.AddConsole()
			.AddDebug();
		}
);

The AddTransient() method doesn't take a logger instance directly. It will only accept a function that returns one, hence the lambda.

The logger works, but it still won't always flush when the console app runs to its end. Unless I've got a Console.ReadKey() to postpone the apps's lifetime end, ofcourse.

From a github issue that you participated in, I've learned that the .NET team won't provide a file logger itself. That's probably why you wrote this one. I would really like to get this working correctly, so that this can become my go-to file logger for .NET Core!

Thanks for the follow-up. I don't think transient instances are what you're after - the loggers will perform and behave much better when there's only a single instance per app run.

If it's possible, I'd try just manually disposing the singleton logger factory when the app exits (register it with the container, but keep an instance around for disposal).

(Using an IoC container with more lifetime control options might help here.)

HTH!

I am now keeping an instance of the logger around on which I call the Dispose() method at the end of the program.

This flushes everything every single time. Problem solved. Thanks!

Great to hear, thanks for closing the loop 👍

Using a dispose on my servicesProvider flushed the Console log for my .Net Core 2.0 console app

var builder = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json");
            IConfiguration config = builder.Build();

var services = new ServiceCollection()
                .AddSingleton(provider => config)
                .AddTransient(...)
services.AddLogging(configure => configure.AddConsole());
ServiceProvider serviceProvider = services.BuildServiceProvider();
// ...
serviceProvider.Dispose();

The following example dont work for me

 var services = new ServiceCollection().AddLogging(builder =>
                        {
                             builder.AddSerilog(new LoggerConfiguration()
                                .Enrich.WithProperty("ApiVersion", "1.2.5000")
                                .WriteTo.File(Path.GetFullPath($"logs/{@event.id}/default.log"), buffered: true)
                                .WriteTo.AzureTableStorageWithProperties(storage, storageTableName: "runs", keyGenerator: new IKeyGen(@event.id.ToString("n")))
                                .CreateLogger());

                        });
                    
                        // create logger factory
                        using (var sp = services.BuildServiceProvider())
                        {
                           //WRITE TO LOGS
                        }


                        await llogBlob.UploadFromFileAsync($"logs/{@event.id}/default.log"); 



I use the workaround: at the very end of the application, I give it some time to get its logging done:

Thread.Sleep(5000);

I don't like this, but it works.

@LeslieMurphy

Using a dispose on my servicesProvider flushed the Console log for my .Net Core 2.0 console app

I just ran into this very same problem again when building a console app. I'm happy to report that this solution also works for me.

Checkout the last line in the following:

public static void Main(string[] args)
{
    Console.Title = "App name here";
    ConfigureSerilog();

    try
    {
        Log.Information("Starting web host");
        CreateWebHostBuilder(args).Build().Run();
    }
    catch (Exception ex)
    {
        Log.Fatal(ex, "Host terminated unexpectedly");
    }
    finally
    {
        Log.CloseAndFlush(); // <<<------ FLUSHES LOG
    }
}

Just fancy way:


var builder = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json");
            IConfiguration config = builder.Build();

using (var services = new ServiceCollection())
{
               services.AddSingleton(provider => config);
               services.AddTransient(...);
               services.AddLogging(configure => configure.AddConsole());
               ServiceProvider serviceProvider = services.BuildServiceProvider();
// ...

}

or

var builder = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json");
            IConfiguration config = builder.Build();

await using var services = new ServiceCollection()
                .AddSingleton(provider => config)
                .AddTransient(...)
services.AddLogging(configure => configure.AddConsole());
ServiceProvider serviceProvider = services.BuildServiceProvider();
// ...

var service = serviceProvider.GetService<ISomeService>();
var result = await service .SomeAsync();

Is there a way to solve this by avoiding an await Task.Delay(TimeSpan.FromSeconds(2)) before returning the exit code?
Without that line all the logs will be lost, especially on first run

@yankarinRG disposing the service provider appears to be the way to go