多年来,.NET中有几种不同的日志记录和跟踪工具,还有许多不同的第三方日志记录程序。尝试将一个应用程序从一种日志记录技术更改为另一种日志记录技术不是一件容易的事情,因为日志记录API的使用分布在整个源代码中。要使日志记录独立于任何日志记录技术,可以使用接口。
.NET Core在NuGet包Microsoft.Extensions.Logging中嵌入了泛型ILogger接口。这个接口定义了Log方法。Log方法定义了参数,来指定LogLevel(枚举值)、事件ID(使用结构EventId)、泛型状态信息、记录异常信息的Exception类型,以及用字符串确定输出格式的格式化程序:
void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
除了Log方法之外,ILogger接口还定义了IsEnabled方法,以基于LogLevel检查日志记录是否启用,该接口也定义了方法BeginScope,为日志记录返回可释放的作用域。ILogger接口中的成员实际上是日志记录所需的全部。Log方法有许多需要填充的参数。为了简化日志记录,在LoggerExtensions类中定义了ILogger接口的扩展方法。例如LogDebug、LogTrace、LogInformation、LogWarning、LogError、LogCritical和BeginScope都有几个重载版本和易于使用的参数。
下面利用依赖注入,并使用包含的类SampleController作为一个泛型参数,注入ILogger接口。泛型参数定义了日志记录器的类别。在泛型参数中,类别是由类名组成的,包括名称空间:
class SampleController
{
private readonly ILogger<SampleController> _logger;
public SampleController(ILogger<SampleController> logger)
=> _logger = logger;
}
在接下来的第3小节中,说明了如何使用类别名来过滤日志。
日志示例使用了以下依赖项和名称空间:
依赖项
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Logging
Microsoft.Extensions.Logging.Configuration
Microsoft.Extensions.Logging.Console
Microsoft.Extensions.Logging.Debug
Microsoft.Extensions.Logging.EventSource
Microosft.Extensions.Logging.Filter
名称空间
Microsoft.Extensions.DependencyInjection
Microsoft.Extensions.Logging
Microsoft.Extensions.Logging.Console
System
System.Net.Http
System.Threading.Tasks
ILogger接口可以简单地用于调用扩展方法,如LogInformation:
_logger.LogInformation("NetworkRequestSampleAsync started");
扩展方法提供重载版本,来传递额外的参数、异常信息和事件ID。为了使用事件ID,应用程序定义了一个枚举:
enum LoggingEvents
{
Injection = 2000,
Networkking = 2002
}
接下来,使用LogInformation和LogError扩展方法显示NetworkRequestSampleAsync方法的开头、结束以及抛出异常时的日志信息:
public async Task NetworkRequestSampleAsync(string url)
{
try
{
_logger.LogInformation((int)LoggingEvents.Networkking,
"NetworkRequestSampleAsync started with url {0}", url);
using (var client = new HttpClient())
{
string result = await client.GetStringAsync(url);
_logger.LogInformation((int)LoggingEvents.Networkking,
"NetworkRequestSampleAsync completed,received {0} characters",result.Length);
}
}
catch (Exception ex)
{
_logger.LogError((int)LoggingEvents.Networkking,ex,
"Error in NetworkRequestSampleAsync,error message: {0},HResult: {1}",ex.Message,ex.HResult);
}
}
注意:
ILogger扩展方法的一个重载版本需要给第一个参数使用EventId。在示例代码中,传递一个int。这是可能的,因为EventId结构实现了一个隐式运算符,来将int转换为EventId。
将消息传递给LogXX方法时,可以提供任何数量的对象,并将其放入格式消息字符串中。此格式字符串使用位置参数传入以下对象。不能使用可格式化的字符串,因为格式字符串通常来自允许这些消息本地化的资源。
接下来,需要配置日志提供程序,以使日志信息可用。
1. 配置提供程序
需要使用ILoggingBuilder定义配置日志的位置。为IServiceCollection调用AddLogging扩展方法(这个方法的一个重载版本接受Action<ILoggingBuilder>参数)时,可以配置ILoggingBuilder。使用ILoggingBuilder时,可以添加提供程序。示例代码为控制台添加提供程序,调试(在Visual Studio的Output窗口中显示),并添加事件源代码:
static void RegisterServices()
{
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddEventSourceLogger();
builder.AddConsole();
#if DEBUG
builder.AddDebug();
#endif
});
services.AddScoped<SampleController>();
AppService = services.BuildServiceProvider();
}
public static IServiceProvider AppService { get; private set; }
示例应用程序的Main()方法调用RegisterServices方法,在依赖注入容器中注册服务,然后调用RunSampleAsync()方法:
private const string s_url = "http://csharp.christiannagel.com";
static async Task Main(string[] args)
{
RegisterServices();
await RunSampleAsync();
}
static async Task RunSampleAsync()
{
var controller = AppService.GetService<SampleController>();
await controller.NetworkRequestSampleAsync(s_url);
}
成功运行应用程序时,可以在控制台输出中看到这些细心日志:
info: LoggingSample.SampleController[2002]
NetworkRequestSampleAsync started with url http://csharp.christiannagel.com
info: LoggingSample.SampleController[2002]
NetworkRequestSampleAsync completed,received 92382 characters
传递无效的主机名,会显示如下错误信息:
info: LoggingSample.SampleController[2002]
NetworkRequestSampleAsync started with url http://csharp.christiannagl.com
fail: LoggingSample.SampleController[2002]
Error in NetworkRequestSampleAsync,error message: 不知道这样的主机。,HResult: -2147467259
System.Net.Http.HttpRequestException: 不知道这样的主机。
---> System.Net.Sockets.SocketException (11001): 不知道这样的主机。
at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
at System.Net.Http.HttpClient.GetStringAsyncCore(Task`1 getTask)
at LoggingSample.SampleController.NetworkRequestSampleAsync(String url) in D:\C#TrainingSamples\EvnetSourceSln\LoggingSample\SampleController.cs:line 26
2. 使用作用域
使用作用域,可以将属于输出的日志信息组合在一起。
调用BeginScope扩展方法,并将消息传递给作用域,可以定义作用域。该消息显示在输出中,并显示作用域内定义的每个日志信息。BeginScope返回一个IDisposable对象。调用Dispose方法(在代码示例中使用using语句完成)结束作用域:
public async Task NetworkRequestSampleAsync(string url)
{
using (_logger.BeginScope("NetworkRequestSampleAsync, url: {0}",url))
{
try
{
_logger.LogInformation((int)LoggingEvents.Networkking,
"NetworkRequestSampleAsync started with url {0}", url);
using (var client = new HttpClient())
{
string result = await client.GetStringAsync(url);
_logger.LogInformation((int)LoggingEvents.Networkking,
"NetworkRequestSampleAsync completed,received {0} characters", result.Length);
}
}
catch (Exception ex)
{
_logger.LogError((int)LoggingEvents.Networkking, ex,
"Error in NetworkRequestSampleAsync,error message: {0},HResult: {1}", ex.Message, ex.HResult);
}
}
}
对于提供程序,需要启用作用域来显示它。可以更改AddConsole方法的配置,来设置IncludeScopes属性:
static void RegisterServices()
{
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddEventSourceLogger();
builder.AddConsole(option=>option.IncludeScopes = true);
#if DEBUG
builder.AddDebug();
#endif
});
services.AddScoped<SampleController>();
AppService = services.BuildServiceProvider();
}
现在运行应用程序时,可以看到在 => 之后传递给作用域的信息,如以下代码片段所示:
info: LoggingSample.SampleController[2002]
=> NetworkRequestSampleAsync, url: http://csharp.christiannagel.com
NetworkRequestSampleAsync started with url http://csharp.christiannagel.com
info: LoggingSample.SampleController[2002]
=> NetworkRequestSampleAsync, url: http://csharp.christiannagel.com
NetworkRequestSampleAsync completed,received 92305 characters
3. 过滤
不需要在任何时候都查看所有日志消息。应用程序在生产环境中运行时,需要注意错误和关键信息。调试应用程序时,可能会改变配置,为特定的跟踪源显示跟踪消息,了解应用程序总的所有事情。可以为日志记录需求定义过滤器。
过滤可以基于日志记录器提供程序和日志类别。
下面的代码片段为ConsoleLoggerProvider和类别名LoggingSample定义了一个过滤器,以仅过滤日志级别为Error和更高级别的错误:
static void RegisterServices()
{
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddEventSourceLogger();
builder.AddConsole();
#if DEBUG
builder.AddDebug();
#endif
builder.AddFilter<ConsoleLoggerProvider>("LoggingSample",LogLevel.Error);
});
services.AddScoped<SampleController>();
AppService = services.BuildServiceProvider();
}
除了指定类别的名称和LogLevel之外,还可以传递带有类别和LogLevel参数的委托。如果类别名称包含SampleController,并且所接收的LogLevel至少是Information,那么下面的代码片段将返回一个过滤器值true。对于所有其他类别,如果LogLevel的值至少是Error,则过滤器返回true:
builder.AddFilter<ConsoleLoggerProvider>((catetory,logLevel)=>
{
if (catetory.Contains("SampleController") && logLevel >= LogLevel.Information)
return true;
else if (logLevel >= LogLevel.Error)
return true;
else
return false;
});
4. 配置日志记录
过滤也可以使用配置文件来定义。
在.NET Core中,可以给配置文件使用提供程序,例如从JSON或XML文件、环境变量或命令行参数中读取配置。只需要从NuGet包Microsoft.Extensions.Configuration中创建一个ConfigurationBuilder,并向此构建起添加提供程序。要添加JSON提供程序,需要调用扩展方法AddJsonFile。构建器的Build方法返回一个实现IConfiguration的对象。可以使用此接口通过任何已配置的提供程序来访问已知配置值。下面的示例代码从配置中检索Logging部分,并将其传递给RegisterServices方法:
var configurationBuilder = new ConfigurationBuilder();
configurationBuilder.AddJsonFile("appsettings.json");
IConfiguration configuration = configurationBuilder.Build();
RegisterServices(configuration);
示例应用程序的配置文件根据提供程序和类别配置不同的配置值。对于Debug提供程序,LogLevel设置为Information。这样,对于所有类别,Information及以上级别都记录到Visual Studio的Output窗口。对于Console提供程序,LogLevel根据类别的不同而不同。在Console提供程序的配置之下,所有其他提供程序的默认配置都是基于类别用特定的日志级别:
{
"Logging": {
"Debug": {
"LogLevel": {
"LoggingSample.SampleController": "Information"
}
},
"Console": {
"LogLevel": {
"LoggingSample.SampleController": "Information",
"Default": "Warning"
}
},
"LogLevel": {
"Default": "Warning",
"System": "Information",
"LoggingSample.SampleController": "Warning"
}
}
}
在日志配置就绪后,现在调用AddConfiguration方法,以传递对IConfiguration对象的引用。AddConfiguration方法需要配置文件中的Logging部分的内容:
static void RegisterServices(IConfiguration configuration)
{
var services = new ServiceCollection();
services.AddLogging(builder =>
{
builder.AddEventSourceLogger();
builder.AddConfiguration(configuration.GetSection("Logging"));
builder.AddConsole(option=>option.IncludeScopes = true);
#if DEBUG
builder.AddDebug();
#endif
//builder.AddFilter<ConsoleLoggerProvider>("LoggingSample",LogLevel.Error);
//builder.AddFilter<ConsoleLoggerProvider>((catetory,logLevel)=>
//{
// if (catetory.Contains("SampleController") && logLevel >= LogLevel.Information)
// return true;
// else if (logLevel >= LogLevel.Error)
// return true;
// else
// return false;
//});
});
services.AddScoped<SampleController>();
AppService = services.BuildServiceProvider();
}
不需要更改任何代码,现在可以灵活地定义日志配置。
5. 使用没有依赖注入的ILogger
依赖注入有很大的优势。但是,也可以使用没有依赖注入的日志记录API。为此,只需要创建一个LoggerFactory,并使用CreateLogger方法创建一个日志记录器。配置提供程序可以添加到logger工厂——类似于为ILoggingBuilder接口提供扩展方法,也提供了ILoggerFactory的扩展方法:
var loggerFactory = new LoggerFactory();
loggerFactory.AddConsole().AddDebug();
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("Info Message");
本文介绍了.NET Core中使用ILogger接口进行日志记录的方法,包括依赖注入、扩展方法、事件ID、日志级别、作用域、过滤和配置。示例代码展示了如何在控制器中注入ILogger,以及如何配置不同的日志提供程序,如Console和Debug,并通过配置文件控制日志级别。
1039

被折叠的 条评论
为什么被折叠?



