日志记录是软件开发中不可或缺的基础设施,良好的日志系统可以帮助开发者快速定位问题、监控应用状态和分析用户行为。本文将全面介绍C#中的各种日志记录方法,从基础实现到高级应用场景。
一、日志记录基础概念
1.1 日志级别及其应用场景
日志级别 | 典型应用场景 |
---|---|
Trace | 最详细的调试信息,通常用于开发阶段 |
Debug | 调试信息,有助于诊断问题但生产环境通常不开启 |
Information | 应用运行的一般信息,如服务启动、配置加载等 |
Warning | 非预期但不影响系统继续运行的情况 |
Error | 导致当前操作失败但应用仍能运行的错误 |
Critical | 导致应用崩溃或关键功能不可用的严重错误 |
1.2 日志记录的核心要素
- 时间戳:记录事件发生的精确时间
- 日志级别:标识日志的重要性
- 消息内容:描述性的日志信息
- 上下文信息:线程ID、请求ID、用户信息等
- 异常信息:相关的异常堆栈跟踪
二、.NET Core内置日志系统
2.1 基本配置与使用
// Program.cs中配置
var builder = WebApplication.CreateBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.AddDebug();
builder.Logging.SetMinimumLevel(LogLevel.Information);
var app = builder.Build();
// 控制器中使用
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public IActionResult Index()
{
_logger.LogInformation("访问首页");
return View();
}
}
2.2 结构化日志
// 使用消息模板
_logger.LogInformation("用户 {UserId} 于 {LoginTime} 登录成功",
user.Id, DateTime.UtcNow);
// 配合Serilog等库可实现更丰富的结构化日志
三、主流日志框架比较
框架名称 | 优势 | 劣势 | 适用场景 |
---|---|---|---|
Serilog | 强大的结构化日志,丰富输出端 | 配置相对复杂 | 需要高级日志功能的项目 |
NLog | 高性能,灵活配置 | 文档组织稍乱 | 企业级应用,性能敏感场景 |
log4net | 历史悠久,稳定可靠 | 更新缓慢,配置繁琐 | 遗留系统维护 |
Microsoft.Extensions.Logging | 原生集成,简单易用 | 功能相对基础 | ASP.NET Core基础项目 |
四、Serilog高级应用
4.1 基本配置
// 安装NuGet包
// Serilog
// Serilog.Sinks.Console
// Serilog.Sinks.File
// Serilog.AspNetCore
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.WriteTo.File("logs/myapp.txt", rollingInterval: RollingInterval.Day)
.CreateLogger();
try
{
Log.Information("应用启动");
// 应用代码
}
catch (Exception ex)
{
Log.Fatal(ex, "应用启动失败");
}
finally
{
Log.CloseAndFlush();
}
4.2 丰富上下文
// 添加属性
using (LogContext.PushProperty("RequestId", Guid.NewGuid()))
{
Log.Information("处理请求");
}
// 输出示例
// {Timestamp} [{Level}] {RequestId} 处理请求
4.3 多种输出端(Sinks)
// 同时输出到多个目标
.WriteTo.Console()
.WriteTo.File("log.txt")
.WriteTo.Seq("http://localhost:5341")
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200")))
五、NLog高级配置
5.1 NLog.config示例
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets>
<target name="logfile" xsi:type="File"
fileName="${basedir}/logs/${shortdate}.log"
layout="${longdate}|${level}|${logger}|${message}"/>
<target name="logconsole" xsi:type="Console" />
</targets>
<rules>
<logger name="*" minlevel="Info" writeTo="logfile,logconsole" />
</rules>
</nlog>
5.2 编程配置
var config = new LoggingConfiguration();
var consoleTarget = new ColoredConsoleTarget();
config.AddTarget("console", consoleTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Debug, consoleTarget));
LogManager.Configuration = config;
var logger = LogManager.GetCurrentClassLogger();
logger.Info("NLog配置完成");
六、日志记录最佳实践
6.1 日志内容规范
- 避免敏感信息:不记录密码、密钥等
- 描述具体行为:用”用户登录失败”而非”出错”
- 包含关键数据:如用户ID、订单号等
- 异常处理:记录完整异常而不仅是消息
// 好例子
_logger.LogError(ex, "处理用户 {UserId} 的订单 {OrderId} 时发生错误", userId, orderId);
// 差例子
_logger.LogError("出错");
6.2 性能优化
- 日志级别控制:生产环境关闭Debug/Trace
- 异步日志:使用Async包装器
.WriteTo.Async(a => a.File("log.txt"))
- 批量写入:配置缓冲
- 避免字符串拼接:使用结构化日志模板
6.3 日志文件管理
- 滚动日志:按时间或大小分割
- 自动清理:保留最近N天日志
- 合理命名:包含应用名和时间信息
七、分布式系统日志
7.1 关联ID实现
// 中间件中添加关联ID
app.Use(async (context, next) =>
{
context.TraceIdentifier = Guid.NewGuid().ToString();
using (LogContext.PushProperty("TraceId", context.TraceIdentifier))
{
await next();
}
});
7.2 集中式日志方案
- ELK Stack:Elasticsearch + Logstash + Kibana
- Seq:专为.NET设计的日志服务器
- Application Insights:Azure监控方案
- Grafana Loki:轻量级日志聚合系统
八、AOP日志实现
8.1 使用Castle DynamicProxy
public class LoggingInterceptor : IInterceptor
{
private readonly ILogger _logger;
public LoggingInterceptor(ILogger logger)
{
_logger = logger;
}
public void Intercept(IInvocation invocation)
{
_logger.LogInformation($"调用 {invocation.Method.Name} 开始");
try
{
invocation.Proceed();
_logger.LogInformation($"调用 {invocation.Method.Name} 成功");
}
catch (Exception ex)
{
_logger.LogError(ex, $"调用 {invocation.Method.Name} 失败");
throw;
}
}
}
8.2 使用ASP.NET Core过滤器
public class LogActionFilter : IActionFilter
{
private readonly ILogger _logger;
public LogActionFilter(ILogger<LogActionFilter> logger)
{
_logger = logger;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation($"执行 {context.ActionDescriptor.DisplayName}");
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception != null)
{
_logger.LogError(context.Exception, "执行异常");
}
}
}
九、日志监控与告警
9.1 关键指标监控
- 错误率:Error日志占比
- 慢请求:结合耗时日志
- 关键操作:如支付成功/失败
9.2 告警规则示例
// 使用Serilog.Expressions定义告警
.WriteTo.Conditional(
"@l = 'Error' and StartsWith(Message, '支付失败')",
wt => wt.Email(
fromAddress: "alerts@example.com",
toAddress: "devops@example.com",
subject: "支付失败告警"))
十、常见问题解决方案
10.1 日志文件权限问题
// 使用具有权限的目录
var logPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
"MyApp/logs");
10.2 日志丢失问题
- 使用可靠写入机制:如FileShare.ReadWrite
- 应用退出时刷新日志:
AppDomain.CurrentDomain.ProcessExit += (s, e) => Log.CloseAndFlush();
10.3 日志性能瓶颈
- 异步写入:所有主要日志框架都支持
- 控制日志量:合理设置日志级别
- 避免同步IO:如Console.WriteLine
十一、现代化日志实践
11.1 源码生成日志
.NET 6+引入了LoggerMessage源代码生成器:
partial class MyLogger
{
[LoggerMessage(EventId = 1000, Level = LogLevel.Error,
Message = "处理订单 {OrderId} 失败")]
public static partial void LogOrderProcessingError(
this ILogger logger, string orderId, Exception ex);
}
// 使用
_logger.LogOrderProcessingError("12345", ex);
11.2 OpenTelemetry集成
// 配置OpenTelemetry日志
builder.Services.AddOpenTelemetry()
.WithLogging(logging => logging
.AddConsoleExporter()
.AddOtlpExporter());
十二、总结
C#生态系统提供了丰富的日志记录解决方案,从简单的控制台输出到复杂的分布式日志聚合。选择适合的日志框架和策略应考虑以下因素:
- 应用规模:小型应用可用内置日志,大型系统考虑Serilog/NLog
- 性能需求:高并发场景关注异步和缓冲
- 运维环境:是否已有日志基础设施(如ELK)
- 团队熟悉度:选择团队熟悉的框架降低维护成本
良好的日志实践应遵循”足够而非过量”原则,在提供充分诊断信息的同时避免I/O和存储压力。随着应用演进,应定期评审日志策略,确保其持续满足运维和开发需求。