Giter Club home page Giter Club logo

serilog-extensions-hosting's Introduction

Serilog Build status NuGet Version NuGet Downloads Stack Overflow

Serilog is a diagnostic logging library for .NET applications. It is easy to set up, has a clean API, and runs on all recent .NET platforms. While it's useful even in the simplest applications, Serilog's support for structured logging shines when instrumenting complex, distributed, and asynchronous applications and systems.

Serilog

Like many other libraries for .NET, Serilog provides diagnostic logging to files, the console, and many other outputs.

using var log = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.File("log.txt")
    .CreateLogger();

log.Information("Hello, Serilog!");

Unlike other logging libraries, Serilog is built from the ground up to record structured event data.

var position = new { Latitude = 25, Longitude = 134 };
var elapsedMs = 34;

log.Information("Processed {@Position} in {Elapsed} ms", position, elapsedMs);

Serilog uses message templates, a simple DSL that extends .NET format strings with named as well as positional parameters. Instead of formatting events immediately into text, Serilog captures the values associated with each named parameter.

The example above records two properties, Position and Elapsed, in the log event. The @ operator in front of Position tells Serilog to serialize the object passed in, rather than convert it using ToString(). Serilog's deep and rich support for structured event data opens up a huge range of diagnostic possibilities not available when using traditional loggers.

Rendered into JSON format for example, these properties appear alongside the timestamp, level, and message like:

{"Position": {"Latitude": 25, "Longitude": 134}, "Elapsed": 34}

Back-ends that are capable of recording structured event data make log searches and analysis possible without log parsing or regular expressions.

Supporting structured data doesn't mean giving up text: when Serilog writes events to files or the console, the template and properties are rendered into friendly human-readable text just like a traditional logging library would produce:

09:14:22 [INF] Processed {"Latitude": 25, "Longitude": 134} in 34 ms.

Upgrading from an earlier Serilog version? Find release notes here.

Features

  • Community-backed and actively developed
  • Format-based logging API with familiar levels like Debug, Information, Warning, Error, and so-on
  • Discoverable C# configuration syntax and optional XML or JSON configuration support
  • Efficient when enabled, extremely low overhead when a logging level is switched off
  • Best-in-class .NET Core support, including rich integration with ASP.NET Core
  • Support for a comprehensive range of sinks, including files, the console, on-premises and cloud-based log servers, databases, and message queues
  • Sophisticated enrichment of log events with contextual information, including scoped (LogContext) properties, thread and process identifiers, and domain-specific correlation ids such as HttpRequestId
  • Zero-shared-state Logger objects, with an optional global static Log class
  • Format-agnostic logging pipeline that can emit events in plain text, JSON, in-memory LogEvent objects (including Rx pipelines) and other formats

Getting started

Serilog is installed from NuGet. To view log events, one or more sinks need to be installed as well, here we'll use the pretty-printing console sink, and a rolling file set:

dotnet add package Serilog
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File

The simplest way to set up Serilog is using the static Log class. A LoggerConfiguration is used to create and assign the default logger, normally in Program.cs:

using Serilog;

Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.File("log.txt",
        rollingInterval: RollingInterval.Day,
        rollOnFileSizeLimit: true)
    .CreateLogger();

try
{
    // Your program here...
    const string name = "Serilog";
    Log.Information("Hello, {Name}!", name);
    throw new InvalidOperationException("Oops...");
}
catch (Exception ex)
{
    Log.Error(ex, "Unhandled exception");
}
finally
{
    await Log.CloseAndFlushAsync(); // ensure all logs written before app exits
}

Find more, including a runnable example application, under the Getting Started topic in the documentation.

Getting help

To learn more about Serilog, check out the documentation - you'll find information there on the most common scenarios. If Serilog isn't working the way you expect, you may find the troubleshooting guide useful.

Serilog has an active and helpful community who are happy to help point you in the right direction or work through any issues you might encounter. You can get in touch via:

We welcome reproducible bug reports and detailed feature requests through our GitHub issue tracker; note the other resource are much better for quick questions or seeking usage help.

Contributing

Would you like to help make Serilog even better? We keep a list of issues that are approachable for newcomers under the up-for-grabs label (accessible only when logged into GitHub). Before starting work on a pull request, we suggest commenting on, or raising, an issue on the issue tracker so that we can help and coordinate efforts. For more details check out our contributing guide.

When contributing please keep in mind our Code of Conduct.

Detailed build status

Branch AppVeyor
dev Build status
main Build status

Serilog is copyright ยฉ Serilog Contributors - Provided under the Apache License, Version 2.0. Needle and thread logo a derivative of work by Kenneth Appiah.

serilog-extensions-hosting's People

Contributors

andrewlock avatar angularsen avatar cbersch avatar ch1sel avatar gfoidl avatar jskimming avatar kuraiandras avatar maximrouiller avatar mburumaxwell avatar merbla avatar mv10 avatar nblumhardt avatar sungam3r avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

serilog-extensions-hosting's Issues

Extensibility opportunity for IDiagnosticContext?

Hi there!

Lately I've been thinking about IDiagnosticContext (further from a question I raised on gitter), and how it might be possible to make use of it outside the context of ASP.NET. I thought I'd open this issue to get the conversation started!

It might be worth thinking about this problem in terms of the SerilogTimings library, as it could be a consumer of a more general IDiagnosticContext.

In an ideal world, it might be possible to do this:

var username = "George";
using var operation = Operation.Begin("Updating phone number for {Username}", username);

try {
  // do something
  _diagnosticContext.Set("PhoneNumberValidated", true);

  operation.Complete();
} catch(Exception e) {
  operation.Abandon(e);
}

Which would emit the log:

{
    "@t": "2021-05-28T15:49:51.6627798Z",
    "@mt": "Updating phone number for {Username} {Outcome} in {Elapsed:0.0} ms",
    "@m": "Updating phone number for George completed in 0.0 ms",
    "@i": "c376adfc",
    "@r": ["0.0"],
    "Username": "George",
    "PhoneNumberValidated": true,
    "Outcome": "completed",
    "Elapsed": 0.0001
}

So what would it take to get here? I'm probably going to be pretty far off reality, but from my crude reading of the code:

  • There needs to be some way to initiate an AmbientDiagnosticContextCollector.Begin to start collecting ambient properties. It feels like this mechanism could handle nested situations where a collection is already in progress (e.g. an operation within another, or operation in an MVC controller). Perhaps if there is a collection already in progress this could be a noop, but maybe you'd want to exclude properties added outside the current call context.
  • Could this be implemented as an enricher that pulls from the ambient collector? It's a standard way of adding properties, so maybe there could be a way to get an enricher from an IDiagnosticContext to pass to a log event or Operation.Complete()?

