当前位置: 首页 > news >正文

C# 日志写入loki

在 C# 中实现日志写入 Loki 最常用的方式是结合 Serilog 日志框架和 Serilog.Sinks.Grafana.Loki 扩展包。这种方式支持结构化日志、自定义标签和灵活的配置,以下是完整的实现步骤:

一、准备工作

  1. 安装 NuGet 包
    在项目中安装必要的依赖包(通过 NuGet 包管理器或命令行):

    # Serilog 核心包
    Install-Package Serilog -Version 3.1.1
    Install-Package Serilog.AspNetCore -Version 8.0.0  # 集成 ASP.NET Core# Loki 接收器(用于将日志发送到 Loki)
    Install-Package Serilog.Sinks.Grafana.Loki -Version 8.0.0
    
  2. 确保 Loki 服务可用
    确认 Loki 已启动并可访问(默认地址:http://localhost:3100),可通过访问 http://localhost:3100/ready 验证,返回 ready 即表示正常运行。

二、配置 Serilog 连接 Loki

Program.cs 中配置 Serilog,设置 Loki 服务地址、日志标签、租户信息(可选)和日志格式:

using Serilog;
using Serilog.Sinks.Grafana.Loki;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;var builder = WebApplication.CreateBuilder(args);// 1. 从配置文件读取 Loki 地址和租户 ID(建议通过 appsettings.json 配置)
var lokiUri = builder.Configuration["Loki:Uri"] ?? "http://localhost:3100";
var tenantId = builder.Configuration["Loki:TenantId"] ?? "default-tenant"; // 多租户标识// 2. 配置 Serilog 日志系统
List<LokiLabel> labels = new List<LokiLabel>();
labels.Add(new LokiLabel { Key = "App", Value = "testproject" });// 1.获取应用程序名称和版本(用于 Elasticsearch 索引命名)
var appName = Assembly.GetExecutingAssembly().GetName().Name;
var appVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "1.0.0";Log.Logger = new LoggerConfiguration().Enrich.FromLogContext()  // 从日志上下文获取属性(如追踪 ID).Enrich.WithProperty("service", "ServiceB")  // 全局标签:服务名.Enrich.WithProperty("environment", "development") // 全局标签:环境.WriteTo.GrafanaLoki("http://localhost:3100", labels).WriteTo.Console()// 设置最小日志级别(Information 及以上).MinimumLevel.Information()// 针对特定命名空间调整日志级别(可选).MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Warning).CreateLogger();
Log.Information("Hello, Grafana Loki!");
// 替换默认日志工厂为 Serilog
builder.Host.UseSerilog();// 3. 替换 ASP.NET Core 默认日志系统为 Serilog
builder.Host.UseSerilog();// 4. 注册服务和中间件
builder.Services.AddControllers();
var app = builder.Build();// 5. 可选:添加分布式追踪 ID 到日志(便于链路追踪)
app.Use(async (context, next) =>
{var activity = System.Diagnostics.Activity.Current;if (activity != null){// 将 TraceId 和 SpanId 注入日志上下文using (LogContext.PushProperty("trace_id", activity.TraceId.ToString()))using (LogContext.PushProperty("span_id", activity.SpanId.ToString())){await next();}}else{await next();}
});app.MapControllers();
app.Run();// 自定义 HTTP 处理器:添加 Loki 租户头(多租户场景)
public class LokiTenantHandler : DelegatingHandler
{private readonly string _tenantId;public LokiTenantHandler(string tenantId){_tenantId = tenantId;InnerHandler = new HttpClientHandler(); // 基础 HTTP 处理器}protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken){// 添加 Loki 租户标识头(多租户必需)request.Headers.Add("X-Scope-OrgID", _tenantId);return base.SendAsync(request, cancellationToken);}
}

三、在业务代码中记录日志

通过 ILogger<T> 接口记录日志,日志会自动序列化为 JSON 并发送到 Loki:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;namespace LokiLoggingDemo.Controllers;[ApiController]
[Route("[controller]")]
public class OrderController : ControllerBase
{private readonly ILogger<OrderController> _logger;// 构造函数注入日志接口public OrderController(ILogger<OrderController> logger){_logger = logger;}[HttpPost]public IActionResult CreateOrder([FromBody] OrderRequest request){// 1. 记录信息日志(包含结构化参数)_logger.LogInformation("用户 {UserId} 发起订单创建请求,商品 ID:{ProductId},数量:{Quantity}",request.UserId, request.ProductId, request.Quantity);try{if (request.Quantity <= 0){throw new ArgumentException("商品数量必须大于 0");}// 模拟订单创建逻辑var orderId = Guid.NewGuid().ToString();// 2. 记录成功日志(包含复杂对象)var orderResult = new { OrderId = orderId, Status = "Created", TotalAmount = request.Quantity * 99.99m };_logger.LogInformation("订单创建成功:{OrderResult}", orderResult);return Ok(new { OrderId = orderId });}catch (Exception ex){// 3. 记录错误日志(包含异常堆栈)_logger.LogError(ex, "用户 {UserId} 订单创建失败,商品 ID:{ProductId}",request.UserId, request.ProductId);return BadRequest(ex.Message);}}
}// 订单请求模型
public class OrderRequest
{public string UserId { get; set; }public string ProductId { get; set; }public int Quantity { get; set; }
}

四、验证日志是否写入 Loki

  1. 运行应用程序
    调用 OrderController.CreateOrder 接口(可通过 Postman 或 Swagger 发送请求),生成测试日志。

  2. 在 Grafana 中查询日志 在这里插入图片描述

    1. 打开 Grafana(默认地址:http://localhost:3000),添加 Loki 数据源(地址填写 Loki 的 HTTP 地址,如 http://localhost:3100)。
    2. 进入 Explore 页面,选择 Loki 数据源,使用标签筛选日志:
      # 筛选 order-service 的日志
      {service="order-service", environment="Development"}
      
    3. 若日志为 JSON 格式,可通过 | json 解析字段并筛选:
      # 筛选用户 ID 为 123 的错误日志
      {service="order-service"} | json | UserId="123" and Level="Error"
      

![(https://i-blog.csdnimg.cn/direct/7aced4b706fa458784f296f279635a87.png)

五、关键配置说明

  1. Loki 地址与租户

    • uri:Loki 的 HTTP 接口地址(默认 http://localhost:3100),若 Loki 部署在远程服务器,需替换为实际 IP 或域名。
    • 多租户场景:通过 X-Scope-OrgID 头指定 tenantId,实现不同租户日志隔离。
  2. 日志标签(labels)
    标签是 Loki 日志筛选的核心,建议包含 service(服务名)、environment(环境)等固定标识,便于后续按服务、环境查询日志。

  3. JSON 格式化
    使用 JsonFormatter 输出 JSON 格式日志,Loki 可直接解析其中的字段(如 UserIdOrderId),支持复杂查询(如按用户 ID 筛选)。

  4. 批量发送
    通过 batchPostingLimitperiod 控制日志批量发送策略,减少网络请求次数,优化性能。

六、常见问题解决

  1. 日志未发送到 Loki

    • 检查 Loki 地址是否正确,确保 http://localhost:3100/ready 可访问。
    • 查看应用程序控制台输出,是否有 Failed to send log batch to Loki 错误(通常是网络不通或 Loki 未启动)。
    • 确认防火墙未拦截 3100 端口。
  2. 多租户日志隔离问题

    • 若租户日志混淆,检查 X-Scope-OrgID 头是否正确添加(可通过抓包工具验证请求头)。
    • 在 Loki 配置中设置 allow_empty_org_id: false,强制客户端必须指定租户 ID。
  3. 日志字段解析失败

    • 若 JSON 日志字段未被 Loki 解析,确保 textFormatter 使用 JsonFormatter,且日志格式为标准 JSON。

通过以上配置,C# 应用程序的日志可无缝发送到 Loki,结合 Grafana 可实现日志的集中管理、查询和可视化,非常适合分布式系统的日志监控。

http://www.xdnf.cn/news/1385353.html

相关文章:

  • 力扣452:用最少数量的箭射爆气球(排序+贪心)
  • 如何编译和使用 tomcat-connectors-1.2.32 源码(连接 Apache 和 Tomcat)​附安装包下载
  • 数据质检之springboot通过yarn调用spark作业实现数据质量检测
  • Dify 1.8.0 全网首发,预告发布
  • 2024-06-13-debian12安装Mariadb-Galera-Cluster+Nginx+Keepalived高可用多主集群
  • 动态UI的秘诀:React中的条件渲染
  • 在PostgreSQL中使用分区技术
  • 【三维渲染技术讨论】Blender输出的三维文件里的透明贴图在Isaac Sim里会丢失, 是什么原因?
  • Blender建模软件基本操作--学习笔记1
  • 查看docker容器内部的环境变量并向docker容器内部添加新的环境变量
  • 第十二节 Spring 注入集合
  • 微服务Eureka组件的介绍、安装、使用
  • 编程与数学 03-004 数据库系统概论 06_需求分析
  • CMake xcode编译器属性设置技巧
  • PDF转图片工具实现
  • R 语言 + 卒中 Meta 分析(续):机器学习 Meta 与结构方程 Meta 完整实现
  • 生成式 AI 的下一个风口:从 “生成内容” 到 “生成工具”,如何落地产业场景?
  • android 不同分辨图片放错对应文件夹会怎样?
  • RxGalleryFinal:全能Android图片视频选择器
  • PHP的header()函数分析
  • 数字孪生技术为UI前端赋能:实现产品性能的实时监测与预警
  • 神经科学启发下的自然语言处理:迈向深层语义理解的探索
  • 从2M到G时代:WiFi如何重塑我们的生活?
  • 高德三维地图航线航点弹出框zMarkerLayer点击事件
  • ArcGIS Pro 地图打包与解包
  • 研究人员发现VS Code漏洞:攻击者可重新发布同名已删除扩展
  • 深入理解会话状态管理:多轮对话与API最佳实践
  • STM32的RTC模块及其应用场景
  • 【项目思维】编程思维学习路线(推荐)
  • Golang 面试题「中级」