Logging and Distributed Trace Tracking in .NET Core

2021年1月9日 64点热度 0人点赞 0条评论
内容目录

.NET Core Logging and Distributed Tracing

Logs recorded by programs generally serve two purposes: troubleshooting and explicitly indicating program runtime status. When a program encounters an issue, logs can help identify the problem, providing a basis for fault detection. Often times, logging is perceived to be simply done through try-catch{} statements that directly output to .txt files, but such logs often fail to assist in locating issues and may be filled with excessive garbage content; thus, logs require manual line-by-line scanning or Ctrl+F searches to review efficiently. Logs output only to text files, with no good management structure.

Next, we will step-by-step learn about writing logs, as well as related knowledge for OpenTracing API and Jaeger distributed tracing.

.NET Core Logging

Console Output

The simplest form of logging is via console output, utilizing the Console.WriteLine() function to directly output information.

Below is a simple information output example, where calling the SayHello function will print a message.

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

Non-intrusive Logging

From the console, we can see that to log, we must write input log code within the function. The pros and cons of this approach need not be elaborated; through an AOP framework, we can achieve aspect-oriented programming to log uniformly.

Here we can use the author's open-source CZGL.AOP framework, which can be found in NuGet.

czgl.aop

By writing unified cutting edge code, this code will execute when the function is called.

Before will be effective before the proxy method is executed or when the proxy property is called, and you can retrieve and modify passed parameters through AspectContext.

After will be effective after the method execution or property call, and you can retrieve and modify the return value via context.

    public class LogAttribute : ActionAttribute
    {
        public override void Before(AspectContext context)
        {
            Console.WriteLine($"{context.MethodInfo.Name} function is executed before");
        }

        public override object After(AspectContext context)
        {
            Console.WriteLine($"{context.MethodInfo.Name} function is executed after");
            return null;
        }
    }

Transform the Hello class as follows:

    [Interceptor]
    public class Hello
    {
        [Log]
        public virtual void SayHello(string content)
        {
            var str = $"Hello,{content}";
            Console.WriteLine(str);
        }
    }

Then create the proxy type:

        static void Main(string[] args)
        {
            Hello hello = AopInterceptor.CreateProxyOfClass<Hello>();
            hello.SayHello("any one");
            Console.Read();
        }

Starting the program will output:

SayHello function is executed before
Hello,any one
SayHello function is executed after

You need not worry about the AOP framework causing performance issues, as the CZGL.AOP framework uses EMIT for writing and comes with caching; once a type has been proxied, it does not need to be regenerated repeatedly.

CZGL.AOP can be combined with the built-in dependency injection framework of .NET Core and Autofac to automatically proxy services within the CI container, eliminating the need for manual calls to AopInterceptor.CreateProxyOfClass.

The CZGL.AOP code is open source, and you can refer to the author's other blog post:

https://www.cnblogs.com/whuanle/p/13160139.html

Microsoft.Extensions.Logging

Some companies lack technical management standards, resulting in different developers using various logging frameworks; a product may incorporate .txt, NLog, Serilog, etc., without a unified encapsulation.

The logging components in .NET Core are many, but popular logging frameworks generally implement Microsoft.Extensions.Logging.Abstractions, hence we can study Microsoft.Extensions.Logging. Microsoft.Extensions.Logging.Abstractions is an official abstraction of logging components. If a logging component does not support Microsoft.Extensions.Logging.Abstractions, integrating that component into projects can lead to complications, making future modularization and coupling reduction difficult.

The Microsoft.Extensions.Logging package contains Logging APIs that cannot operate independently; instead, they are used in conjunction with one or more logging providers that store or display logs to specific outputs such as Console, Debug, and TraceListeners.

The following image shows the hierarchy of Logging API in .NET Core:

Image source: https://www.tutorialsteacher.com/

logginapi

To be honest, I initially found Microsoft.Extensions.Logging quite confusing, with a seemingly complex configuration. Therefore, having a clear structured diagram is important, as it aids understanding of the Logging API within.

logging-api

ILoggerFactory

Many standard interfaces in .NET Core embody the idea of the factory pattern, with ILoggerFactory being the factory pattern interface and LoggerFactory being its implementation.

Its definition is as follows:

public interface ILoggerFactory : IDisposable
{
    ILogger CreateLogger(string categoryName);
    void AddProvider(ILoggerProvider provider);
}

The purpose of the ILoggerFactory factory interface is to create an instance of type ILogger, specifically through the CreateLogger interface.

ILoggerProvider

By implementing the ILoggerProvider interface, one can create their own logging provider that represents a type capable of creating ILogger instances.

Its definition is as follows:

public interface ILoggerProvider : IDisposable
{
    ILogger CreateLogger(string categoryName);
}

