8

I follow this article. Developing a sink

But I need to injection IHttpClientFactory.

public class MySink : ILogEventSink
{
    //...

    public MySink(IFormatProvider formatProvider, IHttpClientFactory httpClientFactory)
    {
        //...
    }
}

public static class MySinkExtensions
{
    public static LoggerConfiguration MySink(
        this LoggerSinkConfiguration loggerConfiguration, 
        IHttpClientFactory httpClientFactory, 
        IFormatProvider formatProvider = null,
        LogEventLevel restrictedToMinimumLevel = LogEventLevel.Verbose)
    {
        return loggerConfiguration.Sink(new MySink(formatProvider, httpClientFactory), restrictedToMinimumLevel);
    }
}

Program Main:

class Program
{
    static async Task Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddHttpClient();

        var logger = new LoggerConfiguration()
            .ReadFrom.Configuration(configuration)
            .WriteTo.MySink(null) //<--How to get IHttpClientFactory?
            .CreateLogger();

        services.AddLogging(configure => configure.ClearProviders().AddSerilog(logger));

        var serviceProvider = services.BuildServiceProvider();
    }
}

appsettings:

{
"Serilog": {
    "Using": [ "Serilog.Sinks.Console", "MyTestProject" ],
    "WriteTo": [
        {
            "Name": "Console",
            "Args": {
                "restrictedToMinimumLevel": "Debug"
            }
        },
        {
            "Name": "MySink",
            "Args": {
                "restrictedToMinimumLevel": "Warning"
            }
        }
    ]
}

Can I use the Args to injection IHttpClientFactory?

Or handle this in Main method?

Note: I am using net core console application.

Update: According to the asnwer

Now, the Program Main is:

class Program
    {
        static async Task Main(string[] args)
        {
            var serviceCollection = new ServiceCollection();
            serviceCollection.AddHttpClient();

            serviceCollection.AddSingleton<Serilog.ILogger>(sp =>
            {
                var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();

                return new LoggerConfiguration()
                    .MinimumLevel.Debug()
                    .WriteTo.MySink(httpClientFactory)
                    .CreateLogger();
            });

            serviceCollection.AddLogging(configure => configure.ClearProviders().AddSerilog());

            var serviceProvider = serviceCollection.BuildServiceProvider();

            Log.Logger = serviceProvider.GetRequiredService<Serilog.ILogger>();
        }
    }

And I can injection ILogger in other class

class Test
{
    private readonly ILogger<Test> _logger;

    Test(ILogger<Test> logger)
    {
        _logger = logger;
    }
}

Update: (2020-07-01)

Now, in asp net core, this package supply dependency injection.

.UseSerilog((hostingContext, services, loggerConfiguration) => loggerConfiguration
.ReadFrom.Configuration(hostingContext.Configuration)
.Enrich.FromLogContext()
.WriteTo.SomeSink(services.GetRequiredService<ISomeDependency>()));
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
Libron
  • 129
  • 2
  • 9
  • Why do you need to inject `IHttpClientFactory`? For a sink you would just pass in configuration options e.g. a URL etc. as the sink would know what it needs to create i.e. an HttpClient, to function. If you look at the code for the [Elastic Search Sink](https://github.com/serilog/serilog-sinks-elasticsearch) you can see they pass in URI's but the sink creates the client itself - [ElasticLowLevelClient](https://github.com/serilog/serilog-sinks-elasticsearch/blob/6b63825dc849e6c16820af62d3a5ff261ff40fec/src/Serilog.Sinks.Elasticsearch/Sinks/ElasticSearch/ElasticsearchSinkState.cs#L99) – Simply Ged Aug 08 '19 at 04:32
  • 1
    Because I found some problem with HttpClient.[Issues with the original HttpClient class available in .NET Core](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net-core). – Libron Aug 08 '19 at 05:15
  • In that case can you just use `new HttpClientFactory()` in the sink instead and call that to create your client? – Simply Ged Aug 08 '19 at 06:29
  • 1
    I can't new HttpClientFactory. IHttpClientFactory is implemented by DefaultHttpClientFactory([DefaultHttpClientFactory](https://github.com/aspnet/HttpClientFactory/blob/master/src/Microsoft.Extensions.Http/DefaultHttpClientFactory.cs)), and it is a internal class. – Libron Aug 08 '19 at 07:50
  • @Libron Have you gotten the concept to work that you added in your update 2020-07-01? – Lars Thorén Dec 22 '22 at 10:17
  • Yes, but please note that using this method will [ignore exceptions raised earlier in program startup](https://github.com/serilog/serilog-extensions-hosting#inline-initialization). – Libron Dec 23 '22 at 05:00

2 Answers2

6

I have played around a bit and found you can use an implementation factory to get the IHttpClientFactory after the service provider has been built. You need to change the service.AddLogging(...) call to use this factory implementation:

static async Task Main(string[] args)
{
    var serviceCollection = new ServiceCollection();
    serviceCollection.AddHttpClient();

    serviceCollection.AddSingleton<Serilog.ILogger>(sp =>
    {
        var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();

        return new LoggerConfiguration()
            .MinimumLevel.Debug()
            //.ReadFrom.Configuration(Configuration)
            .WriteTo.MySink(httpClientFactory)
            .CreateLogger();
    });

    serviceCollection.AddLogging(cfg =>
    {
        cfg.ClearProviders().AddSerilog();
    });


    var serviceProvider = serviceCollection.BuildServiceProvider();


    var logger = serviceProvider.GetService<Serilog.ILogger>();

    logger.Debug("Working");
}

In the code sample we can use a factory implementation for AddSingleton to only create the logger when it is first requested. This allows us to pull any registered services from the service provider and inject them into our class. In this case, we get the IHttpClientFactory and inject it into MySink.

If you put a breakpoint on the line var logger = serviceProvider.GetService<Serilog.ILogger>() when you step into that it will call the lambda in AddSingleton and create your logger for you.

Simply Ged
  • 8,250
  • 11
  • 32
  • 40
0

The issue with using IHttpClientFactory and Serilog Custom Sink is that once the dependency is resolved, DefaultHttpClientFactory (which implements IHttpClientFactory) depends on ILogger. This creates a circular loop if the ILogger is injected directly.

To resolve this issue, use a Lazy implementation to avoid the loop. The following code demonstrates how to do this in a custom sink extension class:

public static class MySinkExtensions
{
    public static LoggerConfiguration MySink(
        this LoggerSinkConfiguration loggerConfiguration, 
        Lazy<IHttpClientFactory> lazyHttpClientFactory, 
        IFormatProvider formatProvider = null,
        LogEventLevel restrictedToMinimumLevel = LogEventLevel.Verbose)
    {
        return loggerConfiguration.Sink(new MySink(formatProvider, 
        lazyHttpClientFactory), restrictedToMinimumLevel);
    }
}
BDarley
  • 697
  • 1
  • 10
  • 19