$3b
You are implementing observability for a .NET application. Read the relevant reference docs below based on what you're building. Always use DI-first patterns — inject ITracer, ILogger<T>, etc. rather than using static accessors.
Good observability answers three questions: What happened? (logs), Where did it happen? (traces), How much? (metrics). Every signal should be correlated — a single request should be traceable across logs, spans, and metrics via trace context. See 12-observability-philosophy.md for the full framework.
Read the relevant docs based on your task:
Log.Information("Processing order {OrderId}", orderId) not Log.Information($"Processing order {orderId}"). Interpolation defeats structured logging.CurrentTransaction — _tracer.CurrentTransaction can be null when no transaction is active (background threads, startup code). Always guard: _tracer.CurrentTransaction?.StartSpan(...).Activity.Current — Activity.Current is null when no listener is registered or no parent activity exists. Always guard before accessing properties.try/finally { span.End(); } or the using pattern.span.CaptureException(ex) or activity?.SetStatus(ActivityStatusCode.Error) in the catch block, before the finally block ends the span.[LogMasked] attributes or custom destructuring policies.IHttpClientFactory. For message queues, manually inject/extract trace context.GET /api/orders/{id}) not resolved paths (GET /api/orders/12345).ITracer and ILogger<T> via constructor injection. Only use Agent.Tracer or Serilog.Log.Logger in non-DI contexts (startup, static helpers).Microsoft.AspNetCore to Warning, System.Net.Http to Warning, Microsoft.EntityFrameworkCore.Database.Command to Warning (or Information if you need query logging).LogContext.PushProperty for request-scoped enrichment — Don't repeat the same property on every log call. Push it once at the middleware/filter level.Task.Run, IHostedService) loses ambient trace context. Explicitly capture and restore Activity.Current or start a new transaction linked to the parent.