隨着微服務的普及,微服務間的調用全鏈路跟蹤也火了起來,Jaeger(https://www.jaegertracing.io/) 是CNCF孵化的全鏈路跟蹤型項目,在.net core中,提供了一個Jaeger的Nuget(https://github.com/jaegertracing/jaeger-client-csharp) 包來使用。
本例封裝就是用這個Nuget包來作成asp.net core中間件和action過濾器:中間件能夠把全部通過管道的請求都跟蹤起來,若是以爲這樣的跟蹤過重,或者沒有必要收集全部請求,只跟蹤有鏈路關聯的請求,那就能夠用action過濾器。git
定義一個Options,這是自定義跟蹤收集數據的配置屬性github
namespace JaegerSharp { /// <summary> /// Jaeger選項 /// </summary> public class JaegerOptions { /// <summary> /// 是否啓用Form數據轉成Span /// </summary> public bool IsFormSpan { get; set; } = false; /// <summary> /// Form數據最大長度 /// </summary> public int FormValueMaxLength { get; set; } = 100; /// <summary> /// 是否啓用Query數據轉成Span /// </summary> public bool IsQuerySpan { get; set; } = false; /// <summary> /// Query最大長度 /// </summary> public int QueryValueMaxLength { get; set; } = 100; /// <summary> /// Form或Query中不做爲Span的key集合 /// </summary> public string[] NoSpanKeys { get; set; } = new string[] { "password", "pwd" }; } }
跟蹤中間件,把全部的請求都收集起來asp.net
using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using OpenTracing; using OpenTracing.Propagation; using System; using System.IO; using System.Linq; using System.Threading.Tasks; namespace JaegerSharp { /// <summary> /// Jaeger中間件 /// </summary> public class JaegerSharpMiddleware { /// <summary> /// jaeger選項 /// </summary> private readonly JaegerOptions _jaegerOptions; private readonly ILogger<JaegerSharpMiddleware> _logger; /// <summary> /// 請求代理 /// </summary> private readonly RequestDelegate _next; public JaegerSharpMiddleware(RequestDelegate next, ILogger<JaegerSharpMiddleware> logger, JaegerOptions jaegerOptions = null) { _next = next; _jaegerOptions = jaegerOptions; _logger = logger; } /// <summary> /// 調用管道 /// </summary> /// <param name="context">上下文</param> /// <param name="tracer">跟蹤器</param> /// <returns></returns> public async Task InvokeAsync(HttpContext context, ITracer tracer) { _logger.LogInformation("jaeger調用"); var path = context.Request.Path; if (Path.HasExtension(path)) { await _next(context); } else { //接收傳入的Headers var callingHeaders = new TextMapExtractAdapter(context.Request.Headers.ToDictionary(m => m.Key, m => m.Value.ToString())); var spanContex = tracer.Extract(BuiltinFormats.HttpHeaders, callingHeaders); ISpanBuilder builder = null; if (spanContex != null) { builder = tracer.BuildSpan("中間件Span").AsChildOf(spanContex); } else { builder = tracer.BuildSpan("中間件Span"); } //開始設置Span using (IScope scope = builder.StartActive(true)) { scope.Span.SetOperationName(path); // 記錄請求信息到span if (_jaegerOptions.IsQuerySpan) { foreach (var query in context.Request.Query) { //包含敏感詞跳出 if (_jaegerOptions.NoSpanKeys.Contains(query.Key)) { continue; } var value = query.Value.ToString().Length > _jaegerOptions.QueryValueMaxLength ? query.Value.ToString()?.Substring(0, _jaegerOptions.QueryValueMaxLength) : query.Value.ToString(); scope.Span.SetTag(query.Key, value); } } if (_jaegerOptions.IsFormSpan && context.Request.HasFormContentType) { foreach (var form in context.Request.Form) { //包含敏感詞跳出 if (_jaegerOptions.NoSpanKeys.Contains(form.Key)) { continue; } var value = form.Value.ToString().Length > _jaegerOptions.FormValueMaxLength ? form.Value.ToString()?.Substring(0, _jaegerOptions.FormValueMaxLength) : form.Value.ToString(); scope.Span.SetTag(form.Key, value); } } await _next(context); } } } } }
擴展中間件async
using Microsoft.AspNetCore.Builder; namespace JaegerSharp { /// <summary> /// 使用JaegerSharp中間件 /// </summary> public static class JaegerSharpMiddlewareExtensions { public static IApplicationBuilder UseJaegerSharp(this IApplicationBuilder builder) { return builder.UseMiddleware<JaegerSharpMiddleware>(new JaegerOptions()); } public static IApplicationBuilder UseJaegerSharp(this IApplicationBuilder builder, JaegerOptions jaegerOptions) { return builder.UseMiddleware<JaegerSharpMiddleware>(jaegerOptions); } } }
action過濾器,用來準確收集跟蹤信息ide
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using OpenTracing; using OpenTracing.Propagation; using System.Linq; namespace JaegerSharp { /// <summary> /// 使用Jaeger的Action過濾器添加Jaeger實列 /// </summary> public class JaegerSharpAttribute : TypeFilterAttribute { public JaegerSharpAttribute() : base(typeof(JaegerFilterAttributeImpl)) { } /// <summary> /// 真正實現Jaeger Scope的類 /// </summary> private class JaegerFilterAttributeImpl : IActionFilter { private readonly ITracer _tracer; public JaegerFilterAttributeImpl(ITracer tracer) { _tracer = tracer; } IScope _scope = null; /// <summary> /// 開始執行Action /// </summary> /// <param name="context"></param> public void OnActionExecuting(ActionExecutingContext context) { var path = context.HttpContext.Request.Path; var callingHeaders = new TextMapExtractAdapter(context.HttpContext.Request.Headers.ToDictionary(m => m.Key, m => m.Value.ToString())); var spanContex = _tracer.Extract(BuiltinFormats.HttpHeaders, callingHeaders); ISpanBuilder builder = null; if (spanContex != null) { builder = _tracer.BuildSpan("中間件Span").AsChildOf(spanContex); } else { builder = _tracer.BuildSpan("中間件Span"); } _scope = builder.StartActive(true); _scope.Span.SetOperationName(path); // 記錄請求信息到span foreach (var query in context.HttpContext.Request.Query) { _scope.Span.SetTag(query.Key, query.Value); } if (context.HttpContext.Request.HasFormContentType) { foreach (var form in context.HttpContext.Request.Form) { _scope.Span.SetTag(form.Key, form.Value); } } } /// <summary> /// Action結束執行 /// </summary> /// <param name="context"></param> public void OnActionExecuted(ActionExecutedContext context) { _scope?.Dispose(); } } } }
封裝Jaeger在asp.net core中的注放步驟,使注入簡單微服務
using Jaeger; using Jaeger.Reporters; using Jaeger.Samplers; using Jaeger.Senders.Thrift; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using OpenTracing; using OpenTracing.Util; using System.Reflection; namespace JaegerSharp { public static class JaegerSharpExtensions { /// <summary> /// 注入Jaeger /// </summary> /// <param name="services">服務容器</param> /// <param name="host">Jaeger agent host</param> /// <param name="port">Jaeger agent port</param> /// <param name="maxPacketSize">Jaeger agent maxpacketsize</param> public static void AddJaegerSharp(this IServiceCollection services, string host, int port, int maxPacketSize) { services.AddSingleton<ITracer>(serviceProvider => { var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>(); var reporter = new RemoteReporter.Builder() .WithLoggerFactory(loggerFactory) .WithSender(new UdpSender(string.IsNullOrEmpty(host) ? UdpSender.DefaultAgentUdpHost : host, port <= 100 ? UdpSender.DefaultAgentUdpCompactPort : port, maxPacketSize <= 0 ? 0 : maxPacketSize)) .Build(); ITracer tracer = new Tracer.Builder(Assembly.GetEntryAssembly().GetName().Name) .WithReporter(reporter) .WithLoggerFactory(loggerFactory) .WithSampler(new ConstSampler(true)) .Build(); GlobalTracer.Register(tracer); return tracer; }); } } }