最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

ASP.Net?Core中的日志與分布式鏈路追蹤

 更新時間:2022年03月30日 09:39:19   作者:癡者工良  
這篇文章介紹了ASP.Net?Core中的日志與分布式鏈路追蹤,文中通過示例代碼介紹的非常詳細。對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下<BR>

程序記錄的日志一般有兩種作用,故障排查、顯式程序運行狀態(tài),當程序發(fā)生故障時,我們可以通過日志定位問題,日志可以給我們留下排查故障的依據(jù)。很多時候,往往會認為日志記錄非常簡單,例如很多程序只是 try-catch{},直接輸出到 .txt,但是這些日志往往無法起到幫助定位問題的作用,甚至日志充斥了大量垃圾內(nèi)容;日志內(nèi)容全靠人眼一行行掃描,或者 Ctrl+F 搜索,無法高效率審查日志;日志單純輸出到文本文件中,沒有很好地管理日志。

接下來,我們將一步步學習日志的編寫技巧,以及 OpenTracing API 、Jaeger 分布式鏈路跟蹤的相關(guān)知識。

.NET Core 中的日志

控制臺輸出

最簡單的日志,就是控制臺輸出,利用 Console.WriteLine() 函數(shù)直接輸出信息。

下面時一個簡單的信息輸出,當程序調(diào)用 SayHello 函數(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)缺點這些就不多說了,我們可以通過 AOP 框架,實現(xiàn)切面編程,同一記錄日志。

這里可以使用筆者開源的 CZGL.AOP 框架,Nuget 中可以搜索到。

czgl.aop

編寫統(tǒng)一的切入代碼,這些代碼將在函數(shù)被調(diào)用時執(zhí)行。

Before 會在被代理的方法執(zhí)行前或被代理的屬性調(diào)用時生效,你可以通過 AspectContext 上下文,獲取、修改傳遞的參數(shù)。

After 在方法執(zhí)行后或?qū)傩哉{(diào)用時生效,你可以通過上下文獲取、修改返回值。

    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í)行后

你完全不需要擔心 AOP 框架會給你的程序帶來性能問題,因為 CZGL.AOP 框架采用 EMIT 編寫,并且自帶緩存,當一個類型被代理過,之后無需重復(fù)生成。

CZGL.AOP 可以通過 .NET Core 自帶的依賴注入框架和 Autofac 結(jié)合使用,自動代理 CI 容器中的服務(wù)。這樣不需要 AopInterceptor.CreateProxyOfClass 手動調(diào)用代理接口。

CZGL.AOP 代碼是開源的,可以參考筆者另一篇博文:

http://m.fzitv.net/article/238462.htm

Microsoft.Extensions.Logging

有些公司無技術(shù)管理規(guī)范,不同的開發(fā)人員使用不同的日志框架,一個產(chǎn)品中可能有 .txt、NLogSerilog等,并且沒有同一的封裝。

.NET Core 中的日志組件有很多,但是流行的日志框架基本都會實現(xiàn) Microsoft.Extensions.Logging.Abstractions,因此我們可以學習Microsoft.Extensions.Logging 。Microsoft.Extensions.Logging.Abstractions 是官方對日志組件的抽象,如果一個日志組件并不支持 Microsoft.Extensions.Logging.Abstractions 那么這個組件很容易跟項目糅合的,后續(xù)難以模塊化以及降低耦合程度。

Microsoft.Extensions.Logging 軟件包中包含 Logging API ,這些 Logging API 不能獨立運行。它與一個或多個日志記錄提供程序一起使用,這些日志記錄提供程序?qū)⑷罩敬鎯蝻@示到特定輸出,例如 Console, Debug, TraceListeners。

下圖是 .NET Core 中 Loggin API 的層次結(jié)構(gòu):

說實話,Microsoft.Extensions.Logging 剛開始是學著很懵,配置感覺很復(fù)雜。因此,有一張清晰的結(jié)構(gòu)圖很重要,可以幫助大家理解里面的 Logging API。

ILoggerFactory

.NET Core 中很多標準接口都實踐了工廠模式的思想,ILoggerFactory 正是工廠模式的接口,而 LoggerFactory 是工廠模式的實現(xiàn)。

其定義如下:

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

ILoggerFactory 工廠接口的作用是創(chuàng)建一個 ILogger 類型的實例,即 CreateLogger 接口。

ILoggerProvider

