乐趣区

关于.net:NET-Core-中的日志与分布式链路追踪

大家好,我是本期的实验室研究员——痴者工良。明天我将通过试验和残缺的操作过程,向大家简略地介绍 .NET Core 中的日志和链路追踪,以及通过链路收集日志。接下来就让咱们一起到实验室中一探到底吧!

微软 MVP 实验室研究员

目录

.NET Core 中的日志

  • 控制台输入
  • 非侵入式日志
  • Microsoft.Extensions.Logging
  • Trace、Debug

链路跟踪

  • OpenTracing
  • 上下文和跟踪性能

分布式链路跟踪

  • 在不同过程中跟踪
  • 在 ASP.NET Core 中跟踪
  • OpenTracing API 和 Jaeger
  • 链路追踪实际

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

程序记录的日志个别有两种作用,故障排查、显示程序运行状态,当程序产生故障时,咱们能够通过日志定位问题,日志能够给咱们留下排查故障的根据。很多时候,往往会认为日志记录非常简单,例如很多程序员只是应用 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 中能够搜寻到。

编写对立的切入代码,这些代码将在函数被调用时执行。

Before 会在被代理的办法执行前或被代理的属性调用时失效,你能够通过 AspectContext 上下文,获取、批改传递的参数。

After 在办法执行后或属性调用时失效,你能够通过上下文获取、批改返回值。

public class LogAttribute : ActionAttribute
{public override void Before(AspectContext context)
    {Console.WriteLine($"{context.MethodInfo.Name} 函数被执行前");
    }

    public override object After(AspectContext context)
    {Console.WriteLine($"{context.MethodInfo.Name} 函数被执行后");
        return null;
    }
}

革新 Hello 类,代码如下:

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

而后创立代理类型:

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

启动程序,会输入:

SayHello 函数被执行前
Hello,any one
SayHello 函数被执行后 

你齐全不须要放心 AOP 框架会给你的程序带来性能问题,因为 CZGL.AOP 框架采纳 EMIT 编写,并且自带缓存,当一个类型被代理过,之后无需反复生成。

CZGL.AOP 能够通过 .NET Core 自带的依赖注入框架和 Autofac 联合应用,主动代理 CI 容器中的服务。这样不须要 AopInterceptor.CreateProxyOfClass 手动调用代理接口。

CZGL.AOP 代码是开源的,能够参考笔者另一篇博文:

  • https://www.cnblogs.com/whuan…

Microsoft.Extensions.Logging

有些公司无技术治理标准,不同的开发人员应用不同的日志框架,一个产品中可能有 .txt、NLog、Serilog 等,并且没有同一的封装。

.NET Core 中的日志组件有很多,然而风行的日志框架根本都会实现 Microsoft.Extensions.Logging.Abstractions,因而咱们能够学习 Microsoft.Extensions.Logging。Microsoft.Extensions.Logging.Abstractions 是官网对日志组件的形象,如果一个日志组件并不反对 Microsoft.Extensions.Logging.Abstractions 那么这个组件很容易跟我的项目糅合的,后续难以模块化以及升高耦合水平。

Microsoft.Extensions.Logging 软件包中蕴含 Logging API,这些 Logging API 不能独立运行。它与一个或多个日志记录提供程序一起应用,这些日志记录提供程序将日志存储或显示到特定输入,例如 Console, Debug, TraceListeners。

下图是 .NET Core 中 Loggin API 的层次结构:

