Estado: Aceptado
Fecha: 2026-05-24
Responsable: Arquitectura
Disposición Evolith: Propuesto para adopción en Evolith — el patrón es neutro en cuanto al runtime; aplicable a cualquier satélite .NET que use Serilog
Relacionados:
UMS procesa información de identificación personal (PII): direcciones de correo electrónico, referencias de identidad, contraseñas, tokens e IDs nacionales. ADR-0053 exige logging estructurado vía Serilog, pero un logging estructurado sin protección crea riesgo de filtrado de PII:
// Riesgo: el email se filtra a todos los sinks de log
_logger.LogInformation("User {Email} activated by {ActorId}", user.Email, actorId);
Existen tres capas de riesgo:
{@object} de Serilog registran todas las propiedades de una clase, incluyendo campos PIIEl enfoque de anotaciones ([Sensitive] en propiedades de dominio) acopla la capa de Dominio a una librería de logging, violando la pureza del dominio. El enmascaramiento por nombre de propiedad en el nivel del pipeline de Serilog no requiere cambios en el dominio.
Aplicar el enmascaramiento de PII a nivel del pipeline de Serilog mediante dos mecanismos complementarios: una política de destructuring y un enricher de eventos de log.
PiiMaskingPolicy — IDestructuringPolicy de SerilogRegistrado vía .Destructure.With<PiiMaskingPolicy>(). Intercepta el destructuring de objetos antes de que cualquier sink lo procese.
La política intercepta a nivel del nombre de propiedad dentro del enricher (ver más abajo) — el método TryDestructure devuelve false para que Serilog continúe la destrucción normal; el enricher luego escanea y redacta.
PiiSanitizerEnricher — ILogEventEnricher de SerilogRegistrado vía .Enrich.With<PiiSanitizerEnricher>(). Se ejecuta después del renderizado del template del mensaje, escaneando todas las propiedades escalares de tipo cadena:
// Nombres de propiedad enmascarados (insensible a mayúsculas/minúsculas):
"email", "emailaddress", "mail",
"password", "passwordhash", "passwordtext",
"identityreference",
"token", "accesstoken", "refreshtoken", "bearertoken", "idtoken",
"secret", "apikey", "apisecret", "clientsecret",
"ssn", "nationalid", "taxid"
Barrido por regex de email — cualquier valor escalar de texto libre que coincida con [^@\s]+@[^@\s]+\.[^@\s]+ se enmascara parcialmente: jo***@***.com. Esto captura filtraciones a través de propiedades con nombres no-PII.
LoggingExtensions.ConfigureUmsSerilogÚnico método de extensión que configura completamente Serilog:
builder.Host.UseSerilog((ctx, cfg) => cfg.ConfigureUmsSerilog(ctx));
Estrategia de salida:
| Entorno | Formato | Justificación |
|---|---|---|
| Development | Consola con texto coloreado | Legible por humanos; prefijo trace/correlación visible |
| Staging/Production | JSON compacto (CompactJsonFormatter) |
Legible por máquinas; consumido por Fluentd / drivers de log de contenedor |
Configuración (appsettings.json):
"Observability": {
"Logging": {
"ConsoleFormat": "CompactJson", // "Text" o "CompactJson"
"MinimumLevel": "Information", // cualquier nivel de Serilog
"OutputTemplate": "..." // solo para modo Text
}
}
Enrichers siempre aplicados:
Enrich.FromLogContext() — recoge scopes de ILogger (CorrelationId, SessionTrackingId del middleware)Enrich.WithMachineName() — identidad del pod/host para despliegues en KubernetesEnrich.WithThreadId() — correlaciona logs de requests concurrentesEnrich.With<PiiSanitizerEnricher>() — enmascaramiento de PIISobreescrituras de nivel:
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Warning)
Prohibidos:
// Concatenación de cadenas — sin campos estructurados
_logger.LogInformation("User " + userId);
// Volcado de objeto no estructurado
_logger.LogInformation(user.ToString());
// Valor PII directo en el template
_logger.LogInformation("Email: {email}", user.Email);
Requeridos:
// Campos estructurados con nombres no-PII
_logger.LogInformation("User {UserId} activated by {ActorId}", userId, actorId);
// Cuando el nombre de campo PII es inevitable, el enricher lo enmascarará
_logger.LogInformation("Verified {Email}", maskedForDisplay);
ConfigureUmsSerilog proporciona un único punto de configuración auditable; todos los sinks reciben eventos enmascaradosuserEmailAddress (no está en la lista) eludirá el enmascaramiento. La revisión de código debe cubrir el nombrado de campos de logif level < Warning) para rutas de alto rendimiento si el profiling lo identifica como hotspotPara enviar logs a un sink remoto (Seq, Elasticsearch, Application Insights, Loki):
Ums.Presentationappsettings.json bajo la sección "Serilog"Los siguientes tienen namespace de UMS pero son trivialmente portables:
PiiMaskingPolicy — sin import de producto, depende solo de Serilog.CorePiiSanitizerEnricher — sin import de producto, depende solo de Serilog.CoreLoggingExtensions.ConfigureUmsSerilog — depende del entorno + configuración, portable con rename menor| Registro ADR | CP-06 Logging PII | ADR-0053 OTel |