链路追踪是微服务体系当中很重要的一部分,它能够帮助开发者迅速发现问题,察觉系统性能瓶颈,也可以帮助我们在站点出现大量Exception的时候,迅速做出预警等。
阿里云链路追踪服务
- 阿里云提供OpenTelemetry Trace数据的原生接入方式:通过OpenTelemetry接入C# Trace
- 也可以通过SkyWalking、Jaeger、Zipkin等方式接入
- Trace实例可以与阿里云日志服务进行绑定(通过project,logstore, 以及索引字段)。接入trace之后,日志当中会记录下uber-trace-id,需要手动开启该字段索引,才能完成绑定。
通常我们的项目会分为开发,测试,线上等多重环境。因此,建议大家在接入trace时,通过ServiceName的命名进行区分。
修改Span名称生成策略
以下是.NET 5.0版本的jaeger trace接入方法。这里对AspNetCore的Span名称生成策略,以及Http请求Span名称生成策略进行了调整。 另外.NET 5.0以上版本的Span过滤方式也与官网描述不同。
if (!string.IsNullOrWhiteSpace($"{Configuration["JaegerToken"]}"))
{
services.AddOpenTracing(builder =>
builder.ConfigureAspNetCore(opt =>
{
opt.Hosting.OperationNameResolver = context => $"{context.Request.Method} {context.Request.Path}";
opt.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path.HasValue && ctx.Request.Path.Value.Contains("health"));
opt.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path.HasValue && ctx.Request.Path.Value.Contains("/api/traces"));
opt.Hosting.IgnorePatterns.Add(ctx => ctx.Request.Path.HasValue && ctx.Request.Path.Value.Contains("/swagger"));
})
.ConfigureHttpHandler(opt=> opt.OperationNameResolver = request => $"{request.Method} {request.RequestUri.Host}{request.RequestUri.AbsolutePath}")
);
services.AddSingleton<ITracer>(serviceProvider =>
{
string serviceName = serviceProvider.GetRequiredService<IWebHostEnvironment>().ApplicationName;
ILoggerFactory loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
var resolver = new SenderResolver(loggerFactory).RegisterSenderFactory<ThriftSenderFactory>();
Configuration.SenderConfiguration senderConfiguration = new Configuration.SenderConfiguration(loggerFactory)
.WithSenderResolver(resolver)
.WithEndpoint($"{Configuration["JaegerToken"]}");
var tracer = new Tracer.Builder($"{serviceName}{Configuration["environmentName"]}")
.WithSampler(new ConstSampler(true))
.WithReporter(new RemoteReporter.Builder().WithSender(senderConfiguration.GetSender()).Build())
.Build();
GlobalTracer.Register(tracer);
return tracer;
});
}
添加了全局异常处理,如何标记Span为Exception?
通常,我们会在项目中增加全局异常处理机制,而jaeger trace只会将发生UnhandledException的Span标记为异常。因此,我们需要在全局异常处理器中,主动将当前Span标记为异常,或者生成一个子Span并标记为异常。
Span可以通过设置SetTag(Tags.Error, true)方式,标记为异常。本质也就是增加一个名为“error”的tag。这样,我们就可以在阿里云统计页面,观察到包含异常Span的链路了。
public class HttpGlobalExceptionFilter : IExceptionFilter
{
/// <summary>
///
/// </summary>
/// <param name="context"></param>
public void OnException(ExceptionContext context)
{
var json = new AjaxResponse(new ErrorInfo(context.Exception.Message));
context.Result = new InternalServerErrorObjectResult(json);
if(context.Exception is not DomainException)
{
ITracer tracer = GlobalTracer.Instance;
ISpan parentSpan = tracer.ActiveSpan;
ISpan childSpan = tracer.BuildSpan($"{context.HttpContext.Request.Method} {context.HttpContext.Request.Path}").AsChildOf(parentSpan).WithTag("error", context.Exception.Message).Start();
tracer.ScopeManager.Activate(childSpan, false);
childSpan.Finish();
}
context.ExceptionHandled = true;
}
}
ILogger
按照阿里云官网的介绍添加了trace之后,Nuget包:OpenTracing.Contrib.NetCore 当中其实为我们创建了一个ILogger的实现,名为OpenTracingLogger,并且在AddOpenTracing时完成注册,只不过它是访问类型为internal。
当我们再使用 _logger.LogError($"{ex.Message}");或_logger.LogInformation($"状态检测完成");
等打印时,其日志类型和内容会作为Span的Log Events保存于链路追踪当中.
我们这个类库可以看到关于AspNetCore, HttpHandler, EFCore, SqlClient等相关Span的生成策略。
例如:在AspNetCore中,会在Microsoft.AspNetCore.Hosting.HttpRequestIn.Start,Microsoft.AspNetCore.Mvc.BeforeAction,Microsoft.AspNetCore.Mvc.BeforeActionResult三个阶段分别创建三个Span,并且依次为父子关系。
假如你认为创建一个单独的ActionResult Span并没有什么作用,并且会干扰到Trace报表的展示,那么你就可以对该项目进行二次改造和封装,设计符合自己项目的Trace方式。