Как избежать загрязнения параметров при передаче контекста в потоке выполнения?C#

Место общения программистов C#
Anonymous
Как избежать загрязнения параметров при передаче контекста в потоке выполнения?

Сообщение Anonymous »

У меня есть часть контекстной информации, которую мне нужно сделать доступной во время выполнения функции Azure:

Код: Выделить всё

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);
}
}
TraceContextScope.cs

Код: Выделить всё

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);
}
}
TraceLogger.cs

Код: Выделить всё

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);
}
Program.cs

Код: Выделить всё

services.AddSingleton();
services.AddScoped();
services.AddScoped();
DemoFunction.cs

Код: Выделить всё

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

Вернуться в «C#»