Exception Handling  

Building a Unified Logging Correlation Model (TraceID Propagation)

Modern distributed applications often include multiple independent components: user interfaces, APIs, background jobs, microservices, message queues, and external systems. When a request flows through these systems, debugging production issues becomes difficult unless logs are connected end-to-end.

A Unified Logging Correlation Model solves this by attaching a consistent identifier, usually called a TraceID, to every log entry across the system. With correlation, you can search one ID and reconstruct the complete execution path, including timing, errors, retries, and external calls.

This article explains how to design and implement TraceID propagation across Angular (frontend), .NET (backend), and distributed services.

Why Correlation Logging Matters

Without correlation IDs

  • Logs are independent and unsearchable.

  • Multi-step business flows cannot be reconstructed.

  • Root cause analysis requires assumptions and speculation.

  • Troubleshooting performance bottlenecks becomes slow.

With correlation

  • One ID gives the full request story.

  • Async flows and retries become traceable.

  • Monitoring and alerting improve accuracy.

  • Distributed tracing becomes possible.

Well-designed TraceID propagation is foundational for observability and production reliability.

Core Principles of Correlation Logging

A TraceID solution must support:

RequirementDescription
Global uniquenessIDs must be collision-free
ConsistencySame ID used across all logs and telemetry
TransportPassed via headers, message properties, or storage
PersistenceLogged everywhere: API, jobs, queue handlers
IndependenceTrace continues even across async or remote boundaries

While standards like W3C Trace Context and tools such as OpenTelemetry help, many enterprises need custom implementations aligned to internal frameworks.

System Architecture Overview

Angular Client
      |
      v
Request with TraceID Header
      |
      v
API Gateway / .NET Web Application
      |
      v
Service Bus / Microservices / Background Workers
      |
      v
Infrastructure Logs, Metrics, Dashboards

Workflow Diagram

User Action
     |
     v
Generate or Retrieve TraceID
     |
     v
Send TraceID with Request Headers
     |
     v
Backend Logs Using Same TraceID
     |
     v
Service-to-Service Calls propagate TraceID
     |
     v
Store in Logging Platform for Correlation

Flowchart

START
  |
  v
Incoming HTTP request contains TraceID?
  |
  |--YES--> Use provided TraceID
  |
  NO
  |
  v
Generate new TraceID
  |
  v
Store TraceID in context
  |
  v
Log request, operations, errors with TraceID
  |
  END

TraceID Format

A TraceID should be:

  • Unique

  • Lightweight

  • Easy to search

Common formats:

  • GUID

  • ULID

  • Timestamp + Random Segment

Example format

TID-2025-02-21-4eac9f21a1b7

Angular Implementation

Generate or Retrieve TraceID

export function generateTraceId(): string {
  return 'tid-' + crypto.randomUUID();
}

Interceptor

@Injectable()
export class TraceInterceptor implements HttpInterceptor {
    private traceId = generateTraceId();

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        const modified = req.clone({
            setHeaders: { 'X-Trace-ID': this.traceId }
        });
        return next.handle(modified);
    }
}

Every API request now consistently carries a TraceID.

.NET Server Implementation

Middleware to Extract and Persist TraceID

public class TraceMiddleware
{
    private readonly RequestDelegate _next;

    public TraceMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        var traceId = context.Request.Headers["X-Trace-ID"].FirstOrDefault()
                      ?? Guid.NewGuid().ToString();

        context.Items["TraceID"] = traceId;

        using (LogContext.PushProperty("TraceID", traceId))
        {
            await _next(context);
        }
    }
}

Register in Startup

app.UseMiddleware<TraceMiddleware>();

Logging with TraceID

Example using Serilog

Log.Information("Order Created for Customer {customerId}", id);

Output becomes

2025-02-21T09:41:33 | TraceID=tid-830a9d | Order Created for Customer 5543

Propagating TraceID Across Microservices

Every service-to-service call must forward the header:

var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("X-Trace-ID", traceId);

Queue and Background Job Propagation

Store TraceID inside event payload or message metadata:

{
  "TraceId": "tid-830a9d",
  "EventType": "ShipmentCreated",
  "Payload": {...}
}

Workers attach the same TraceID before logging.

Persistence in Database Logs

Add TraceID column in audit tables:

ALTER TABLE AuditLogs ADD TraceId NVARCHAR(100);

This enables replay reconstruction and compliance auditing.

Integration with Observability Tools

ToolSupported
Elastic StackYes
Azure MonitorYes
Application InsightsYes
Loki/GrafanaYes
SplunkYes

All modern platforms support TraceID-based log correlation.

Error Handling and TraceID Exposure

Every error response should return the TraceID to the user:

return Problem(title: "Unexpected error occurred", 
               detail: "Reference: tid-830a9d");

This enables support teams to trace incidents quickly.

Performance and Scalability Notes

  • TraceID adds negligible overhead.

  • Avoid per-request GUID generation inside tight loops.

  • Cache context-level TraceID for async operations.

Governance and Best Practices

  • Store TraceIDs only, not sensitive user data.

  • Do not overwrite existing TraceID unless reset is intentional.

  • Include TraceID in alert policies and dashboards.

  • Use the same TraceID format across platforms.

Conclusion

A Unified Logging Correlation Model transforms logging from raw text storage into a powerful investigation and observability tool. By propagating a TraceID across Angular, .NET APIs, asynchronous services, and background systems, enterprises gain complete request lineage visibility.

The result is faster debugging, stronger reliability, and operational maturity expected in modern distributed systems.