To provide a real use case, I'd like to implement an IDiagnosticContext-like mechanism around our message handlers to reduce our log volume and increase cardinality, but currently there's no way to control the lifecycle of this collector so I'd need to implement it all myself.

I'd be keen to push this forward, but I'm a bit unsure of what the implementation of this might look like. What are peoples thoughts?

Thanks!

UseSerilog makes rabbitmq client unable to receive messages

I have interesting issue using rabbitmq using generic host

running IHostedService and if i use UseSerilog(with configuration etc) rabbit doesnt catch message (most of the time, sometimes at start it does get a single message)

using following (tried using all 3 versions of hosting)

Positional format parameters not correct

When I use the Serilog extensions then my positional format parameters are incorrectly formatted.

The following example:

` class Program
{

    public static void Main(string[] args)
    {
        
        var builder = Host.CreateDefaultBuilder(args);

        builder
            .ConfigureServices((context, services) =>
            {
                services.AddHostedService<Test>();
            })
            .UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration
                .MinimumLevel.Information()
                .WriteTo.Console())
            .UseConsoleLifetime()
            .Build().Run();
    }

    class Test : IHostedService
    {
        private readonly ILogger<Test> _logger;

        public Test(ILogger<Test> logger)
        {
            _logger = logger;
        }
        public Task StartAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine(".NET Core string.Format {1}/{0}", "second", "first");
            _logger.LogInformation("SeriLogExtensions {1}/{0}", "second", "first");
            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }
    }
}`

Note that the format string does not have the positional parameters in the same order as the arguments.

The .NET Console writeline will print first/second, but when using the serilog extensions I have the reverse.

Expected:
.NET Core string.Format first/second
[15:30:57 INF] SeriLogExtensions first/second

Actual:
.NET Core string.Format first/second
[15:30:57 INF] SeriLogExtensions second/first

.NET Standard 2.1 target makes development pre-releases unusable w/ .NET Framework

I would like to use the new "bootstrap logger" feature in the dev releases, but I noticed it only targets .NET Standard 2.1. Is it possible for this library to keep targeting .NET Standard 2.0?

I maintain an internal library project that provides hosting and logging configuration boilerplate to several application projects, including a few still targeting .NET Framework 4.8. I'm not able to upgrade these legacy projects to .NET 5 yet, and .NET Framework 4.8 is not compatible with .NET Standard 2.1.

[Question] What package should be used for .NET Core background service?

I have a question regarding this line:

ASP.NET Core applications should consider using Serilog.AspNetCore instead, which bundles this package and includes other ASP.NET Core-specific features.

from Readme.md

How about .NET Core 3.x Console application? What package should be used?
How about .NET Core 3.x Background Worker project? What package should be used?

DiagnosticContext doesn't use the logger created by LoggerConfiguration

When using the

public static IHostBuilder UseSerilog(
    this IHostBuilder builder,
    Action<HostBuilderContext, IServiceProvider, LoggerConfiguration> configureLogger,
    bool preserveStaticLogger = false,
    bool writeToProviders = false)

extension and calling it with preserveStaticLogger: true, the ILogger, which is created by LoggerConfiguration, is not used by DiagnosticContext.
I would expect, the logger instance itself to be always passed to DiagnosticContext.

Logs not flushing

Hi,
I am using the UsingSerilog extension to configure logging in a GenericHostBuilder service, which uses ElasticSearch. However, the logs are not being flushed on service start up errors.

Below is some code to illustrate what I am doing.