ILogger

The ILogger interface provides methods for logging to underlying storage, defined as follows:

public interface ILogger
{
    void Log<TState>(LogLevel logLevel, 
                     EventId eventId, 
                     TState state, 
                     Exception exception, 
                     Func<TState, Exception, string> formatter);
    
    bool IsEnabled(LogLevel logLevel);
    IDisposable BeginScope<TState>(TState state);
} 

Logging Providers

Logging providers refer to logging implementations.

Logging Providers display or store logs to specific mediums, such as console, debugging events, event logs, Trace listeners, etc.

Microsoft.Extensions.Logging offers the following types of logging providers, which can be obtained via NuGet:

  • Microsoft.Extensions.Logging.Console
  • Microsoft.Extensions.Logging.AzureAppServices
  • Microsoft.Extensions.Logging.Debug
  • Microsoft.Extensions.Logging.EventLog
  • Microsoft.Extensions.Logging.EventSource
  • Microsoft.Extensions.Logging.TraceSource

Meanwhile, Serilog provides File, Console, Elasticsearch, Debug, MSSqlServer, Email, etc.

There are numerous logging providers, and we need not delve into them; if a logging component does not provide compatibility with Microsoft.Extensions.Logging, it should not be incorporated.

In reality, many programs directly utilize File.Write("Log.txt"). How good can such product quality be?

Usage

Previously, we introduced the composition of Microsoft.Extensions.Logging; here we will learn how to use a Logging Provider to input logs.

It is crucial to note that it merely provides a Logging API; thus, to output logs, we must select a suitable Logging Provider program. Here, we choose

Microsoft.Extensions.Logging.Console, which should be referenced from NuGet.

Below is a structural diagram illustrating the combination of Logging Provider and ConsoleLogger:

console-logger

From a conventional approach, I realized that configuring it was challenging...

            ConsoleLoggerProvider consoleLoggerProvider = new ConsoleLoggerProvider(
                new OptionsMonitor<ConsoleLoggerOptions>(
                    new OptionsFactory<ConsoleLoggerOptions>(
                        new IEnumerable<IConfigureOptions<TOptions>>(... ... ...))));

Thus, we can only use the following code to quickly create a factory:

            using ILoggerFactory loggerFactory =
                LoggerFactory.Create(builder =>
                    builder.AddSimpleConsole(options =>
                    {
                        options.IncludeScopes = true;
                        options.SingleLine = true;
                        options.TimestampFormat = "hh:mm:ss ";
                    }));

Or:

ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());

Of course, other logging providers can be added to the factory; for example:

            using ILoggerFactory loggerFactory =
                LoggerFactory.Create(builder =>
                    builder.AddSimpleConsole(...)
                    .AddFile(...)
                    .Add()...
                    );

Then fetch an ILogger instance:

  ILogger logger = loggerFactory.CreateLogger<Program>();

Log the information:

            logger.LogInformation("Logging information.");

Log Levels

In the Logging API, seven log levels are defined as follows:

public enum LogLevel
{
  Debug = 1,
  Verbose = 2,
  Information = 3,
  Warning = 4,
  Error = 5,
  Critical = 6,
  None = int.MaxValue
}

We can output several levels of logs using functions within ILogger:

            logger.LogInformation("Logging information.");
            logger.LogCritical("Logging critical information.");
            logger.LogDebug("Logging debug information.");
            logger.LogError("Logging error information.");
            logger.LogTrace("Logging trace");
            logger.LogWarning("Logging warning.");

Regarding Microsoft.Extensions.Logging, no further details will be provided here; readers can refer to the following links for more related knowledge:

https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0#log-exceptions

https://www.tutorialsteacher.com/core/fundamentals-of-logging-in-dotnet-core

https://docs.microsoft.com/en-us/archive/msdn-magazine/2016/april/essential-net-logging-with-net-core

Trace and Debug

The namespaces for the Debug and Trace classes are System.Diagnostics. Debug and Trace provide a set of methods and properties that facilitate code debugging.

Readers can refer to the author's other article:

https://www.cnblogs.com/whuanle/p/14141213.html#3

Output to the console:

Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));
Debug.WriteLine("Information");

Tracing

Tracing helps developers quickly identify performance bottlenecks in distributed application architectures, improving diagnosis efficiency in the era of microservices.

OpenTracing

The aforementioned Trace and Debug are APIs provided in .NET Core for developers to diagnose programs and output information, while tracing refers to the tracing function in the OpenTracing API.

Traditional logging has significant drawbacks, as it involves recording a log for each method. It's challenging to connect multiple methods called during a workflow. When an exception occurs in a method, it is difficult to know which task process failed, and we can only see which method encountered an error and its caller.

