Concepts
What you will learn: When payment is slow, determine whether time is spent in the application, database, or an external service, then choose a log, metric, or that answers the question. Connect measurements to a service goal and an alert that helps the team act before the issue grows.
Before you start: A log records an event, a metric summarizes a measurement over time, and a trace follows a request across components. is response time; an is a stated service target. No specific monitoring product is required—the key is knowing what question to answer.
Select a term with a dotted underline for its meaning, then continue reading from the same place.
#Logs, metrics and traces: different questions
- Assume a payment takes two seconds. A metric shows how many slow payments occurred in a window; a log records an event such as a particular bank rejection.
- A follows one request through the , database call and bank call. Each timed operation is a , with relationships showing the journey.
- If the bank call consumes most of the time, investigate that boundary first. Correlate logs with trace identity and remember that may omit individual traces.
“Payments are slow” is not a diagnosis. You need impact, the delayed stage and failure details. Observability is the ability to infer internal system behavior from its signals, rather than merely installing a dashboard.
| Signal | Question and trade-off |
|---|---|
| Logs | What happened in one event? Rich detail with volume and retention costs. |
| Metrics | How much traffic, and failure over time? Useful aggregation for alerts, with lost event detail. |
| Traces | Where did one request travel and wait? Related spans, with sampling and storage trade-offs. |
Connect signals with service, environment, version and trace identity. Sampling means not every trace is stored, and no error log does not prove success. Telemetry needs limits, redaction and export-failure monitoring too.
#Structured logging, LoggerMessage and Serilog
A flattened log string makes questions such as “which orders exceeded this amount?” require parsing. A message template preserves named properties when the provider retains structure. Keep property names stable across requests.
logger.LogInformation($"Order {orderId} paid {amount}");logger.LogInformation("Order {OrderId} paid {Amount}", orderId, amount);using Microsoft.Extensions.Logging;
public static partial class OrderLog
{
[LoggerMessage(EventId = 1001, Level = LogLevel.Information,
Message = "Order {OrderId} paid {Amount}")]
public static partial void Paid(
ILogger logger, Guid orderId, decimal amount); // (1)
}
// At the call site: OrderLog.Paid(logger, orderId, amount);
// Packages: Serilog.AspNetCore and Serilog.Sinks.Console
using Serilog;
builder.Host.UseSerilog((context, configuration) => configuration
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console()); // (2)
- (1) The generator implements the partial method and reduces and template work on hot logging paths. Its containing type must be partial.
- (2) Serilog receives structured events. Its default console rendering is text; use a suitable formatter when downstream expects . ILogger scopes and LogContext add context according to the integration.
| Older practice | Preferred approach |
|---|---|
| String concatenation and hand-written parsing | ILogger templates or LoggerMessage generated methods |
| Different schemas in every service | Shared property names, event IDs and redaction rules |
Do not log , passwords, card data or entire request bodies by default. Avoid logging one exception at every layer; record it at the boundary responsible for its outcome. See source-generated logging.
#Log levels, scopes and correlation
| Level | Appropriate use |
|---|---|
| / Debug | Temporarily enabled diagnostic detail with controlled volume and exposure. |
| Information | Expected operational milestones such as order completion. |
| Warning | Recoverable degradation or unexpected behavior, such as a transient retry. |
| Error | A failed operation requiring investigation, rather than every validation rejection. |
| Critical | A severe condition threatening service continuity or data integrity. |
W3C traceparent contains version, trace ID, parent ID and flags. Each span has its own ID, while related spans share a trace ID. A business correlation ID may span retries or days and need not equal a trace ID.
using System.Diagnostics;
using var scope = logger.BeginScope(new Dictionary<string, object?>
{
["OrderId"] = orderId,
["TraceId"] = Activity.Current?.TraceId.ToString() // (1)
});
logger.LogWarning("Payment confirmation is delayed"); // (2)
- (1) Activity.Current can be null; the provider must preserve scopes to expose these fields.
- (2) The scope supplies context without repeating IDs at every call. Some providers automatically add TraceId and SpanId when configured.
and HttpClient support propagation, but messages need context injection and extraction through or a framework. Inbound correlation headers are not identity evidence: validate format and size, and never put secrets in baggage.
#OpenTelemetry instrumentation and OTLP
- You want CreateOrder duration. records an operation's start and finish, producing a with a name, duration and bounded attributes.
- In , ActivitySource creates spans and Meter provides measurements. The configures collection, processing and export from selected sources.
- An exporter sends data using a protocol such as , possibly through a collector to a backend. Instrumentation, transport and storage are distinct roles; configuring one does not automatically configure the others.
Instrument application behavior independently of a specific backend. In .NET, ActivitySource produces spans and Meter produces measurements. The OpenTelemetry SDK selects sources, processes signals, samples and exports. OTLP is a transport protocol, rather than a database or dashboard.
// Packages: OpenTelemetry.Extensions.Hosting, OpenTelemetry.Exporter.OpenTelemetryProtocol,
// OpenTelemetry.Instrumentation.AspNetCore, OpenTelemetry.Instrumentation.Http,
// OpenTelemetry.Instrumentation.Runtime
using OpenTelemetry;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resource.AddService("orders-api")) // (1)
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("Shop.Checkout")
.AddOtlpExporter()) // (2)
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddRuntimeInstrumentation()
.AddMeter("Shop.Orders")
.AddOtlpExporter()); // (3)
builder.Logging.AddOpenTelemetry(logging => logging.AddOtlpExporter()); // (4)
- (1) The service name identifies the component; add version and environment resource attributes from deployment configuration.
- (2) Select spans and the custom source; application spans need their source registered for collection.
- (3) Measurements come from registered instrumentation and the custom meter.
- (4) Logs use a separate pipeline; WithTracing alone does not export ILogger events.
Configure OTLP endpoint, protocol, and authentication for the collector, commonly using OTEL_EXPORTER_OTLP_ENDPOINT. A collector can batch, redact and route telemetry. Bound queues and monitor dropped data; exporting does not prove storage, and unintended duplicate pipelines increase cost. See .NET OpenTelemetry guidance.
#Custom spans and bounded-cardinality metrics
An measures a request but may not explain an inventory-reservation delay. Add spans around meaningful business steps and metrics for their durations. Never use order IDs or emails as metric labels: each new value creates another time series.
using System.Diagnostics;
using System.Diagnostics.Metrics;
public static class OrderTelemetry
{
public static readonly ActivitySource Source = new("Shop.Checkout");
public static readonly Meter Meter = new("Shop.Orders");
public static readonly Counter<long> Completed =
Meter.CreateCounter<long>("orders.completed"); // (1)
public static readonly Histogram<double> Duration =
Meter.CreateHistogram<double>("orders.duration", "s"); // (2)
}
// Inside an async service method; ReserveAsync is an application operation.
using var activity = OrderTelemetry.Source.StartActivity("reserve-stock");
long started = Stopwatch.GetTimestamp();
try
{
await ReserveAsync(ct);
OrderTelemetry.Completed.Add(1);
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.GetType().Name); // (3)
throw;
}
finally
{
OrderTelemetry.Duration.Record(
Stopwatch.GetElapsedTime(started).TotalSeconds); // (4)
}
- (1) This counter counts successful reservations; define “completed” carefully rather than implying a paid order. Use a stage-specific name or expand to the entire operation.
- (2) The records a distribution in seconds for backend percentile calculation.
- (3) StartActivity can return null without a listener or when does not require an Activity. The example records failures; expected cancellation needs separate classification.
- (4) Finally measures successes and failures using an elapsed-time clock rather than DateTime.Now subtraction.
Keep ActivitySource and Meter long-lived. Bounded operation and outcome tags suit metrics; identifiers belong in appropriately protected logs or . Head sampling is cheaper, while collector tail sampling can select slow or failed traces at the cost of buffering and processing.
#RED, USE, histograms and percentiles
- Sort 100 request durations. If around 95 are at or below 300 milliseconds and the rest are slower, is around 300, depending on the estimator.
- The average can hide the slowest five. A retains a distribution in ranges, supporting threshold counts and percentile estimates.
- busyness is utilization; work waiting for CPU illustrates saturation. A growing wait queue adds information that high utilization alone does not provide.
describes services: Rate, Errors and Duration. describes resources: Utilization, Saturation and Errors. Utilization differs from saturation: queued CPU work or connection-pool waiters can reveal a bottleneck before requests fail.
| Instrument | Example |
|---|---|
| Counter | Requests and failures; calculate rates or deltas while accounting for process resets. |
| Gauge / observable gauge | Queue depth or a current value, rather than a cumulative count. |
| Histogram | or payload-size distributions with buckets useful for the . |
A p95 means approximately 95% of observations in the window are at or below that value, rather than necessarily 95% of users. Averages hide tail latency. Do not average instance p95 values; compatible distributions before computing the percentile.
Requests: 95 at 100 ms, 5 at 2000 ms // (1)
Mean: (95 * 100 + 5 * 2000) / 100 = 195 ms
Tail: five requests still take two seconds
SLO threshold: count requests <= 300 ms / eligible requests // (2)
- (1) A reasonable mean hides poor tail experience. Exact percentile output depends on estimator and buckets.
- (2) For a fixed threshold, a good-event ratio can measure the objective more directly than an approximate percentile.
#SLIs, SLOs, error budgets and alerts
An measures service experience, such as successful checkouts among eligible requests. An sets a target over a window; an is a contractual commitment with possible consequences. Define success, exclusions and measurement source: an 200 can still contain a failed business operation.
SLO: 99.9% successful eligible requests over 30 days
Budget fraction: 1 - 0.999 = 0.001 // (1)
At 1,000,000 eligible requests: 1,000 failures allowed
Observed error ratio: 1% => burn rate = 0.01 / 0.001 = 10 // (2)
- (1) This is a request-based budget, rather than uptime minutes; do not switch units without defining the SLI.
- (2) A ten-times burn rate exceeds the sustainable rate, but exhaustion still depends on duration and traffic.
Page for actionable symptoms: user failures, , oldest-message age and sustained rapid budget burn. Combine short and long windows to confirm impact and reduce noise. Each alert needs an owner, runbook, dashboard and action. High alone often belongs in investigation or capacity planning rather than automatic paging.
The budget informs priorities: heavy consumption favors reliability work and lower deployment risk under an agreed policy, rather than an automatic ban on all feature development.
#Liveness, readiness and startup checks
Liveness asks whether the process needs restarting; readiness asks whether it can accept traffic now. An orchestrator's startup probe can allow initialization time before normal probes apply. Restarting cannot repair an unavailable external database; place that dependency in readiness if it is essential.
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
builder.Services.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy(), tags: ["live"])
.AddCheck<DatabaseReadiness>("database", tags: ["ready"]); // (1)
// After builder.Build():
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = registration => registration.Tags.Contains("live") // (2)
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = registration => registration.Tags.Contains("ready")
});
// Application check; IOrderStore is an injected database adapter.
public sealed class DatabaseReadiness(IOrderStore store) : IHealthCheck
{
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context, CancellationToken ct = default)
{
return await store.CanConnectAsync(ct)
? HealthCheckResult.Healthy()
: HealthCheckResult.Unhealthy("Database unavailable"); // (3)
}
}
- (1) The custom check requires an adapter registered in ; implement CanConnectAsync as a cheap, time-bounded probe. Thrown exceptions are classified by the health-check framework under registration settings.
- (2) A self response proves the pipeline can respond, rather than establishing all business behavior is healthy.
- (3) The platform can remove an unready instance from traffic without restarting it. Restrict public dependency details.
Do not make every optional dependency a readiness requirement; that can remove all useful degraded service. Bound timeouts, probe frequency and thresholds so do not overload dependencies. See health-check guidance.
#Production debugging and .NET diagnostics
Start with impact rather than a tool. When users are failing now, a safe mitigation can matter more than completing the investigation first.
- Establish affected users, failure rate, timing and critical paths; choose evidence-based rollback, feature or dependency mitigation.
- Use metrics to narrow endpoint, region and version, comparing deployments and traffic changes.
- Follow an affected 's critical path and read related logs using its trace ID.
- Test one hypothesis with suitable evidence: samples, thread stacks or heap retention.
- Reproduce, add a regression test, roll out a fix gradually and watch the same .
- Record a blameless timeline, contributing factors and owned, dated follow-up actions.
# Tools must be installed; replace 1234 with the target process ID.
dotnet-counters monitor --process-id 1234 --counters System.Runtime
dotnet-trace collect --process-id 1234 --duration 00:00:30
dotnet-dump collect --process-id 1234 --output process.dmp
dotnet-dump analyze process.dmp
| Tool | Evidence |
|---|---|
| dotnet-counters | CPU, , and thread-pool trends; growing queues with low CPU can suggest starvation but are not proof. |
| dotnet-trace | EventPipe events and configured profiles over a bounded window; distinct from a distributed request trace. |
| dotnet-dump | A process snapshot for managed stacks, heap and GC roots; not a full native debugger. |
Bound collection duration, disk use and operational impact. Dumps can contain and personal data. A growing heap is not automatically a leak; inspect retention and roots over time. A high thread count alone does not justify increasing ThreadPool minimums. See counters, trace collection and dump analysis.
#The Aspire dashboard in the development loopBonus
Optional but valuable: not asked in every interview, but knowing it deepens your understanding and sets you apart.
Running an , database and locally can make cross-service debugging difficult. Aspire provides local orchestration and service defaults; its dashboard presents resource status, logs, and metrics from connected applications and helps follow requests between services.
- Start resources through an AppHost or use a standalone dashboard appropriate to the environment.
- Point exporters at the endpoint, protocol and credentials provided by that environment.
- Issue a request, inspect its trace and navigate to related logs and resources.
- Simulate a slow or failed dependency and inspect actual timeout, retry and readiness behavior.
Service defaults are template choices, rather than automatic of every business operation. A local dashboard does not automatically replace production storage, retention and alerting. Secure network access because telemetry contains sensitive operational detail. See the Aspire dashboard overview.
Interview questions
No results
Check your reasoning
Why does this choice fit? When would it fail? What changes under higher load or a dependency failure?
20.1What are logs, metrics and traces used for?Junior
I use metrics to understand behavior such as traffic, failure rate and , logs for detailed event context, and to follow a request across components. I connect them with service metadata and trace identity so I can move from a symptom to an affected request and its details. Each has a cost and limitation: metrics lose event detail, logs can become expensive, and sampled traces may not include every request. Together they support investigation rather than automatically proving a root cause.
20.2Why use structured message templates instead of interpolation?Junior
I use stable message templates so values remain named properties rather than disappearing into rendered text. That lets a backend filter by OrderId or by outcome without parsing arbitrary strings. Interpolation also evaluates the string before the logging call, even when that level is disabled. For hot paths I consider source-generated LoggerMessage methods to reduce overhead further. I still check the provider and output format, because structured input does not guarantee every sink stores structured fields.
20.3What does LoggerMessage source generation provide?Mid
I use LoggerMessage attributes on partial methods in partial types to generate strongly typed logging implementations at compile time. This reduces repeated template processing and compared with common extension-method calls, and diagnostics catch signature mistakes. It is useful for frequent events with stable schemas. It does not replace ILogger providers, retention policies or privacy controls. I still choose meaningful levels and event IDs, avoid expensive arguments when disabled, and ensure the configured sink preserves the properties I need.
20.4How do you choose log levels?Junior
I choose levels according to operational meaning. Information records useful expected milestones, Warning records recoverable degradation, Error records failed operations requiring investigation, and Critical indicates a severe threat to service continuity or integrity. Debug and provide temporary diagnostic detail with controlled volume. I do not turn every rejected input into an error or log the same exception at every layer. The team agrees on these meanings so dashboards and alerts reflect impact rather than inconsistent developer preferences.
20.5How do trace IDs, span IDs and correlation IDs differ?Mid
I use a ID to identify a distributed execution and a ID to identify one operation inside it. W3C traceparent carries trace context so downstream services can continue the relationship. A business correlation ID can connect a longer workflow, including retries or separate traces, and therefore need not equal the trace ID. I propagate context through and explicitly through messaging integrations. I validate external correlation fields and never treat them as authentication or permission evidence.
20.6What does OpenTelemetry do in a .NET application?Mid
I use OpenTelemetry to collect and export telemetry through standard and configurable pipelines. In , ActivitySource and Activity represent , while Meter produces measurements. I register the relevant sources and meters, configure and resources, and export through to a collector or backend. Logs have their own pipeline. OpenTelemetry is not itself a storage database or dashboard, and changing backends can still require configuration and semantic adjustments even when application instrumentation stays the same.
20.7Why can ActivitySource.StartActivity return null?Mid
I expect StartActivity to return null when no interested listener exists or the decision does not require an Activity. That keeps unobserved inexpensive. I use nullable access for tags and status, and register the ActivitySource name in the OpenTelemetry tracing configuration when I want it collected. I also avoid constructing expensive diagnostic payloads unnecessarily. Instrumentation should preserve business behavior whether a listener is present, tracing is sampled or the exporter is temporarily unavailable.
20.8What are RED and USE metrics?Mid
I use to describe a service through request rate, errors and duration, and to inspect resources through utilization, saturation and errors. RED tells me whether users are experiencing a problem; USE helps investigate whether , memory, disk or connection capacity contributes. I distinguish busy resources from queued demand, because high utilization alone is not always harmful. I correlate these measurements with deployments and instead of assuming one high resource value proves the cause.
20.9Why use histograms for latency, and can you average p95 values?Mid
I use because is a distribution and an average can hide a small group of very slow requests. A backend estimates percentiles from the observations or buckets, so bucket boundaries and the measurement window matter. I do not average values from different instances because that does not produce the overall p95. I compatible distributions first. For a fixed latency objective, I also measure the fraction of eligible requests below the threshold directly.
20.10What is metric cardinality and why does it matter?Senior
I treat as the number of distinct label combinations producing time series. Adding user IDs or order IDs can grow that count with every request, increasing memory, storage and query costs. I use bounded dimensions such as operation and outcome for metrics, and move individual identifiers to protected logs or where appropriate. I also review combinations of seemingly small dimensions, because their product can still become large, and monitor dropped measurements and backend limits.
20.11Explain SLIs, SLOs and error budgets.Senior
I define an as a measurement of service experience and an as its target over a specified window. The error budget is the allowed shortfall, such as the permitted fraction of failed eligible requests. I define eligibility, business success and the data source before calculating it. Burn rate compares observed failures with the sustainable budget rate. The budget then guides reliability priorities and deployment risk under an agreed policy, rather than becoming a number disconnected from user experience.
20.12What should trigger an on-call alert?Senior
I page for actionable user-impacting symptoms or rapid sustained budget consumption, rather than every unusual infrastructure value. Examples include checkout failures, excessive and a growing age of unprocessed payment events. I use suitable windows and thresholds to avoid flapping and low-volume noise. Every page has an owner, a runbook and a useful next action. Resource signals such as high remain valuable for diagnosis and capacity planning even when they do not justify waking someone immediately.
20.13How do liveness and readiness differ?Mid
I use liveness to indicate whether restarting the process is appropriate and readiness to indicate whether it should receive traffic. A database outage can make a service unready without making the process itself unhealthy for liveness. Putting shared dependencies in liveness can create restart storms without repairing the dependency. I keep checks cheap and bounded, include only essential readiness dependencies, and let startup probing accommodate initialization when supported by the platform. Detailed health output is restricted rather than exposed publicly.
20.14How do you investigate a production performance incident?Senior
I establish the affected users, timing and severity, then mitigate safely through rollback or when needed. Metrics narrow the endpoint, version and resource pattern; show the critical path, and correlated logs add details. I choose diagnostics based on a hypothesis: counters for trends, a bounded trace for sampled execution, or a dump for retained objects and stacks. I reproduce the issue, add a regression test, roll out gradually and document contributing factors with owned follow-up actions.
20.15How does the Aspire dashboard help, and what are its limits?Mid
I use the Aspire dashboard to inspect connected resources and navigate between logs, metrics and distributed during development. Service defaults and local orchestration make it easier to reproduce dependency failures and verify propagation. I still instrument meaningful business operations and configure exporters correctly. The dashboard does not automatically provide a production retention and alerting strategy, and standalone use needs deliberate connectivity and authentication. I protect access because telemetry can expose operational details and sensitive application context.
Test yourself
Quiz answers and completed-topic marks are saved in this browser; they do not sync across devices.
Which signal follows one request through API, database and payment service?
Why prefer a stable message template in logging?
What should you do when StartActivity returns null?
Which label is generally suitable for a request-count metric?
How should you compute a service-wide p95 across instances?
A 99.9% request-success SLO permits how many failures per million eligible requests?
The database is unavailable but the API process responds normally. Which is appropriate?
What is OTLP?
Which tool is suited to inspecting a managed heap snapshot and GC roots?
What makes an on-call page useful?