A generic base for hosted services that includes logging, just a simple: starting, started, and error wrapper (as best I can see logging isn't provided out the box, please correct me if I am wrong :-)).

    public abstract class AbstractHostedService : IHostedService
    {
        private readonly ILogger _logger;
        private readonly string _serviceName;

        protected AbstractHostedService(ILogger logger, string serviceName)
        {
            _logger = logger;
            _serviceName = serviceName;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            try
            {
                _logger.LogInformation("Starting service {ServiceName}...", _serviceName);

                await DoStartAsync(cancellationToken);

                _logger.LogInformation("Service {ServiceName} started.", _serviceName);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Service {ServiceName} failed to start.", _serviceName);
                throw;
            }
        }

        protected abstract Task DoStartAsync(CancellationToken ct);

        public async Task StopAsync(CancellationToken cancellationToken)
        {
            try
            {
                _logger.LogInformation("Stopping service {ServiceName}...", _serviceName);

                await DoStopAsync(cancellationToken);

                _logger.LogInformation("Stopped {ServiceName} started.", _serviceName);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Service {ServiceName} failed to stop.", _serviceName);
                throw;
            }
        }

        protected abstract Task DoStopAsync(CancellationToken ct);

Startup configuration, MyService inherits AbstractHostedService.

        static int Main(string[] args)
        {
            try
            {
                return (int)CreateHostBuilder(args).RunAsTopshelfService(s =>
                {
                    // Service config
                });
            }
            finally
            {
                Log.CloseAndFlush();
            }
        }

        public static IHostBuilder CreateHostBuilder(string[] args)
        {
            return new HostBuilder()
                .ConfigureAppConfiguration((hostingContext, config) => config
                 // Config file setup
                 )
                .ConfigureServices((h, s) => s
                    .AddHostedService<MyService>()
                    // Services setup
                 )
                // ElasticSearch configured using appSettings.json
                .UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration
                    .ReadFrom.Configuration(hostingContext.Configuration)
                    .WriteTo.ColoredConsole());
        }

As you can see I've enclosed the host start in a try/finally that forces a flush. My understanding, having a brief look over the code here, is the flushing should be happening automatically for me. If I don't force a flush then I don't seem to receive any error logs in ElasticSearch (but they do come through in the console logging), so my only solution is the force flush without preserving the static logger. I suppose it could be an indication of a problem with the ES sink, but I figured this seemed like a good starting point.

Am I doing something wrong or is there a problem here?

Thanks,
Adam

Logs from within UseSerilog() not printed

Hello!

So, I configure Log.Logger initially in Main() and this works up until and then after, but not inside of UseSerilog(IHostBuilder, Action<HostBuilderContext, IServiceProvider, LoggerConfiguration>, bool, bool). I think it has to do with the previous sinks not being flushed before the new configuration is applied because when using the Console and File sinks without the Async sink, the Console sink outputs as expected, yet the File sink still does not. I am referring to log inside the if statement within UseSerilog() (and the regular WriteLine() does print).

The output appears in both desired sinks when preserveStaticLogger is set to true, but this isn't an ideal solution because the logs for the File sink are split across two separate files.

I'm not sure if this is something that simply not supported, a problem on my part, or a bug in UseSerilog().

Here's my Program.cs file, as most of it is relevant:

internal static class Program
{
    private const string LogTemplate = 
        "[{Timestamp:HH:mm:ss} {Level:u3}] ({SourceContext}) {Message:lj}{NewLine}{Exception}";

    private static ILogger _log;

    private static async Task Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration().ConfigureLogger().CreateBootstrapLogger();
        _log = Log.ForContext(typeof(Program));
        
        try
        {
            _log.Information("Starting...");
            await CreateHostBuilder(args).Build().RunAsync();
        }
        catch (Exception e)
        {
            _log.Fatal(e, "Terminating: unhandled exception");
        }
        finally
        {
            _log.Information("Shutting down...");
            Log.CloseAndFlush();
        }
    }

    private static LoggerConfiguration ConfigureLogger(this LoggerConfiguration config,
        LoggingLevelSwitch logLevel = null,
        bool seqEnabled = false,
        string seqUrl = "",
        string seqToken = "",
        string template = LogTemplate)
    {
        config ??= new LoggerConfiguration();
        logLevel ??= new LoggingLevelSwitch(LogEventLevel.Debug);
        
        if (seqEnabled && (seqUrl == string.Empty || seqToken == string.Empty))
            throw new ArgumentException($"{nameof(seqUrl)} and/or {nameof(seqToken)} is empty while {nameof(seqEnabled)} is {true}");

        return config
            .Enrich.WithEnvironmentName()
            .Enrich.FromLogContext()
            .Enrich.WithProperty("ProjectName", "PlaylistCopy.Api")
            .MinimumLevel.ControlledBy(logLevel)
//                .MinimumLevel.Override("System", LogEventLevel.Information)
            .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
            .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
//                .WriteTo.Async(a => a.Console(outputTemplate: template))
            .WriteTo.Console(outputTemplate: template)
//                .WriteTo.Async(a => a.File(
            .WriteTo.File(
                new CompactJsonFormatter(),
                "logs/playlist-copy-api-.log",
                rollingInterval: RollingInterval.Day)
            .EnableProductionOnlySinks(seqEnabled, seqUrl, seqToken);
    }

    private static LoggerConfiguration EnableProductionOnlySinks(this LoggerConfiguration config,
        bool production,
        string seqUrl,
        string seqToken) =>
        production ? config.WriteTo.Seq(seqUrl, apiKey: seqToken) : config;

    private static IHostBuilder CreateHostBuilder(string[] args) => Host
        .CreateDefaultBuilder(args)
        .ConfigureServices((context, services) =>
        {
            var logConfig = context.Configuration.GetSection("Logging");
            var selectedLogLevel = logConfig.GetValue("Level", defaultValue: LogEventLevel.Information);
            services.AddSingleton(new LoggingLevelSwitch(selectedLogLevel));
            _log.Information("test");
        })
        .UseSerilog(preserveStaticLogger: false, configureLogger: (context, services, configureLog) =>
        {
            var logConfig = context.Configuration.GetSection("Logging");
            var seqConfig = logConfig.GetSection("Seq");
            var seqEnable = seqConfig.GetValue("Enable", defaultValue: false);
            var seqUrl = seqConfig.GetValue("Url", defaultValue: string.Empty);
            var seqToken = seqConfig.GetValue("Token", defaultValue: string.Empty);
            var logLevel = services.GetRequiredService<LoggingLevelSwitch>();

            if (seqEnable && (string.IsNullOrWhiteSpace(seqUrl) || string.IsNullOrWhiteSpace(seqToken)))
            {
                _log.Warning("Seq sink disabled: required parameter empty");
                Console.WriteLine("this prints");
                seqEnable = false;
            }

            configureLog.ConfigureLogger(logLevel, seqEnable, seqUrl, seqToken);
        })
        .ConfigureWebHostDefaults(webBuilder => webBuilder
            .UseStartup<Startup>()
            .UseKestrel()
            .ConfigureKestrel((context, kestrelOptions) => kestrelOptions
                .Listen(IPAddress.Parse(context.Configuration["Ip"]),
                    context.Configuration.GetValue<int>("Port"),
                    listenOptions => listenOptions
                    .UseHttps(context.Configuration["CertFilename"], context.Configuration["CertPassword"]))));
}

Thank you!

ReloadableLogger not allowed in netstandard2.0 projects

What is the goal of preventing use of ReloadableLogger in netstandard projects (Serilog.Extensions.Hosting.csproj and LoggerConfigurationExtensions.cs)? I am building a common library that should be reused across dozens of projects to simplify and standardize logging for both worker service and ASP.NET Core apps.

To work around this I am forced to target a dotnet TFM which causes compatibility issues where consuming projects use older TFMs.

Two-stage initialization with exception during `UseSerilog` is not logged to files

Example:

Log.Logger = new LoggerConfiguration()
    .WriteTo.Debug()
    .WriteTo.Console()
    .WriteTo.File(BootstrapLogFilePath, LogEventLevel.Error)
    .CreateBootstrapLogger();
	
var builder = WebApplication.CreateBuilder(args);
	
builder.Host.UseSerilog((context, services, loggerConfiguration) => loggerConfiguration
    .ReadFrom.Configuration(context.Configuration)
    .ReadFrom.Services(services));

If an exception happens in the configureLogger action passed to UseSerilog (e.g. because the configuration in appsettings.json is invalid) then the resulting exception will be logged in the console and debug output, but it will not be logged in the file.

The Serilog SelfLog logs a related exception: "Caught exception while emitting to sink Serilog.Core.Sinks.RestrictedSink: System.ObjectDisposedException: Cannot write to a closed TextWriter."
It looks like the bootstrap logger is already disposed, but the new logger doesn't exist yet. So the exception can't be logged to any file.

Would it be possible to only dispose the bootstrap logger once the new logger is fully loaded? Then the exception would be logged to the file sink from the bootstrap logger.

In any case, exceptions that happen during the configureLogger action passed to UseSerilog should be written to the SelfLog.

