Код: Выделить всё
Function(context) -> Service(context) -> Repository(context) -> ApiClient(context) -> ...
Код: Выделить всё
public record TraceContext(
Guid TraceId,
string Domain = ""
);
Более того, любой из слоев может обновить контекст, это изменение должно распространяться только на текущий слой и его дочерние элементы, не затрагивая экземпляр родительского слоя.
Я думал, что AsyncLocal может быть вариантом, хотя для меня он относительно новый. В настоящее время у меня есть реализация, подобная этой.
TraceContextAccessor.cs
Код: Выделить всё
public class TraceContextAccessor
{
private static readonly AsyncLocal Current = new();
public virtual TraceContext TraceContext
{
get => Current.Value ?? new TraceContext(Guid.Empty);
set => Current.Value = value;
}
public TraceContextScope CreateScope(TraceContext traceContext)
{
return new TraceContextScope(this, traceContext);
}
}
Код: Выделить всё
public class TraceContextScope : IDisposable
{
private readonly TraceContextAccessor _accessor;
private readonly TraceContext _originalTraceContext;
private bool _disposed;
public TraceContextScope(TraceContextAccessor accessor, TraceContext newTraceContext)
{
_accessor = accessor;
// Take a snapshot of the original value
_originalTraceContext = accessor.TraceContext;
// Set the new value
SetTraceContext(newTraceContext);
}
private void SetTraceContext(TraceContext traceContext)
{
_accessor.TraceContext = traceContext;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
// Restore the original value
SetTraceContext(_originalTraceContext);
_disposed = true;
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
Код: Выделить всё
public class TraceLogger(TraceContextAccessor traceContextAccessor)
{
private readonly TraceContextAccessor _traceContextAccessor = traceContextAccessor;
public Task LogMessage(string message)
{
var context = _traceContextAccessor.TraceContext;
Console.WriteLine($"{message} - [{context.TraceId}].[{context.Domain}]");
return Task.CompletedTask;
}
public string BuildDomain(params string[] segments) => string.Join("_", segments);
}
Код: Выделить всё
services.AddSingleton();
services.AddScoped();
services.AddScoped();
Код: Выделить всё
public class DemoFunction(
DemoService demoService,
TraceContextAccessor traceContextAccessor,
TraceLogger traceLogger)
{
private readonly DemoService _demoService = demoService;
private readonly TraceContextAccessor _traceContextAccessor = traceContextAccessor;
private readonly TraceLogger _traceLogger = traceLogger;
[Function(nameof(DemoFunction))]
public async Task Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
{
var initialContext = new TraceContext(TraceId: Guid.NewGuid(), Domain: "Demo");
using (_traceContextAccessor.CreateScope(initialContext))
{
await _traceLogger.LogMessage($"{nameof(DemoFunction)} executing {nameof(Run)}.");
await _demoService.Process();
await _traceLogger.LogMessage($"{nameof(DemoFunction)} executed {nameof(Run)}.");
}
return new OkResult();
}
}
public class DemoService(
TraceLogger traceLogger,
TraceContextAccessor traceContextAccessor)
{
private readonly TraceLogger _traceLogger = traceLogger;
private readonly TraceContextAccessor _traceContextAccessor = traceContextAccessor;
public async Task Process()
{
// Log using the parent TraceContext
await _traceLogger.LogMessage($"{nameof(DemoService)} executing {nameof(Process)}.");
foreach (var country in new[] { "UK", "DE" })
{
// Create a new TraceContext
var tcCurrent = _traceContextAccessor.TraceContext;
var tcNew = tcCurrent with
{
Domain = _traceLogger.BuildDomain(tcCurrent.Domain, country)
};
// Switch to the the new TraceContext
using (_traceContextAccessor.CreateScope(tcNew))
{
await _traceLogger.LogMessage($"{nameof(DemoService)} executing {nameof(Process)} for {country}.");
var entities = await GetEntitiesByCountry(country);
foreach (var entity in entities)
{
await ProcessEntity(entity);
}
await _traceLogger.LogMessage($"{nameof(DemoService)} executed {nameof(Process)} for {country}.");
}
}
// Log using the parent TraceContext which should not have been modified by the loop
await _traceLogger.LogMessage($"{nameof(DemoService)} executed {nameof(Process)}.");
}
public async Task ProcessEntity(string entity)
{
// Create a new TraceContext
var tcCurrent = _traceContextAccessor.TraceContext;
var tcNew = tcCurrent with
{
Domain = _traceLogger.BuildDomain(tcCurrent.Domain, entity)
};
// Switch to the the new TraceContext
using (_traceContextAccessor.CreateScope(tcNew))
{
await _traceLogger.LogMessage($"{nameof(DemoService)} executing {nameof(ProcessEntity)}");
// Process entity
}
}
public async Task GetEntitiesByCountry(string countryCode)
{
await _traceLogger.LogMessage($"{nameof(DemoService)} executing {nameof(GetEntitiesByCountry)}");
// Call API with the HTTP headers from _traceContextAccessor.TraceContext
return ["Entity01", "Entity02"];
}
}
- Методы могут создать экземпляр TraceContext и сделать его доступны дочерним областям.
- Дочерние области могут получать доступ к текущим метаданным журнала с помощью внедрения зависимостей. Они также могут создавать новый контекст, не затрагивая контекст в родительской области.
- Контекст прикрепляется к контексту асинхронного выполнения и не используется совместно различными выполнениями функции/действия.
- Видите ли вы какие-либо проблемы с этой реализацией?
- Можно ли этого добиться более простым способом путь?
Подробнее здесь: https://stackoverflow.com/questions/791 ... ecution-fl