通過實現(xiàn)ILoggerProvider接口可以創(chuàng)建自己的日志記錄提供程序,表示可以創(chuàng)建 ILogger 實例的類型。

其定義如下:

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等。

這些日志提供程序有很多,我們不必細究;如果一個日志組件,不提供兼容 Microsoft.Extensions.Logging 的實現(xiàn),那么根本不應(yīng)該引入他。

實際上,很多程序是直接 File.Write("Log.txt") ,這種產(chǎn)品質(zhì)量能好到哪里去呢?

怎么使用

前面,介紹了 Microsoft.Extensions.Logging 的組成,這里將學習如何使用 Logging Provider 輸入日志。

起碼提到,它只是提供了一個 Logging API,因此為了輸出日志,我們必須選擇合適的 Logging Provider 程序,這里我們選擇

Microsoft.Extensions.Logging.Console,請在 Nuget 中引用這個包。

下圖是 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());

當然工廠中可以添加其它日志提供程序,示例:

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

然后獲取 ILogger 實例:

  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 這兩個類的命名空間為 System.Diagnostics,Debug 、Trace 提供一組有助于調(diào)試代碼的方法和屬性。

讀者可以參考筆者的另一篇文章:

//m.fzitv.net/article/242562.htm

輸出到控制臺:

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

鏈路跟蹤

鏈路追蹤可以幫助開發(fā)者快速定位分布式應(yīng)用架構(gòu)下的性能瓶頸,提高微服務(wù)時代的開發(fā)診斷效率。

OpenTracing

前面提到的 Trace 、Debug 是 .NET Core 中提供給開發(fā)者用于診斷程序和輸出信息的 API,而接著提到的 trace 只 OpenTracing API 中的 鏈路跟蹤(trace)。

普通的日志記錄有很大的缺點,就是每個方法記錄一個日志,我們無法將一個流程中被調(diào)用的多個方法聯(lián)系起來。當一個方法出現(xiàn)異常時,我們很難知道是哪個任務(wù)過程出現(xiàn)的異常。我們只能看到哪個方法出現(xiàn)錯誤,已經(jīng)它的調(diào)用者。

在 OpenTracing 中,Trace 是具有 Span(跨度) 的有向無環(huán)圖。一個 Span 代表應(yīng)用程序中完成某些工作的邏輯表示,每個 Span 都具有以下屬性:

  • 操作名稱
  • 開始時間
  • 結(jié)束時間

為了弄清楚,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)
        {
            // 創(chuàng)建一個 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 是一個鏈路追蹤實例,BuildSpan() 可以創(chuàng)建其中一個 Span;
  • 每個 ISpan 都有一個操作名稱,例如 say-hello;
  • 使用 Start() 開始一個 Span;使用 Finish() 結(jié)束一個 Span;
  • 跟蹤程序會自動記錄時間戳;

當然,我們運行上面的程序時,是沒有出現(xiàn)別的信息以及 UI 界面,這是因為 GlobalTracer.Instance 會返回一個無操作的 tracer。當我們定義一個 Tracer 時,可以觀察到鏈路追蹤的過程。

在 Nuget 中,引入 Jaeger。

在 Program 中,添加一個靜態(tài)函數(shù),這個函數(shù)返回了一個自定義的 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 是很不友好的,這時,我們需要結(jié)構(gòu)化日志。

當然,ISpan 提供了結(jié)構(gòu)化日志的方法,我們可以編寫一個方法,用于格式化日志。

跟蹤單個功能

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

另外,我們還可以封裝一個輸出字符串信息的函數(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();
        }

改以上代碼的原因是,不要在一個方法中糅合太多代碼,可以嘗試將一些代碼復(fù)用,封裝一個統(tǒng)一的代碼。

但是,原本我們只需要調(diào)用 SayHello 一個方法,這里一個方法會繼續(xù)調(diào)用另外兩個方法。原本是一個 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 已經(jīng)結(jié)束。

優(yōu)點:從代碼上看,SayHello -> FormaString ,SayHello -> PrintHello,我們可以清晰知道調(diào)用鏈路;

缺點:從輸出來看,Span reported 不同,我們無法中輸出中判斷三個函數(shù)的因果關(guān)系;

我們不可能時時刻刻都盯著代碼來看,運維人員和實施人員也不可能拿著代碼去對比以及查找代碼邏輯。

將多個跨度合并到一條軌跡中

ITracer 負責創(chuàng)建鏈路追蹤,因此 ITracer 也提供了組合多個 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)建了一個 rootSpan ,接著創(chuàng)建一個延續(xù) 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

