.NET 微服务日志系统:Serilog + Loki + Grafana 实践指南
在 .NET 微服务架构中,日志是问题排查、系统监控和业务分析的核心支撑。传统日志存储(如本地文件)存在分散、查询效率低、可视化弱等问题,而 Serilog(日志采集)+ Loki(日志存储与索引)+ Grafana(日志可视化与分析) 组合,能实现“采集-存储-分析-可视化”全链路日志管理,尤其适配微服务分布式场景。
一、技术栈核心角色与优势
先明确三者的分工的协同逻辑,理解为何这套组合适合 .NET 微服务:
组件 | 核心角色 | 适配 .NET 微服务的优势 |
---|---|---|
Serilog | 日志采集器(Logger) | 1. 原生支持 .NET 全框架(.NET Framework/.NET Core/.NET 5+); 2. 结构化日志(JSON 格式),便于 Loki 解析; 3. 丰富的 Sink(输出目标),可直接对接 Loki; 4. 支持微服务核心字段(服务名、实例ID、TraceID)的自动注入。 |
Loki | 日志存储与索引引擎(Log Store) | 1. 轻量级设计,占用资源远低于 ELK(Elasticsearch); 2. “标签索引+原始日志”存储模式,查询效率高(只索引关键标签,不全文索引); 3. 原生支持 Prometheus 生态,可与微服务监控(如 Prometheus)联动; 4. 支持分布式场景下的日志聚合,按服务/实例/环境筛选。 |
Grafana | 日志可视化与分析平台(UI) | 1. 提供 Loki 专属数据源插件,查询语法直观(LogQL); 2. 支持日志与监控指标(如 Prometheus metrics)联动展示; 3. 可自定义仪表盘(Dashboard),适配微服务多维度日志分析; 4. 支持告警配置,日志异常时实时通知。 |
二、系统架构与数据流向
在 .NET 微服务集群中,日志的完整流转路径如下:
- 日志产生:.NET 微服务内部通过 Serilog 记录日志(如接口请求、异常、业务事件);
- 日志结构化:Serilog 将日志格式化为 JSON 结构,包含
ServiceName
(服务名)、InstanceId
(实例ID)、TraceId
(调用链ID)、Level
(日志级别)等核心标签; - 日志发送:通过 Serilog.Sinks.Loki 插件,将结构化日志推送到 Loki 服务;
- 日志存储:Loki 对日志按标签建立索引(如
service=order-service
),并存储原始日志数据; - 日志查询与可视化:Grafana 连接 Loki 数据源,通过 LogQL 查询日志,并以仪表盘、表格、图表等形式展示,支持多维度筛选和异常告警。
三、分步实现:从环境搭建到微服务集成
1. 前置准备
- 环境要求:Docker(快速部署 Loki 和 Grafana,避免复杂的本地安装);.NET 6+ 微服务项目(示例以 .NET 8 为例);
- 工具:Docker Compose(一键启动 Loki 和 Grafana)、Visual Studio/VS Code(开发 .NET 项目)。
2. 第一步:用 Docker Compose 部署 Loki + Grafana
通过 Docker Compose 快速启动依赖服务,无需手动配置 Loki 存储、Grafana 插件。
步骤1:创建 docker-compose.yml
文件
version: "3.8"
services:
# Loki 服务(日志存储)
loki:
image: grafana/loki:2.9.2 # 选择稳定版本
ports:
- "3100:3100" # Loki 默认端口
volumes:
- ./loki/data:/loki # 持久化存储日志数据
command: -config.file=/etc/loki/local-config.yaml # 使用默认配置(适合测试,生产需自定义)
networks:
- log-network
# Grafana 服务(日志可视化)
grafana:
image: grafana/grafana:10.2.2
ports:
- "3000:3000" # Grafana 默认端口
volumes:
- ./grafana/data:/var/lib/grafana # 持久化 Grafana 配置(如数据源、仪表盘)
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin # 初始管理员密码(生产需修改)
- GF_INSTALL_PLUGINS=grafana-loki-datasource # 自动安装 Loki 数据源插件
depends_on:
- loki # 确保 Loki 启动后再启动 Grafana
networks:
- log-network
networks:
log-network:
driver: bridge
步骤2:启动服务
在 docker-compose.yml
所在目录执行命令:
# 启动服务(后台运行)
docker-compose up -d
# 验证服务是否正常启动
docker-compose ps
- Loki 验证:访问
http://localhost:3100/ready
,返回ready
表示正常; - Grafana 验证:访问
http://localhost:3000
,使用账号admin
、密码admin
登录,首次登录需修改密码(可选)。
3. 第二步:配置 Grafana 连接 Loki 数据源
Grafana 启动后,需先配置 Loki 数据源,才能查询日志:
- 登录 Grafana 后,点击左侧菜单 Configuration > Data sources;
- 点击 Add data source,搜索
Loki
并选择; - 在 Settings 页配置:
- URL:
http://loki:3100
(Docker 内部网络,直接用服务名访问;若外部访问,改为http://localhost:3100
); - 其他配置默认,点击 Save & test,显示 “Data source is working” 即配置成功。
- URL:
4. 第三步:.NET 微服务集成 Serilog 并对接 Loki
步骤1:安装 Serilog 相关 NuGet 包
在 .NET 微服务项目中,通过 NuGet 安装以下包:
# 核心日志库
Install-Package Serilog
# .NET 主机集成(适配 ASP.NET Core)
Install-Package Serilog.AspNetCore
# Loki 输出插件(将日志推送到 Loki)
Install-Package Serilog.Sinks.Loki
# 结构化日志格式化(可选,推荐 JSON 格式)
Install-Package Serilog.Formatting.Compact
步骤2:配置 Serilog(Program.cs)
在 .NET 微服务的启动类中,替换默认日志系统为 Serilog,并配置 Loki 输出:
using Serilog;
using Serilog.Sinks.Loki;
var builder = WebApplication.CreateBuilder(args);
// 1. 移除默认日志系统,配置 Serilog
builder.Host.UseSerilog((context, services, configuration) =>
{
// 微服务核心配置(从 appsettings.json 读取,也可硬编码)
var serviceName = context.Configuration["ServiceSettings:ServiceName"] ?? "unknown-service";
var lokiUrl = context.Configuration["Logging:Loki:Url"] ?? "http://localhost:3100";
// 2. 配置 Loki 标签(核心:用于后续筛选日志)
var lokiLabels = new List<LokiLabel>
{
new LokiLabel("service", serviceName), // 服务名(如 order-service)
new LokiLabel("environment", context.HostingEnvironment.EnvironmentName), // 环境(Development/Production)
new LokiLabel("instance", Environment.MachineName) // 实例ID(区分同一服务的多个实例)
};
// 3. 配置 Serilog 规则
configuration
.ReadFrom.Configuration(context.Configuration) // 从 appsettings.json 读取日志级别等配置
.ReadFrom.Services(services) // 集成 ASP.NET Core 服务(如 IHttpContextAccessor)
.Enrich.FromLogContext() // 从日志上下文添加额外字段(如 TraceID)
.Enrich.WithMachineName() // 添加机器名
.WriteTo.Console() // 同时输出到控制台(开发环境调试用)
// 4. 输出到 Loki
.WriteTo.Loki(lokiUrl, lokiLabels,
// 配置日志格式(结构化 JSON,包含所有字段)
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}");
});
// 其他服务注册(如控制器、Swagger 等)
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// 中间件配置(确保 Serilog 捕获 HTTP 请求日志)
app.UseSerilogRequestLogging(); // 自动记录 HTTP 请求日志(Method、Path、Status Code 等)
// 其他中间件(Swagger、路由等)
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
步骤3:配置 appsettings.json(日志级别、Loki 地址)
在 appsettings.json
中添加日志相关配置,便于环境切换:
{
"ServiceSettings": {
"ServiceName": "order-service" // 当前微服务名称(如订单服务)
},
"Logging": {
"LogLevel": {
"Default": "Information", // 默认日志级别
"Microsoft.AspNetCore": "Warning", // 过滤 ASP.NET Core 框架的冗余日志
"Microsoft.EntityFrameworkCore": "Warning" // 过滤 EF Core 冗余日志
},
"Loki": {
"Url": "http://localhost:3100" // Loki 服务地址(生产环境需改为集群地址)
}
},
"AllowedHosts": "*"
}
步骤4:测试日志输出
在控制器中添加接口,手动记录日志:
using Microsoft.AspNetCore.Mvc;
using Serilog;
namespace OrderService.Controllers;
[ApiController]
[Route("[controller]")]
public class OrderController : ControllerBase
{
[HttpPost]
public IActionResult CreateOrder([FromBody] string orderId)
{
try
{
// 1. 记录信息日志(业务事件)
Log.Information("订单创建请求:OrderId={OrderId}, UserId={UserId}",
orderId, HttpContext.User.Identity?.Name ?? "anonymous");
// 模拟业务逻辑
if (string.IsNullOrEmpty(orderId))
{
// 2. 记录警告日志
Log.Warning("订单ID为空,请求被拒绝");
return BadRequest("订单ID不能为空");
}
// 3. 模拟异常(记录错误日志)
if (orderId == "error-test")
{
throw new InvalidOperationException("模拟订单创建失败");
}
return Ok($"订单 {orderId} 创建成功");
}
catch (Exception ex)
{
// 4. 记录错误日志(包含异常堆栈)
Log.Error(ex, "订单创建失败:OrderId={OrderId}", orderId);
return StatusCode(500, "服务器内部错误");
}
}
}
5. 第四步:在 Grafana 中查询与可视化日志
步骤1:使用 LogQL 查询日志
- 登录 Grafana,点击左侧菜单 Explore,选择 Loki 数据源;
- 在查询框中输入 LogQL(Loki 专用查询语法),示例:
- 查询所有
order-service
的日志:{service="order-service"}
- 查询
order-service
的错误日志:{service="order-service"} |= "Error"
- 查询特定订单ID的日志:
{service="order-service"} |= "OrderId=12345"
- 查询所有
- 点击 Run query,下方将展示匹配的日志,支持按时间范围筛选(右上角选择时间,如“Last 5 minutes”)。
步骤2:创建自定义仪表盘(Dashboard)
为了更直观地监控微服务日志(如日志级别分布、请求量趋势),可创建 Grafana 仪表盘:
- 点击左侧菜单 Dashboards > New dashboard;
- 点击 Add visualization,选择 Loki 数据源;
- 配置图表类型:
- 日志列表:展示原始日志,适合实时查看;
- 柱状图:统计不同日志级别的数量(如 Error/Warning/Info 的占比);
- 表格:按字段(如 OrderId、UserId)汇总日志,便于排查特定业务问题;
- 保存仪表盘,命名为“Order-Service 日志监控”,后续可直接访问。
四、生产环境优化建议
Loki 配置优化:
- 生产环境需自定义
loki-config.yaml
,配置分布式存储(如 S3、MinIO)替代本地存储,避免单点故障; - 开启日志压缩(如 Snappy),减少存储占用;
- 配置日志保留期(如
retention_period: 720h
,保留30天),避免磁盘溢出。
- 生产环境需自定义
Serilog 优化:
- 避免日志冗余:过滤重复日志(如健康检查接口的
Information
日志); - 敏感信息脱敏:使用
Serilog.Enrichers.Sensitive
插件,对密码、手机号等字段脱敏; - 批量发送日志:配置
Serilog.Sinks.Loki
的批量发送参数(如batchPostingLimit: 1000
),减少网络请求。
- 避免日志冗余:过滤重复日志(如健康检查接口的
Grafana 优化:
- 配置日志告警:当错误日志数量超过阈值(如5分钟内10条 Error),通过邮件、Slack 通知;
- 权限控制:为不同角色(如开发、运维)分配 Grafana 只读/编辑权限,避免误操作;
- 联动监控指标:将 Loki 日志与 Prometheus 指标(如接口响应时间、错误率)放在同一仪表盘,实现“日志+指标”联动分析。
微服务日志规范:
- 统一标签:所有微服务必须包含
service
、environment
、instance
标签,便于跨服务日志聚合; - 日志级别规范:
Error
(业务异常)、Warning
(非致命问题)、Information
(正常业务事件)、Debug
(开发调试,生产禁用); - 必含字段:TraceID(调用链追踪)、BusinessId(业务ID,如订单ID、用户ID),便于定位全链路问题。
- 统一标签:所有微服务必须包含
五、常见问题排查
日志未推送到 Loki:
- 检查 Loki 地址是否正确(.NET 服务能否访问 Loki,Docker 网络是否互通);
- 查看 .NET 服务日志:是否有
Serilog.Sinks.Loki
相关错误(如网络超时、404 错误); - 验证 Loki 接收日志:访问
http://localhost:3100/loki/api/v1/query?query={service="order-service"}
,查看是否返回日志。
Grafana 查不到 Loki 日志:
- 检查数据源配置:URL 是否正确,是否能连通 Loki;
- 检查 LogQL 语法:标签是否匹配(如
service
标签值是否为order-service
); - 检查时间范围:日志是否在 Grafana 选择的时间范围内(如日志是10分钟前的,需选择“Last 15 minutes”)。
日志字段缺失(如 TraceID):
- 确保
app.UseSerilogRequestLogging()
中间件已添加(自动捕获 HTTP 请求的 TraceID); - 若使用分布式追踪(如 OpenTelemetry),需配置
Serilog.Enrich.FromOpenTelemetry()
插件,从追踪上下文获取 TraceID。
- 确保
通过以上步骤,即可搭建一套适配 .NET 微服务的高可用日志系统,实现日志的集中采集、高效查询和可视化监控,为微服务运维和问题排查提供有力支撑。