SoFunction
Updated on 2025-03-07

Application of .NET core project AsyncLocal in link tracking

Preface

Logging is essential in project production. In .net projects, log components are mentioned.log4netThere is definitely a place. With the development of the company's business, microservices will inevitably be inevitable. It is particularly critical to analyze performance or troubleshooting points through logs across services. The emergence of link tracking technology solves these pain points.

Distributed link tracking requires collecting all services that a single request passes, and in order to know the request details, specific business logs need to be connected, and the basis of all this is to pass atraceidPassing from beginning to end is equivalent to associating all logs generated by the request process.traceid, only need to know when troubleshooting problems afterwardstraceid, you can pull out all the logs associated with it in the log.

Of course, not all companies need link tracking. For some small companies, only a few single systems do not need these at all. For example, we uselog4netWhen it is added to the log templateThreadId, for example, a template like this

"%date [%thread] %-5level - %message%newline"

Although the request logs of multiple users are mixed together when concurrency is high, we can still concatenate the requested logs according to the thread number. This solves our problem very well in most of the time.

Old traditional practice

Even if the thread number above is very useful in a small system, there is no business scenario where there is no need for multi-threading? When a request comes in, multiple asynchronous threads may be opened to execute, and the thread number above seems to be ineffective, which means that it is impossible to extract the related logs in one go.

But this is not a problem for us. We can customize a random string as the unique identifier of the request at the beginning of the business, and then pass the variable to the downstream method through the parameters. The downstream method also passes it layer by layer, and outputs the field when printing the log. Many people have used this method.

TraceIdentifier for AspNetCore

Is there no elegant way to connect the unique identifier of a process of our request (including multithreading)?

existASPNetCoreIn fact, there has always been an inconspicuous attribute, which can be said to be provided by the framework.traceid, we can inject everything we needHttpContextTo get this parameter, of course it is not that troublesome. You just need to get the value for the log component. When the log output of any leave log is output, the log component can output it. This is absolutely fine. You can go deeper and study it. Some log components can be configured directly to output it.TraceIdentifierThe value can be used to each log, and it can also be passed to the downstream service when called across the application. For example, http requests can carry the value through the header, which is retrieved from the header and used as its own.TraceIdentifierContinue to pass.

AsyncLocal's application in link tracking

ThreadLoaclIt is familiar, and each thread is isolated from each other. Each thread operates on its own thread objects, which can achieve each thread without affecting it.AsyncLocalIt's not a new feature, it's just that there are not many scenarios used and rarely used

definition

Represents ambient data that is local to a given asynchronous control flow, such as an asynchronous method.

Represents the environment data that is local data for a given asynchronous control flow (such as an asynchronous method).

Example

using System;
using ;
using ;
class Example
{
    static AsyncLocal<string> _asyncLocalString = new AsyncLocal<string>();
    static ThreadLocal<string> _threadLocalString = new ThreadLocal<string>();
    static async Task AsyncMethodA()
    {
        // Start multiple async method calls, with different AsyncLocal values.
        // We also set ThreadLocal values, to demonstrate how the two mechanisms differ.
        _asyncLocalString.Value = "Value 1";
        _threadLocalString.Value = "Value 1";
        var t1 = AsyncMethodB("Value 1");
        _asyncLocalString.Value = "Value 2";
        _threadLocalString.Value = "Value 2";
        var t2 = AsyncMethodB("Value 2");
        // Await both calls
        await t1;
        await t2;
     }
    static async Task AsyncMethodB(string expectedValue)
    {
        ("Entering AsyncMethodB.");
        ("   Expected '{0}', AsyncLocal value is '{1}', ThreadLocal value is '{2}'", 
                          expectedValue, _asyncLocalString.Value, _threadLocalString.Value);
        await (100);
        ("Exiting AsyncMethodB.");
        ("   Expected '{0}', got '{1}', ThreadLocal value is '{2}'", 
                          expectedValue, _asyncLocalString.Value, _threadLocalString.Value);
    }
    static async Task Main(string[] args)
    {
        await AsyncMethodA();
    }
}
// The example displays the following output:
//   Entering AsyncMethodB.
//      Expected 'Value 1', AsyncLocal value is 'Value 1', ThreadLocal value is 'Value 1'
//   Entering AsyncMethodB.
//      Expected 'Value 2', AsyncLocal value is 'Value 2', ThreadLocal value is 'Value 2'
//   Exiting AsyncMethodB.
//      Expected 'Value 2', got 'Value 2', ThreadLocal value is ''
//   Exiting AsyncMethodB.
//      Expected 'Value 1', got 'Value 1', ThreadLocal value is ''

To be simple, after assigning a value to the variable, it affects its own child thread, that is, other threads initiated by the current thread, including threads in the thread pool, can obtain the value. The child thread modifying the value has no effect on the parent thread.

And this feature seems to be the kind of feature we look for that can elegantly mark the same request. Define a global variable, assign a random string to the variable at the starting point of each request, and then all threads involved in this request access the value, which are the values ​​we assigned at the entrance.

Project Application

We can define a global variable anywhere, preferably in LogHelper

AspNet4

public static class LogHelper{
    public static AsyncLocal<string> Traceid = new AsyncLocal<string>();
    ...
}

Assign this value in the authorization filter. Generally, authorization filtering is performed first and can be used as the entry point for the request

 = ().ToString();

existlog4netLogHelper is used in the log template

"%date [%property{trace}] [%thread] %-5level - %message%newline"
public static void Info(object message)
{
    ["trace"] = ;
    (message);
}
...

AspNetCore

Register middleware to set values, register your own middleware at the forefront

(delegate (HttpContext ctx, RequestDelegate next)
{
     = ;
    return next(ctx);
});

Proven to match expectations, this implementation does not rely on the AspnetCore framework, provides a way to realize transmission in link trackingTraceIdIf there are any incorrect ideas, please correct them. If this idea is helpful to you, please like and share.

The above is the detailed content of the application of AsyncLocal in link tracking. For more information about AsyncLocal link tracking, please follow my other related articles!