ASP.NET Core Logging & Observability Interview Questions for Senior .NET Developers (2026)
Logging and observability have quietly become one of the most reliable differentiators between junior and senior .NET candidates. Most developers can configure Serilog to write to a file โ but senior engineers are expected to reason about structured logging strategies, OpenTelemetry correlation, health check semantics, and what "production-ready observability" actually means for a distributed system. The interview questions in this guide cover exactly that territory. If you want to go deeper with hands-on code and production-ready patterns beyond the conceptual level, the full annotated implementations are available on Patreon โ wired together in a complete ASP.NET Core API so you can see how each piece fits.
Chapter 14 of the ASP.NET Core Web API: Zero to Production course covers structured logging with Serilog, OpenTelemetry traces and metrics, and health check endpoints with liveness and readiness semantics โ exactly the topics that come up in senior .NET interviews. It walks through a full production codebase so the context is always clear.
The questions are grouped by difficulty โ Basic, Intermediate, and Advanced โ so you can start with fundamentals and work toward the senior-level topics that separate candidates in 2026.
Basic Questions
What is the difference between ILogger<T> and ILoggerFactory in ASP.NET Core?
ILogger<T> is a strongly-typed logger scoped to a specific class. The generic type argument T becomes the category name, which is typically the fully qualified class name. This makes it easy to filter logs by class in production.
ILoggerFactory is a lower-level abstraction used to create loggers with arbitrary category names. It is useful in cases where the category name cannot be determined at compile time โ for example, inside a helper class that creates child loggers dynamically.
In practice, inject ILogger<T> in your application classes. Use ILoggerFactory only when you genuinely need to create loggers with dynamic category names, such as inside framework-level infrastructure code.
What are log levels in ASP.NET Core, and when should you use each?
ASP.NET Core defines six log levels in ascending severity:
- Trace โ the most detailed, typically disabled in production; used for step-by-step flow tracing during development.
- Debug โ development-time diagnostics; disabled by default in production.
- Information โ significant application events such as startup, request completion, and business-level milestones.
- Warning โ unexpected but recoverable situations; for example, a configuration value falling back to a default.
- Error โ a failure that affects the current operation but not the overall application; for example, an unhandled exception for a single request.
- Critical โ a failure that requires immediate attention and may prevent the application from continuing; for example, a database connection failure at startup.
At a senior level, the key distinction interviewers expect is: use Warning for recoverable anomalies that indicate a potential problem worth investigating, and use Error only for failures that impact a specific operation without crashing the process. Over-using Error creates alert fatigue in production monitoring systems.
What is structured logging, and why does it matter?
Structured logging means recording log entries as structured data โ typically key-value pairs โ rather than plain text strings. Instead of writing "User 42 placed order 99", you write a log event with discrete properties: userId = 42, orderId = 99, eventType = "OrderPlaced".
This matters in production because structured logs can be queried, aggregated, and filtered by tools like Seq, Grafana Loki, or Azure Application Insights without parsing text. You can ask: "Show me all requests where orderId = 99 completed with a 500 status" and get exact results.
In ASP.NET Core, structured logging is supported through the ILogger message template syntax using named placeholders: _logger.LogInformation("Order {OrderId} placed by user {UserId}", orderId, userId). The curly-brace syntax binds values to named properties rather than positional string substitution.
How do you configure Serilog in ASP.NET Core?
Serilog replaces the built-in logging pipeline via UseSerilog() on the host builder. You configure it by defining sinks (outputs) such as Console, File, or Seq, and setting minimum log levels per namespace.
A minimal production-ready setup typically uses at least a Console sink for container environments and a rolling-file sink for local debugging. Enrichers like FromLogContext() and WithMachineName() add contextual properties automatically to every log event.
The recommended pattern is to configure Serilog early in Program.cs โ before the host is built โ so that startup errors are captured rather than silently lost.
For a detailed comparison of Serilog, NLog, and the built-in ILogger across enterprise scenarios, see ASP.NET Core Structured Logging: Serilog vs NLog vs ILogger.
What is UseSerilogRequestLogging() and when should you use it?
UseSerilogRequestLogging() is middleware from the Serilog.AspNetCore package that replaces ASP.NET Core's default per-request log output with a single, richer structured event per request.
The default ASP.NET Core logging emits two or more log lines per request across different categories. Serilog's middleware collapses these into a single event that includes the HTTP method, path, status code, elapsed milliseconds, and any properties added to the log context during the request.
Use it in every production ASP.NET Core application. It reduces log volume, improves query performance in log aggregation tools, and makes request-level diagnostics significantly easier.
Intermediate Questions
What is the difference between Log.Warning and _logger.LogWarning, and which should you use?
Log.Warning is the static Serilog.Log API โ a static facade that writes to the globally-configured Serilog logger. _logger.LogWarning uses the ILogger<T> interface injected via ASP.NET Core's DI system.
Always prefer _logger.LogWarning (the ILogger<T> interface) in application code. It is testable โ you can mock ILogger<T> in unit tests and verify log calls. It is also DI-friendly, keeping your code decoupled from the specific logging library.
The static Log facade is acceptable for bootstrapping code (e.g., early Program.cs startup before DI is available), but it should not be used inside services, controllers, or handlers.
What are log scopes in ASP.NET Core, and how do you use them?
Log scopes allow you to attach contextual properties to all log events emitted within a defined code block. They are created using ILogger.BeginScope().
A common use case is attaching a correlation ID or tenant ID to every log event produced while processing a specific request or job, without having to pass the value explicitly to every log call. When the scope is disposed (typically at the end of a using block), the properties are automatically removed.
In Serilog, scopes map to enrichment via LogContext.PushProperty(). ASP.NET Core's ILogger interface bridges the two transparently when Serilog is configured as the provider.
Senior candidates are expected to distinguish between log scopes (contextual enrichment within a code block) and log categories (the class-level name on ILogger<T>) โ they serve different purposes and are often confused.
What is the three-pillar model of observability, and how does ASP.NET Core support it?
Observability in distributed systems is built on three signals:
- Traces โ distributed, causally-linked records of work across services; essential for understanding latency and failures in request flows that span multiple components.
- Metrics โ numerical measurements over time (counters, gauges, histograms); used for dashboards, SLOs, and alerting.
- Logs โ discrete, time-stamped records of events with context; used for debugging and audit.
ASP.NET Core supports all three natively. Structured logging via ILogger covers logs. The System.Diagnostics namespace with Activity and ActivitySource covers traces. System.Diagnostics.Metrics covers metrics.
OpenTelemetry provides the vendor-neutral export layer that connects these signals to backend platforms like Jaeger, Prometheus, Azure Monitor, or Grafana LGTM.
Senior candidates are expected to articulate how all three work together โ not treat them as separate tools.
How does OpenTelemetry work in ASP.NET Core, and why is it preferred over direct SDK integrations?
OpenTelemetry is a vendor-neutral observability framework that provides a single API and SDK for capturing traces, metrics, and logs. In ASP.NET Core, it integrates via the OpenTelemetry.Extensions.Hosting package.
You configure it by adding trace providers (e.g., AddAspNetCoreInstrumentation(), AddHttpClientInstrumentation()), metric providers, and exporters in Program.cs. The exporters send data to your observability backend โ OTLP for Jaeger/Tempo/Grafana, Prometheus for metrics, or Azure Monitor for Microsoft-stack shops.
The advantage over direct SDK integrations (such as the Application Insights SDK or Datadog's .NET agent) is portability. With OpenTelemetry, you can switch your observability backend without changing application code. You write instrumentation once; exporters handle the transport.
For a full walkthrough, see OpenTelemetry in ASP.NET Core: A Complete Guide for .NET Developers.
What is the difference between liveness, readiness, and startup probes in ASP.NET Core health checks?
These three probe types come from Kubernetes terminology, and ASP.NET Core health checks map directly to them:
Liveness โ answers "Is this process alive and functional?" If a liveness check fails, Kubernetes restarts the pod. A liveness check should be minimal โ typically just verifying that the application is not deadlocked or in an unrecoverable error state. Connecting to a database in a liveness check is an anti-pattern; a flaky DB connection should not restart your pod.
Readiness โ answers "Is this instance ready to receive traffic?" A readiness check failure removes the pod from the load balancer rotation without restarting it. Include dependency checks here (database reachability, downstream service availability) if those dependencies are required to serve requests.
Startup โ answers "Has the application finished initialising?" Relevant for apps with long startup sequences. Kubernetes waits for the startup probe to pass before enabling liveness and readiness probes.
In ASP.NET Core, you register health checks via AddHealthChecks() and tag each check with HealthCheckTags.Live or HealthCheckTags.Ready. Map them to separate endpoints: /health/live for liveness and /health/ready for readiness.
For a deeper comparison of probe semantics, see ASP.NET Core Health Checks: Liveness vs Readiness vs Startup Probes.
You can also explore production-ready health check implementations in the GitHub repo โ with custom IHealthCheck implementations, tagged checks, and Kubernetes-ready endpoints.
What is AddDbContextCheck() and when should you use it?
AddDbContextCheck<TContext>() is a built-in health check extension from Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore that verifies a DbContext can connect to its database. It executes a CanConnectAsync() call against the database under the hood.
Use it in your readiness check, not your liveness check. A database connectivity failure means your application cannot serve requests (readiness concern), but it does not mean the application process itself is unhealthy (liveness concern).
Important caveat: AddDbContextCheck() only verifies connectivity, not schema consistency or query performance. For production scenarios, consider supplementing it with a lightweight query against a known table.
Advanced Questions
How do you implement correlation IDs in a distributed ASP.NET Core system, and how do they connect to distributed traces?
Correlation IDs are unique identifiers attached to a request that flow through all log events and downstream calls produced during that request's lifetime. They enable you to reconstruct the complete execution path for a single user request across multiple services and log aggregation queries.
In ASP.NET Core, the common pattern is middleware that inspects an X-Correlation-ID header on incoming requests. If the header is present, it uses the provided value; if not, it generates a new Guid. The ID is then added to the current ILogger scope via LogContext.PushProperty() (Serilog) or BeginScope() (ILogger), so every log event in that request automatically includes the correlation ID.
For distributed traces, the W3C traceparent header propagation built into System.Diagnostics.Activity and OpenTelemetry is the modern replacement. Activity.TraceId and Activity.SpanId are the trace-native equivalents of manual correlation IDs. The key insight for senior candidates: when using OpenTelemetry, you should correlate log events to trace spans using Activity.Current.TraceId rather than managing a separate correlation ID.
What is LoggerMessage source generation, and when should you use it?
LoggerMessage is a code-generation approach to structured logging that pre-compiles log messages into strongly-typed static delegates, eliminating the allocations and string parsing overhead of the standard ILogger.Log methods.
There are two approaches: the classic LoggerMessage.Define() factory methods (pre-.NET 6) and the newer source-generated approach using [LoggerMessage] attribute on partial methods (available from .NET 6 onwards). The source-generated version is cleaner and recommended for all new code.
Use LoggerMessage-generated methods in hot paths โ high-throughput endpoints, inner loops, or request handlers where the logging overhead is measurable. In practice, this means anywhere you might log hundreds of thousands of events per second.
For typical line-of-business APIs, the overhead of regular ILogger.LogInformation() calls is negligible. The cost-benefit of LoggerMessage is only meaningful at high throughput. Senior candidates should know when to use it โ not reflexively apply it everywhere.
How do you handle PII (Personally Identifiable Information) in ASP.NET Core logs?
PII in logs is a compliance and security risk. Strategies for managing it fall into three categories:
1. Destructuring policies (Serilog): Serilog's IDestructuringPolicy lets you intercept and redact or mask properties on objects before they are serialised into log events. You can register policies that replace email addresses, phone numbers, or credit card fields with masked equivalents.
2. Log templates discipline: The simplest approach is never logging raw user-supplied data. Pass only identifiers (e.g., userId, orderId) into log messages, never full names, emails, or addresses. This requires consistent code review discipline rather than automated enforcement.
3. Minimum log levels and filter configuration: Set minimum log levels for namespaces that may produce PII-heavy output (e.g., Microsoft.AspNetCore.HttpLogging logs request/response bodies by default). Disable body logging in production unless you have an explicit, compliant reason to enable it.
The key point for senior interviews: PII handling in logs is not just a technical problem โ it is a governance problem. The technical controls enforce what the team decides as policy. Engineers need to understand both layers.
What is the difference between metrics, traces, and logs, and when would you use each for production debugging?
These three signals serve different diagnostic purposes and are most effective when used together:
Logs are the first tool for understanding what happened during a specific request or operation. They provide exact event records with context. Use logs when you know the time window and can search by entity identifiers.
Metrics tell you the shape of a problem across the fleet. High error rate, elevated p99 latency, or a drop in requests per second are all visible in metrics before any individual log is examined. Use metrics for detection โ they tell you something is wrong before you know what.
Traces bridge the gap between "something is slow" (metrics) and "here is exactly where the time went" (logs). A distributed trace shows the causal chain of spans across services, making latency attribution in multi-service architectures possible. Use traces when you need to understand where time is spent or where failures propagate across service boundaries.
The senior-level answer is not to choose one โ it is to use all three in the correct sequence: metrics surface the problem, traces isolate the location, logs explain the details.
How do you avoid common structured logging mistakes in production ASP.NET Core APIs?
The most costly structured logging mistakes in production:
1. Synchronous sinks on hot paths. File and database sinks should always be configured as asynchronous. Synchronous writes from within request-handling code add measurable latency under load. Serilog's WriteTo.Async() wrapper buffers writes off the request thread.
2. Over-logging at high severity. Excessive use of LogError for expected, recoverable conditions (e.g., 404 Not Found) causes alert noise. LogWarning for 4xx client errors and LogError only for 5xx server-side failures is the correct split.
3. String interpolation instead of structured templates. _logger.LogInformation($"User {userId} logged in") loses the structured property. _logger.LogInformation("User {UserId} logged in", userId) preserves it. This is a common mistake that turns structured logging back into plain text.
4. Capturing exception messages on 500s. exception.Message should never appear in 500-level responses. Log the full exception object (via LogError(ex, ...)) for internal diagnostics, but never expose it to API consumers.
5. No minimum log level configuration per namespace. Logging Microsoft.EntityFrameworkCore at Verbose in production generates enormous SQL query logs. Always configure per-namespace minimum levels in appsettings.json.
For a more detailed treatment of these patterns, see 7 Common ASP.NET Core Logging Mistakes (And How to Fix Them).
โ Prefer a one-time tip? Buy us a coffee โ every bit helps keep the content coming!
Frequently Asked Questions
What observability topics are most commonly asked about in senior .NET interviews?
The most frequent topics are structured logging (Serilog configuration, named placeholders, enrichers), OpenTelemetry integration (auto-instrumentation, trace correlation, exporters), health check semantics (liveness vs readiness), and production anti-patterns like logging PII or using synchronous sinks. Interviewers in 2026 also increasingly ask about LoggerMessage source generation for performance-conscious candidates.
Is knowing Serilog required for .NET senior interviews, or is the built-in ILogger enough?
You are expected to understand ILogger<T> deeply โ it is the abstraction used in all production code. Serilog knowledge is a strong differentiator. Most production .NET teams use Serilog or NLog as the logging provider behind ILogger, and interviewers at companies running real production systems will often ask about Serilog specifically. Understanding both the abstraction and a concrete provider is the safe baseline.
What is the difference between OpenTelemetry and Application Insights SDK? Application Insights SDK is a Microsoft-specific, proprietary telemetry solution that sends data directly to Azure Monitor. OpenTelemetry is a vendor-neutral standard that can export to any compatible backend, including Azure Monitor (via the Azure Monitor Exporter), Jaeger, Grafana, or Datadog. OpenTelemetry is the modern, portable choice; the Application Insights SDK ties you to Azure. In interviews, the correct answer is to prefer OpenTelemetry for new projects and use the Azure Monitor Exporter if you need Azure Monitor.
How do I explain health checks to a non-technical interviewer or system design discussion? Frame it as: "Health checks expose endpoints that tell infrastructure orchestrators โ like Kubernetes โ whether this instance is ready to serve requests and whether it is functioning correctly." Liveness tells Kubernetes whether to restart the pod. Readiness tells the load balancer whether to route traffic to it. The analogy is a car's dashboard warning lights: one tells you to pull over immediately (liveness), another tells you the engine isn't warm enough yet (readiness).
What is Activity.TraceId, and how does it relate to distributed tracing in ASP.NET Core?
Activity.TraceId is a 16-byte identifier from System.Diagnostics.Activity that uniquely identifies a distributed trace across all services involved in a single request. In ASP.NET Core with OpenTelemetry, the W3C traceparent header propagates this ID between services automatically. Every span in the trace shares the same TraceId, enabling tools like Jaeger or Grafana Tempo to stitch all related spans into a single trace view. Activity.SpanId identifies the individual unit of work within that trace.
Should I log request and response bodies in production for debugging purposes? Generally, no. Request and response body logging should be disabled in production unless there is a specific, compliance-reviewed need. Body logging at scale adds significant I/O overhead, risks capturing PII or sensitive tokens, and generates enormous log volume. For targeted debugging, prefer structured event logs with entity identifiers and status codes. If body logging is genuinely needed for compliance audit purposes, ensure it is scoped to specific endpoints, PII is masked, and retention is governed by your data policies.
What's the cleanest way to add a correlation ID to every log event in ASP.NET Core?
Register middleware early in the pipeline that extracts or generates the correlation ID, stores it in HttpContext.Items, and pushes it to the ILogger scope with LogContext.PushProperty() (Serilog) or BeginScope() (ILogger). All downstream middleware, controllers, and services then emit log events that automatically include the correlation ID without any additional plumbing. This is preferable to passing the ID as a parameter through every method call.






