ASP.Net Core中的日志与分布式链路追踪

2022-04-18 10:22:52
目录
.NET Core 中的日志控制台输出非侵入式日志Microsoft.Extensions.LoggingILoggerFactoryILoggerProviderILoggerLogging Providers怎么使用日志等级Trace、Debug链路跟踪OpenTracing上下文和跟踪功能跟踪单个功能将多个跨度合并到一条轨迹中传播过程中的上下文分布式链路跟踪在不同进程中跟踪在 ASP.NET Core 中跟踪OpenTracing API 和 Jaeger链路追踪实践

程序记录的日志一般有两种作用,故障排查、显式程序运行状态,当程序发生故障时,我们可以通过日志定位问题,日志可以给我们留下排查故障的依据。很多时候,往往会认为日志记录非常简单,例如很多程序只是 try-catch{},直接输出到 .txt,但是这些日志往往无法起到帮助定位问题的作用,甚至日志充斥了大量垃圾内容;日志内容全靠人眼一行行扫描,或者 Ctrl+F 搜索,无法高效率审查日志;日志单纯输出到文本文件中,没有很好地管理日志。

接下来,我们将一步步学习日志的编写技巧,以及 OpenTracing API 、Jaeger 分布式链路跟踪的相关知识。

.NET Core 中的日志

控制台输出

最简单的日志,就是控制台输出,利用 Console.WriteLine() 函数直接输出信息。

下面时一个简单的信息输出,当程序调用 SayHello 函数时,SayHello 会打印信息。

    public class Hello    {        public void SayHello(string content)        {            var str = $"Hello,{content}";            Console.WriteLine(str);        }    }    class Program    {        static void Main(string[] args)        {            Hello hello = new Hello();            hello.SayHello("any one");            Console.Read();        }    }

非侵入式日志

通过控制台,我们可以看到,为了记录日志,我们必须在函数内编写输入日志的代码,优缺点这些就不多说了,我们可以通过 AOP 框架,实现切面编程,同一记录日志。

这里可以使用笔者开源的 CZGL.AOP 框架,Nuget 中可以搜索到。

程序来实现 FormatString 服务。

创建一个 ASP.NET Core 应用程序,在模板中选择带有视图模型控制器的模板。

添加一个 FormatController 控制器在 Controllers 目录中,其代码如下:

using Microsoft.AspNetCore.Mvc;namespace WebApplication1.Controllers{    [Route("api/[controller]")]    public class FormatController : Controller    {        [HttpGet]        public string Get()        {            return "Hello!";        }        [HttpGet("{helloTo}", Name = "GetFormat")]        public string Get(string helloTo)        {            var formattedHelloString = $"Hello, {helloTo}!";            return formattedHelloString;        }    }}

Web 应用将作为微服务中的其中一个服务,而这个服务只有一个 API ,这个 API 很简单,就是提供字符串的格式化。你也可以编写其它 API 来提供服务。

将 Program 的 CreateHostBuilder 改一下,我们固定这个服务的 端口。

        public static IHostBuilder CreateHostBuilder(string[] args) =>            Host.CreateDefaultBuilder(args)                .ConfigureWebHostDefaults(webBuilder =>                {                    webBuilder.UseUrls("http://*:8081");                    webBuilder.UseStartup<Startup>();                });

再到 Startup 中删除 app.UseHttpsRedirection();

修改之前控制台程序的代码,把 FormatString 方法改成:

        private string FormatString(string helloTo)        {            using (var scope = _tracer.BuildSpan("format-string").StartActive(true))            {                using WebClient webClient = new WebClient();                var url = $"http://localhost:8081/api/format/{helloTo}";                var helloString = webClient.DownloadString(url);                scope.Span.Log(new Dictionary<string, object>                {                    [LogFields.Event] = "string.Format",                    ["value"] = helloString                });                return helloString;            }        }

启动 Web 程序后,再启动 控制台程序。

控制台程序输出:

info: Jaeger.Reporters.LoggingReporter[0]      Span reported: c587bd888e8f1c19:2e3273568e6e373b:c587bd888e8f1c19:1 - format-stringinfo: ConsoleApp1.Hello[0]      Hello, This trace!info: Jaeger.Reporters.LoggingReporter[0]      Span reported: c587bd888e8f1c19:f0416a0130d58924:c587bd888e8f1c19:1 - print-helloinfo: Jaeger.Reporters.LoggingReporter[0]      Span reported: c587bd888e8f1c19:c587bd888e8f1c19:0000000000000000:1 - say-hello

接着,我们可以将 Formating 改成:

        private string FormatString(string helloTo)        {            using (var scope = _tracer.BuildSpan("format-string").StartActive(true))            {                using WebClient webClient = new WebClient();                var url = $"http://localhost:8081/api/format/{helloTo}";                var helloString = webClient.DownloadString(url);                var span = scope.Span                    .SetTag(Tags.SpanKind, Tags.SpanKindClient)                    .SetTag(Tags.HttpMethod, "GET")                    .SetTag(Tags.HttpUrl, url);                var dictionary = new Dictionary<string, string>();                _tracer.Inject(span.Context, BuiltinFormats.HttpHeaders, new TextMapInjectAdapter(dictionary));                foreach (var entry in dictionary)                    webClient.Headers.Add(entry.Key, entry.Value);                return helloString;            }        }

