【问题标题】:Custom Serilog sink with injection?带有注入的自定义 Serilog 接收器?
【发布时间】:2022-03-08 18:41:47
【问题描述】:

我创建了一个简单的 Serilog sink 项目,如下所示:

namespace MyApp.Cloud.Serilog.MQSink
{
    public class MessageQueueSink: ILogEventSink
    {
        private readonly IMQProducer _MQProducerService;
        public MessageQueueSink(IMQProducer mQProducerService)
        {
            _MQProducerService = mQProducerService;
        }
        public void Emit(LogEvent logEvent)
        {
            _MQProducerService.Produce<SendLog>(new SendLog() { LogEventJson = JsonConvert.SerializeObject(logEvent)});
        }
    }
}

消费微服务是这样启动的:

        var configurationBuilder = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
        var appSettings = configurationBuilder.Get<AppSettings>();

        configurationBuilder = new ConfigurationBuilder().AddJsonFile("ExtendedSettings.json").Build();

            Host.CreateDefaultBuilder(args)
                .UseMyAppCloudMQ(context => context.UseSettings(appSettings.MQSettings))
                .UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration))
                .ConfigureServices((hostContext, services) =>
                {
                    services
                        .AddHostedService<ExtendedProgService>()
                        .Configure<MQSettings>(configurationBuilder.GetSection("MQSettings"))
                })
                .Build().Run();

appsettings.json 的 serilog 部分如下所示:

  "serilog": {
    "Using": [ "Serilog.Sinks.File", "Serilog.Sinks.Console", "MyApp.Cloud.Serilog.MQSink" ],
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "Enrich": [ "FromLogContext", "WithMachineName", "WithProcessId" ],
    "WriteTo": [
      {
        "Name": "MessageQueueSink",
        "Args": {}
        }
    ]
  }

添加了 MQSink 项目作为对微服务项目的引用,我可以看到 MQSink dll 最终位于 bin 文件夹中。

问题是当在微服务中执行 _logger.LogInformation(...) 时,Emit 永远不会被触发,但是如果我添加一个控制台接收器,它会输出数据吗?我也怀疑注入的MQ不能正常工作?

如何解决?

编辑:

打开Serilog内部日志,可以看到MessageQueueSink方法找不到。我没有找到任何方法让它与 appsetings.json 一起工作,所以我开始研究如何在代码中绑定它。

要让它工作,需要创建一个扩展程序:

public static class MySinkExtensions
    {
        public static LoggerConfiguration MessageQueueSink(
                  this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration,
                  MyApp.Cloud.MQ.Interface.IMQProducer mQProducer = null)
        {
            return loggerConfiguration.Sink(new MyApp.Cloud.Serilog.MQSink.MessageQueueSink(mQProducer));
        }
    }

这使得添加自定义接收器成为可能:

Host.CreateDefaultBuilder(args)
                    .UseMyAppCloudMQ(context => context.UseSettings(appSettings.MQSettings))
                     .ConfigureServices((hostContext, services) =>
                    {
                        services
                            .Configure<MQSettings>(configurationBuilder.GetSection("MQSettings"))
                    })
                    .UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration).WriteTo.MessageQueueSink())
                    .Build().Run();

自定义接收器已加载并触发 Emit 但我仍然不知道如何将 MQ 注入接收器?如果我能在 appsettings.json 文件中完成 Serilog 和 sink 的所有配置,那就更好了。

【问题讨论】:

  • MessageQueueSink 找不到
  • 顺便说一句,您可以像这样传递IMQProducer.WriteTo.MessageQueueSink(services.GetRequiredService&lt;IMQProducer&gt;());
  • @PeterCsala,我已经尝试过 GetRequierdService 但问题是它仍然会创建一个循环关系,自定义接收器的构造函数将永远循环。

标签: c# code-injection serilog ilogger


【解决方案1】:

为了让依赖注入流处理这一切,您可以使用ReadFrom.Services 扩展方法。
此扩展方法是Serilog.Extensions.Hosting NuGet 包的一部分,该包也与Serilog.AspNetCore NuGet 包捆绑在一起。