(图片起源:https://www.tutorialsteacher….)

说实话,Microsoft.Extensions.Logging 刚开始是学着很懵,配置感觉很简单。因而,有一张清晰的结构图很重要,能够帮忙大家了解外面的 Logging API。

ILoggerFactory

.NET Core 中很多标准接口都实际了工厂模式的思维,ILoggerFactory 正是工厂模式的接口,而 LoggerFactory 是工厂模式的实现。

其定义如下:

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

ILoggerFactory 工厂接口的作用是创立一个 ILogger 类型的实例,即 CreateLogger 接口。

ILoggerProvider

通过实现 ILoggerProvider 接口能够创立本人的日志记录提供程序,示意能够创立 ILogger 实例的类型。

其定义如下:

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

ILogger

ILogger 接口提供了将日志记录到根底存储的办法,其定义如下:

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 称为日志记录程序。

Logging Providers 将日志显示或存储到特定介质,例如 console, debugging event, event log, trace listener 等。

Microsoft.Extensions.Logging 提供了以下类型的 logging providers,咱们能够通过 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

而 Serilog 则有 File、Console、Elasticsearch、Debug、MSSqlServer、Email 等。

这些日志提供程序有很多,咱们不用细究;如果一个日志组件,不提供兼容 Microsoft.Extensions.Logging 的实现,那么基本不应该引入他。

实际上,很多程序是间接 File.Write(“Log.txt”),这种产品质量能好到哪里去呢?

怎么应用

后面,介绍了 Microsoft.Extensions.Logging 的组成,这里将学习如何应用 Logging Provider 输出日志。

起码提到,它只是提供了一个 Logging API,因而为了输入日志,咱们必须抉择适合的 Logging Provider 程序,这里咱们抉择

Microsoft.Extensions.Logging.Console,请在 Nuget 中援用这个包。

下图是 Logging Provider 和 ConsoleLogger 联合应用的结构图:

从惯例办法来弄,配置很麻烦:

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

咱们能够应用工厂模式或扩大办法疾速配置:

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

当然工厂中能够增加其它日志提供程序,示例:

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

而后获取 ILogger 实例:

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

记录日志:

            logger.LogInformation("记录信息");

日志等级

Logging API 中,规定了 7 种日志等级,其定义如下:​​​​​​​

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

咱们能够通过 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.");

对于 Microsoft.Extensions.Logging 这里就不再赘述,读者能够等级以下链接,理解更多相干常识:

  • https://docs.microsoft.com/zh…
  • https://www.tutorialsteacher….
  • https://docs.microsoft.com/en…

Trace、Debug

Debug、Trace 这两个类的命名空间为 System.Diagnostics,Debug、Trace 提供一组有助于调试代码的办法和属性。

读者能够参考笔者的另一篇文章:

  • https://www.cnblogs.com/whuan…

输入到控制台:

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

链路跟踪

链路追踪能够帮忙开发者疾速定位分布式应用架构下的性能瓶颈,进步微服务时代的开发诊断效率。

OpenTracing

后面提到的 Trace、Debug 是 .NET Core 中提供给开发者用于诊断程序和输入信息的 API,而接着提到的 trace 只 OpenTracing API 中的 链路跟踪 (trace)。

一般的日志记录有很大的毛病,就是每个办法记录一个日志,咱们无奈将一个流程中被调用的多个办法分割起来。当一个办法出现异常时,咱们很难晓得是哪个工作过程呈现的异样。咱们只能看到哪个办法呈现谬误,曾经它的调用者。

在 OpenTracing 中,Trace 是具备 Span(跨度) 的有向无环图。一个 Span 代表应用程序中实现某些工作的逻辑示意,每个 Span 都具备以下属性:

  • 操作名称
  • 开始工夫
  • 完结工夫

为了弄清楚,Trace 和 Span 是什么,OpenTracing 又是什么,请在 Nuget 中引入 OpenTracing。

编写 Hello 类如下:

   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)
        {
            // 创立一个 Span 并开始
            var spanBuilder = _tracer.BuildSpan("say-hello");
            // -------------------------------
            var span = spanBuilder.Start(); // |
            var str = $"Hello,{content}";   // |
            _logger.LogInformation(str);    // |
            span.Finish();                  // |
            // ---------------------------------
        }
    }

启动程序,并开始追踪:

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

在以上过程中,咱们应用了 OpenTracing API,上面是对于代码中一些元素的阐明:

  • ITracer 是一个链路追踪实例,BuildSpan() 能够创立其中一个 Span;
  • 每个 ISpan 都有一个操作名称,例如 say-hello;
  • 应用 Start() 开始一个 Span;应用 Finish() 完结一个 Span;
  • 跟踪程序会自动记录工夫戳;

当然,咱们运行下面的程序时,是没有呈现别的信息以及 UI 界面,这是因为 GlobalTracer.Instance 会返回一个无操作的 tracer。当咱们定义一个 Tracer 时,能够察看到链路追踪的过程。

在 Nuget 中,引入 Jaeger。

在 Program 中,增加一个动态函数,这个函数返回了一个自定义的 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();}

批改 Main 函数内容如下:

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

残缺代码:

  • https://gist.github.com/whuan…

上下文和跟踪性能

