BeyondNetCode.Shell.Logger.Serilog (shell library portable)ADRs relacionados:
Los command handlers necesitan logging de entrada/salida/excepción enriquecido con el envelope completo de observabilidad (TenantId, CorrelationId, SessionTrackingId, TraceId, SpanId, BoundedContext) sin:
ILogger o SerilogExtender StructuredAopLoggerBase (shell library) para crear un adaptador de logger respaldado por Serilog. Registrarlo vía una interfaz marcador como servicio DI con clave. Los handlers declaran la intención con [LoggerAspect(Type = typeof(IUmsLogger), ...)] — sin acoplamiento en tiempo de ejecución.
[LoggerAspect(Type = typeof(IUmsLogger))] ← Capa de Aplicación (solo atributo)
│
▼ (DispatchProxy intercepta)
UmsSerilogLogger : StructuredAopLoggerBase ← Capa de Infraestructura
│
├── ResolveExecutionContext() lee snapshot de RequestContextAccessor
├── TenantId() lee IUserContext (scoped)
├── InferBoundedContext(Type) parsea namespace (p.ej. Identity, Authorization)
│
▼
ILogger<THandler> (MEL respaldado por Serilog)
│
▼
PiiSanitizerEnricher → Sinks (Console / OTel / Loki)
// BeyondNetCode.Shell.Logger.Serilog
public abstract class StructuredAopLoggerBase : ILogger
{
private readonly IExecutionContextAccessor _accessor;
protected StructuredAopLoggerBase(IExecutionContextAccessor accessor)
=> _accessor = accessor;
/// <summary>
/// Resuelve el envelope completo de observabilidad para el request actual.
/// Prioridad: snapshot de RequestContextAccessor → baggage de Activity.Current → parámetro requestId → ""
/// </summary>
protected ExecutionContextSnapshot ResolveExecutionContext(string requestId) { ... }
/// <summary>
/// Infiere el bounded context desde el namespace del tipo del handler.
/// Ums.Application.Identity.Tenant.Commands.* → "Identity"
/// </summary>
protected static string InferBoundedContext(Type targetType) { ... }
// Contrato abstracto ILogger — implementar en subclase específica del satélite
public abstract void OnEntry(IJoinPoint jp, Argument[] args, string requestId);
public abstract void OnExit(IJoinPoint jp, Return ret, string requestId, long duration);
// ... otras sobrecargas
public abstract void OnException(IJoinPoint jp, string requestId, Exception ex);
}
// Ums.Infrastructure/Aop/UmsSerilogLogger.cs
public sealed class UmsSerilogLogger(ILoggerFactory loggerFactory,
IUserContext userContext,
IExecutionContextAccessor accessor) : StructuredAopLoggerBase(accessor), IUmsLogger
{
public override void OnEntry(IJoinPoint jp, Argument[] args, string requestId)
{
var log = loggerFactory.CreateLogger(jp.TargetType);
if (!log.IsEnabled(LogLevel.Information)) return;
var ctx = ResolveExecutionContext(requestId);
var tenant = userContext.TenantId ?? "system";
var bc = InferBoundedContext(jp.TargetType);
// Seguro de PII: solo nombres + tipos CLR, nunca valores
var argSummary = args is { Length: > 0 }
? string.Join(", ", args.Select(a => $"{a.Name}:{a.Type}"))
: string.Empty;
log.LogInformation("→ {BoundedContext} {Handler}.{Method} params=[{Params}] | "
+ "tenant={TenantId} cid={CorrelationId} sid={SessionTrackingId} "
+ "trace={TraceId} span={SpanId}",
bc, jp.TargetType.Name, jp.MethodInfo.Name, argSummary,
tenant, ctx.CorrelationId, ctx.SessionTrackingId, ctx.TraceId, ctx.SpanId);
}
public override void OnException(IJoinPoint jp, string requestId, Exception ex)
{
var log = loggerFactory.CreateLogger(jp.TargetType);
var ctx = ResolveExecutionContext(requestId);
var tenant = userContext.TenantId ?? "system";
log.LogError(ex,
" {BoundedContext} {Handler}.{Method} threw {ExType} | "
+ "tenant={TenantId} cid={CorrelationId} sid={SessionTrackingId}",
InferBoundedContext(jp.TargetType),
jp.TargetType.Name, jp.MethodInfo.Name, ex.GetType().Name,
tenant, ctx.CorrelationId, ctx.SessionTrackingId);
}
// ... las sobrecargas de OnExit siguen el mismo patrón
}
// Ums.Application/Common/Aop/IUmsLogger.cs
// Marcador — cero código en tiempo de ejecución; selecciona el servicio DI con clave
public interface IUmsLogger : ILogger; // ILogger = BeyondNetCode.Shell.Aspects.ILogger
// Después de AddAop() — registra PointCut, AspectExecutor, aspectos incorporados
services.AddAop();
// Registrar adaptador de logger bajo clave de interfaz marcador
services.AddKeyedTransient<BeyondNetCode.Shell.Aspects.ILogger, UmsSerilogLogger>(typeof(IUmsLogger));
// Envolver handler con DispatchProxy — debe ir DESPUÉS de AddMediatR()
services.AddAopProxy<
IRequestHandler<CreateTenantCommand, Result<CreateTenantResponse>>,
CreateTenantCommandHandler>();
// Capa de Aplicación — sin import de Infraestructura
[LoggerAspect(Type = typeof(IUmsLogger), LogDuration = true, LogException = true, LogArguments = [])]
public async Task<Result<CreateTenantResponse>> Handle(CreateTenantCommand request, CancellationToken ct)
{
// lógica de negocio pura — sin código de logging
}
→ Identity CreateTenantCommandHandler.Handle params=[request:CreateTenantCommand] | tenant=acme cid=a3f1b7c2 sid=f9d8e1a0 trace=4bf92f35... span=00f067aa...
← Identity CreateTenantCommandHandler.Handle in 42ms | tenant=acme cid=a3f1b7c2 sid=f9d8e1a0
Identity CreateTenantCommandHandler.Handle threw ValidationException | tenant=acme cid=a3f1b7c2 sid=f9d8e1a0
| Adaptador | Clave de interfaz | Nivel | Enriquecimiento | Cuándo usar | |
|---|---|---|---|---|---|
MelLogger |
IMelLogger |
Debug | Ninguno más allá de los scopes MEL | Dev, trazado ligero | |
UmsSerilogLogger |
IUmsLogger |
Information | TenantId, CorrelationId, SessionTrackingId, TraceId, SpanId, BoundedContext | Todos los command handlers de producción | — |