ASP.Net|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.SayHello("any one"); Console.Read(); }
启动程序,会输出:
SayHello 函数被执行前Hello,any oneSayHello 函数被执行后
你完全不需要担心 AOP 框架会给你的程序带来性能问题,因为 CZGL.AOP 框架采用 EMIT 编写,并且自带缓存,当一个类型被代理过,之后无需重复生成。
CZGL.AOP 可以通过 .NET Core 自带的依赖注入框架和 Autofac 结合使用,自动代理 CI 容器中的服务。这样不需要
AopInterceptor.CreateProxyOfClass
手动调用代理接口。CZGL.AOP 代码是开源的,可以参考笔者另一篇博文:
https://www.jb51.net/article/238462.htm
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 的层次结构:
文章图片
说实话,
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(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter); bool IsEnabled(LogLevel logLevel); IDisposable BeginScope (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
这些日志提供程序有很多,我们不必细究;如果一个日志组件,不提供兼容
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(new OptionsFactory (new IEnumerable (... ... ...))));
所以只能使用以下代码快速创建工厂:
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();
记录日志:
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
这里就不再赘述。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 都具有以下属性:
- 操作名称
- 开始时间
- 结束时间
OpenTracing
。编写 Hello 类如下:
public class Hello{private readonly ITracer _tracer; private readonly ILogger _logger; public Hello(ITracer tracer, ILoggerFactory loggerFactory){_tracer = tracer; _logger = loggerFactory.CreateLogger(); }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; - 跟踪程序会自动记录时间戳;
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{[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-stringinfo: ConsoleApp1.Hello[0]Hello, This trace!info: Jaeger.Reporters.LoggingReporter[0]Span reported: cebd31b028a27882:cebd31b028a27882:0000000000000000:1 - print-helloinfo: 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"); // Avar 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-stringinfo: ConsoleApp1.Hello[0]Hello, This trace!info: Jaeger.Reporters.LoggingReporter[0]Span reported: 2f2c7b36f4f6b0b9:9824227a41539786:2f2c7b36f4f6b0b9:1 - print-helloinfo: Jaeger.Reporters.LoggingReporter[0]Span reported: 2f2c7b36f4f6b0b9:2f2c7b36f4f6b0b9:0000000000000000:1 - say-hello
Span reported: 2f2c7b36f4f6b0b9
输出顺序为执行完毕的顺序,say-hello 是最后才执行完成的。
传播过程中的上下文 从什么代码中,大家发现,代码比较麻烦,因为:
- 要将 Span 对象作为第一个参数传递给每个函数;
- 每个函数中加上冗长的
try-finally{}
确保能够完成 Span
修改
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{[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{[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 方法;
分布式链路跟踪
在不同进程中跟踪
微服务将多个程序分开部署,每个程序提供不同的功能。在前面,我们已经学会了 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
中删除 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{[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(); _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 LazyTracer = new Lazy (() =>{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 的端口作用如下:
Collector14250 tcpgRPC 发送 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(); 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 ,我们可以分析链路中函数的执行速度以及服务器性能情况。
【ASP.Net|ASP.Net Core中的日志与分布式链路追踪】以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
推荐阅读
- 聊聊.Net|聊聊.Net,Core配置Nlog.md的问题
- Python中的if嵌套【语法、实例、执行流程】
- 为什么研发团队中的管理者往往占比过高,研发管理的效果提升并不明显()
- 局部变量表中的起始PC
- 视觉Transformer中的输入可视化方法
- 减少计算、简化架构——TDengine在灌区信息化平台中的应用
- 机器使用成本下降|机器使用成本下降 50%,TDengine 在同程旅行基础监控中的实践
- 知识点深掘|Java编码中的设计模式
- Ant|Ant design vue中的联动选择取消操作
- 超分算法在 WebRTC 高清视频传输弱网优化中的应用