In OpenTracing, a Trace is a directed acyclic graph having Spans. A Span represents a logical representation of completing some work within an application, and each Span has the following properties:

  • Operation Name
  • Start Time
  • End Time

To clarify what Trace and Span are, and what OpenTracing encompasses, please introduce OpenTracing from NuGet.

.

Implementing the Hello Class

public class Hello
{
    private readonly ITracer _tracer;
    private readonly ILogger<Hello> _logger;
    
    public Hello(ITracer tracer, ILoggerFactory loggerFactory)
    {
        _tracer = tracer;
        _logger = loggerFactory.CreateLogger<Hello>();
    }

    public void SayHello(string content)
    {
        // Create and start a Span
        var spanBuilder = _tracer.BuildSpan("say-hello");
        // -------------------------------
        var span = spanBuilder.Start(); // |
        var str = $"Hello,{content}";   // |
        _logger.LogInformation(str);    // |
        span.Finish();                  // |
        // ---------------------------------
    }
}

Starting the Program and Initiating Trace

static void Main(string[] args)
{
    using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());

    Hello hello = new Hello(GlobalTracer.Instance, loggerFactory);
    hello.SayHello("This trace");
    Console.Read();
}

In the process above, we use the OpenTracing API. Below are some explanations of the elements in the code:

  • ITracer is a tracing instance, and BuildSpan() can create a Span;
  • Each ISpan has an operation name, for example, say-hello;
  • Use Start() to begin a Span; use Finish() to end a Span;
  • The tracer automatically records timestamps;

Of course, when we run the above program, no other information or UI interface appears. This is because GlobalTracer.Instance returns a no-op tracer. When we define a Tracer, we can observe the tracing process.

Introducing Jaeger in NuGet

In the Program, add a static function that returns a custom Tracer:

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

Modify the contents of the Main function as follows:

static void Main(string[] args)
{
    using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
    var tracer = InitTracer("hello-world", loggerFactory);
    Hello hello = new Hello(tracer, loggerFactory);
    hello.SayHello("This trace");
    Console.Read();
}

Complete Code

Context and Tracing Features

However, directly outputting string logs is unfriendly, and we need structured logging.

Of course, ISpan provides methods for structured logging, and we can write a method to format the logs.

Tracing Individual Functions

Add the following code to the Hello class:

private string FormatString(ISpan rootSpan, string helloTo)
{
    var span = _tracer.BuildSpan("format-string").Start();
    try
    {
        var helloString = $"Hello, {helloTo}!";
        span.Log(new Dictionary<string, object>
        {
            [LogFields.Event] = "string.Format",
            ["value"] = helloString
        });
        return helloString;
    }
    finally
    {
        span.Finish();
    }
}

Additionally, we can encapsulate a function to output string information:

private void PrintHello(ISpan rootSpan, string helloString)
{
    var span = _tracer.BuildSpan("print-hello").Start();
    try
    {
        _logger.LogInformation(helloString);
        span.Log("WriteLine");
    }
    finally
    {
        span.Finish();
    }
}

Change the SayHello method to:

public void SayHello(string content)
{
    var spanBuilder = _tracer.BuildSpan("say-hello");
    var span = spanBuilder.Start();
    var str = FormatString(span, content);
    PrintHello(span, str);
    span.Finish();
}

The reason for modifying the above code is to avoid mixing too much code in one method. Instead, we can try to reuse some code and encapsulate a unified code.

However, originally we just needed to call the SayHello method once, but now one method continues to call the other two methods. Originally it was one Span, but it has become three.

info: Jaeger.Configuration[0]
info: Jaeger.Reporters.LoggingReporter[0]
      Span reported: 77f1a24676a3ffe1:77f1a24676a3ffe1:0000000000000000:1 - format-string
info: ConsoleApp1.Hello[0]
      Hello, This trace!
info: Jaeger.Reporters.LoggingReporter[0]
      Span reported: cebd31b028a27882:cebd31b028a27882:0000000000000000:1 - print-hello
info: Jaeger.Reporters.LoggingReporter[0]
      Span reported: 44d89e11c8ef51d6:44d89e11c8ef51d6:0000000000000000:1 - say-hello

Note: 0000000000000000 indicates that a Span has already ended.

Advantages: From the code perspective, we can clearly see the call chain for SayHello -> FormatString and SayHello -> PrintHello.

Disadvantages: From the output perspective, the Span reported is different. We cannot identify the causal relationship between the three functions from the output.

We cannot always keep an eye on the code. Operation and implementation personnel also cannot compare and find code logic with the code.

Merging Multiple Spans into One Trace

ITracer is responsible for creating tracing, so ITracer also provides an API to combine the causal relationship of multiple Spans.

