Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Example Analysis of Log and distributed Link tracing in ASP.Net Core

2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/01 Report--

This article mainly introduces the ASP.Net Core log and distributed link tracking example analysis, has a certain reference value, interested friends can refer to, I hope you can learn a lot after reading this article, let the editor take you to know it.

Log console output in .NET Core

The simplest log is the console output, which uses the Console.WriteLine () function to output information directly.

Here is a simple message output that SayHello prints when the program calls the SayHello function.

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 () }} non-invasive log

Through the console, we can see that in order to record the log, we have to write the input log code in the function, not to mention the advantages and disadvantages, we can achieve aspect programming through the AOP framework, recording the same log.

Here you can use the author's open source CZGL.AOP framework, which can be found in Nuget.

Write uniform cut-in code that will be executed when the function is called.

Before takes effect before the proxy method is executed or when the proxy property is called. You can obtain and modify the passed parameters through the AspectContext context.

After takes effect after the method is executed or when the property is called, and you can get and modify the return value through the context.

Public class LogAttribute: ActionAttribute {public override void Before (AspectContext context) {Console.WriteLine (before $"{context.MethodInfo.Name} function is executed");} public override object After (AspectContext context) {Console.WriteLine (after $"{context.MethodInfo.Name} function is executed"); return null;}}

Modify the Hello class with the following code:

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

Then create the proxy type:

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

Start the program and output:

Before the SayHello function is executed, after the Hello,any oneSayHello function is executed

You don't need to worry about the performance problems that the AOP framework will bring to your program, because the CZGL.AOP framework is written in EMIT and comes with its own cache, and when a type is proxied, it does not need to be generated repeatedly.

CZGL.AOP can be used in conjunction with Autofac through the dependency injection framework included with the .NET Core to automatically proxy services in the CI container. This eliminates the need for AopInterceptor.CreateProxyOfClass to manually invoke the proxy interface.

The CZGL.AOP code is open source, so you can refer to another blog post by the author:

Https://www.yisu.com/article/238462.htm

Microsoft.Extensions.Logging

Some companies do not have technical management specifications, different developers use different logging frameworks, a product may have .txt, NLog, Serilog, etc., and do not have the same package.

There are many logging components in .NET Core, but most popular logging frameworks implement Microsoft.Extensions.Logging.Abstractions, so we can learn from Microsoft.Extensions.Logging. Microsoft.Extensions.Logging.Abstractions is an official abstraction of logging components. If a logging component does not support Microsoft.Extensions.Logging.Abstractions, it is easy to mix with the project, and it is difficult to modularize and reduce coupling later.

The Microsoft.Extensions.Logging package contains Logging API, and these Logging API cannot be run independently. It is used with one or more logging providers that store or display logs to specific output, such as Console, Debug, TraceListeners.

The following figure shows the hierarchy of Loggin API in .NET Core:

To tell you the truth, Microsoft.Extensions.Logging is very confused at first, and the configuration feels very complicated. Therefore, it is important to have a clear structure diagram to help you understand the Logging API in it.

ILoggerFactory

Many standard interfaces in .NET Core practice the idea of factory pattern. ILoggerFactory is the interface of factory pattern, and LoggerFactory is the implementation of factory pattern.

It is defined as follows:

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

The purpose of the ILoggerFactory factory interface is to create an instance of type ILogger, the CreateLogger interface.

ILoggerProvider

By implementing the ILoggerProvider interface, you can create your own logging provider that represents the type of ILogger instance that can be created.

It is defined as follows:

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

The ILogger interface provides a method for logging to the underlying storage, which is defined as follows:

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 is called a logging program.

Logging Providers displays or stores logs to specific media, such as console, debugging event, event log, trace listener, etc.

Microsoft.Extensions.Logging provides the following types of logging providers, which we can obtain through 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

While Serilog has File, Console, Elasticsearch, Debug, MSSqlServer, Email and so on.

There are many of these logging providers that we don't need to delve into; if a logging component does not provide an Microsoft.Extensions.Logging-compliant implementation, it should not be introduced at all.

In fact, many programs are direct File.Write ("Log.txt"). How good can the quality of this product be?

How to use it

Earlier, I introduced the composition of Microsoft.Extensions.Logging, and here you will learn how to enter logs using Logging Provider.

To say the least, it only provides a Logging API, so in order to output the log, we have to choose the appropriate Logging Provider program, here we choose

Microsoft.Extensions.Logging.Console, please reference this package in Nuget.

The following is a structure diagram used in conjunction with Logging Provider and ConsoleLogger:

From the conventional method, the author found that it is impossible to configure ah.