Please document that `writeToProviders` will ignore filtering rules of the .NET logging

Hi!

I was resolving an issue that while AddAplicationInsightsLoggerProvider() sets the default filter rule to LogLevel.Warning (here) in the end it was ignored.

I used Serilog with UseSerilog() and writeToProviders: true.

As I understand this is because Serilog installs its own SerilogLoggerFactory which ignores .NET logging filters.

It is okay but please make it obvious from the readme documentation.

P.S. The same is true for Serilog.AspNetCore

Leverage 'DiagnosticSources' for collecting data instead of custom 'DiagnosticContext'

I just stumbled upon this native library from Microsoft that is eerily similar to what IDiagnosticContext is doing for Serilog

My proposal would be for authors to reconsider keeping a custom abstraction in place and instead migrate over to the native approach by dropping IDiagnosticContext altogether in favor of DiagnosticSources.

As an example, AspNetCore could be fully instrumented using this approach. Here are some of the events it fires that could be captured:
https://github.com/aspnet/Hosting/blob/e4350945c56d69523fe36bf33c58b955db3d8985/src/Microsoft.AspNetCore.Hosting/Internal/HostingApplicationDiagnostics.cs#L18

I found out about this system through this issue on AWS XRay repository

I believe trying to reuse standard abstractions like activities and DiagnosticSources and DiagnosticListeners would benefit the community more than coming up with fully custom abstractions like DiagnosticContext which are specific to Serilog. It would also help bring awareness to these systems to more people and push then to leverage them more (I was completely unaware it existed).

This is related to both of these in the end of the day:

Filter doesn't apply to custom sink with injectable dependencies added via appsettings.json

I have written a custom sink that depends on a service and I can successfully add that sink via appsettings.json but if I try to wrap it in a Logger with a Filter, the filter does not apply to my custom sink, but it does apply to other sinks in the same Logger Configuration.

I have added the custom sink with a filter manually and via appsettings.json. The filter is supposed to only let messages from sineme.Services.Include through. The example custom sink redirects messages to Console.WriteLine via the ConsoleWriter service.

Program.cs

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Core;
using Serilog.Filters;
using sineme.Serilog.Sinks.ConsoleWriter;
using sineme.Services.ConsoleWriter;
using sineme.Services.Exclude;
using sineme.Services.Include;

var app = Host.CreateDefaultBuilder()
	.ConfigureAppConfiguration((context, configurationBuilder) =>
	{
		configurationBuilder
			.AddJsonFile("appsettings.json");
	})
	.ConfigureServices((context, services) =>
	{
		services
			.AddTransient<IConsoleWriter, ConsoleWriter>()
			.AddTransient<ILogEventSink, ConsoleWriterSink>()
			.AddSingleton<InsideByIncludeOnlyNamespaceService>()
			.AddSingleton<OutsideByIncludeOnlyNamespaceService>();
	})
	.UseSerilog((context, serviceProvider, loggerConfiguration) =>
	{
		loggerConfiguration
                        // add custom sink with filter manually
			.WriteTo.Logger(loggerConfiguration =>
			{
				loggerConfiguration
					.Filter.ByIncludingOnly(Matching.FromSource("sineme.Services.Include"))
					.WriteTo.Sink(new ConsoleWriterSink(serviceProvider.GetRequiredService<IConsoleWriter>()));
			})
			.ReadFrom.Services(serviceProvider)
			.ReadFrom.Configuration(context.Configuration);
	})
	.Build();

var serviceProvider = app.Services;

serviceProvider.GetRequiredService<InsideByIncludeOnlyNamespaceService>().Execute();
serviceProvider.GetRequiredService<OutsideByIncludeOnlyNamespaceService>().Execute();

Services.cs

using Microsoft.Extensions.Logging;

// simple service that writes messages to the console
namespace sineme.Services.ConsoleWriter
{
	internal interface IConsoleWriter
	{
		void Write(string message);
	}

	internal class ConsoleWriter : IConsoleWriter
	{
		public void Write(string message)
		{
			System.Console.WriteLine(message);
		}
	}
}

// contains a service that simply logs a message which should be included by the sinks' filter
namespace sineme.Services.Include
{
	internal class InsideByIncludeOnlyNamespaceService
	{
		private readonly ILogger<InsideByIncludeOnlyNamespaceService> _logger;

		public InsideByIncludeOnlyNamespaceService(ILogger<InsideByIncludeOnlyNamespaceService> logger)
		{
			_logger = logger;
		}

		public void Execute()
		{
			_logger.LogInformation("Message from service inside ByIncludeOnly namespace.");
		}
	}
}

// contains a service that simply logs a message which should be excluded by the sinks' filter
namespace sineme.Services.Exclude
{
	internal class OutsideByIncludeOnlyNamespaceService
	{
		private readonly ILogger<OutsideByIncludeOnlyNamespaceService> _logger;

		public OutsideByIncludeOnlyNamespaceService(ILogger<OutsideByIncludeOnlyNamespaceService> logger)
		{
			_logger = logger;
		}

		public void Execute()
		{
			_logger.LogInformation("Message from service outside the ByIncludingOnly namespace.");
		}
	}
}

Sink.cs

using Serilog.Core;
using Serilog.Events;
using sineme.Services.ConsoleWriter;

// contains a simple sink with injected dependencies
namespace sineme.Serilog.Sinks.ConsoleWriter
{
	internal class ConsoleWriterSink : ILogEventSink
	{
		private readonly IConsoleWriter _consoleWriter;
		private readonly IFormatProvider? _formatProvider;

		public ConsoleWriterSink(IConsoleWriter consoleWriter, IFormatProvider? formatProvider = null)
		{
			_consoleWriter = consoleWriter;
			_formatProvider = formatProvider;
		}

		public void Emit(LogEvent logEvent)
		{
			var message = logEvent.RenderMessage(_formatProvider);
			_consoleWriter.Write(message);
		}
	}
}

appsettings.json

{
  "Serilog": {
    "Using": [
      "Serilog.Sinks.Console",
      "sineme.Serilog.ConsoleWriter",
      "Serilog.Expressions"
    ],
    "MinimumLevel": {
      "Default": "Debug"
    },
    "WriteTo": [
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
              {
                "Name": "ByIncludingOnly",
                "Args": {
                  "expression": "SourceContext = 'sineme.Services.Include' or StartsWith(SourceContext, 'sineme.Services.Include.')"
                }
              }
            ],
            "WriteTo": [
              {
                "Name": "Console",
                "Args": {
                  "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level} {SourceContext} {Message:lj}{NewLine}{Exception}"
                }
              },
              {
                "Name": "ConsoleWriter",
                "Args": {}
              }
            ]
          }
        }
      }
    ]
  }
}