The usage is as follows:

var rootSapn = _tracer.BuildSpan("say-hello");  // A
var span = _tracer.BuildSpan("format-string").AsChildOf(rootSpan).Start(); // B
// A -> B

We create a rootSpan, and then create a span that continues from rootSpan, forming rootSpan -> span.

info: Jaeger.Reporters.LoggingReporter[0]
      Span reported: 2f2c7b36f4f6b0b9:3dab62151c641380:2f2c7b36f4f6b0b9:1 - format-string
info: ConsoleApp1.Hello[0]
      Hello, This trace!
info: Jaeger.Reporters.LoggingReporter[0]
      Span reported: 2f2c7b36f4f6b0b9:9824227a41539786:2f2c7b36f4f6b0b9:1 - print-hello
info: Jaeger.Reporters.LoggingReporter[0]
      Span reported: 2f2c7b36f4f6b0b9:2f2c7b36f4f6b0b9:0000000000000000:1 - say-hello
Span reported: 2f2c7b36f4f6b0b9

The output order corresponds to the execution completion order, with say-hello being the last to complete.

Context Propagation During Tracing

From the code, it can be observed that the code is quite cumbersome because:

  • The Span object must be passed as the first parameter to each function;
  • Each function includes a lengthy try-finally{} block to ensure the Span is completed.

To this end, the OpenTracing API provides a better method, allowing us to avoid passing Span as a parameter and call _tracer directly.

Modify FormatString and PrintHello code as follows:

private string FormatString(string helloTo)
{
    using var scope = _tracer.BuildSpan("format-string").StartActive(true);
    var helloString = $"Hello, {helloTo}!";
    scope.Span.Log(new Dictionary<string, object>
    {
        [LogFields.Event] = "string.Format",
        ["value"] = helloString
    });
    return helloString;
}

private void PrintHello(string helloString)
{
    using var scope = _tracer.BuildSpan("print-hello").StartActive(true);
    _logger.LogInformation(helloString);
    scope.Span.Log(new Dictionary<string, object>
    {
        [LogFields.Event] = "WriteLine"
    });
}

Modify the SayHello code as follows:

public void SayHello(string helloTo)
{
    using var scope = _tracer.BuildSpan("say-hello").StartActive(true);
    scope.Span.SetTag("hello-to", helloTo);
    var helloString = FormatString(helloTo);
    PrintHello(helloString);
}

With the above code, we have eliminated those annoying bits of code.

  • StartActive() replaces Start() and keeps the span in “active” status by storing it in thread-local storage;
  • StartActive() returns an IScope object instead of an ISpan object. IScope is a container for the current active scope. By accessing the active span scope.Span, once the scope is closed, the previous span will become the current one, reactivating the earlier active scope in the current thread;
  • IScope inherits from IDisposable, allowing us to use the using syntax;
  • StartActive(true) tells the Scope to finish when it is processed;
  • StartActive() automatically creates a reference to the previous active scope as a ChildOf, so we don't have to explicitly use the builder method AsChildOf();

When running this program, we will see that all three reported spans have the same trace ID.

Distributed Tracing

Tracking Across Different Processes

Microservices separate deployment of multiple programs, each providing different functionalities. Previously, we learned OpenTracing tracing. Next, we will split the code and the console program will no longer implement the FormatString function; instead, we will use a web program to implement the FormatString service.

Create an ASP.NET Core application, choosing the template with view model controllers.

Add a FormatController in the Controllers directory with the following code:

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

The web application will serve as one of the services in the microservice architecture, and this service has only one API, which is very simple and provides string formatting. You can also write other APIs to provide services.

Modify the Program's CreateHostBuilder to fix the port of this service:

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

Next, remove app.UseHttpsRedirection(); from the Startup class.

Modify the code in the console program to change the FormatString method as follows:

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

Start the web program and then launch the console program.

控制台程序输出:

info: Jaeger.Reporters.LoggingReporter[0]
      Span reported: c587bd888e8f1c19:2e3273568e6e373b:c587bd888e8f1c19:1 - format-string
info: ConsoleApp1.Hello[0]
      Hello, This trace!
info: Jaeger.Reporters.LoggingReporter[0]
      Span reported: c587bd888e8f1c19:f0416a0130d58924:c587bd888e8f1c19:1 - print-hello
info: 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 界面。

JaegerUI

Jaeger 的端口作用如下:

Collector
14250 tcp  gRPC 发送 proto 格式数据
14268 http 直接接受客户端数据
14269 http 健康检查
Query
16686 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 ,我们可以分析链路中函数的执行速度以及服务器性能情况。

痴者工良

高级程序员劝退师

文章评论