🧪 Problem-Driven Systems Lab

🔵 Caso 03 — .NET 8#

⬅️ Caso 03 · ⚖️ Comparativa de los 7 stacks · 🔵 Perfil de .NET · 🧬 Todos los perfiles

Stack .NET operativo del caso 03. Contraste entre logs opacos (Console.WriteLine sin contexto) vs estructurados con correlation ID propagado por el pipeline async.

Primitivas .NET nativas#

PrimitivaRol
AsyncLocal<RequestContext>Contexto de correlation ID que fluye por await sin propagar manualmente. Equivalente moderno del ThreadLocal Java en codigo async.
Guid.NewGuid()Generacion de correlation_id por request.
System.Text.Json (BCL)Logs estructurados serializados sin libreria externa.
record RequestContextSnapshot inmutable del contexto.

Contraste#

Legacy — log sin contexto:

csharp
Console.WriteLine("[INFO] processing checkout");
if (total > 500) {
    Console.WriteLine("[ERROR] checkout failed");  // sin id, sin total, sin razon
}

Observable — correlation ID + campos estructurados:

csharp
CTX.Value = new RequestContext(corrId, "checkout-observable", DateTime.UtcNow.ToString("o"));
StructuredLog("error", "checkout_failed", new Dictionary<string,string> {
    ["total"]  = total.ToString(),
    ["reason"] = "exceeds_limit",
    ["limit"]  = "500"
});
// → {"ts":"...","level":"error","event":"checkout_failed","correlation_id":"<guid>",
//    "route":"checkout-observable","total":"600.0","reason":"exceeds_limit","limit":"500"}

Rutas#

RutaQue muestra
/healthliveness
/checkout-legacy?total=600log opaco a stdout, sin id
/checkout-observable?total=600log estructurado + correlation_id en respuesta y en /logs
/logsultimos 200 logs estructurados (JSON)
/diagnostics/summarycontraste de requests/errors entre variantes
/reset-lablimpia logs y contadores

Hub#

docker compose -f compose.dotnet.yml up -d --build
curl "http://127.0.0.1:8500/03/checkout-observable?total=600"
curl http://127.0.0.1:8500/03/logs

Modo aislado#

docker compose -f cases/03-poor-observability-and-useless-logs/dotnet/compose.yml up -d --build
curl http://127.0.0.1:853/health

Por que AsyncLocal<T> y no ThreadLocal<T>#

ThreadLocal<T> no sobrevive a un await que retoma en otro thread del ThreadPool. AsyncLocal<T> es la primitiva que el CLR sigue por ExecutionContext a traves de cada await. Es exactamente el equivalente del ScopedValue JDK21 o de contextvars en Python — codifica "el contexto sigue al flujo logico, no al thread fisico". Para un pipeline HTTP async es la unica opcion correcta.

Ver esta carpeta en GitHub ↗