輸出順序為執(zhí)行完畢的順序,say-hello 是最后才執(zhí)行完成的。

傳播過程中的上下文

從什么代碼中,大家發(fā)現(xiàn),代碼比較麻煩,因為:

  • 要將 Span 對象作為第一個參數(shù)傳遞給每個函數(shù);
  • 每個函數(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);
}

通過上面的代碼,我們實現(xiàn)去掉了那些煩人的代碼。

  • StartActive() 代替Start(),通過將其存儲在線程本地存儲中來使 span 處于“活動”狀態(tài);
  • StartActive() 返回一個IScope對象而不是一個對象ISpan。IScope是當前活動范圍的容器。我們通過訪問活動跨度scope.Span,一旦關(guān)閉了作用域,先前的作用域?qū)⒊蔀楫斍白饔糜颍瑥亩匦录せ町斍熬€程中的先前活動范圍;
  • IScope 繼承 IDisposable,它使我們可以使用using語法;
  • StartActive(true)告訴Scope,一旦它被處理,它就應(yīng)該完成它所代表的范圍;
  • StartActive()自動創(chuàng)建 ChildOf 對先前活動范圍的引用,因此我們不必AsChildOf()顯式使用 builder 方法;

如果運行此程序,我們將看到所有三個報告的跨度都具有相同的跟蹤ID。

分布式鏈路跟蹤

在不同進程中跟蹤

微服務(wù)將多個程序分開部署,每個程序提供不同的功能。在前面,我們已經(jīng)學會了 OpenTracing 鏈路跟蹤。接下來,我們將把代碼拆分,控制臺程序?qū)⒉辉偬峁?FormatString 函數(shù)的實現(xiàn),我們使用 一個 Web 程序來實現(xiàn) FormatString 服務(wù)。

創(chuàng)建一個 ASP.NET Core 應(yīng)用程序,在模板中選擇帶有視圖模型控制器的模板。

添加一個 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ù)中的其中一個服務(wù),而這個服務(wù)只有一個 API ,這個 API 很簡單,就是提供字符串的格式化。你也可以編寫其它 API 來提供服務(wù)。

將 Program 的 CreateHostBuilder 改一下,我們固定這個服務(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è)置標簽,我們?yōu)楸敬握埱蟮?Web 的 Span,設(shè)置一個標簽,并且存儲請求的 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 中跟蹤

在上面,我們實現(xiàn)了 Client 在不同進程的追蹤,但是還沒有實現(xiàn)在 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?}");
            });
        }
    }
}

這樣不同的進程各種都可以實現(xiàn)追蹤。

OpenTracing API 和 Jaeger

OpenTracing 是開放式分布式追蹤規(guī)范,OpenTracing API 是一致,可表達,與供應(yīng)商無關(guān)的API,用于分布式跟蹤和上下文傳播。

Jaeger 是 Uber 開源的分布式跟蹤系統(tǒng)。

OpenTracing 的客戶端庫以及規(guī)范,可以到 Github 中查看:https://github.com/opentracing/

詳細的介紹可以自行查閱資料。

這里我們需要部署一個 Jaeger 實例,以供微服務(wù)以及事務(wù)跟蹤學習需要。

使用 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 健康檢查

接下來我們將學習如何通過代碼,將數(shù)據(jù)上傳到 Jaeger 中。

鏈路追蹤實踐

要注意,數(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,然后點擊底下的 Find Traces。

search

hello-world

通過 Jaeger ,我們可以分析鏈路中函數(shù)的執(zhí)行速度以及服務(wù)器性能情況。

以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論

嘉义县| 泸定县| 庆元县| 宁远县| 绿春县| 大关县| 泽州县| 六盘水市| 乐清市| 岳池县| 婺源县| 奉化市| 宜良县| 通渭县| 乌鲁木齐县| 伊吾县| 平武县| 正镶白旗| 合江县| 小金县| 白河县| 梅河口市| 景宁| 凉山| 民和| 盐源县| 吉水县| 双流县| 胶南市| 青阳县| 沧州市| 龙陵县| 武穴市| 迁安市| 宝鸡市| 石家庄市| 宁安市| 汾西县| 汉沽区| 荆门市| 麻阳|