Console output

Message from service inside ByIncludeOnly namespace.
Message from service inside ByIncludeOnly namespace.
2023-10-07 13:23:50.014 +02:00 Information sineme.Services.Include.InsideByIncludeOnlyNamespaceService Message from service Inside ByIncludeOnly namespace.
Message from service outside the ByIncludingOnly namespace.

The first two messages are from my custom sink and show that it is added both in the code and via appsettings.json with the injected dependencies. The third message shows that the console sink is also added from appsettings.json. The fourth message shows, that the filter is not applied to one of my custom sinks, if I remove my sink from appsettings.json, the message disappears, therefore it seems the Filter is ignored for custom Sinks that require services and are added via appsettings.json.

I expect the Filter to work on my custom sink if it is added via appsettings.json

Exception thrown getting IDiagnosticContext dependency after registering serilog with a logger

If I register using SerilogHostBuilderExtensions.UseSerilog(logger) I get an InvalidOperationException when getting a IDiagnosticContext dependency.

This issue has been introduced by the v5 release, and changes in the way service registration is implemented, specifically the use of the RegisteredLogger private class to prevent the logging from being disposed by the DI system.

Exception Stack trace
System.InvalidOperationException: No service for type 'Serilog.SerilogHostBuilderExtensions+RegisteredLogger' has been registered.
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Serilog.SerilogHostBuilderExtensions.<>c__DisplayClass4_0.<ConfigureDiagnosticContext>b__0(IServiceProvider services)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at Serilog.SerilogHostBuilderExtensions.<>c.<ConfigureDiagnosticContext>b__4_1(IServiceProvider services)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitRootCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.CreateServiceAccessor(Type serviceType)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
   at lambda_method2(Closure , IServiceProvider , Object[] )
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider.<>c__DisplayClass7_0.<CreateActivator>b__0(ControllerContext controllerContext)
   at Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider.<>c__DisplayClass6_0.<CreateControllerFactory>g__CreateController|0(ControllerContext controllerContext)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Configure enricher that requires a dependency in appsettings.json

I have this enricher:

public class CustomDataEnricher : ILogEventEnricher
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        public CustomDataEnricher(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public void Enrich(LogEvent logEvent, ILogEventPropertyFactory pf)
        {

        }
    }

I have this extentsion method:

public static LoggerConfiguration WithCustomData(this LoggerEnrichmentConfiguration enrich)
        {
            if (enrich == null) throw new ArgumentNullException(nameof(enrich));

            return enrich.With(new CustomDataEnricher(*should I even insert a IHttpContextAccessor dependency here?*));
        }

I'm doing all my configurations in appsettings.json and added this in the enrichers:

"Enrich": [
      "WithMachineName",
      "WithEnvironmentUserName",
      "FromLogContext",
      "WithCustomData"
    ]

Is this advisable and if not what's the proper way to do this?

Problem targeting Microsoft.Extensions.Hosting.Abstractions 2.1.1

I'd love to use these extensions for my app, but I get this runtime issue when targeting v2.1.1 of Microsoft.Extensions.Hosting.Abstractions:

System.IO.FileLoadException: 'Could not load file or assembly 'Microsoft.Extensions.Hosting.Abstractions, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)'

CreateBootstrapLogger doesn't work with non-static Logger

Hi there!

I'm kicking the tyres on CreateBootstrapLogger so that we can pull our log sink config from a secure store. To support parallel test running we don't use a static logger and instead inject it, but I don't see an override of UseSerilog/AddSerilog that accepts both an ILogger and a reconfiguration callback.

This code appears to only check to see if the static logger exists - should there perhaps be an overload that just accepts an ILogger and checks if it or the static logger is reloadable? Happy to add it if this is the right way to go :)

#if !NO_RELOADABLE_LOGGER
var reloadable = Log.Logger as ReloadableLogger;
var useReload = reloadable != null && !preserveStaticLogger;
#else

UseSerilog inline configuration called multiple times during host initialization. Missing cleanup?

Hi,

During application wiring and startup UseSerilog inline configuration may be called multiple times if service registrations need to create a logger instance using a temporary service provider.

public static IServiceCollection AddMyService(this IServiceCollection services)
{
    // create a service provider with registered services so far
    var provider = services.BuildServiceProvider();

    // resolve logger
    // This produces a call to UseSerilog((hostingContext, serviceProvider, loggerConfiguration) => {})
    var logger = provider.GetRequiredService<ILogger<MyServiceExtensions>>();

    logger.LogInformation("adding my service with following config...");

    // register whatever needed
    ...
}

In this context of logging during service registration and using a file sink, I end up with multiple log files, each one having exactly one message.

log20200919.txt
log20200919_001.txt
log20200919_002.txt

It seems as the previous existing logger doesn't get property disposed and the file sinks are still holding a lock to the log file. Calling Log.CloseAndFlush() before returning from the inline configuration resolves this issue.

.UseSerilog((hostingContext, serviceProvider, loggerConfiguration) => {
    
    // This doesn't resolve issue either
    // Log.CloseAndFlush();

    loggerConfiguration
        .WriteTo.Console();

    if(hostingContext.HostingEnvironment.IsProduction())
    {
        loggerConfiguration
            .WriteTo.File("Logs/log.txt", rollingInterval: RollingInterval.Day);
    }
})

Maybe some cleanup is needed to ensure that the inline configuration doesn't leak resources even if the framewok calls it multiple times.

Thanks

Be consistent about registering Serilog's `ILogger` for DI

Currently, UseSerilog() won't register an ILogger for dependency injection, but UseSerilog(logger) will (as will the inline-initialization syntax).

We should be consistent about registering an ILogger, with an opt-in or opt-out also consistently available.

Configuring Serilog inline raise infinite loop