然而,日志间接输入 string 是很不敌对的,这时,咱们须要结构化日志。

当然,ISpan 提供了结构化日志的办法,咱们能够编写一个办法,用于格式化日志。

跟踪单个性能

在 Hello 类中增加以下代码:

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

另外,咱们还能够封装一个输入字符串信息的函数:

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

将 SayHello 办法改成:

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

改以上代码的起因是,不要在一个办法中糅合太多代码,能够尝试将一些代码复用,封装一个对立的代码。

然而,本来咱们只须要调用 SayHello 一个办法,这里一个办法会持续调用另外两个办法。本来是一个 Span,最初变成三个 Span。

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

注:0000000000000000 示意一个 Span 曾经完结。

长处:从代码上看,SayHello -> FormaString ,SayHello -> PrintHello,咱们能够清晰晓得调用链路;

毛病:从输入来看,Span reported 不同,咱们无奈中输入中判断三个函数的因果关系;

咱们不可能时时刻刻都盯着代码来看,运维人员和施行人员也不可能拿着代码去比照以及查找代码逻辑。

将多个跨度联合到一条轨迹中

ITracer 负责创立链路追踪,因而 ITracer 也提供了组合多个 Span 因果关系的 API。

应用办法如下:

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

咱们创立了一个 rootSpan,接着创立一个连续 rootSpan 的 sapn,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

​​输入程序为执行结束的程序,say-hello 是最初才执行实现的。

流传过程中的上下文

从什么代码中,大家发现,代码比拟麻烦,因为:

  • 要将 Span 对象作为第一个参数传递给每个函数;
  • 每个函数中加上简短的 try-finally{} 确保可能实现 Span

为此,OpenTracing API 提供了一种更好的办法,咱们能够防止将 Span 作为参数传递给代码,能够对立自行调用 _tracer 即可。

批改 FormatString 和 PrintHello 代码如下:

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

批改 SayHello 代码如下:

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

通过下面的代码,咱们实现去掉了那些烦人的代码。

  • StartActive() 代替 Start(),通过将其存储在线程本地存储中来使 span 处于“流动”状态;
  • StartActive() 返回一个 IScope 对象而不是一个对象 ISpan。IScope 是以后流动范畴的容器。咱们通过拜访流动跨度 scope.Span,一旦敞开了作用域,先前的作用域将成为以后作用域,从而从新激活以后线程中的先前流动范畴;
  • IScope 继承 IDisposable,它使咱们能够应用 using 语法;
  • StartActive(true) 通知 Scope,一旦它被解决,它就应该实现它所代表的范畴;
  • StartActive() 主动创立 ChildOf 对先前流动范畴的援用,因而咱们不用 AsChildOf() 显式应用 builder 办法;

如果运行此程序,咱们将看到所有三个报告的跨度都具备雷同的跟踪 ID。

分布式链路跟踪

在不同过程中跟踪

微服务将多个程序离开部署,每个程序提供不同的性能。在后面,咱们曾经学会了 OpenTracing 链路跟踪。接下来,咱们将把代码拆分,控制台程序将不再提供 FormatString 函数的实现,咱们应用 一个 Web 程序来实现 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-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/opentracin… 理解。

在 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 界面。

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。

通过 Jaeger,咱们能够剖析链路中函数的执行速度以及服务器性能状况。

总结

在试验中,咱们探索了 .NET Core 中日志和链路追踪的应用办法,通过在链路追踪中正当携带日志,以便应用剖析平台剖析链路的性能和状态,查找每个服务的性能状况和申请状态。


微软最有价值专家(MVP)

微软最有价值专家是微软公司授予第三方技术专业人士的一个寰球奖项。28 年来,世界各地的技术社区领导者,因其在线上和线下的技术社区中分享专业知识和教训而取得此奖项。

MVP 是通过严格筛选的专家团队,他们代表着技术最精湛且最具智慧的人,是对社区投入极大的激情并乐于助人的专家。MVP 致力于通过演讲、论坛问答、创立网站、撰写博客、分享视频、开源我的项目、组织会议等形式来帮忙别人,并最大水平地帮忙微软技术社区用户应用 Microsoft 技术。
更多详情请登录官方网站:
https://mvp.microsoft.com/zh-cn


这里有更多微软官网学习材料和技术文档,扫码获取免费版!
内容将会不定期更新哦!

退出移动版