ConsoleLoggerProvider consoleLoggerProvider = new ConsoleLoggerProvider (new OptionsMonitor (new OptionsFactory (new IEnumerable builder.AddSimpleConsole (options = > {options.IncludeScopes = true; options.SingleLine = true; options.TimestampFormat = "hh:mm:ss" }))

Or:

ILoggerFactory loggerFactory = LoggerFactory.Create (builder = > builder.AddConsole ())

Of course, other log providers can be added to the factory, for example:

Using ILoggerFactory loggerFactory = LoggerFactory.Create (builder = > builder.AddSimpleConsole (...) .AddFile (...) .add ().. );

Then get the ILogger instance:

ILogger logger = loggerFactory.CreateLogger ()

Log:

Logger.LogInformation ("record information"); log level

Seven log levels are specified in Logging API, which are defined as follows:

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

We can output the following levels of logs through the functions in 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.")

I won't dwell on Microsoft.Extensions.Logging here. Readers can rank the following links to learn more about it:

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

The namespaces of the Debug and Trace classes provide System.Diagnostics,Debug and Trace with a set of methods and properties that help you debug your code.

Readers can refer to another article by the author:

/ / www.yisu.com/article/242562.htm

Output to the console:

Trace.Listeners.Add (new TextWriterTraceListener (Console.Out)); Debug.WriteLine ("information"); link tracking

Link tracing can help developers quickly locate performance bottlenecks in distributed application architecture and improve the efficiency of development and diagnosis in the era of micro-services.

OpenTracing

The Trace and Debug mentioned earlier are the API provided to developers in the .NET Core for diagnostics and output information, while the trace mentioned next is only the link tracking (trace) in OpenTracing API.

The big disadvantage of ordinary logging is that each method records a log, and we can't connect multiple methods that are called in a process. When an exception occurs in a method, it is difficult to know which task process caused the exception. We can only see which method has an error and has its caller.

In OpenTracing, Trace is a directed acyclic graph with Span (span). A Span represents a logical representation of some work done in an application, and each Span has the following properties:

Operation name

Start time

End time

To figure out what Trace and Span are and what OpenTracing is, introduce OpenTracing into Nuget.

Write the Hello class as follows:

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) {/ / create a Span and start var spanBuilder = _ tracer.BuildSpan ("say-hello") / /-- var span = spanBuilder.Start (); / / | var str = $"Hello, {content}"; / / | _ logger.LogInformation (str); / / | span.Finish () / / | /--}}

Start the program and start tracking:

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

In the above process, we used OpenTracing API, and here is a description of some elements in the code:

ITracer is a link tracing instance, and BuildSpan () can create one of the Span

Each ISpan has an operation name, such as say-hello

Start a Span; with Start () and end a Span with Finish ()

The tracker automatically records the timestamp

Of course, when we run the above program, there is no other information or UI interface, because GlobalTracer.Instance will return a tracer with no operation. When we define a Tracer, we can observe the process of link tracing.

In Nuget, introduce Jaeger.

In Program, add a static function that returns a custom 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 ();}

Modify the Main function as follows:

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

Complete code: https://gist.github.com/whuanle/b57fe79c9996988db0a9b812f403f00e

Context and tracking function

However, it is unfriendly for logs to output string directly, so we need to structure logs.

Of course, ISpan provides a way to structure logs, and we can write a method for formatting logs.

Track individual functions

Add the following code to the Hello class:

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

In addition, we can encapsulate a function that outputs string information:

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

Change the SayHello method to:

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

The reason for changing the above code is not to mix too much code in one method, but to try to reuse some code to encapsulate a unified code.

However, originally we only need to call one method SayHello, here one method will continue to call the other two methods. It used to be one Span, but finally it became three Span.

Info: Jaeger.Configuration [0] info: Jaeger.Reporters.LoggingReporter [0] Span reported: 77f1a24676a3ffe1:77f1a24676a3ffe1:0000000000000000:1-format-stringinfo: ConsoleApp1.Hello [0] Hello, This traceworthy 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

Note: 0000000000000000 indicates that a Span has ended.

Advantages: from the code point of view, SayHello-> FormaString, SayHello-> PrintHello, we can clearly know the calling link

Disadvantages: judging from the output, the Span reported is different, and we cannot judge the causality of the three functions in the output.

It is impossible for us to stare at the code all the time, and it is impossible for operators and implementers to compare and find the logic of the code.

Merge multiple spans into one track

ITracer is responsible for creating link traces, so ITracer also provides API that combines multiple Span causality.

The method of use is as follows:

Var rootSapn = _ tracer.BuildSpan ("say-hello"); / / Avar span = _ tracer.BuildSpan ("format-string") .AsChildof (rootSpan) .Start (); / / Bhand / A-> B

We create a rootSpan, and then create a sapn,rootSpan-> span that continues the rootSpan.

Info: Jaeger.Reporters.LoggingReporter [0] Span reported: 2f2c7b36f4f6b0b9:3dab62151c641380:2f2c7b36f4f6b0b9:1-format-stringinfo: ConsoleApp1.Hello [0] Hello, This tracekeeper info: Jaeger.Reporters.LoggingReporter [0] Span reported: 2f2c7b36f4f6b0b9:9824227a41539786:2f2c7b36f4f6b0b9:1-print-helloinfo: Jaeger.Reporters.LoggingReporter [0] Span reported: 2f2c7b36f4f6b0b9:2f2c7b36f4f6b0b9:0000000000000000:1-say-helloSpan reported: 2f2c7b36f4f6b0b9

The output order is the order in which the execution is completed, and the say-hello is the last to be executed.

The context in the process of communication