Sorry, my english is poor.
So just show my code.

        public static IHostBuilder CreateWebHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }).UseSerilog(ConfigureLogger);

        private static void ConfigureLogger(HostBuilderContext context, IServiceProvider sp, LoggerConfiguration loggerConfiguration)
        {
            LoggerSinkConfiguration.Wrap(loggerConfiguration.WriteTo, (w) =>
            {
                var memoryCache = sp.GetRequiredService<IMemoryCache>();
                return new LogEventSink(memoryCache);
            }, (LoggerSinkConfiguration) =>
            {
                LoggerSinkConfiguration.Console();
            });
        }

        public class LogEventSink : ILogEventSink
        {
            public LogEventSink(IMemoryCache memoryCache)
            {
            }

            public void Emit(LogEvent logEvent)
            {
            }
        }

image

'WriteTo.Seq' not working with new 'CreateBootstrapLogger'

Hi,

First off, the new CreateBootstrapLogger looks great and this promises to greatly simplify bootstrap code. However I am bumping in to an issue and I am not sure if I have misunderstood something or there is a bug in the new feature.

I have code in my Program.cs as listed at the end of this issue.

Expected result
Asp.Net core bootstrapping logs are written to both Seq and Console.

Actual result
Asp.Net core bootstrapping logs are only written to the Console. The only log which is written to Seq is 'Starting service.'.

Code

public class Program
    {
        public readonly static AssemblyName AssemblyName = Assembly.GetEntryAssembly().GetName();

        /// CLI Startup routine
        public static int Main(string[] args)
        {

            var loggerConfig = new LoggerConfiguration()
                .WriteTo.Console();

            var seqUrl = Environment.GetEnvironmentVariable("SEQ_URL");

            if (!string.IsNullOrEmpty(seqUrl))
            {
                loggerConfig.WriteTo.Seq(seqUrl);
            }

            Log.Logger = loggerConfig.CreateBootstrapLogger();

            Log.Information("Starting service.");

            try
            {
                var host = BuildWebHost(args);

                MigrateDatabase(host);

                host.Run();

                return 0;
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Host terminated unexpectedly.");
                throw;
            }
            finally
            {
                // TODO: This is not getting logged at the moment.
                Log.Information("Terminated.");
                Log.CloseAndFlush();
            }
        }

        private static void MigrateDatabase(IHost host)
        {
           // Omitted for brevity.
        }

        private static IHost BuildWebHost(string[] args)
        {
            return Host.CreateDefaultBuilder(args)
                .UseSerilog((context, services, loggerConfig) =>
                {
                    loggerConfig
                        .Enrich.WithProperty("AssemblyName", AssemblyName.Name.ToString())
                        .Enrich.WithProperty("AssemblyVersion", AssemblyName.Version.ToString())
                        .ReadFrom.Configuration(context.Configuration);
#if DEBUG
                    Log.Information("Compiled with 'DEBUG' flag so enriching with debugger attached property.");
                    loggerConfig.Enrich.WithProperty("DebuggerAttached", Debugger.IsAttached);
#endif
                })
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseContentRoot(Directory.GetCurrentDirectory());
                    webBuilder.UseStartup<Startup>();
                })
                .Build();
        }

    }

Add IServiceCollection extension methods to enable serilog

With dotnet/runtime#61634 (comment) and dotnet/runtime#65109, Microsoft.Extensions.Hosting added a new way of creating a hosted app that doesn't use IHostBuilder.

We should add overloads to SerilogHostBuilderExtensions that accept an IServiceCollection instead of an IHostBuilder. This will allow consumers using the new HostApplicationBuilder to use Serilog easily.

Since the methods will extend IServiceCollection, the methods should probably be named AddSerilog instead of UseSerilog. This follows the naming patterns used elsewhere.

See also dotnet/runtime#68580 and dotnet/aspnetcore#43056

cc @davidfowl

Possible Reloadable Logger Issue

