# 日志 (03_Logging)
最后更新: 2024-09-20
# 📚 概述
DarkM 日志模块基于 Serilog 构建,支持结构化日志记录,并集成 OpenTracing 实现分布式链路追踪(Jaeger)。
源代码位置: DarkM/src/Framework/Logging
# 🏗️ 模块架构
# 完整目录结构
Logging/
├── Logging.Serilog/ # Serilog 日志核心
│ ├── WebHostBuilderExtensions.cs # Web 主机扩展
│ └── Logging.Serilog.csproj
│
├── Logging.Serilog.Enricher/ # 日志增强器
│ ├── SerilogLoggerEnricher.cs # 日志增强器
│ ├── SerilogLoggerExtensions.cs # 日志扩展
│ └── SerilogLoggerConfiguration.cs # 日志配置
│
├── Logging.Serilog.GenericHost/ # 通用主机日志
│ └── GenericHostBuilderExtensions.cs # 通用主机扩展
│
└── Logging.Serilog.OpenTracing/ # OpenTracing 链路追踪
├── Options/
│ ├── OpenTracingConfig.cs # OpenTracing 配置
│ └── JaegerConfig.cs # Jaeger 配置
├── OpenTracing/
│ ├── UpdatableReporter.cs # 可更新报告器
│ └── UpdatableSampler.cs # 可更新采样器
├── Enrichers/
│ ├── RequestContextLogEventEnricher.cs # 请求上下文增强
│ └── OpenTracingContextLogEventEnricher.cs # 追踪上下文增强
├── OpenTracingSink.cs # OpenTracing Sink
└── ServiceCollectionExtensions.cs # DI 扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 🔧 Serilog 配置
# appsettings.json 配置
{
"Serilog": {
// 日志输出最小等级
"MinimumLevel": {
"Default": "Error",
"Override": {
"Microsoft": "Error",
"System": "Error"
}
},
"WriteTo": [
// 输出到控制台
{
"Name": "Console",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}"
}
},
// 输出到文件
{
"Name": "File",
"Args": {
// 文件路径
"path": "log/log.log",
// 文件滚动方式
"rollingInterval": "Day",
// 消息输出格式
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}",
// 文件数量
"retainedFileCountLimit": 60,
// 使用缓冲,提高写入效率
"buffered": false
}
}
]
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 配置项详解
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| MinimumLevel.Default | LogEventLevel | Error | 默认日志等级 |
| MinimumLevel.Override | Object | - | 覆盖命名空间日志等级 |
| WriteTo | Array | - | 输出目标数组 |
| WriteTo.Name | string | - | 输出方式(Console/File/等) |
| WriteTo.Args | Object | - | 输出方式参数 |
# 日志等级说明
| 等级 | 说明 | 使用场景 |
|---|---|---|
| Verbose | 最详细日志 | 调试模式 |
| Debug | 调试信息 | 开发环境 |
| Information | 一般信息 | 生产环境(推荐) |
| Warning | 警告信息 | 需要注意的问题 |
| Error | 错误信息 | 生产环境(默认) |
| Fatal | 致命错误 | 系统崩溃级别 |
# 🔧 OpenTracing 链路追踪
# OpenTracingConfig
文件位置: Logging.Serilog.OpenTracing/Options/OpenTracingConfig.cs
public class OpenTracingConfig
{
/// <summary>
/// 是否启用追踪
/// </summary>
public bool EnableTracing { get; set; }
/// <summary>
/// Jaeger 配置
/// </summary>
public JaegerConfig JaegerConfig { get; set; }
}
public class JaegerConfig
{
public SamplerOption SamplerOption { get; set; }
public SenderOption SenderOption { get; set; }
public ReporterOption ReporterOption { get; set; }
}
public class SamplerOption
{
/// <summary>
/// 采样器类型(Const/RemoteControlled)
/// </summary>
public string SamplerType { get; set; }
/// <summary>
/// 采样参数(0-1 之间,表示采样比例)
/// </summary>
public int Param { get; set; }
/// <summary>
/// 采样端点
/// </summary>
public string SamplingEndpoint { get; set; }
/// <summary>
/// 管理器主机端口
/// </summary>
public int ManagerHostPort { get; set; }
}
public class SenderOption
{
/// <summary>
/// Agent 主机地址
/// </summary>
public string AgentHost { get; set; }
/// <summary>
/// Agent 端口
/// </summary>
public int AgentPort { get; set; }
/// <summary>
/// gRPC 目标地址
/// </summary>
public string GrpcTarget { get; set; }
/// <summary>
/// 端点地址
/// </summary>
public string Endpoint { get; set; }
/// <summary>
/// 认证令牌
/// </summary>
public string AuthToken { get; set; }
}
public class ReporterOption
{
/// <summary>
/// 是否记录 Span 日志
/// </summary>
public bool LogSpans { get; set; }
/// <summary>
/// 最大队列大小
/// </summary>
public int MaxQueueSize { get; set; }
/// <summary>
/// 刷新间隔(毫秒)
/// </summary>
public int FlushInterval { get; set; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# 💡 配置说明
# appsettings.json 完整配置
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "log/log.log",
"rollingInterval": "Day",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}",
"retainedFileCountLimit": 60,
"buffered": false
}
}
]
},
"OpenTracing": {
// 是否启用链路追踪
"EnableTracing": true,
// Jaeger 配置
"JaegerConfig": {
// 采样器配置
"SamplerOption": {
"SamplerType": "RemoteControlled",
"Param": 1,
"SamplingEndpoint": "",
"ManagerHostPort": 0
},
// 发送器配置(UDP 方式)
"SenderOption": {
"AgentHost": "localhost",
"AgentPort": 6831,
"GrpcTarget": "",
"GrpcRootCertificate": "",
"Endpoint": "",
"AuthToken": "",
"AuthUsername": "",
"AuthPassword": ""
},
// 报告器配置
"ReporterOption": {
"LogSpans": true,
"MaxQueueSize": 100,
"FlushInterval": 1000
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# OpenTracing 配置项详解
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| EnableTracing | bool | false | 是否启用 OpenTracing |
| SamplerType | string | RemoteControlled | 采样器类型 |
| Param | int | 1 | 采样比例(1=100%) |
| AgentHost | string | localhost | Jaeger Agent 地址 |
| AgentPort | int | 6831 | Jaeger Agent UDP 端口 |
| LogSpans | bool | true | 记录 Span 日志 |
| MaxQueueSize | int | 100 | 最大队列大小 |
| FlushInterval | int | 1000 | 刷新间隔(毫秒) |
# 🏗️ 依赖注入
# ServiceCollectionExtensions
文件位置: Logging.Serilog.OpenTracing/ServiceCollectionExtensions.cs
public static class ServiceCollectionExtensions
{
/// <summary>
/// 添加 OpenTracing 日志
/// </summary>
public static IServiceCollection AddOpenTracingLog(
this IServiceCollection services,
IConfiguration cfg)
{
var config = new OpenTracingConfig();
var section = cfg.GetSection("OpenTracing");
section?.Bind(config);
if (config.EnableTracing)
{
services.AddSingleton(config);
// 加入 OpenTracing
services.AddOpenTracing();
services.AddSingleton<ITracer>(serviceProvider =>
{
var loggerFactory = serviceProvider
.GetRequiredService<ILoggerFactory>();
string serviceName = serviceProvider
.GetRequiredService<IHostingEnvironment>()
.ApplicationName;
var sendOption = config.JaegerConfig.SenderOption;
// 配置发送器(使用 UDP 发送器)
Configuration.SenderConfiguration.DefaultSenderResolver =
new SenderResolver(loggerFactory)
.RegisterSenderFactory<ThriftSenderFactory>();
// 创建报告器
var reporter = new RemoteReporter.Builder()
.WithLoggerFactory(loggerFactory)
.WithSender(new UdpSender(
sendOption.AgentHost,
sendOption.AgentPort,
0))
.Build();
// 创建 Tracer
var tracer = new Tracer.Builder(serviceName)
.WithLoggerFactory(loggerFactory)
.WithSampler(new RemoteControlledSampler
.Builder(serviceName)
.Build())
.WithReporter(reporter)
.Build();
// 注册全局 Tracer
GlobalTracer.Register(tracer);
return tracer;
});
}
return services;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# 💡 使用示例
# 1. 启用 OpenTracing
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// 添加 OpenTracing 日志
builder.Services.AddOpenTracingLog(builder.Configuration);
var app = builder.Build();
1
2
3
4
5
6
7
2
3
4
5
6
7
# 2. 手动创建 Span
using OpenTracing;
using OpenTracing.Util;
public class OrderService
{
private readonly ITracer _tracer;
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
_logger = logger;
_tracer = GlobalTracer.Instance;
}
public async Task<OrderEntity> CreateOrderAsync(CreateOrderRequest request)
{
// 创建 Span
using var scope = _tracer.BuildSpan("CreateOrder")
.WithTag("order.type", request.OrderType)
.WithTag("order.amount", request.Amount)
.StartActive(true);
var span = scope.Span;
try
{
_logger.LogInformation("开始创建订单:{OrderType}", request.OrderType);
// 业务逻辑
var order = await _orderRepository.InsertAsync(request.ToEntity());
span.SetTag("order.id", order.Id);
span.Log("订单创建成功");
_logger.LogInformation("订单创建成功:{OrderId}", order.Id);
return order;
}
catch (Exception ex)
{
span.SetTag("error", true);
span.Log($"订单创建失败:{ex.Message}");
_logger.LogError(ex, "订单创建失败");
throw;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# 3. 链路追踪上下文传递
public class PaymentService
{
private readonly ITracer _tracer;
public PaymentService()
{
_tracer = GlobalTracer.Instance;
}
public async Task<bool> ProcessPaymentAsync(int orderId, decimal amount)
{
// 获取当前 Span 上下文
var parentSpan = _tracer.ActiveSpan;
// 创建子 Span
using var scope = _tracer.BuildSpan("ProcessPayment")
.AsChildOf(parentSpan)
.WithTag("payment.orderId", orderId)
.WithTag("payment.amount", amount)
.StartActive(true);
var span = scope.Span;
try
{
// 调用第三方支付接口
var result = await _paymentGateway.PayAsync(orderId, amount);
span.Log($"支付成功:{result.TransactionId}");
return result.Success;
}
catch (Exception ex)
{
span.SetTag("error", true);
span.Log($"支付失败:{ex.Message}");
throw;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 🔧 Jaeger 部署
# Docker 部署 Jaeger(All-in-One)
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 14250:14250 \
-p 9411:9411 \
jaegertracing/all-in-one:1.46
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
访问 Jaeger UI: http://localhost:16686
# Docker Compose 部署
version: '3.8'
services:
jaeger:
image: jaegertracing/all-in-one:1.46
environment:
- COLLECTOR_ZIPKIN_HOST_PORT=:9411
ports:
- "5775:5775/udp"
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
- "16686:16686"
- "14268:14268"
- "14250:14250"
- "9411:9411"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 📊 链路追踪示例
# 典型请求链路
[API Gateway]
↓
[Web Controller] ── Span: GET /api/orders
↓
[Order Service] ──── Span: CreateOrder
↓
[Payment Service] ─ Span: ProcessPayment
↓
[Payment Gateway] ─ Span: HTTP POST /pay
↓
[Database] ──────── Span: DB.Insert
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# Jaeger UI 查看步骤
- 访问 Jaeger UI:
http://localhost:16686 - 选择 Service(应用名称)
- 选择 Operation(操作名称)
- 点击 Find Traces
- 展开 Trace 查看详细 Span
# 🔍 常见问题
# Q1: 如何配置采样率?
解决方案:
{
"OpenTracing": {
"JaegerConfig": {
"SamplerOption": {
"SamplerType": "Probabilistic",
"Param": 0.1 // 10% 采样率
}
}
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
采样类型:
Const- 固定采样(0=不采样,1=全部采样)Probabilistic- 概率采样(0-1 之间)RateLimiting- 速率限制采样RemoteControlled- 远程控制采样(推荐)
# Q2: 如何配置 Jaeger Agent 地址?
解决方案:
{
"OpenTracing": {
"JaegerConfig": {
"SenderOption": {
"AgentHost": "jaeger-agent",
"AgentPort": 6831
}
}
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# Q3: 如何禁用某些请求的追踪?
解决方案:
services.Configure<HttpHandlerDiagnosticOptions>(options =>
{
options.IgnorePatterns.Add(req =>
req.RequestUri.AbsolutePath.Contains("/health"));
options.IgnorePatterns.Add(req =>
req.RequestUri.AbsolutePath.Contains("/metrics"));
});
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# Q4: 如何更改日志等级?
解决方案:
{
"Serilog": {
"MinimumLevel": {
"Default": "Debug" // 修改为 Debug 记录更详细的日志
}
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
# Q5: Serilog 支持哪些输出方式?
Serilog 提供了很多种日志输出方式(Sinks):
| Sink | 说明 | NuGet 包 |
|---|---|---|
| Console | 控制台输出 | Serilog.Sinks.Console |
| File | 文件输出 | Serilog.Sinks.File |
| Database | 数据库输出 | Serilog.Sinks.MSSqlServer |
| Elasticsearch | Elasticsearch 输出 | Serilog.Sinks.Elasticsearch |
| Seq | Seq 日志服务器 | Serilog.Sinks.Seq |
| Redis | Redis 输出 | Serilog.Sinks.Redis |
参考:Provided Sinks (opens new window)
# 📚 相关文档
# 🔗 参考链接
- 源代码 (opens new window) -
src/Framework/Logging - Serilog 文档 (opens new window)
- OpenTracing 文档 (opens new window)
- Jaeger 文档 (opens new window)
- .NET Core 日志记录 (opens new window)
最后更新: 2024-09-20