来自方法描述

使用服务中的组件配置记录器。
如果存在,记录器将收到的实现/实例 Serilog.Core.LoggingLevelSwitch, Serilog.Core.IDestructuringPolicy, Serilog.Core.ILogEventFilter, Serilog.Core.ILogEventEnricherSerilog.Core.ILogEventSinkSerilog.Configuration.ILoggerSettings.

因此,如果您将 MessageQueueSink 注册到 DI 容器中,它将被注入到 Serilogs Logger 并且反过来也会注入自己的依赖项(此处:IMQProducer 实例)。
这将发生在静态 Serilog.Log.Logger 以及任何在某处注入的 ILogger&lt;T&gt; 实例。

请注意,此设置不需要/使用LoggerSinkConfiguration 上的扩展方法。

找到下面的代码部分。


DI 容器和 Serilog 的设置

class Program
{
    static void Main(string[] args)
    {
        using var host = CreateHostBuilder(args);
        host.Services.GetService<IMyService>().Run();
        host.Run();
    }

    public static IHost CreateHostBuilder(string[] args)
        => Host.CreateDefaultBuilder(args)
                .ConfigureServices((hostBuilderContext, services) => 
                {
                    services.AddTransient<ILogEventSink, MessageQueueSink>();
                    services.AddTransient<IMQProducer, MQProducer>();
                    // ...
                    services.AddTransient<IMyService, MyService>();
                })
                .UseSerilog((context, services, configuration) 
                    => configuration
                        .ReadFrom.Services(services)
                        .ReadFrom.Configuration(context.Configuration)
                )
                .Build();
}

为了完整性,上面提到的其他类:

水槽

public class MessageQueueSink : ILogEventSink
{
    private readonly IMQProducer _mqProducer;

    public MessageQueueSink(IMQProducer mqProducer)
        => _mqProducer = mqProducer;
    
    public void Emit(LogEvent logEvent)
        => _mqProducer.Produce(logEvent);
}

上面提到的其他服务/依赖项

// Stub for the real MQ stuff.
public interface IMQProducer
{
    void Produce(LogEvent logEvent);
}

public class MQProducer : IMQProducer
{
    public void Produce(LogEvent logEvent)
        => Console.WriteLine($">>> Producing log event: {logEvent.RenderMessage()}");
}   

public interface IMyService
{
    void Run();
}

public class MyService : IMyService
{
    private readonly Microsoft.Extensions.Logging.ILogger _logger;

    public MyService(ILogger<MyService> logger)
        => _logger = logger;

    public void Run()
    {
        _logger.LogError("From injected logger");
        Serilog.Log.Logger.Error("From static logger");
    }
}

【讨论】:

  • 我试过这个并且服务没有启动,经过一些挖掘我可以设置自定义接收器没有加载并且没有来自 serilog 的信息。我试图删除自定义接收器中的注入参数,这使得服务启动并加载自定义接收器。所以serilog注入有问题,问题是为什么?暂时没有任何信息。 IMQProducer 确实存在于注入系统中,其他类得到一个适当的实例。有什么建议吗?
  • @Banshee 设置diagnostic logging 可能会为您提供更多信息。你能试着围绕上面的代码构建一个测试用例吗?确保所有依赖项都注册到容器中;从您问题中的代码很难看出。
  • 已尝试开启 SelfLog,但未输出任何信息。我还发现,如果我删除 ReadFrom.Services(services) 项目就会启动。创建一个测试用例有点棘手,但我会研究一下。
  • 我发现了问题,如果注入的类有一个 ILogger 那么它会冻结。我确实需要能够登录注入的MQ类,有没有办法解决这个问题?
  • 我倾向于说这是一个相当奇特的设置,但您可以考虑使用静态 Logger.Log 或注入 ILoggerFactoryIServiceProvider 在运行时创建/解析记录器。
【解决方案2】:

