Skip to content

.NET Core OpenTelemetry

🏷️ .NET Core OpenTelemetry

在开发 .NET Core 微服务时有一个比较麻烦的事情就是关联服务调用的日志,各个服务的日志已经通过 ELK 统一收集了,但是还无法关联同一次请求中调用的多个服务的日志。

比如 一个请求调用 A 服务,然后 A 服务又调用了 B 服务。现在 A 服务和 B 服务的日志没有办法关联起来,不知道哪些日志是属于哪一个请求的。单个服务中的日志可以通过线程上下文或者类似的处理关联,但是跨服务的就比较麻烦了。

性能监控中也有类似的需求,每个系统监控的框架都有一套自己的跟踪机制。比如曾经使用过的 SkyWalking 就可以获取到一个全局的跟踪 ID(GlobalTraceId),可惜的是后来版本升级后就获取不到了。再比如公司正在使用的 听云 ,也有类似的 TracingIdRequestId ,可惜只能在监控系统中看到,代码中无法获取。

为了便于定位类似微服务系统的问题追踪,W3C 制订了 跟踪上下文规范.NET Core 推出了一个基于这个规范的开源工具 OpenTelemetry,详细的说明可以参考官方的 Blog Improvements in .NET Core 3.0 for troubleshooting and monitoring distributed apps

示例代码

上面官方 Blog 示例的完整代码见 ot-demo-2019-11
下面简要的说一下具体的使用方法(由于暂时还是测试版本,使用方法可能会有变动)。

1. 服务端

后台服务项目在 Start.cs 中通过 IServiceCollection.AddOpenTelemetry 扩展方法添加 OpenTelemetry 支持。

csharp
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();
    
    services.AddOpenTelemetry(b => {
        b.AddRequestCollector()
        .UseZipkin(o =>
        {
            o.ServiceName = "BackEndApp";
            o.Endpoint = new Uri("http://192.168.99.100:9411/api/v2/spans");
        });
    });
}

之后在 Controller 中可以通过 _tracer.CurrentSpan.Context.TraceId 获取到当前请求的跟踪 ID。
这里使用了构造函数注入来注入 ILoggerTracer 的实例(这里由于升级了 OpenTelemetry 的版本,示例代码稍有改动)。

csharp
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly ILogger<WeatherForecastController> _logger;
    private Tracer _tracer;

    public WeatherForecastController(ILogger<WeatherForecastController> logger, TracerFactoryBase tracerFactory)
    {
        _logger = logger;
        _tracer = tracerFactory.GetTracer("custom");
    }
    
    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        var rng = new Random();

        _logger.LogInformation($"traceId: {_tracer.CurrentSpan.Context.TraceId}.");

        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
    }
}

2. 客户端

默认情况下,Trace Id 会在流程中第一个被访问的后台服务生成,但是也支持客户端发起跟踪(如果客户端也需要跟踪的话,如 WinForm 程序)。
通过使用 System.Diagnostics.Activity 类可以发起跟踪(Activity.Start()Activity.Stop())。

csharp
var activity = new Activity("CallToBackend")
    .AddBaggage("FlightID", "red");

activity.Start();

try
{
    _ = GetWeatherForecast();
}
finally
{
    activity.Stop();
}

本地测试环境搭建

基于上面的文章和示例代码,在本地搭建了下测试环境,记录如下。

1. 安装 Zipkin

我这里参考 Zipkin - Quickstart 使用 Docker 的方式运行,命令如下。

bash
docker run -d -p 9411:9411 openzipkin/zipkin

如何在 Windows 上安装 Docker 可以参考 这篇博客

启动可以通过访问 http://dokcer-ip-address:9411/ 访问,其中 IP 为 Docker 启动时显示的 IP。

2. 运行示例代码

ot-demo-2019-11 下载示例代码并运行。

示例代码中 FrontEndApp 没有安装 OpenTelemetry 包,如需安装需要先将 BackEndApp 项目下的 BackEndApp 复制过来。
另外,我这里 OpenTelemetry 升级到了最新的测试版 0.2.0-alpha.164

修改后的代码:ot-demo-2019-11.zip

查看 Request.Headers 的内容如下:

{[Host, {localhost:5001}]}
{[traceparent, {00-ec688f7d8f5f674fa50a7c73cee32fc0-89f16980caa1da45-01}]}
{[Correlation-Context, {FlightID=red}]}

可以通过 _tracer.CurrentSpan.Context.TraceId 获取到当前请求的跟踪 ID。

csharp
_logger.LogInformation($"traceId: {_tracer.CurrentSpan.Context.TraceId}.");

输出的日志如下:

BackEndApp.Controllers.WeatherForecastController: Information: traceId: ec688f7d8f5f674fa50a7c73cee32fc0.

3. 查看 Zipkin

根据 tracd id 查询到的 Zipkin 界面如下: