.NET Core OpenTelemetry
在开发 .NET Core 微服务时有一个比较麻烦的事情就是关联服务调用的日志,各个服务的日志已经通过 ELK 统一收集了,但是还无法关联同一次请求中调用的多个服务的日志。
比如 一个请求调用 A 服务,然后 A 服务又调用了 B 服务。现在 A 服务和 B 服务的日志没有办法关联起来,不知道哪些日志是属于哪一个请求的。单个服务中的日志可以通过线程上下文或者类似的处理关联,但是跨服务的就比较麻烦了。
性能监控中也有类似的需求,每个系统监控的框架都有一套自己的跟踪机制。比如曾经使用过的 SkyWalking 就可以获取到一个全局的跟踪 ID(GlobalTraceId),可惜的是后来版本升级后就获取不到了。再比如公司正在使用的 听云 ,也有类似的 TracingId 和 RequestId ,可惜只能在监控系统中看到,代码中无法获取。
为了便于定位类似微服务系统的问题追踪,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 支持。
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。
这里使用了构造函数注入来注入 ILogger 和 Tracer 的实例(这里由于升级了 OpenTelemetry 的版本,示例代码稍有改动)。
[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()
)。
var activity = new Activity("CallToBackend")
.AddBaggage("FlightID", "red");
activity.Start();
try
{
_ = GetWeatherForecast();
}
finally
{
activity.Stop();
}
本地测试环境搭建
基于上面的文章和示例代码,在本地搭建了下测试环境,记录如下。
1. 安装 Zipkin
我这里参考 Zipkin - Quickstart 使用 Docker 的方式运行,命令如下。
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。
_logger.LogInformation($"traceId: {_tracer.CurrentSpan.Context.TraceId}.");
输出的日志如下:
BackEndApp.Controllers.WeatherForecastController: Information: traceId: ec688f7d8f5f674fa50a7c73cee32fc0.
3. 查看 Zipkin
根据 tracd id 查询到的 Zipkin 界面如下: