Geo*_*ope 5 c# dependency-injection serilog .net-6.0
如何修复依赖项注入,以便可以.ForContext(...)在我的工作人员/服务类中进行访问?
[.Net Core 6 用于混合控制台/WindowsService 应用程序]
在我的主程序类和工作/服务类中,我让 Serilog 正确工作以进行基本日志记录...
程序.cs
Log.Information($"Application Directory: {baseDir}");
Run Code Online (Sandbox Code Playgroud)
工人.cs
_logger.LogInformation("Service Starting");
Run Code Online (Sandbox Code Playgroud)
服务A.cs
_logger.LogInformation("In Service A");
Run Code Online (Sandbox Code Playgroud)
然而,我遇到的问题是我需要 .ForContext 也能够在任何地方工作......并且它在我的主程序类中:
程序.cs
Log
.ForContext("EventID", 42)
.Information("Log This with EventID === 42");
Run Code Online (Sandbox Code Playgroud)
...但是,当我尝试在任一工作人员/服务类中执行相同操作时...
工人.cs
_logger
.ForContext("EventID", 42)
.Information("Log This with EventID === 42");
Run Code Online (Sandbox Code Playgroud)
...它不起作用,我收到以下错误:
错误CS1061
'ILogger<Worker>' does not contain a definition for 'ForContext' and no accessible extension method 'ForContext' accepting a first argument of type 'ILogger<Worker>' could be found
Run Code Online (Sandbox Code Playgroud)
...所以我对此进行了调查,并遇到了以下非常接近的问题(这两个问题我都无法应用,请参阅下面代码中的注释):
...(以及其他一些地方)但我无法将答案集成到代码库中:
程序.cs
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseWindowsService()
.ConfigureAppConfiguration((context, config) =>
{
// Configure the app here.
})
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
services.Configure<AppSettings>(hostContext.Configuration.GetSection("AppSettings"));
services.AddScoped<IServiceA, ServiceA>();
services.AddScoped<IServiceB, ServiceB>();
//?? I'm not sure if this .AddLogging(...) is needed ??
services.AddLogging(x =>
{
x.ClearProviders();
x.AddSerilog(dispose: true);
});
//?? ... having/not having it doesn't seem to affect execution
})
.UseSerilog();
Run Code Online (Sandbox Code Playgroud)
工人.cs
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
//private readonly Serilog.ILogger<Worker> _logger;
//?? ... wrt this attempt, Serilog.ILogger won't allow <Worker> ...
//?? private readonly ILogger _log = Log.ForContext<SomeService>();
//private readonly ILogger _logger = Log.ForContext<Worker>();
//?? ... wrt this attempt, Log isn't available ...
private FileSystemWatcher _folderWatcher;
private readonly string _inputFolder;
private readonly IServiceProvider _services;
public Worker(ILogger<Worker> logger, IOptions<AppSettings> settings, IServiceProvider services)
{
_logger = logger;
_services = services;
_inputFolder = settings.Value.InputFolder;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.CompletedTask;
}
public override Task StartAsync(CancellationToken cancellationToken)
{
_logger
.ForContext("EventID", 1001)
.LogInformation("Service Starting - need to assign EventID === 1001");
//?? here is where .ForContext is needed (but doesn't work)
... ... ...
Run Code Online (Sandbox Code Playgroud)
前言:
Microsoft.Extensions.Logging。ILogger用于完全不同的类型,因此此答案始终将名称出现的歧义消除为或。
ILoggerMEL.ILoggerSerilog.ILoggerILogger<T>是通用的。没有Serilog.ILogger<T>,只有Serilog.ILogger。.ForContext):Serilog.ILogger.ForContext(String propertyName, Object? value, Boolean destructureObjects = false)创建一个“子”Serilog.ILogger实例,并将单个事件属性添加到子记录器记录的所有事件中。
ILogger<T>'s并传递参数),它与 具有不同的语义。这就是为什么你不应该将 Serilog 惯用法与 MEL 惯用法混合。BeginScopeusing( log.BeginScope(...) )state: Dictionary<String,Object?>ForContextSerilog.ILogger.ForContext(Type)和Serilog.ILogger.ForContext<TSource>都相当于调用ForContext(String).
ForContext<TSource>()传递typeof(TSource)到ForContext(Type),并且ForContext(Type)刚刚传递Type.FullName到ForContext(String))。MEL 相当于ForContext<TSource>...
ILogger<TSource>作为TSource构造函数参数来使用构造函数参数依赖注入。ILoggerFactory并指定TSource为.CreateLogger<T>扩展方法的泛型类型参数。ILoggerFactory添加一个"SourceContext"(或“类别”)事件属性。typeof(TSource).FullName因此,如果您将 MEL 注入ILogger<TService>到您的class TService构造函数中,那么......
ForContext自己来设置"SourceContext"(或“类别”)。ILogger.BeginScope()而不是使用 MEL。Serilog.ILogger.ForContextForContextMEL ILogger(通用版本和非通用版本),您将收到编译器错误,如果您尝试使用BeginScopewith Serilog.ILogger- 也是如此,因为这些是仅为其预期类型定义的扩展方法。有 2 种不同的方法可以在使用Microsoft.Extensions.DependencyInjection.
MEL.ILogger<T>注入类型构造函数的“前端”接口。static,global::Serilog.Log或通过注入global::Serilog.ILogger),同时仍然为 Serilog 连接 MEL,以便使用 MEL 的其他组件(不是您编写的)仍将出现在 Serilog 输出中。选项2是Serilog作者的偏好......
在应用程序代码中,我专门使用
Serilog.ILogger和static global::Serilog.Log.*方法。我也不喜欢让每个构造函数都带有
MEL.ILogger<T>参数(构造函数参数列表是非常有价值的资产),并且发现一旦代码退出 DI 并开始调用想要进行一些日志记录的非 DI 实例化类,就会出现可用性问题(更多参数传递)。
...但是我(恭敬地)不同意@nblumhardt 的推理,因为:
staticAPI(不仅仅是 Serilog 的Serilog.Log)都是一个坏主意,因为管理static单元和集成测试中的状态是非常困难的,如果不是不可能的话,这取决于您使用的 API以及您的测试运行程序如何工作。
Serilog.Log每个测试测试用例,否则所有测试输出将被混在一起。static Serilog.Log方法并没有更好:这意味着现在在静态 Serilog 类型不可用的情况下更难重用类。
MEL.ILogger<T>对于这种情况,我的首选解决方案是为该类型定义一个静态工厂方法,该方法提供(例如)的库存或 NOOP 实现NullLogger<T>,或者定义一个提供其自己的默认值的替代构造函数(并应用于[ActivatorUtilitiesConstructor]DI 构造函数)。Microsoft.Extensions.Logging现在已被确立为基线日志记录库,现在每个 .NET 环境中都存在 - 考虑到.csproj过去 5 年中创建的任何随机项目,它更有可能MEL.ILogger<T>代替(如果不是补充的话)SerilogNuGet可用包裹。
IHost才没有 MEL 可用。此外,无论如何,每个ASP.NET Core 项目都有 MEL。如果您使用不同的 DI 系统(例如 Simple Injector、Autofac、Ninject 等),情况会有所不同,因此,如果您不直接使用,请不要遵循本文的建议。Microsoft.Extensions.DependencyInjection
对于选项 1,我在项目中是这样做的:
如果这是一个多项目解决方案,并且有一个.exe引用您其他项目的“入口点”项目......
.exe项目应引用Microsoft.Extensions.Logging、Serilog和Serilog.Extensions.LoggingNuGet 包。Microsoft.Extensions.Logging.Abstractions(而不是主Microsoft.Extensions.Logging包)。如果这是单个项目解决方案,则参考Microsoft.Extensions.Logging、Serilog和Serilog.Extensions.Logging。
如果您正在使用Host.CreateDefaultBuilder或 ,WebHost.CreateDefaultBuilder那么这些方法已经调用了.AddLogging您,您不需要自己执行此操作,但您确实需要在调用之前调用UseSerilog( Serilog.SerilogHostBuilderExtensions.UseSerilog) 。IHostBuilder.Build()
.AddSerilog。在您的服务类型(即在其构造函数参数中具有服务接口的类型)中,使用Microsoft.Extensions.Logging.ILogger<T>whereT与构造函数的声明类型相同的类型(是的,我同意这是多余的)。
using Serilog在任何其他文件中包含该文件。.csProgram.csconfigureLogger然后,在运行时,当Microsoft.Extensions.DependencyInjection容器实例化您的类型时,它将Microsoft.Extensions.Logging.ILoggerFactory.ILoggerFactory.CreateLogger(String categoryName)自动调用您。
categoryName的类型名称)TMEL.ILogger<T>ForContext记录器工厂,返回的Serilog.ILogger结果由 MEL 的MEL.ILogger<T>.一个实际的例子:
程序.cs
using System;
using Microsoft.Extensions.Logging;
// No `using Serilog` here.
static async Task<Int32> Main( String[] args )
{
using( IHost host = CreateHost( args ) )
{
await host.RunAsync();
return 0;
}
}
static IHost CreateHost( String[] args )
{
IHostBuilder b = Host.CreateDefaultBuilder( args );
// CreateDefaultBuilder calls `MEL.AddLogging` for you already.
b = b.UseStartup<YourStartupClass>(); // or `b.ConfigureServices()`;
// Explicit extension method call because there's no `using Serilog` to avoid ambiguity issues:
b = global::Serilog.SerilogHostBuilderExtensions.UseSerilog(
builder : b,
configureLogger : ConfigureSerilog,
preserveStaticLogger: true
);
return b.Build();
}
static void ConfigureSerilog( HostBuilderContext ctx, Serilog.LoggerConfiguration logCfg )
{
_ = logCfx
.Enrich.WithExceptionDetails()
.Enrich.FromLogContext()
.MinimumLevel.Is( /* etc */ )
.WriteTo.File( /* etc */ );
}
Run Code Online (Sandbox Code Playgroud)
示例服务类型.cs
using System;
using Microsoft.Extensions.Logging;
// No `using Serilog` here either.
public MyService
{
private readonly ILogger log;
public MyService( ILogger<MyService> log )
{
this.log = log ?? throw new ArgumentNullException(nameof(log));
}
public void Foo( String name )
{
this.log.LogInformation( "hello {Name}", name );
}
}
Run Code Online (Sandbox Code Playgroud)
如果您发现自己MyService在 DI 不可用的地方重用,那么您可以定义一个替代构造函数,如下所示:
using System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.DependencyInjection;
// No `using Serilog` here either.
public MyService
{
private readonly ILogger log;
[ActivatorUtilitiesConstructor]
public MyService( ILogger<MyService> log )
{
this.log = log ?? throw new ArgumentNullException(nameof(log));
}
public MyService()
: this( log: NullLogger<MyService>.Instance )
{
}
public void Foo( String name )
{
this.log.LogInformation( "hello {Name}", name );
}
}
Run Code Online (Sandbox Code Playgroud)
所以这将起作用(尽管在NullLogger<T>专门使用时,不会记录任何内容,因此可能或可能不理想):
void Bar()
{
MyService svc = new MyService();
svc.Foo();
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1951 次 |
| 最近记录: |