ProductionOpsAugust 2026 · 7 min read

Production-Ready .NET Apps: Configuration, Logging, and Observability

TL;DR

Production-Ready .NET Apps earn their name through observability: IOptions-bound configuration that fails loudly, Serilog structured logging with correlation IDs, OpenTelemetry metrics and traces, and health checks that reflect real dependency state.

Production-Ready .NET Apps are only as trustworthy as their observability. Configuration tells the application how to behave, logging records what it did, and metrics reveal how well it performed — and in a .NET 10 application all three should be first-class concerns from the first commit, not retrofitted after an incident. This article walks through the exact configuration, logging, and observability setup that separates demo code from software you can operate in production.

Configuration: Where Production-Ready .NET Apps Keep Settings

ASP.NET Core loads configuration from a hierarchy of sources: JSON files, environment variables, and command-line arguments. The trick is giving each environment its own file and then binding the values into strongly typed options classes, so the compiler catches typos and runtime validation catches bad values. appsettings.Development.json, appsettings.Staging.json, and appsettings.Production.json each override the base file, and the environment variable ASPNETCORE_ENVIRONMENT decides which one wins.

// appsettings.Production.json
{
  "Database": {
    "ConnectionString": "Server=prod-db;Database=erp;Trusted_Connection=true;Encrypt=true"
  },
  "Features": {
    "AuditEnabled": true,
    "CacheTtlMinutes": 30
  }
}

// Program.cs
builder.Services.AddOptions<DatabaseOptions>()
    .Bind(builder.Configuration.GetSection("Database"))
    .ValidateDataAnnotations()
    .ValidateOnStart();

builder.Services.AddOptions<FeatureOptions>()
    .Bind(builder.Configuration.GetSection("Features"));

Validation on startup means a missing section or an out-of-range value stops the application before it ever accepts traffic. For anything sensitive — passwords, API keys, connection strings — keep the value out of the JSON file entirely and inject it through environment variables or a secrets manager, which the configuration provider reads with the same IOptions pattern.

The environment variable ASPNETCORE_ENVIRONMENT is what selects the active file: Development, Staging, or Production. Setting it once per environment removes a whole class of "works on my machine" defects, because the deployed process can never fall back to developer defaults. Add a small sanity check at startup that logs the resolved environment and the sections the application actually uses, and the next person to debug the system will thank you for the fifty seconds it saves.

Logging: Structured Output for Production-Ready .NET Apps

Text logs are almost useless at scale because you cannot query them reliably. Structured logging writes each event as key-value pairs, so the log line "the order failed" becomes something you can filter by OrderId, TenantId, and UserId. Serilog is the de facto standard on .NET, and its configuration syntax makes the switch nearly painless.

builder.Host.UseSerilog((ctx, cfg) => cfg
    .ReadFrom.Configuration(ctx.Configuration)
    .Enrich.WithCorrelationId()
    .WriteTo.Console()
    .WriteTo.Seq("http://seq:5341"));

var app = builder.Build();
app.UseCorrelationId();

Two practices matter more than the logger itself. First, correlation IDs: generate one per request or per circuit, pass it through every log call, and the whole journey of one user action becomes searchable. Second, never log secrets — no passwords, tokens, or PII in message templates. A structured logger makes this easier because values are typed, but the discipline still has to live in the code that writes the events.

Control verbosity from configuration, not from code. Set the minimum log level per environment — Information for development, Warning or Error for production — so production logs stay quiet until something actually needs attention. Wire a sink to an alerting channel, such as an email inbox or a chat room, and configure it to fire on Error and Fatal events. Structured logging then does double duty: it powers the daily debugging workflow and doubles as the early-warning system for incidents.

Observability: Metrics, Traces, and Health Checks in Production-Ready .NET Apps

Logging answers "what happened". Observability answers "what is happening right now". OpenTelemetry is the vendor-neutral standard for metrics and distributed traces in .NET 10, and the SDK is built into the ASP.NET Core ecosystem. Add the instrumentation packages, export to a collector such as Grafana or Azure Monitor, and you get request rates, latencies, and error counts without writing a single counter yourself.

builder.Services.AddOpenTelemetry()
    .WithMetrics(m => m
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddMeter("Microsoft.AspNetCore.Hosting"))
    .WithTracing(t => t
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddEntityFrameworkCoreInstrumentation());

Health checks close the loop between observability and operations. A ready endpoint verifies the database and downstream services before the load balancer sends traffic; a live endpoint only proves the process is alive. Configure both, wire the ready check to the same dependencies your features call, and your orchestration finally has accurate information to act on.

Building the Observability Stack

Put the three layers together and a single dashboard answers the questions that used to start with "hey, is it down?". Configuration makes the app behave identically in every environment, logs make every event traceable, metrics make performance visible, and health checks make recovery automatic. A request that slows down shows up as a latency spike in the metrics chart; the same request appears in the logs with its correlation ID; and the health check tells the orchestrator whether the dependency causing the slowdown is still available. That is the difference between a deployment and an incident waiting to happen.

Start minimal and expand only when a question actually needs answering. The mistake is not starting observability too late — it is starting with a tool sprawl that nobody reads. Three well-wired tools beat a dozen dashboards no one opens.

This is the stack every Indotalent product ships with out of the box. Each .NET 10 codebase includes environment-aware configuration, Serilog structured logging, OpenTelemetry instrumentation, and health checks — so the observability story described here is already written. Complete source code — $21 each.

Key Takeaways

  • Bind configuration into strongly typed IOptions classes and validate on startup.
  • Use environment-specific appsettings files plus environment variables for secrets.
  • Switch to structured logging with Serilog and add correlation IDs per request.
  • Instrument metrics and traces with OpenTelemetry and export to a collector.
  • Expose ready and live health endpoints that reflect real dependency state.
  • Indotalent products include this full observability stack in their .NET 10 source code.

FAQ

Is Serilog the only option for structured logging in .NET 10?

No. The built-in ILogger is also structured and works well. Serilog adds convenient configuration syntax, sink packages, and enrichment out of the box, which is why most production .NET codebases use it.

Do I need a metrics server like Prometheus to use OpenTelemetry?

You need an exporter, but Prometheus is only one option. OpenTelemetry exports to Azure Monitor, Grafana Cloud, Datadog, and many others, so you can start with whatever your team already uses.

What is the difference between a ready check and a live check?

A live check verifies the process is running; a ready check verifies dependencies like the database are reachable. Orchestrators restart unhealthy live instances and route traffic only to ready ones.

How does Indotalent handle observability in its products?

Every product ships as a .NET 10 codebase with environment-aware configuration, Serilog, OpenTelemetry instrumentation, and health checks already configured — full source at $21 each.

Ready to work with a fully observable codebase?

Every Indotalent product ships with Serilog, OpenTelemetry, health checks, and environment-aware configuration already wired up. Complete .NET 10 source code — $21 each.

Explore Products