Skip to main content

Distributed Tracing Reference

End-to-end trace context propagation from Next.js frontend to .NET backend to Azure services.

Trace Flow Overview​

Next.js Server Action (withSpan)
│ fetchWithTimeout() → injectTraceContextHeaders()
│ ├── traceparent: 00-{traceId}-{spanId}-01
│ ├── tracestate: (empty)
│ └── X-Request-Id: {traceId}
▼
ASP.NET Core API
│ AddAspNetCoreInstrumentation() auto-extracts traceparent
│ Creates Activity with same traceId
│ Enriches: http.request.id, enduser.id, http.client.ip
│ Adds baggage: enduser.id (propagated downstream)
│
├── InvoiceProcessingService.CreateInvoice (child span)
│ └── InvoiceOrchestrationService (grandchild span)
│ └── InvoiceStorageFoundationService (great-grandchild span)
│ └── InvoiceNoSqlBroker (great-great-grandchild span)
│ ├── Cosmos DB SDK (native OTel spans via Azure.Cosmos.Operation)
│ └── Tags: db.system, db.operation, db.cosmosdb.request_charge
│
├── HttpClient → Azure Form Recognizer (child span, traceparent auto-injected)
├── HttpClient → Azure OpenAI (child span, traceparent auto-injected)
└── HttpClient → Azure Translator (child span, traceparent auto-injected)
│
▼
Azure Monitor / Application Insights
All spans share the same traceId for end-to-end correlation

W3C Header Propagation​

HopHeaderAuto-Injected?Mechanism
Frontend → Backendtraceparent✅propagation.inject() in injectTraceContextHeaders()
Frontend → BackendX-Request-Id✅Fallback correlation ID = traceId
Backend receivestraceparent✅AddAspNetCoreInstrumentation() auto-extracts
Backend → Azure OpenAItraceparent✅AddHttpClientInstrumentation() auto-injects
Backend → Form Recognizertraceparent✅AddHttpClientInstrumentation() auto-injects
Backend → Cosmos DBSDK-internal✅CosmosClientTelemetryOptions.DisableDistributedTracing = false

Backend Service Layer Hierarchy​

Each layer calls InvoicePackageTracing.StartActivity(nameof(Method)). The .NET Activity API automatically parents via Activity.Current, forming a child chain:

HTTP GET /rest/v1/invoices (ASP.NET Core root span)
└── CreateInvoice (Processing — service.layer=Processing)
└── CreateInvoiceObject (Orchestration — service.layer=Orchestration)
└── CreateInvoiceObject (Foundation — service.layer=Foundation)
└── CreateInvoiceAsync (Broker — service.layer=Broker)
├── db.system=cosmosdb, db.operation=create
├── db.cosmosdb.request_charge=5.2
└── Azure.Cosmos.Operation (SDK-native span)

ActivitySource Registry​

ActivitySource NameBounded ContextRegistered In
arolariu.Backend.CommonCommon infrastructureTracingExtensions.cs
arolariu.Backend.CoreCore APITracingExtensions.cs
arolariu.Backend.AuthAuthenticationTracingExtensions.cs
arolariu.Backend.Domain.InvoicesInvoice domainTracingExtensions.cs
Azure.Cosmos.OperationCosmos DB SDKTracingExtensions.cs

Querying Traces in Application Insights (KQL)​

Find end-to-end trace by operation​

union requests, dependencies, traces
| where operation_Id == "<traceId>"
| order by timestamp asc
| project timestamp, itemType, name, duration, success, resultCode,
customDimensions["service.layer"],
customDimensions["db.operation"],
customDimensions["db.cosmosdb.request_charge"]

Find slow invoice operations (>2s)​

requests
| where name contains "invoices"
| where duration > 2000
| project timestamp, name, duration, operation_Id, resultCode
| order by duration desc
| take 50

Cosmos DB RU consumption by operation type​

dependencies
| where type == "Azure DocumentDB" or customDimensions["db.system"] == "cosmosdb"
| summarize avg_ru=avg(todouble(customDimensions["db.cosmosdb.request_charge"])),
p95_ru=percentile(todouble(customDimensions["db.cosmosdb.request_charge"]), 95),
count=count()
by tostring(customDimensions["db.operation"]),
tostring(customDimensions["db.cosmosdb.container"])
| order by avg_ru desc

Frontend-to-backend trace correlation​

requests
| where customDimensions["service.namespace"] == "arolariu.ro"
| where isnotempty(operation_Id)
| join kind=inner (
dependencies
| where customDimensions["cloud.role"] == "api"
) on operation_Id
| project timestamp,
frontend_name=name,
backend_name=name1,
total_duration=duration + duration1,
operation_Id
| order by total_duration desc
| take 20

Failed traces with full span tree​

requests
| where success == false
| project operation_Id, name, duration, resultCode, timestamp
| join kind=inner (
union requests, dependencies, traces, exceptions
| project operation_Id, itemType, name, message, timestamp
) on operation_Id
| order by timestamp asc

User-specific traces (via baggage propagation)​

union requests, dependencies
| where customDimensions["enduser.id"] == "<userId>"
| order by timestamp desc
| take 100

Span Enrichment Reference​

Automatic (ActivityEnrichingProcessor)​

Every span receives: correlation.id, root.trace_id, parent.span_id, span.depth, environment.name, host.name, process.id, thread.id, start.timestamp_utc, duration.ms, display.name, and any baggage.* items.

ASP.NET Core Request Spans​

http.request.id, http.client.ip, enduser.id, http.response.content_length.

Broker Layer Spans​

service.layer, service.component, operation.type, db.system, db.name, db.operation, db.cosmosdb.container, db.cosmosdb.partition_key, db.cosmosdb.request_charge, db.statement, db.query.type.

Domain Context​

invoice.id, invoice.user_id, invoice.merchant_id, merchant.id, user.id.

// was this page useful?