Fork me on GitHub

引入Jaeger——封装

  随着微服务的普及,微服务间的调用全链路跟踪也火了起来,Jaeger(https://www.jaegertracing.io/)是CNCF孵化的全链路跟踪型项目,在.net core中,提供了一个Jaeger的Nuget(https://github.com/jaegertracing/jaeger-client-csharp)包来使用。

  本例封装就是用这个Nuget包来做成asp.net core中间件和action过滤器:中间件可以把所有经过管道的请求都跟踪起来,如果觉得这样的跟踪太重,或者没有必要收集所有请求,只跟踪有链路关联的请求,那就可以用action过滤器。

 

 定义一个Options,这是自定义跟踪收集数据的配置属性

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" };

    }
}

  跟踪中间件,把所有的请求都收集起来

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;
        /// <summary>
        /// 日志
        /// </summary>
        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);
                }
            }
        }
    }
}

  扩展中间件

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过滤器,用来准确收集跟踪信息

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;
            });
        }
    }
}

 

  想要更快更方便的了解相关知识,可以关注微信公众号 
 

 

 

posted @ 2022-02-02 19:53  桂素伟  阅读(72)  评论(0编辑  收藏  举报