I've got .netcore31 application (both a generic host and ASP.NET) application where a logger is throwing an exception when using the reloadable logger. I haven't developed a minimum test case, but it may have to do with static loggers being accessed early in the lifetime. The pattern is as follows:

  1. Create a class with a static logger (private static ILogger _log = Log.ForContext();
  2. Add another static method/property to the class, call a log method (i.e. _log.Debug) in the method.
  3. Add a non-static method that calls three log methods (i.e. _log.Debug x3)
  4. During services initialization, call the static method
  5. Initialize the CreateBootstrapLogger/UseSerilog as normal
  6. In the worker, get an instance of the class and call the non-static method.
  7. The first log method will succeed, as will the second
  8. The third throws an exception, because _cached is somehow null but _frozen is true (so it's trying to directly call the _cached logger)

If I get some time, I'll try to get a minimum reproducible example written, but I'm under some tight work deadlines at the moment. Hopefully the description above will help find the logic issue. Presumably, _cached = null and _frozen = true should never happen.

Add support for `IAsyncDisposable` loggers

Serilog 2.12 supports IAsyncDisposable on Logger and sinks. Async disposal helps avoid deadlocks in sinks that flush via async APIs on disposal.

This will require checking and changing dispose handling code here as well as in _Serilog.Extensions.Logging`.

Serilog not working on Host initialization

I'm using the following nuget packages:

<PackageReference Include="Serilog.Extensions.Hosting" Version="2.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="3.1.0" />

Following the documentation example the logger works:

public class Program
{
	public static void Main(string[] args)
	{
		Log.Logger = new LoggerConfiguration()
					 .MinimumLevel.Debug()
					 .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
					 .Enrich.FromLogContext()
					 .WriteTo.Console()
					 .CreateLogger();
	}

	public static IHostBuilder CreateHostBuilder(string[] args)
	{
		return new HostBuilder()
			.UseSerilog();
	}
}

However if I use my preferred way (also documented):

.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration																 
    .ReadFrom.Configuration(hostingContext.Configuration)
    .Enrich.FromLogContext()
    .WriteTo.Console());

Same situation if i use the appsettings.json:

.UseSerilog((hostingContext, loggerConfiguration) =>
		   {
			   loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration)
								  .Enrich.FromLogContext();
		   });

Expected behavior: log messages appear on the console.
Actual behavior: no log on console.

BootstrapLogger and reading config from appsettings (using Serilog.Settings.Configuration) not playing well together

I'm following the example and have the following code:

Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .CreateBootstrapLogger();

try
{
    Log.Information("Starting");

    HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
    builder.Services.AddSerilog((services, loggerConfiguration) =>
    {
        loggerConfiguration
            .ReadFrom.Configuration(builder.Configuration)
            .ReadFrom.Services(services);
    });

    IHost host = builder.Build();
    await host.RunAsync();

    Log.Information("Stopping");
}
catch (Exception ex)
{
    Log.Fatal(ex, "Failing");
}
finally
{
    await Log.CloseAndFlushAsync();
}

And this appsettings.json:

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.File" ],
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "Filename.log",
          "fileSizeLimitBytes": "10485760",
          "rollingInterval": "Day",
          "rollOnFileSizeLimit": true,
          "retainedFileCountLimit": 30
        }
      }
    ],
    "Enrich": [ "FromLogContext" ]
  }
}

Using the following packages:

<PackageReference Include="Serilog.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />

When starting this I would expect all the messages appearing in Filename.log, but the "Starting" message appears in the Console. Everything else appears in the file as expected.
Why doesn't the "Starting" message appear in the log file, and what can I do to have everything appear in the file? (and everything logged to console if something fails)

UseSerilog Infinite loop: ReadFrom.Services + KinesisFirehoseSink

If you use the Two-stage initialization from here:
https://github.com/serilog/serilog-aspnetcore#two-stage-initialization

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .UseSerilog((context, services, configuration) => configuration
                    .ReadFrom.Configuration(context.Configuration)
                    .ReadFrom.Services(services)
                    .Enrich.FromLogContext()
                    .WriteTo.Console())
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

and configure a KinesisFirehoseSink like so...

public void ConfigureServices(IServiceCollection services)
{
	services.AddAWSService<IAmazonKinesisFirehose>();
	services.AddSingleton(new KinesisFirehoseSinkOptions("LoggingStream"));
	services.AddTransient<ILogEventSink, KinesisFirehoseSink>();
}

You end up in an infinite loop because the IAmazonKinesisFirehose factory is looking for an ILoggerFactory.

Argument ordering on configuration lambda

In #20 (still in beta package) a new overload of UseSerilog() has been introduced

UseSerilog(
    this IHostBuilder builder,
    Action<HostBuilderContext, IServiceProvider, LoggerConfiguration> configureLogger,
    bool preserveStaticLogger = false,
    bool writeToProviders = false)

in addition to the already existing (publicly released)

UseSerilog(
    this IHostBuilder builder,
    Action<HostBuilderContext, LoggerConfiguration> configureLogger,
    bool preserveStaticLogger = false,
    bool writeToProviders = false)

Wouldn't it be more logical to change the ordering of the arguments on the configureLogging lambda of the newly added overload to HostBuilderContext, LoggerConfiguration, IServiceProvider? That makes refactoring of code just a little easier. I can contribute a PR, but only of course if this aligns with the views of the maintainers.

UseSerilog support for .net8 IHostApplicationBuilder

This package has extension methods for IHostBuilder. In .net7, the official documentation for Generic Host encourages use of Host.CreateApplicationBuilder which returns a HostApplicationBuilder. The trouble is that HostApplicationBuilder does not implement the IHostBuilder interface.

I propose that, in addition to the current capabilities, the Serilog.Extensions.Hosting package should offer a .UseSerilog extension method for IHostApplicationBuilder.

Reference:

Edit: I now see that IHostApplicationBuilder is included in .net8. Updating title. Best course of action for .net7 would probaby be to use Host.CreateDefaultBuilder to get the .net6 style API (using callbacks).

CreateBootstrapLogger no longer available in .NET Standard 2.1 since 8.0.0

Hello there,

in the 8.0.0 release, the target framework netstandard2.1 was removed (commit f22e3fd), and in turn, support for a reloadable bootstrap logger was removed for any .NET Standard 2.1 project using this library. See also the code snippet below:

<PropertyGroup Condition=" '$(TargetFramework)' == 'net462' or '$(TargetFramework)' == 'netstandard2.0' ">
<DefineConstants>$(DefineConstants);NO_RELOADABLE_LOGGER</DefineConstants>
</PropertyGroup>

No logging with .NET Framework Host Application DI

I am attempting to add Serilog as my logger for a host application targeting .NET Framework 4.8.1. I have added the latest 8.0.0 Microsoft.Extensions.Hosting, Microsoft.Extensions.Logging, and Serilog.Extensions.Hosting packages. I also have all of the relevant expressions and enrichers packages. I'm basically mirroring a setup I have with a .NET 8 application that works properly.

The default Microsoft provided logger successfully write to the console, but adding Serilog results in nothing written to the console unless the Log.Logger is used directly.

appsettings.json

{
  "Serilog": {
    "Using": [
      "Serilog.Sinks.Console",
      "Serilog.Sinks.File"
    ],
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft.AspNetCore": "Warning"
      }
    },
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz}] [{ThreadName}-{ThreadId}] - {Level:u3} - {SourceContext} - {Message}{NewLine}{Exception}"
        }
      }
    ],
    "Enrich": [
      "FromLogContext",
      "WithMachineName",
      "WithThreadId",
      "WithThreadName"
    ]
  }
}

The program

public static void Main(string[] args)
{
    var builder = Host.CreateApplicationBuilder(args);

    var logger = Log.Logger = new LoggerConfiguration()
        .ReadFrom.Configuration(builder.Configuration)
        .Enrich.FromLogContext()
        .CreateLogger();
            
    builder.Logging.ClearProviders();
    builder.Logging.AddSerilog(logger);
            
    builder.Services.AddHostedService<SimpleService>();
            
    var app = builder.Build();
    app.Run();
}

SimpleService

public class SimpleService : BackgroundService
{
    private readonly ILogger<SimpleService> _logger;
    
    public SimpleService(ILogger<SimpleService> logger)
    {
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while(true)
        {
            _logger.LogInformation("Log");
            await Task.Delay(1000);
        }
    }
}

Default (no Serilog)

info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\MyUserProfile\TestProject\TestProject\bin\Debug\net481
info: My.Testing.Namespace.Services.SimpleService[0]
      Log

With Serilog

Calling Log.Logger in SimpleService

Formatting from appsettings.json seems to be intact and it prints to console or any other log sink I have defined in appsettings.json.

[2024-04-25 21:59:00.264 -05:00] [-1] - INF -  - Log

This exact same setup in .NET 8 works fine. So I can't figure out if this is a bug with Serilog.Extensions.Hosting or Microsoft.Extensions.Hosting or Microsoft.Extensions.Logging.

Can anyone else replicate this?

Automatic CI build triggering

Having a little trouble getting AppVeyor to trigger automatically upon dev branch changes. May be resolved after some appveyor.yml fixes, but the first build still required manual triggering.

Transitive dependencies on MS.Extensions.* should align with the major version shipped with the SDK

Hello,

Spotted this on https://www.nuget.org/packages/Serilog.Extensions.Hosting

net6.0
Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
Serilog (>= 3.1.1)
Serilog.Extensions.Logging (>= 8.0.0)

net7.0
Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
Serilog (>= 3.1.1)
Serilog.Extensions.Logging (>= 8.0.0)

net8.0
Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
Serilog (>= 3.1.1)
Serilog.Extensions.Logging (>= 8.0.0)

We have a .net6 and .net7 projects that had compilation errors after upgrading to v8 of this library. Specifically things like:

Error	CS0121	The call is ambiguous between the following methods or properties: 'Microsoft.Extensions.DependencyInjection.OptionsBuilderExtensions.ValidateOnStart<TOptions>(Microsoft.Extensions.Options.OptionsBuilder<TOptions>)' and 'Microsoft.Extensions.DependencyInjection.OptionsBuilderExtensions.ValidateOnStart<TOptions>(Microsoft.Extensions.Options.OptionsBuilder<TOptions>)'	MyType.MyMethod

There are likely breaking changes in these MS.Ext packages between major versions so I wouldn't expect v8 versions to be compatible on net7.0 and net6.0 projects without conflicts (either compile time or runtime).

What I would expect to see for a library that tries to be compatible across the major TFM, and have MS.Ext dependencies, would to have such dependencies locked to the major version shipped with that SDK.

For example:

net6.0
Microsoft.Extensions.DependencyInjection.Abstractions (>= 6.0.0)
Microsoft.Extensions.Hosting.Abstractions (>= 6.0.0)
Microsoft.Extensions.Logging.Abstractions (>= 6.0.0)
Serilog (>= 3.1.1)
Serilog.Extensions.Logging (>= 6.0.0)

net7.0
Microsoft.Extensions.DependencyInjection.Abstractions (>= 7.0.0)
Microsoft.Extensions.Hosting.Abstractions (>= 7.0.0)
Microsoft.Extensions.Logging.Abstractions (>= 7.0.0)
Serilog (>= 3.1.1)
Serilog.Extensions.Logging (>= 7.0.0)

net8.0
Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.0)
Microsoft.Extensions.Hosting.Abstractions (>= 8.0.0)
Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
Serilog (>= 3.1.1)
Serilog.Extensions.Logging (>= 8.0.0)

Here is an example project that does it in a way that works: https://www.nuget.org/packages/Duende.AccessTokenManagement and the corresponding csproj: https://github.com/DuendeSoftware/Duende.AccessTokenManagement/blob/main/src/Duende.AccessTokenManagement/Duende.AccessTokenManagement.csproj

Cheers!

Serilog.Extensions.Hosting.dll Corrupted with Null Bytes During Unplanned Shutdown

We are using Serilog in an application that runs as a Windows Service. One of our customers has recently experienced unexpected shutdowns of their machines. Upon restarting, our Service fails to start and we find that Serilog.Extensions.Hosting.dll has been completely overwritten with NUL bytes:

image

The size and last modified date of the file remain the same. The customer can fix the problem by running a Repair of the installation via Add/Remove Programs. This has happened several times and this is the only file affected.

I have found reports of this issue online (link, link) except that these relate to files which were being written at the time of the power loss. I do not know why this would be the case for a .NET assembly.

I have re-emphasised to our customer the importance of preventing sudden unplanned shutdowns, and have also asked them to exempt our application from anti-virus scanning. However I am asking here whether there is anything special about Serilog.Extensions.Hosting.dll, or perhaps in the way we are using it, that would make it prone to this kind of corruption?

We are using v5.0.1 of the Serilog.Extensions.Hosting package and v2.12.0 of Serilog.

I include our Serilog setup code for completeness:

Log.Logger = new LoggerConfiguration()                        
                        .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
                        .Enrich.FromLogContext()
                        .WriteTo.File(Paths.LogFilePath, rollingInterval: RollingInterval.Day)
                        .WriteTo.File(Paths.ErrorLogFilePath, restrictedToMinimumLevel: LogEventLevel.Warning)
#if DEBUG
                        .WriteTo.Console(Serilog.Events.LogEventLevel.Debug, outputTemplate: SerilogOutputTemplate)                        
#endif
                        .CreateLogger(); 

System.InvalidOperationException: Cannot bind parameter 'logger' to type ILogger.

I'm attempting to add Serilog to an Azure WebJobs sdk project and getting a parameter binding exception:

Microsoft.Azure.WebJobs.Host.Indexers.FunctionIndexingException: Error indexing method 'Functions.ProcessQueueMessage'
---> System.InvalidOperationException: Cannot bind parameter 'logger' to type ILogger. Make sure the parameter Type is supported by the binding. If you're using binding extensions (e.g. Azure Storage, ServiceBus, Timers, etc.) make sure you've called the registration method for the extension(s) in your startup code (e.g. builder.AddAzureStorage(), builder.AddServiceBus(), builder.AddTimers(), etc.).

Here's my repro example:

class Program
{
    static async Task Main()
    {
        var builder = new HostBuilder();
        builder.UseSerilog();
        builder.ConfigureWebJobs(b =>
        {
            b.AddAzureStorageCoreServices();
            b.AddAzureStorage();
        });
        var host = builder.Build();
        try { await host.RunAsync(); }
        catch (Exception ex) { Log.Logger.Fatal(ex.Message, ex); }
    }
}

public class Functions
{
    public static void ProcessQueueMessage([QueueTrigger("queue")] string message, ILogger logger)
    {
        logger.Information(message); // Serilog ILogger
    }
}

For completeness, these are the package references:
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions" Version="4.0.1" />
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage" Version="4.0.4" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="5.0.0" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />

Add overload to UseSerilog that extends ``IServiceCollection``.

The host builder is great, until you have a usecase for using a ServiceCollection in place of the hosting package stuff.

Or at least a special library that provides an UseSerilog that extends IServiceCollection. I have a use case for this because I need to debug my application to find out where the issue is where it consumes most of my VPS's RAM (the VPS is ubuntu with 2 GB of RAM) (which oddly is not reproducible on my pc). The way I will debug this is to instead keep the database part (which will require serilog to log the things it logs) while creating and populating the service collection will be done in python (using pythonnet) and discord.py (so I can rule out Remora.Discord or efcore as the RAM hoggers).

I probably could use the generic host, however I would need to figure out if I could then map python objects to .NET objects (as in from python only objects to .NET ones).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.