在 .NET Core 中,ILogger 是日志记录的抽象接口,而 ILoggerProvider 负责创建具体的日志记录器实例并决定日志的输出目标(如控制台、文件、数据库等)。
要实现控制台日志,通常有两种方式:使用内置的 ConsoleLoggerProvider(推荐)或实现自定义的 ILoggerProvider。
使用内置 ConsoleLoggerProvider
项目依赖
确保项目中包含以下 NuGet 包:
- Microsoft.Extensions.Logging
- Microsoft.Extensions.Logging.Console
- Microsoft.Extensions.Configuration.Json (用于 JSON 配置)
下面是一个较为完整的代码示例:.NET Core 内置提供程序的使用、配置、过滤、作用域追踪以及自定义格式化。
cs
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
namespace ConsoleLoggingDemo
{
class Program
{
static void Main(string[] args)
{
// 1. 构建配置 (模拟从 appsettings.json 读取)
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();
// 2. 创建 LoggerFactory 并添加控制台提供程序
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
// 添加控制台日志提供程序
builder.AddConsole(options =>
{
options.TimestampFormat = "[HH:mm:ss] "; // 设置时间戳格式
});
// 3. 应用过滤器 (AddFilter)
// 全局默认级别为 Information
builder.SetMinimumLevel(LogLevel.Information);
// 针对特定命名空间过滤:Microsoft 开头的日志只记录 Warning 及以上
builder.AddFilter("Microsoft", LogLevel.Warning);
// 自定义过滤逻辑:如果类别包含 "Sensitive",仅记录 Error
builder.AddFilter((category, level) =>
{
if (category.Contains("Sensitive"))
return level >= LogLevel.Error;
return true;
});
// 绑定配置文件中的 Logging 节
builder.AddConfiguration(configuration.GetSection("Logging"));
});
// 4. 创建 Logger 实例
// 类别名称通常为当前类的全名,便于过滤和分组
ILogger<Program> logger = loggerFactory.CreateLogger<Program>();
ILogger sensitiveLogger = loggerFactory.CreateLogger("SensitiveDataProcessor");
// 5. 定义事件 ID
EventId eventId = new EventId(1001, "AppStart");
// 6. 使用 BeginScope 进行日志追踪 (结构化上下文)
using (logger.BeginScope(new Dictionary<string, object>
{
["RequestId"] = Guid.NewGuid().ToString(),
["UserId"] = "User_12345",
["Operation"] = "ProcessOrder"
}))
{
logger.Log(eventId, LogLevel.Information, "Application started successfully.");
logger.LogInformation("Processing order...");
// 嵌套 Scope
using (logger.BeginScope(new { Step = "Validation" }))
{
logger.LogInformation("Validating data...");
}
}
// 测试敏感数据过滤
sensitiveLogger.LogInformation("This should be filtered out.");
sensitiveLogger.LogError("This sensitive error should appear.");
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}
JSON 配置 (appsettings.json
cs
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"Console": {
"LogLevel": {
"Default": "Debug"
}
}
}
}
ILogger 与 ILoggerProvider 的关系
ILogger:应用程序代码直接使用的接口,用于写入日志。它不关心日志写到哪里,只关心写什么。
ILoggerProvider : 工厂模式的核心,负责根据类别名称创建 ILogger 实例。例如,ConsoleLoggerProvider 会创建 ConsoleLogger,后者知道如何将消息格式化并写入 Console.Out。
ILoggerFactory: 管理所有的 ILoggerProvider。当你调用 factory.CreateLogger<T>() 时,工厂会遍历所有注册的 Provider,为每个 Provider 创建一个对应的 Logger 实例,并将它们组合成一个聚合 Logger。
日志级别 (LogLevel)
在 .NET Core (ASP.NET Core) 中,日志级别由 Microsoft.Extensions.Logging.LogLevel 枚举定义。系统根据严重性从低到高排列,数值越小表示日志越详细,数值越大表示问题越严重。
.NET Core:定义了以下标准级别(数值越小越详细):
Trace (0): 最详细的信息,可能包含敏感数据。
Debug (1):开发调试用。
Information (2):常规业务流跟踪。
Warning (3):非错误异常或意外情况。
Error (4): 导致当前操作失败的错误。
Critical (5):系统级崩溃,需要立即关注。
None (6): 不记录任何日志。
.NET Core 日志级别一览表
| 日志级别 (LogLevel) | 数值 | 描述与应用场景 |
|---|---|---|
| Trace | 0 | 最详细。包含最敏感的 ứng用程序数据,通常仅在开发或诊断复杂问题时短暂启用。可能包含密码等敏感信息,生产环境严禁开启。 |
| Debug | 1 | 调试信息。用于开发期间的交互式调试,短期有用。例如记录变量值、方法进入/退出等。生产环境通常关闭。 |
| Information | 2 | 常规流程 。跟踪应用程序的一般流程。例如:用户登录成功、请求开始/结束、服务启动。这是生产环境的推荐默认级别。 |
| Warning | 3 | 警告。表示发生了意外事件,但应用程序仍能继续运行。例如:配置文件缺失使用默认值、API 调用延迟较高。需要关注但不需立即修复。 |
| Error | 4 | 错误。表示当前活动因故障而停止。例如:数据库连接失败、未处理的异常、业务逻辑验证失败。需要立即调查。 |
| Critical | 5 | 严重。表示应用程序或系统崩溃、数据丢失或不可恢复的错误。例如:内存溢出、磁盘空间耗尽。需要立即采取紧急措施。 |
| None | 6 | 无。不写入任何日志消息。用于完全禁用日志记录。 |
级别过滤机制:日志系统会根据配置的"最低级别"进行过滤。如果配置的最低级别是 Warning,那么只有 Warning、Error 和 Critical 级别的日志会被输出,Trace、Debug 和 Information 将被忽略。
生产环境默认级别:通常设置为 Information,将 Microsoft、System 等命名空间的级别设置为 Warning 或 Error,以避免框架内部的大量调试信息淹没业务日志。
cs
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"MyApp.Namespace": "Debug"
}
}
}
开发环境默认级别:
通常设置为 Debug 或 Information,以便获取更多的执行细节。
避免在生产环境中长期使用 Debug 或 Trace,因为这会导致性能下降和日志文件爆炸式增长。
日志过滤 (AddFilter)
基于类别的过滤
基于类别的过滤: builder.AddFilter("Microsoft", LogLevel.Warning) 会抑制所有以 "Microsoft" 开头的类别中低于 Warning 级别的日志。
cs
using Microsoft.Extensions.Logging;
using System;
namespace LoggingFilterDemo
{
class Program
{
static void Main(string[] args)
{
// 创建 LoggerFactory
using ILoggerFactory factory = LoggerFactory.Create(builder =>
{
// 添加控制台提供程序
builder.AddConsole();
// ==========================================
// 1. 基于类别的过滤 (Category-based Filtering)
// ==========================================
// 规则:对于所有以 "Microsoft" 开头的类别(通常是框架内部日志),
// 只记录 Warning 及以上级别的日志。
// 这意味着 Information, Debug, Trace 级别的微软框架日志将被抑制。
builder.AddFilter("Microsoft", LogLevel.Warning);
// 可以为特定子命名空间设置更严格的规则
// 例如:只记录 EntityFramework 的 Error 级别日志
builder.AddFilter("Microsoft.EntityFrameworkCore", LogLevel.Error);
// ==========================================
// 2. 基于委托的过滤 (Delegate-based Filtering)
// ==========================================
// 规则:允许执行复杂的逻辑判断。
// 这里演示:如果是 "MyApp.Sensitive" 类别,只记录 Error 以上;
// 其他所有类别,记录 Information 以上。
builder.AddFilter((category, level) =>
{
// 模拟动态配置或运行时状态检查
bool isSensitiveModule = category.StartsWith("MyApp.Sensitive");
if (isSensitiveModule)
{
// 敏感模块:仅记录严重错误,减少敏感信息泄露风险
return level >= LogLevel.Error;
}
// 默认业务逻辑:记录 Information 及以上
return level >= LogLevel.Information;
});
// 注意:如果有多个 AddFilter,它们通常会组合生效(取最严格的限制或按注册顺序,
// 具体行为取决于实现,通常建议针对特定 Category 使用字符串过滤,全局逻辑使用委托)
});
// 创建不同类别的 Logger 进行测试
var microsoftLogger = factory.CreateLogger("Microsoft.AspNetCore.Hosting");
var sensitiveLogger = factory.CreateLogger("MyApp.Sensitive.DataProcessor");
var businessLogger = factory.CreateLogger("MyApp.Services.OrderService");
Console.WriteLine("--- 测试 Microsoft 类别过滤 (应只显示 Warning/Error) ---");
microsoftLogger.LogInformation("This ASP.NET Core info log should be FILTERED OUT.");
microsoftLogger.LogWarning("This ASP.NET Core warning log should APPEAR.");
microsoftLogger.LogError("This ASP.NET Core error log should APPEAR.");
Console.WriteLine("\n--- 测试 Sensitive 类别委托过滤 (应只显示 Error/Critical) ---");
sensitiveLogger.LogInformation("Sensitive info: User password check... (FILTERED OUT)");
sensitiveLogger.LogWarning("Sensitive warning: High latency... (FILTERED OUT)");
sensitiveLogger.LogError("Sensitive error: Auth failed! (SHOULD APPEAR)");
Console.WriteLine("\n--- 测试普通业务类别委托过滤 (应显示 Information+) ---");
businessLogger.LogInformation("Order created successfully. (SHOULD APPEAR)");
businessLogger.LogDebug("Order details serialized. (FILTERED OUT)");
}
}
}
- 原理:这是最常用且性能较好的过滤方式。.NET Core 的日志类别通常对应于创建 Logger 时传入的名称(如 typeof(Program).FullName)。
- 匹配规则:采用前缀匹配。
- builder.AddFilter("Microsoft", LogLevel.Warning) 会匹配 Microsoft、Microsoft.AspNetCore、Microsoft.EntityFrameworkCore 等所有以该字符串开头的类别。
- 如果存在更具体的规则(如同时定义了 Microsoft 和 Microsoft.AspNetCore),系统通常会应用最具体(最长匹配)的规则。
- 作用:有效抑制第三方库或框架产生的大量低级别调试日志,保持主日志文件的整洁。
基于委托的过滤
基于委托的过滤: 允许更复杂的逻辑,如根据动态配置或运行时状态决定。
在配置 ILoggerFactory 时,使用 AddFilter 的重载方法,传入一个 Func<string, LogLevel, bool> 委托。
cs
using Microsoft.Extensions.Logging;
using System;
public class Program
{
public static void Main(string[] args)
{
// 创建 LoggerFactory
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
// 添加控制台提供程序
builder.AddConsole();
// ==========================================
// 基于委托的过滤示例
// ==========================================
builder.AddFilter((category, level) =>
{
// 逻辑1:完全忽略所有来自 "Microsoft" 命名空间的 Debug 级别日志
if (category.StartsWith("Microsoft") && level == LogLevel.Debug)
{
return false; // 过滤掉,不记录
}
// 逻辑2:如果是 "MyApp.Sensitive" 类别,只记录 Error 及以上级别
if (category.Contains("Sensitive"))
{
return level >= LogLevel.Error;
}
// 逻辑3:模拟动态配置(例如从内存变量或配置服务中读取)
// 假设有一个全局开关 enableVerboseLogging
// if (!enableVerboseLogging && level == LogLevel.Trace) return false;
// 默认策略:记录 Information 及以上级别的日志
return level >= LogLevel.Information;
});
});
// 创建不同类别的 Logger 进行测试
var microsoftLogger = loggerFactory.CreateLogger("Microsoft.AspNetCore.Hosting");
var sensitiveLogger = loggerFactory.CreateLogger("MyApp.Sensitive.AuthService");
var businessLogger = loggerFactory.CreateLogger("MyApp.Services.OrderService");
Console.WriteLine("--- 测试 Microsoft 类别 (Debug 应被过滤) ---");
microsoftLogger.LogDebug("This debug log from Microsoft should be FILTERED OUT.");
microsoftLogger.LogInformation("This info log from Microsoft should APPEAR.");
Console.WriteLine("\n--- 测试 Sensitive 类别 (Info/Warning 应被过滤) ---");
sensitiveLogger.LogInformation("Sensitive info: User login attempt... (FILTERED OUT)");
sensitiveLogger.LogWarning("Sensitive warning: Invalid token... (FILTERED OUT)");
sensitiveLogger.LogError("Sensitive error: Authentication failed! (SHOULD APPEAR)");
Console.WriteLine("\n--- 测试普通业务类别 (Default 策略) ---");
businessLogger.LogDebug("Order details... (FILTERED OUT by default policy)");
businessLogger.LogInformation("Order created successfully. (SHOULD APPEAR)");
}
}
结合运行时状态
你可以根据应用程序当前的模式(开发/生产)或特定的功能开关来动态调整过滤规则。
cs
// 模拟运行时状态
bool isProduction = true;
string criticalFeatureFlag = "Enabled";
builder.AddFilter((category, level) =>
{
// 场景:在生产环境中,除非是 Critical 错误,否则不记录第三方库的日志
if (isProduction && category.StartsWith("ThirdPartyLib"))
{
return level >= LogLevel.Critical;
}
// 场景:如果某个关键功能标志未启用,忽略相关模块的所有日志
if (category.Contains("ExperimentalFeature") && criticalFeatureFlag != "Enabled")
{
return false;
}
// 其他情况遵循默认级别
return level >= LogLevel.Information;
});
作用域追踪 (BeginScope)
在 .NET Core 中,BeginScope 是 ILogger 接口提供的一个强大功能,用于创建日志记录的**作用域(Scope)**。它允许你将特定的上下文信息(如请求 ID、用户 ID、事务 ID 等)附加到该作用域内记录的所有日志中。这对于在复杂的异步或多线程环境中追踪业务流程至关重要。
字符串模板作用域
使用字符串模板创建作用域。作用域内的所有日志都会自动包含这些上下文信息。
cs
using Microsoft.Extensions.Logging;
using System;
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
}
public void ProcessOrder(int orderId, string userId)
{
// 创建作用域,上下文信息为 UserId 和 OrderId
// 注意:使用命名占位符 {UserId} 而不是字符串拼接,以支持结构化日志
using (_logger.BeginScope("UserId={UserId}, OrderId={OrderId}", userId, orderId))
{
_logger.LogInformation("Starting order processing...");
try
{
// 模拟业务逻辑
ValidateOrder(orderId);
_logger.LogInformation("Order validated successfully.");
ChargePayment(orderId);
_logger.LogInformation("Payment charged successfully.");
}
catch (Exception ex)
{
// 即使发生异常,日志中也会自动携带 UserId 和 OrderId
_logger.LogError(ex, "Failed to process order.");
}
}
// 离开 using 块后,作用域结束,后续日志不再包含上述上下文
}
private void ValidateOrder(int orderId) { /* ... */ }
private void ChargePayment(int orderId) { /* ... */ }
}
字典对象作用域(推荐用于结构化日志)
为了获得更好的结构化日志支持(例如被 Seq、Application Insights 或 ELK 解析),建议传递一个 Dictionary<string, object> 或匿名对象作为状态。
cs
using System.Collections.Generic;
public void ProcessOrderAdvanced(int orderId, string userId)
{
var scopeState = new Dictionary<string, object>
{
["RequestId"] = Guid.NewGuid().ToString(),
["UserId"] = userId,
["OrderId"] = orderId,
["Source"] = "WebAPI"
};
using (_logger.BeginScope(scopeState))
{
_logger.LogInformation("Processing order with advanced context.");
// 嵌套作用域示例
using (_logger.BeginScope(new { Step = "PaymentGateway" }))
{
_logger.LogInformation("Calling external payment API...");
}
}
}
appsettings.json 中配置
cs
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
},
"Console": {
"IncludeScopes": true,
"TimestampFormat": "[HH:mm:ss] "
}
}
}
在代码中配置
cs
using Microsoft.Extensions.Logging;
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole(options =>
{
options.IncludeScopes = true; // 显式启用作用域
});
// 或者使用简单控制台格式化程序
builder.AddSimpleConsole(formatter =>
{
formatter.IncludeScopes = true;
formatter.TimestampFormat = "[HH:mm:ss] ";
});
});
实现自定义控制台格式化程序
如果你需要完全控制控制台输出的格式(例如 JSON 格式、特定颜色、自定义字段顺序),可以实现 ConsoleFormatter。
cs
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
using System;
using System.IO;
public class CustomJsonConsoleFormatter : ConsoleFormatter
{
public CustomJsonConsoleFormatter() : base("CustomJson") { }
public override void Write<TState>(
in LogEntry<TState> logEntry,
IExternalScopeProvider scopeProvider,
TextWriter textWriter)
{
string message = logEntry.Formatter?.Invoke(logEntry.State, logEntry.Exception);
if (string.IsNullOrEmpty(message)) return;
// 简单的 JSON 格式示例
var timestamp = DateTime.UtcNow.ToString("o");
var level = logEntry.LogLevel.ToString();
var category = logEntry.Category;
textWriter.Write($"{{\"Timestamp\":\"{timestamp}\",\"Level\":\"{level}\",\"Category\":\"{category}\",\"Message\":\"{message.Replace("\"", "\\\"")}\"");
// 添加 Exception
if (logEntry.Exception != null)
{
textWriter.Write($",\"Exception\":\"{logEntry.Exception.Message.Replace("\"", "\\\"")}\"");
}
// 添加 Scope 上下文 (简化处理)
scopeProvider?.ForEachScope((scope, state) =>
{
if (scope is IDictionary<string, object> dict)
{
foreach (var item in dict)
{
state.Write($",\"{item.Key}\":\"{item.Value}\"");
}
}
}, textWriter);
textWriter.WriteLine("}");
}
}
注册自定义格式化程序:
cs
builder.AddConsole(options =>
{
options.FormatterName = "CustomJson";
})
.AddConsoleFormatter<CustomJsonConsoleFormatter, ConsoleFormatterOptions>();