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

目录
  • .NET Core 中的日志
    • 控制台输出
    • 非侵入式日志
    • Microsoft.Extensions.Logging
    • ILoggerFactory
    • ILoggerProvider
    • ILogger
    • Logging 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 中可以搜索到。

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

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.jb51.net/article/238462.htm

Microsoft.Extensions.Logging

有些公司无技术管理规范,不同的开发人员使用不同的日志框架,一个产品中可能有 .txtNLogSerilog等,并且没有同一的封装。

.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 的层次结构:

说实话,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 =&gt; 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-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、Debug

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

读者可以参考笔者的另一篇文章:

//www.jb51.net/article/242562.htm

输出到控制台:

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/whuanle/b57fe79c9996988db0a9b812f403f00e

上下文和跟踪功能

但是,日志直接输出 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 的 sapnrootSpan -> 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&lt;string, object&gt;
        {
            [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&lt;string, object&gt;
        {
            [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/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 界面。

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 ,我们可以分析链路中函数的执行速度以及服务器性能情况。

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

(0)

相关推荐

  • ASP.NET Core使用NLog记录日志

    目录 一.前言 二.使用NLog 1.引入NLog 2.添加配置文件 3.在控制器中使用 4.读取指定位置的配置文件 5.封装 一.前言 在所有的应用程序中,日志功能是不可或缺的模块,我们可以根据日志信息进行调试.查看产生的错误信息,在ASP.NET Core中我们可以使用log4net或者NLog日志组件来实现记录日志的功能,这里讲解如何在ASP.NET Core中使用NLog. 这里采用的是.NET Core 3.1创建应用程序. 那么什么是NLog呢? NLog是一个基于.NET平台编写的

  • Asp.Net Core轻松学之利用日志监视进行服务遥测详解

    前言 在 Net Core 2.2 中,官方文档表示,对 EventListener 这个日志监视类的内容进行了扩充,同时赋予了跟踪 CoreCLR 事件的权限:通过跟踪 CoreCLR 事件,比如通过跟踪 CoreCLR 事件,可以了解和收集到比如 GC,JIT,ThreadPool,intreop 这些运行时服务的行为:通过使用配置注入,我们将获得一种动态跟踪事件的能力. 1. EventListener 介绍 1.1 EventListener 中文直译为:事件侦听器 EventListe

  • ASP.NET Core开发教程之Logging利用NLog写日志文件

    前言 本文主要介绍了ASP.NET Core 开发-Logging 使用NLog 写日志文件的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧 NLog 可以适用于 .NET Core 和 ASP.NET Core . ASP.NET Core已经内置了日志支持,可以轻松输出到控制台. 学习Logging 组件的相关使用,使用NLog 将日志写入到文件记录. Logging 使用 新建一个 ASP.NET Core 项目,为了方便,我选择Web 应用程序,改身份验证 改为

  • ASP.NET Core扩展库之Http日志的使用详解

    最佳实践都告诉我们不要记录请求的详细日志,因为这有安全问题,但在实际开发中,请求的详细内容对于快速定位问题却是非常重要的,有时也是系统的强力证据.Xfrogcn.AspNetCore.Extensions扩展库提供了服务端和客户端的详细日志功能,通过配置可以开启. 服务端日志通过请求中间件来完成,中间件会以Trace级别记录请求和应答详情,以Debug级别记录请求耗时.服务的请求日志的名称为ServerRequest.Logger 要开启服务端详情日志,只需将扩展库配置中的ServerReque

  • ASP.NET Core扩展库之日志功能的使用详解

    上一篇我们对Xfrogcn.AspNetCore.Extensions扩展库功能进行了简单的介绍,从这一篇文章开始,我将逐步介绍扩展库中的核心功能.     日志作为非业务的通用领域基础功能,有非常多的技术实现,这些第三方库避免了我们花费时间去重复实现,不过,很多日志库配置复杂,不易于使用,入手较难,而有些库可能与ASP.NET Core的结合并不好.     如果我们没有对所使用的日志库进行详细了解,日志库也可能产生严重的问题,在我的开发生涯中,曾经遇到过多次因为日志库而导致的生产事故.  

  • Asp.Net Core用NLog记录日志操作方法

    需求 1.日志自动写入到数据库.写入到文件 2.appsettings.json数据库连接更改后,不需要去改NLog中的连接地址,启动网站或项目时自动检测变动然后去更改,以appsettings.json为准,保持同步. 3.写入日志时,除了NLog自带的字段,新增LogType自定义字段记录日志类型,例如网站日志.中间件日志等 4.统一的写日志方法,不用每次get一个logger对象(或依赖注入)来记日志 安装包 在nuget中安装NLog和NLog.Web.AspNetCore ,这两个是N

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

    目录 .NET Core 中的日志 控制台输出 非侵入式日志 Microsoft.Extensions.Logging ILoggerFactory ILoggerProvider ILogger Logging Providers 怎么使用 日志等级 Trace.Debug 链路跟踪 OpenTracing 上下文和跟踪功能 跟踪单个功能 将多个跨度合并到一条轨迹中 传播过程中的上下文 分布式链路跟踪 在不同进程中跟踪 在 ASP.NET Core 中跟踪 OpenTracing API 和

  • ASP.NET Core 2.0 WebApi全局配置及日志实例

    最新在将原来写的一些webSerivce转换为WebApi,直接就用了ASP.Net Core 2.0的框架,在使用中,发现的与原有的asp.net不同的地方,通过搜索已经慢慢解决,记录下来备用. 一.全局配置 在asp.net中,全局变更配置写在web.config中,如下所示 <?xml version="1.0"?> <configuration> <connectionStrings> <add name="conn"

  • ASP.NET Core使用NLog输出日志记录

    ASP.NET Core 中的日志记录 https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1 日志级别:Trace ->Debug-> Information ->Warning-> Error-> Critical 级别包含范围由大到小 ,如 Trace 就包含了所有信息. 基础用法 public class HomeController : Contro

  • 详解ASP.NET Core应用中如何记录和查看日志

    日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性.我们知道ASP.NET Core使用的是一个极具扩展性的日志系统,该系统由Logger.LoggerFactory和LoggerProvider这三个核心对象组成.我们可以通过简单的配置实现对LoggerFactory的定制,以及对LoggerProvider添加. 一. 配置LoggerFactory 我们在上面一节演示了一个展示ASP.NET Core默认注册服务的实例,细心的读者一定会看到显

  • ASP.NET Core使用Log4net实现日志记录功能

    一.安装Log4net 1.使用Nuget包进行安装 在依赖项上面右键,选择“管理NuGet程序包”,如下图所示: 在浏览界面输入log4net,然后点击安装,如下图所示: 2.使用程序包管理器控制台进行安装 使用Install-Package Log4net命令进行安装,如下图所示: 二.配置log4net使用的配置文件 配置文件如下: <?xml version="1.0" encoding="utf-8" ?> <configuration&

随机推荐