SetTag 可以设置标签,我们为本次请求到 Web 的 Span,设置一个标签,并且存储请求的 URL。

                var span = scope.Span                    .SetTag(Tags.SpanKind, Tags.SpanKindClient)                    .SetTag(Tags.HttpMethod, "GET")                    .SetTag(Tags.HttpUrl, url);

通过 Inject 将上下文信息注入。

                _tracer.Inject(span.Context, BuiltinFormats.HttpHeaders, new TextMapInjectAdapter(dictionary));

这些配置规范,可以到 https://github.com/opentracing/specification/blob/master/semantic_conventions.md 了解。

在 ASP.NET Core 中跟踪

在上面,我们实现了 Client 在不同进程的追踪,但是还没有实现在 Server 中跟踪,我们可以修改 Startup.cs 中的代码,将以下代码替换进去:

using Jaeger;using Jaeger.Samplers;using Microsoft.AspNetCore.Builder;using Microsoft.Extensions.Configuration;using Microsoft.Extensions.DependencyInjection;using Microsoft.Extensions.Logging;using OpenTracing.Util;using System;namespace WebApplication1{    public class Startup    {        private static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());        private static readonly Lazy<Tracer> Tracer = new Lazy<Tracer>(() =>        {            return InitTracer("webService", loggerFactory);        });        private static Tracer InitTracer(string serviceName, ILoggerFactory loggerFactory)        {            var samplerConfiguration = new Configuration.SamplerConfiguration(loggerFactory)                .WithType(ConstSampler.Type)                .WithParam(1);            var reporterConfiguration = new Configuration.ReporterConfiguration(loggerFactory)                .WithLogSpans(true);            return (Tracer)new Configuration(serviceName, loggerFactory)                .WithSampler(samplerConfiguration)                .WithReporter(reporterConfiguration)                .GetTracer();        }        public Startup(IConfiguration configuration)        {            Configuration = configuration;        }        public IConfiguration Configuration { get; }        // This method gets called by the runtime. Use this method to add services to the container.        public void ConfigureServices(IServiceCollection services)        {            services.AddMvc();            GlobalTracer.Register(Tracer.Value);            services.AddOpenTracing();        }        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.        public void Configure(IApplicationBuilder app)        {            app.UseRouting();            app.UseEndpoints(endpoints =>            {                endpoints.MapControllerRoute(                    name: "default",                    pattern: "{controller=Home}/{action=Index}/{id?}");            });        }    }}

这样不同的进程各种都可以实现追踪。

OpenTracing API 和 Jaeger

OpenTracing 是开放式分布式追踪规范,OpenTracing API 是一致,可表达,与供应商无关的API,用于分布式跟踪和上下文传播。

Jaeger 是 Uber 开源的分布式跟踪系统。

OpenTracing 的客户端库以及规范,可以到 Github 中查看:https://github.com/opentracing/

详细的介绍可以自行查阅资料。

这里我们需要部署一个 Jaeger 实例,以供微服务以及事务跟踪学习需要。

使用 docker 部署很简单,只需要执行下面一条命令即可:

docker run -d -p 5775:5775/udp -p 16686:16686 -p 14250:14250 -p 14268:14268 jaegertracing/all-in-one:latest

访问 16686 端口,即可看到 UI 界面。

ASP.NetCore中的日志与分布式链路追踪

Jaeger 的端口作用如下:

Collector14250 tcp  gRPC 发送 proto 格式数据14268 http 直接接受客户端数据14269 http 健康检查Query16686 http jaeger的UI前端16687 http 健康检查

接下来我们将学习如何通过代码,将数据上传到 Jaeger 中。

链路追踪实践

要注意,数据上传到 Jaeger ,上传的是 Span,是不会上传日志内容的。

继续使用上面的控制台程序,Nuget 中添加 Jaeger.Senders.Grpc 包。

我们可以通过 UDP (6831端口)和 gRPC(14250) 端口将数据上传到 Jaeger 中,这里我们使用 gRPC。

修改控制台程序的 InitTracer 方法,其代码如下:

        private static Tracer InitTracer(string serviceName, ILoggerFactory loggerFactory)        {            Configuration.SenderConfiguration.DefaultSenderResolver = new SenderResolver(loggerFactory)                .RegisterSenderFactory<GrpcSenderFactory>();            var reporter = new RemoteReporter.Builder()                .WithLoggerFactory(loggerFactory)                .WithSender(new GrpcSender("180.102.130.181:14250", null, 0))                .Build();            var tracer = new Tracer.Builder(serviceName)                .WithLoggerFactory(loggerFactory)                .WithSampler(new ConstSampler(true))                .WithReporter(reporter);            return tracer.Build();        }

分别启动 Web 和 控制台程序,然后打开 Jaeger 界面,在 ”Service“ 中选择 hello-world,然后点击底下的 Find Traces

search

hello-world

通过 Jaeger ,我们可以分析链路中函数的执行速度以及服务器性能情况。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。