From what kind of code, we find that the code is more troublesome because:

To pass the Span object as the first argument to each function

Add a lengthy try-finally {} to each function to ensure that the Span can be completed

To this end, OpenTracing API provides a better way to avoid passing Span as a parameter to the code, and can call _ tracer on its own.

Modify the FormatString and PrintHello codes as follows:

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

Modify the SayHello code as follows:

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

Through the above code, we get rid of the annoying code.

StartActive () replaces Start () to make span "active" by storing it in thread-local storage

StartActive () returns an IScope object instead of an object ISpan. IScope is the container for the current active scope. By accessing the activity span scope.Span, once the scope is closed, the previous scope becomes the current scope, thus reactivating the previous activity scope in the current thread

IScope inherits IDisposable, which enables us to use the using syntax

StartActive (true) tells Scope that once it is processed, it should complete the scope it represents

StartActive () automatically creates a reference to the previous activity scope by ChildOf, so we don't have to use the builder method explicitly with AsChildOf ()

If we run this program, we will see that the span of all three reports has the same trace ID.

Distributed link tracking in different processes

Microservices deploy multiple programs separately, and each program provides different functions. Previously, we have learned about OpenTracing link tracking. Next, we will split the code, the console program will no longer provide the implementation of the FormatString function, we will use a Web program to implement the FormatString service.

Create an ASP.NET Core application and select the template with the view model controller in the template.

Add a FormatController controller in the Controllers directory with the following code:

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

The Web application will be one of the microservices, and this service has only one API, and this API is simple enough to provide string formatting. You can also write other API to provide services.

Change the CreateHostBuilder of Program to fix the port of this service.

Public static IHostBuilder CreateHostBuilder (string [] args) = > Host.CreateDefaultBuilder (args) .ConfigureWebHostDefaults (webBuilder = > {webBuilder.UseUrls ("http://*:8081"); webBuilder.UseStartup ();}))

Then delete app.UseHttpsRedirection (); in Startup.

Modify the previous console program code to change the FormatString method to:

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

After starting the Web program, start the console program.

Console program output:

Info: Jaeger.Reporters.LoggingReporter [0] Span reported: c587bd888e8f1c19:2e3273568e6e373b:c587bd888e8f1c19:1-format-stringinfo: ConsoleApp1.Hello [0] Hello, This tracekeeper 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

Next, we can change the Formating to:

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 can be tagged. We set a tag for the Span to Web for this request, and store the requested URL.

Var span = scope.Span .SetTag (Tags.SpanKind, Tags.SpanKindClient) .SetTag (Tags.HttpMethod, "GET") .SetTag (Tags.HttpUrl, url)

Context information is injected through Inject.

_ tracer.Inject (span.Context, BuiltinFormats.HttpHeaders, new TextMapInjectAdapter (dictionary))

These configuration specifications can be found in https://github.com/opentracing/specification/blob/master/semantic_conventions.md.

Trace in ASP.NET Core

Above, we have implemented Client tracking in different processes, but not in Server yet. We can modify the code in Startup.cs to replace the following code:

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

In this way, different processes can be tracked.

OpenTracing API and Jaeger

OpenTracing is an open distributed tracking specification. OpenTracing API is a consistent, expressive, vendor-independent API for distributed tracking and context propagation.

Jaeger is an open source distributed tracking system for Uber.

The client libraries and specifications of OpenTracing can be viewed in Github: https://github.com/opentracing/

You can consult the data for detailed introduction.

Here we need to deploy a Jaeger instance for microservices and transaction tracking learning needs.

Deployment with Docker is simple, as long as the following command is executed:

Docker run-d-p 5775:5775/udp-p 16686 jaegertracing/all-in-one:latest 16686-p 14250 jaegertracing/all-in-one:latest 14250

Access port 16686 to see the UI interface.

The port functions of Jaeger are as follows:

Collector14250 tcp gRPC sends data in proto format 14268 http directly accepts client data 14269 http health check Query16686 http jaeger's UI front end 16687 http health check

Next we will learn how to upload data to Jaeger through code.

Link tracing practice

Note that if the data is uploaded to Jaeger, the upload is Span, and the log content will not be uploaded.

Continue to use the console program above and add the Jaeger.Senders.Grpc package to Nuget.

We can upload data to Jaeger through UDP (port 6831) and gRPC (14250) ports, where we use gRPC.

Modify the InitTracer method of the console program with the following code:

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 ILoggerFactory loggerFactory 14250", null, 0)) .build () Var tracer = new Tracer.Builder (serviceName) .WithLoggerFactory (loggerFactory) .WithSampler (new ConstSampler (true)) .WithReporter (reporter); return tracer.Build ();}

Start the Web and console programs respectively, then open the Jaeger interface, select hello-world in "Service", and click Find Traces below.

Through Jaeger, we can analyze the execution speed of functions in the link and the performance of the server.

Thank you for reading this article carefully. I hope the article "sample Analysis of logs and distributed Link tracing in ASP.Net Core" shared by the editor will be helpful to you. At the same time, I also hope that you will support and pay attention to the industry information channel. More related knowledge is waiting for you to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report