網(wǎng)站首頁 編程語言 正文
程序記錄的日志一般有兩種作用,故障排查、顯式程序運(yùn)行狀態(tài),當(dāng)程序發(fā)生故障時(shí),我們可以通過日志定位問題,日志可以給我們留下排查故障的依據(jù)。很多時(shí)候,往往會認(rèn)為日志記錄非常簡單,例如很多程序只是?try-catch{}
,直接輸出到?.txt
,但是這些日志往往無法起到幫助定位問題的作用,甚至日志充斥了大量垃圾內(nèi)容;日志內(nèi)容全靠人眼一行行掃描,或者?Ctrl+F
?搜索,無法高效率審查日志;日志單純輸出到文本文件中,沒有很好地管理日志。
接下來,我們將一步步學(xué)習(xí)日志的編寫技巧,以及 OpenTracing API 、Jaeger 分布式鏈路跟蹤的相關(guān)知識。
.NET Core 中的日志
控制臺輸出
最簡單的日志,就是控制臺輸出,利用?Console.WriteLine()
?函數(shù)直接輸出信息。
下面時(shí)一個(gè)簡單的信息輸出,當(dāng)程序調(diào)用?SayHello
?函數(shù)時(shí),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(); } }
非侵入式日志
通過控制臺,我們可以看到,為了記錄日志,我們必須在函數(shù)內(nèi)編寫輸入日志的代碼,優(yōu)缺點(diǎn)這些就不多說了,我們可以通過 AOP 框架,實(shí)現(xiàn)切面編程,同一記錄日志。
這里可以使用筆者開源的 CZGL.AOP 框架,Nuget 中可以搜索到。
編寫統(tǒng)一的切入代碼,這些代碼將在函數(shù)被調(diào)用時(shí)執(zhí)行。
Before
?會在被代理的方法執(zhí)行前或被代理的屬性調(diào)用時(shí)生效,你可以通過?AspectContext
?上下文,獲取、修改傳遞的參數(shù)。
After 在方法執(zhí)行后或?qū)傩哉{(diào)用時(shí)生效,你可以通過上下文獲取、修改返回值。
public class LogAttribute : ActionAttribute { public override void Before(AspectContext context) { Console.WriteLine($"{context.MethodInfo.Name} 函數(shù)被執(zhí)行前"); } public override object After(AspectContext context) { Console.WriteLine($"{context.MethodInfo.Name} 函數(shù)被執(zhí)行后"); return null; } }
改造 Hello 類,代碼如下:
[Interceptor] public class Hello { [Log] public virtual void SayHello(string content) { var str = $"Hello,{content}"; Console.WriteLine(str); } }
然后創(chuàng)建代理類型:
static void Main(string[] args) { Hello hello = AopInterceptor.CreateProxyOfClass<Hello>(); hello.SayHello("any one"); Console.Read(); }
啟動程序,會輸出:
SayHello 函數(shù)被執(zhí)行前 Hello,any one SayHello 函數(shù)被執(zhí)行后
你完全不需要擔(dān)心 AOP 框架會給你的程序帶來性能問題,因?yàn)?CZGL.AOP 框架采用 EMIT 編寫,并且自帶緩存,當(dāng)一個(gè)類型被代理過,之后無需重復(fù)生成。
CZGL.AOP 可以通過 .NET Core 自帶的依賴注入框架和 Autofac 結(jié)合使用,自動代理 CI 容器中的服務(wù)。這樣不需要?AopInterceptor.CreateProxyOfClass
?手動調(diào)用代理接口。
CZGL.AOP 代碼是開源的,可以參考筆者另一篇博文:
https://www.jb51.net/article/238462.htm
Microsoft.Extensions.Logging
有些公司無技術(shù)管理規(guī)范,不同的開發(fā)人員使用不同的日志框架,一個(gè)產(chǎn)品中可能有?.txt
、NLog
、Serilog
等,并且沒有同一的封裝。
.NET Core 中的日志組件有很多,但是流行的日志框架基本都會實(shí)現(xiàn)?Microsoft.Extensions.Logging.Abstractions
,因此我們可以學(xué)習(xí)Microsoft.Extensions.Logging
?。Microsoft.Extensions.Logging.Abstractions
?是官方對日志組件的抽象,如果一個(gè)日志組件并不支持?Microsoft.Extensions.Logging.Abstractions
?那么這個(gè)組件很容易跟項(xiàng)目糅合的,后續(xù)難以模塊化以及降低耦合程度。
Microsoft.Extensions.Logging
?軟件包中包含 Logging API ,這些 Logging API 不能獨(dú)立運(yùn)行。它與一個(gè)或多個(gè)日志記錄提供程序一起使用,這些日志記錄提供程序?qū)⑷罩敬鎯蝻@示到特定輸出,例如 Console, Debug, TraceListeners。
下圖是 .NET Core 中 Loggin API 的層次結(jié)構(gòu):
說實(shí)話,Microsoft.Extensions.Logging
?剛開始是學(xué)著很懵,配置感覺很復(fù)雜。因此,有一張清晰的結(jié)構(gòu)圖很重要,可以幫助大家理解里面的 Logging API。
ILoggerFactory
.NET Core 中很多標(biāo)準(zhǔn)接口都實(shí)踐了工廠模式的思想,ILoggerFactory 正是工廠模式的接口,而 LoggerFactory 是工廠模式的實(shí)現(xiàn)。
其定義如下:
public interface ILoggerFactory : IDisposable { ILogger CreateLogger(string categoryName); void AddProvider(ILoggerProvider provider); }
ILoggerFactory 工廠接口的作用是創(chuàng)建一個(gè) ILogger 類型的實(shí)例,即?CreateLogger
?接口。
ILoggerProvider
通過實(shí)現(xiàn)ILoggerProvider
接口可以創(chuàng)建自己的日志記錄提供程序,表示可以創(chuàng)建 ILogger 實(shí)例的類型。
其定義如下:
public interface ILoggerProvider : IDisposable { ILogger CreateLogger(string categoryName); }
ILogger
ILogger 接口提供了將日志記錄到基礎(chǔ)存儲的方法,其定義如下:
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 將日志顯示或存儲到特定介質(zhì),例如 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等。
這些日志提供程序有很多,我們不必細(xì)究;如果一個(gè)日志組件,不提供兼容?Microsoft.Extensions.Logging
?的實(shí)現(xiàn),那么根本不應(yīng)該引入他。
實(shí)際上,很多程序是直接?File.Write("Log.txt")
?,這種產(chǎn)品質(zhì)量能好到哪里去呢?
怎么使用
前面,介紹了?Microsoft.Extensions.Logging
?的組成,這里將學(xué)習(xí)如何使用 Logging Provider 輸入日志。
起碼提到,它只是提供了一個(gè) Logging API,因此為了輸出日志,我們必須選擇合適的 Logging Provider 程序,這里我們選擇
Microsoft.Extensions.Logging.Console
,請?jiān)?Nuget 中引用這個(gè)包。
下圖是 Logging Provider 和 ConsoleLogger 結(jié)合使用的結(jié)構(gòu)圖:
從常規(guī)方法來弄,筆者發(fā)現(xiàn),沒法配置呀。。。
ConsoleLoggerProvider consoleLoggerProvider = new ConsoleLoggerProvider( new OptionsMonitor<ConsoleLoggerOptions>( new OptionsFactory<ConsoleLoggerOptions>( new IEnumerable<IConfigureOptions<TOptions>(... ... ...))));
所以只能使用以下代碼快速創(chuàng)建工廠:
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());
當(dāng)然工廠中可以添加其它日志提供程序,示例:
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder.AddSimpleConsole(...) .AddFile(...) .Add()... );
然后獲取 ILogger 實(shí)例:
ILogger logger = loggerFactory.CreateLogger<Program>();
記錄日志:
logger.LogInformation("記錄信息");
日志等級
Logging API 中,規(guī)定了 7 種日志等級,其定義如下:
public enum LogLevel { Debug = 1, Verbose = 2, Information = 3, Warning = 4, Error = 5, Critical = 6, None = int.MaxValue }
我們可以通過 ILogger 中的函數(shù),輸出以下幾種等級的日志:
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.");
關(guān)于?Microsoft.Extensions.Logging
?這里就不再贅述。
Trace、Debug
Debug 、Trace 這兩個(gè)類的命名空間為?System.Diagnostics
,Debug 、Trace 提供一組有助于調(diào)試代碼的方法和屬性。
讀者可以參考筆者的另一篇文章:
//www.jb51.net/article/242562.htm
輸出到控制臺:
Trace.Listeners.Add(new TextWriterTraceListener(Console.Out)); Debug.WriteLine("信息");
鏈路跟蹤
鏈路追蹤可以幫助開發(fā)者快速定位分布式應(yīng)用架構(gòu)下的性能瓶頸,提高微服務(wù)時(shí)代的開發(fā)診斷效率。
OpenTracing
前面提到的 Trace 、Debug 是 .NET Core 中提供給開發(fā)者用于診斷程序和輸出信息的 API,而接著提到的 trace 只 OpenTracing API 中的 鏈路跟蹤(trace)。
普通的日志記錄有很大的缺點(diǎn),就是每個(gè)方法記錄一個(gè)日志,我們無法將一個(gè)流程中被調(diào)用的多個(gè)方法聯(lián)系起來。當(dāng)一個(gè)方法出現(xiàn)異常時(shí),我們很難知道是哪個(gè)任務(wù)過程出現(xiàn)的異常。我們只能看到哪個(gè)方法出現(xiàn)錯(cuò)誤,已經(jīng)它的調(diào)用者。
在 OpenTracing 中,Trace 是具有 Span(跨度) 的有向無環(huán)圖。一個(gè) Span 代表應(yīng)用程序中完成某些工作的邏輯表示,每個(gè) Span 都具有以下屬性:
- 操作名稱
- 開始時(shí)間
- 結(jié)束時(shí)間
為了弄清楚,Trace 和 Span 是什么,OpenTracing 又是什么,請?jiān)?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) { // 創(chuàng)建一個(gè) 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,下面是關(guān)于代碼中一些元素的說明:
- ITracer 是一個(gè)鏈路追蹤實(shí)例,BuildSpan() 可以創(chuàng)建其中一個(gè) Span;
- 每個(gè) ISpan 都有一個(gè)操作名稱,例如?
say-hello
; - 使用?
Start()
?開始一個(gè) Span;使用?Finish()
?結(jié)束一個(gè) Span; - 跟蹤程序會自動記錄時(shí)間戳;
當(dāng)然,我們運(yùn)行上面的程序時(shí),是沒有出現(xiàn)別的信息以及 UI 界面,這是因?yàn)?GlobalTracer.Instance
?會返回一個(gè)無操作的 tracer。當(dāng)我們定義一個(gè) Tracer 時(shí),可以觀察到鏈路追蹤的過程。
在 Nuget 中,引入?Jaeger
。
在 Program 中,添加一個(gè)靜態(tài)函數(shù),這個(gè)函數(shù)返回了一個(gè)自定義的 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 函數(shù)內(nèi)容如下:
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 是很不友好的,這時(shí),我們需要結(jié)構(gòu)化日志。
當(dāng)然,ISpan 提供了結(jié)構(gòu)化日志的方法,我們可以編寫一個(gè)方法,用于格式化日志。
跟蹤單個(gè)功能
在 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(); } }
另外,我們還可以封裝一個(gè)輸出字符串信息的函數(shù):
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(); }
改以上代碼的原因是,不要在一個(gè)方法中糅合太多代碼,可以嘗試將一些代碼復(fù)用,封裝一個(gè)統(tǒng)一的代碼。
但是,原本我們只需要調(diào)用 SayHello 一個(gè)方法,這里一個(gè)方法會繼續(xù)調(diào)用另外兩個(gè)方法。原本是一個(gè) Span,最后變成三個(gè) 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
?表示一個(gè) Span 已經(jīng)結(jié)束。
優(yōu)點(diǎn):從代碼上看,SayHello -> FormaString ,SayHello -> PrintHello,我們可以清晰知道調(diào)用鏈路;
缺點(diǎn):從輸出來看,Span reported 不同,我們無法中輸出中判斷三個(gè)函數(shù)的因果關(guān)系;
我們不可能時(shí)時(shí)刻刻都盯著代碼來看,運(yùn)維人員和實(shí)施人員也不可能拿著代碼去對比以及查找代碼邏輯。
將多個(gè)跨度合并到一條軌跡中
ITracer 負(fù)責(zé)創(chuàng)建鏈路追蹤,因此 ITracer 也提供了組合多個(gè) Span 因果關(guān)系的 API。
使用方法如下:
var rootSapn = _tracer.BuildSpan("say-hello"); // A var span = _tracer.BuildSpan("format-string").AsChildOf(rootSpan).Start(); // B // A -> B
我們創(chuàng)建了一個(gè) rootSpan ,接著創(chuàng)建一個(gè)延續(xù) 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
輸出順序?yàn)閳?zhí)行完畢的順序,say-hello 是最后才執(zhí)行完成的。
傳播過程中的上下文
從什么代碼中,大家發(fā)現(xiàn),代碼比較麻煩,因?yàn)椋?/p>
- 要將 Span 對象作為第一個(gè)參數(shù)傳遞給每個(gè)函數(shù);
- 每個(gè)函數(shù)中加上冗長的?
try-finally{}
?確保能夠完成 Span
為此, OpenTracing API 提供了一種更好的方法,我們可以避免將 Span 作為參數(shù)傳遞給代碼,可以統(tǒng)一自行調(diào)用 _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); }
通過上面的代碼,我們實(shí)現(xiàn)去掉了那些煩人的代碼。
-
StartActive()
?代替Start()
,通過將其存儲在線程本地存儲中來使 span 處于“活動”狀態(tài); -
StartActive()
?返回一個(gè)IScope
對象而不是一個(gè)對象ISpan
。IScope是當(dāng)前活動范圍的容器。我們通過訪問活動跨度scope.Span
,一旦關(guān)閉了作用域,先前的作用域?qū)⒊蔀楫?dāng)前作用域,從而重新激活當(dāng)前線程中的先前活動范圍; -
IScope
?繼承?IDisposable
,它使我們可以使用using
語法; -
StartActive(true)
告訴Scope,一旦它被處理,它就應(yīng)該完成它所代表的范圍; -
StartActive()
自動創(chuàng)建?ChildOf
?對先前活動范圍的引用,因此我們不必AsChildOf()
顯式使用 builder 方法;
如果運(yùn)行此程序,我們將看到所有三個(gè)報(bào)告的跨度都具有相同的跟蹤ID。
分布式鏈路跟蹤
在不同進(jìn)程中跟蹤
微服務(wù)將多個(gè)程序分開部署,每個(gè)程序提供不同的功能。在前面,我們已經(jīng)學(xué)會了 OpenTracing 鏈路跟蹤。接下來,我們將把代碼拆分,控制臺程序?qū)⒉辉偬峁?FormatString 函數(shù)的實(shí)現(xiàn),我們使用 一個(gè) Web 程序來實(shí)現(xiàn) FormatString 服務(wù)。
創(chuàng)建一個(gè) ASP.NET Core 應(yīng)用程序,在模板中選擇帶有視圖模型控制器的模板。
添加一個(gè)?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 應(yīng)用將作為微服務(wù)中的其中一個(gè)服務(wù),而這個(gè)服務(wù)只有一個(gè) API ,這個(gè) API 很簡單,就是提供字符串的格式化。你也可以編寫其它 API 來提供服務(wù)。
將 Program 的 CreateHostBuilder 改一下,我們固定這個(gè)服務(wù)的 端口。
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
?可以設(shè)置標(biāo)簽,我們?yōu)楸敬握埱蟮?Web 的 Span,設(shè)置一個(gè)標(biāo)簽,并且存儲請求的 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));
這些配置規(guī)范,可以到?https://github.com/opentracing/specification/blob/master/semantic_conventions.md?了解。
在 ASP.NET Core 中跟蹤
在上面,我們實(shí)現(xiàn)了 Client 在不同進(jìn)程的追蹤,但是還沒有實(shí)現(xiàn)在 Server 中跟蹤,我們可以修改 Startup.cs 中的代碼,將以下代碼替換進(jìn)去:
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?}"); }); } } }
這樣不同的進(jìn)程各種都可以實(shí)現(xiàn)追蹤。
OpenTracing API 和 Jaeger
OpenTracing 是開放式分布式追蹤規(guī)范,OpenTracing API 是一致,可表達(dá),與供應(yīng)商無關(guān)的API,用于分布式跟蹤和上下文傳播。
Jaeger 是 Uber 開源的分布式跟蹤系統(tǒng)。
OpenTracing 的客戶端庫以及規(guī)范,可以到 Github 中查看:https://github.com/opentracing/
詳細(xì)的介紹可以自行查閱資料。
這里我們需要部署一個(gè) Jaeger 實(shí)例,以供微服務(wù)以及事務(wù)跟蹤學(xué)習(xí)需要。
使用 Docker 部署很簡單,只需要執(zhí)行下面一條命令即可:
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 發(fā)送 proto 格式數(shù)據(jù) 14268 http 直接接受客戶端數(shù)據(jù) 14269 http 健康檢查 Query 16686 http jaeger的UI前端 16687 http 健康檢查
接下來我們將學(xué)習(xí)如何通過代碼,將數(shù)據(jù)上傳到 Jaeger 中。
鏈路追蹤實(shí)踐
要注意,數(shù)據(jù)上傳到 Jaeger ,上傳的是 Span,是不會上傳日志內(nèi)容的。
繼續(xù)使用上面的控制臺程序,Nuget 中添加?Jaeger.Senders.Grpc
?包。
我們可以通過 UDP (6831端口)和 gRPC(14250) 端口將數(shù)據(jù)上傳到 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
,然后點(diǎn)擊底下的?Find Traces
。
通過 Jaeger ,我們可以分析鏈路中函數(shù)的執(zhí)行速度以及服務(wù)器性能情況。
原文鏈接:https://www.cnblogs.com/whuanle/p/14256858.html
相關(guān)推薦
- 2022-07-20 python鼠標(biāo)繪圖附代碼_python
- 2022-07-19 Android通過交互實(shí)現(xiàn)貝塞爾曲線的繪制_Android
- 2022-06-30 C語言從基礎(chǔ)到進(jìn)階全面講解數(shù)組_C 語言
- 2022-10-19 C++模板編程特性之移動語義_C 語言
- 2022-09-13 C#?wpf使用ListBox實(shí)現(xiàn)尺子控件的示例代碼_C#教程
- 2022-07-01 Android?TextView跑馬燈實(shí)現(xiàn)原理及方法實(shí)例_Android
- 2021-12-11 C++嵌入式內(nèi)存管理詳情_C 語言
- 2023-01-26 C#實(shí)現(xiàn)批量Word轉(zhuǎn)換Html的示例代碼_C#教程
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支