如果您参考Provided Sinks 列表并检查其中一些的源代码,您会注意到该模式通常是:

  1. 构造接收器配置(通常从IConfiguration、内联或两者的组合中获取值)
  2. 将配置传递给接收器注册。

然后接收器实现实例化所需的服务以将日志推送到。

我可以建议的另一种方法是不带任何参数 (UseSerilog()) 注册 Serilog,然后使用内置的 IServiceProvider 配置静态 Serilog.Log 类:

var host = Host.CreateDefaultBuilder(args)
    // Register your services as usual
    .UseSerilog()
    .Build()
    
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(host.Services.GetRequiredService<IConfiguration>())
    .WriteTo.MessageQueueSink(host.Services.GetRequiredService<IMQProducer>())
    .CreateLogger();

host.Run();

【讨论】:

    【解决方案3】:

    只要您的接收器的依赖项有一个默认构造函数,它们将被实例化,一个完整的仅配置方法是可能的。

    在下面来自appsettings.json 的片段中,WriteTo 部分提到了Serilog 激活扩展方法MessageQueueSink 的名称 - 更进一步 -
    以及必须实例化并传递给该扩展方法的参数mqProducer 的对象的type

    (我的程序集被称为MyApplication 并且有一个MyApplication.System 命名空间。)

    "Serilog": {
        "Using": [ "MyApplication" ],
        "MinimumLevel": {
            "Default": "Information"
        },
        "WriteTo": [
            {
                "Name": "MessageQueueSink",
                "Args": {
                    "mqProducer": "MyApplication.System.MQProducer, MyApplication"
                }
            }
        ]
    }
    

    致电ReadFrom.Configuration 即可。

    namespace MyApplication.System          
    {           
        class Program
        {
            static void Main(string[] args)
            {
                using var host = CreateHostBuilder(args);
                host.Services.GetService<IMyService>().Run();
                host.Run();
            }
    
            public static IHost CreateHostBuilder(string[] args)
                => Host.CreateDefaultBuilder(args)
                        .ConfigureServices((hostBuilderContext, services)
                            => services.AddTransient<IMyService, MyService>()
                        )
                        .UseSerilog((context, services, configuration) 
                            => configuration
                                    .ReadFrom.Configuration(context.Configuration)
                        )
                        .Build();
            }
        }
    }
    

    这种方法需要Serilog.Configuration.LoggerSinkConfiguration 上的扩展方法,因为这是json 设置中提到的方法。

    由于上面的json 设置已指定"Args": { "mqProducer": "MyApplication.System.MQProducer, MyApplication" },因此将自动传入该类型的实例。

    public static class LoggerSinkConfigurationExtensions
    {
        public static LoggerConfiguration MessageQueueSink(
            this Serilog.Configuration.LoggerSinkConfiguration loggerConfiguration,
            IMQProducer mqProducer = null
            ) =>  loggerConfiguration.Sink(new MessageQueueSink(mqProducer));        
    }
    

    如果该类型无法通过默认构造函数实例化,您可能需要自己进行;
    例如loggerConfiguration.Sink(new MessageQueueSink(new MQProducer( /* arguments */ )))


    水槽

    public class MessageQueueSink : ILogEventSink
    {
        private readonly IMQProducer _mqProducer;
    
        public MessageQueueSink(IMQProducer mqProducer)
            => _mqProducer = mqProducer;
        
        public void Emit(LogEvent logEvent)
            => _mqProducer.Produce(logEvent);
    }
    

    上面提到的其他服务/依赖项

    public interface IMQProducer
    {
        void Produce(LogEvent logEvent);
    }
    
    public class MQProducer : IMQProducer
    {
        public void Produce(LogEvent logEvent)
            => Console.WriteLine($">>> Producing log event: {logEvent.RenderMessage()}");
    }
    
    public interface IMyService
    {
        void Run();
    }
    
    public class MyService : IMyService
    {
        private readonly Microsoft.Extensions.Logging.ILogger _logger;
    
        public MyService(ILogger<MyService> logger)
            => _logger = logger;
    
        public void Run()
        {
            _logger.LogError("From injected logger");
            Serilog.Log.Logger.Error("From static logger");
        }
    }
    

    【讨论】:

    • 那么,虽然我的接收器有一个自定义构造函数,但根本无法从配置文件中配置它?
    • @Banshee Serilog 查找默认构造函数或其所有参数都具有默认值的构造函数,请参阅source code。我了解您的用例需要依赖注入。
    【解决方案4】:

    @pfx 帮助了解如何加载 Serilog 自定义接收器,为此我给了他赏金,谢谢!然而,这不是我的最终解决方案。

    依赖注入意味着类的构造函数将把必要的对象作为参数。虽然无法使用构造函数参数加载自定义接收器,但必须通过服务来完成。

    public static IHost CreateHostBuilder(string[] args)
               => Host.CreateDefaultBuilder(args)
                        .ConfigureServices((hostContext, services) =>
                        {
                            services
                                .AddTransient<IMyService, MyService>()
                                .AddTransient<ICommunicator, Communicator>()
                                .AddTransient<ILogEventSink, CustomSerilogSink>();
                        })
                       .UseSerilog((context, services, configuration) => configuration
                                                                           .ReadFrom.Configuration(context.Configuration)
                                                                           .ReadFrom.Services(services)
                                                                           .WriteTo.Console(LogEventLevel.Information)
                                                                           .Enrich.FromLogContext())
               .Build();
            }
    

    问题是 MQ 控制器类注入了 ILogger,这会创建一个循环引用,从而导致释放。 MQ 控制器是共享的,因此我无法对其进行太多更改,但我可以将所需的方法设为静态。而不是使用本地 MQ 对象,而是将它们发送到方法中。

    但是,我无法删除 MS ILogger 要求,我仍然需要能够在自定义接收器中登录。所以首先我创建了一个这样的 Serilog:

    private static readonly ILogger _logger = Log.ForContext<MessageQueueSink>() as ILogger;
    

    然后可以将该记录器包装在 MS Ilogger 类中并发送到静态生产者方法:

    public class CustomSerilogger : Microsoft.Extensions.Logging.ILogger
    {
        private readonly ILogger _logger;
        public CustomSerilogger(ILogger logger)
        { _logger = logger; }
    
        public IDisposable BeginScope<TState>(TState state) => default!;
    
        public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) { return _logger.IsEnabled(LogLevelToLogEventLevel(logLevel)); }
    
        public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel))
                return;
    
            _logger.Write(LogLevelToLogEventLevel(logLevel), exception, state.ToString());
        }
    
        private LogEventLevel LogLevelToLogEventLevel(Microsoft.Extensions.Logging.LogLevel loglevel)
        {
            switch(loglevel)
            {
                case Microsoft.Extensions.Logging.LogLevel.Debug:
                    return LogEventLevel.Debug;
                case Microsoft.Extensions.Logging.LogLevel.Information:
                    return LogEventLevel.Information;
                case Microsoft.Extensions.Logging.LogLevel.Warning:
                    return LogEventLevel.Warning;
                case Microsoft.Extensions.Logging.LogLevel.Error:
                    return LogEventLevel.Error;
                case Microsoft.Extensions.Logging.LogLevel.Critical:
                    return LogEventLevel.Fatal;
                case Microsoft.Extensions.Logging.LogLevel.None:
                    return LogEventLevel.Verbose;
                case Microsoft.Extensions.Logging.LogLevel.Trace:
                    return LogEventLevel.Verbose;
            }
            return LogEventLevel.Verbose;
        }
    }
    

    要配置接收器,我必须在 appsettings.json 文件中创建自定义接收器设置部分,然后将其读入设置对象。

    var mqSinkSettings = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build()
                                                        .GetSection("MessageQueueSinkSettings").Get<MessageQueueSinkSettings>();
    

    终于成功了!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-27
      • 2023-04-06
      • 2021-08-12
      • 1970-01-01
      • 1970-01-01
      • 2019-03-10
      相关资源
      最近更新 更多