Guide to .NET 10 LTS Application Monitoring and Logging on IIS

When a .NET app misbehaves in production, the difference between a five-minute fix and a five-hour outage is whether you can see what happened. Logging and monitoring aren't glamorous, but they're the cheapest insurance you'll ever buy — and on Windows + IIS hosting you have several complementary layers to wire up, from your application's own ILogger calls down to IIS's stdout capture and up to modern OpenTelemetry traces.

This guide is a practical, end-to-end walkthrough of observability for .NET 10 LTS apps on Windows Server 2022 + IIS 10: structured application logging, Serilog sinks, IIS-level diagnostics, health checks IIS can watch, OpenTelemetry, and the production hygiene that keeps it all useful instead of noisy. Everything here is achievable on Adaptive Web Hosting's managed Windows plans through the Plesk panel — no server-admin access required.

OTelFirst-class in .NET 10

/healthPlesk-pingable checks

SQL 2022Included log sink

The three layers of observability on Windows + IIS

Good monitoring isn't one tool — it's three layers that answer different questions. Application logs tell you what your code did. Platform logs tell you what IIS and the runtime did around your code. Telemetry tells you how the whole system behaves over time.

ILogger / Serilog

Structured logs from your own code, to rolling files and SQL Server. The bulk of your day-to-day debugging signal.

✅ Platform

IIS + ASP.NET Core Module

stdout capture, W3C request logs, and Failed Request Tracing — invaluable when the app won't even start.

✅ Telemetry

OpenTelemetry / health checks

Metrics, traces, and liveness probes that show trends and let Plesk monitoring watch your app.

Structured application logging with ILogger

.NET 10 ships with Microsoft.Extensions.Logging built in. The one habit that makes logs searchable later is structured logging — pass values as named parameters, never string-interpolate them into the message:

public class OrderService(ILogger<OrderService> logger)

{

public async Task ProcessAsync(string orderId)

{

// ✅ Structured: orderId is a searchable property, not baked into text

logger.LogInformation("Processing order {OrderId}", orderId);

using (logger.BeginScope("Order {OrderId}", orderId))

{

try

{

// ... work ...

logger.LogInformation("Order {OrderId} completed in {ElapsedMs}ms",

orderId, sw.ElapsedMilliseconds);

}

catch (Exception ex)

{

logger.LogError(ex, "Order {OrderId} failed", orderId);

throw;

}

}

}

}

logger.LogInformation("Order {OrderId} failed", id) lets you later query "show every log where OrderId = ORD-123" across your whole sink. $"Order {id} failed" throws that structure away the moment it's written. Same effort, vastly more useful.

Set log levels per environment in appsettings.json — verbose in development, Warning and above in production so the signal isn't buried:

// appsettings.Production.json

{

"Logging": {

"LogLevel": {

"Default": "Warning",

"Microsoft.AspNetCore": "Warning",

"YourApp": "Information"

}

}

}

Serilog with file and SQL Server sinks

The built-in logger is fine, but Serilog adds the sinks you actually want on Windows hosting: rolling files for high-volume debug data and a SQL Server table for structured, queryable warnings and audit events. Adaptive Web Hosting includes SQL Server 2022 on every plan, so the SQL sink needs no extra service.

// Program.cs

builder.Host.UseSerilog((context, config) => config

.MinimumLevel.Information()

.Enrich.FromLogContext()

.WriteTo.File(

path: @".\logs\app-.log", // app-relative, always writable

rollingInterval: RollingInterval.Day,

retainedFileCountLimit: 14)

.WriteTo.MSSqlServer(

connectionString: context.Configuration.GetConnectionString("Default"),

sinkOptions: new() { TableName = "Logs", AutoCreateSqlTable = true },

restrictedToMinimumLevel: LogEventLevel.Warning));

Write log files to a path inside your own site folder (e.g. .\logs\) — it's guaranteed writable, unlike system directories on shared hosting. And keep the SQL sink at Warning+: logging every Information row to a database hammers it and burns your included DB capacity. High-volume detail belongs in files.

Rolling files with a retention limit prevent logs from quietly filling your disk allocation. If you do log heavily to SQL Server, index the table and prune old rows — our SQL Server optimization guide covers keeping write-heavy tables fast.

IIS-level logging: stdout, W3C, and Failed Request Tracing

When your app logs nothing because it never started, you need the layer below your code. The ASP.NET Core Module can capture the worker process's stdout/stderr — the fastest way to see a startup crash:

<!-- web.config -->

<aspNetCore processPath="dotnet" arguments=".\YourApp.dll"

stdoutLogEnabled="true"

stdoutLogFile=".\logs\stdout"

hostingModel="inprocess" />

stdout logging is a diagnostic, not a permanent sink — it has no rotation and can grow unbounded. Switch it on to catch a startup failure like HTTP 500.30 or to work through why an app won't start on IIS, read the crash, then set stdoutLogEnabled="false" again.

Two more IIS-level tools:

W3C request logs — IIS records every request (URL, status, time-taken, client IP). These are your access logs; pair them with app logs to correlate a slow page with what the code was doing.

Failed Request Tracing (FREB) — the right tool for intermittent errors. Configure a rule that captures a detailed trace only when a request returns, say, a 500 or takes over N seconds. It records the full pipeline so you can see exactly where a sporadic failure happens.

For recurring gateway-level failures, our HTTP 502 Bad Gateway guide shows how to read these IIS signals together.

Windows Event Log — and its limits on shared hosting

.NET 10 can write to the Windows Event Log via the EventLog provider, which is handy for surfacing critical errors in a familiar place:

builder.Logging.AddEventLog(settings =>

{

settings.SourceName = "YourApp";

settings.LogName = "Application";

});

On shared Windows hosting you typically don't have machine-wide Event Log administration or the rights to register a new event source — that's a server-admin operation. Treat the Event Log as a nice-to-have and make your file + SQL Server sinks (and an external APM) the system of record. They live entirely inside resources you control.

Health checks IIS can watch

A health endpoint turns "is the app alive?" from guesswork into a single HTTP call. .NET 10's health-checks library lets you compose checks — including a real SQL Server probe — and split liveness (is the process up?) from readiness (can it serve traffic?):

builder.Services.AddHealthChecks()

.AddSqlServer(

connectionString: builder.Configuration.GetConnectionString("Default")!,

name: "sql-server",

tags: ["ready"]);

var app = builder.Build();

// Liveness: process is running

app.MapHealthChecks("/health/live", new() { Predicate = _ => false });

// Readiness: dependencies (SQL Server) are reachable

app.MapHealthChecks("/health/ready", new()

{

Predicate = check => check.Tags.Contains("ready")

});

Point the Plesk monitoring panel at your /health/ready endpoint so you're alerted the moment your app can't reach SQL Server — often before a single user notices. Combine it with IIS Application Initialization (covered in our Blazor Server scalability guide) so the first health check hits a warm worker.

OpenTelemetry in .NET 10

OpenTelemetry (OTel) is now the standard, well-supported way to emit metrics and distributed traces from .NET. Instrument ASP.NET Core, HttpClient, and SQL Server calls, then export to any OTLP-compatible backend:

builder.Services.AddOpenTelemetry()

.ConfigureResource(r => r.AddService("YourApp"))

.WithTracing(t => t

.AddAspNetCoreInstrumentation()

.AddHttpClientInstrumentation()

.AddSqlClientInstrumentation()

.AddOtlpExporter())

.WithMetrics(m => m

.AddAspNetCoreInstrumentation()

.AddRuntimeInstrumentation()

.AddOtlpExporter());

The OTLP exporter sends data outbound to a collector or hosted backend (Grafana, Honeycomb, Jaeger, or an Azure setup) — all of which work fine from a Windows hosting plan since they only need outbound HTTPS. If you prefer a managed, all-in-one option, Application Insights plugs in with a single AddApplicationInsightsTelemetry() call and the same outbound model.

Metrics and live counters

.NET 10 exposes rich built-in metrics through System.Diagnostics.Metrics — ASP.NET Core request rates and durations, Kestrel connections, HttpClient timings, and runtime counters for GC, thread pool, and exceptions. For a live look during an incident, dotnet-counters attaches to the running process and streams them:

dotnet-counters monitor -n YourApp \

--counters Microsoft.AspNetCore.Hosting,System.Runtime

Watch request duration, requests-in-flight, GC heap size, and thread-pool queue length. A climbing thread-pool queue or GC heap is an early warning. If CPU is the symptom, our guide to diagnosing high CPU on .NET apps in IIS turns these counters into a root cause.

Correlation IDs and request logging

To follow a single user request across many log lines, you need a correlation ID. .NET 10 honors the W3C Trace Context standard, so an incoming traceparent header flows through Activity.Current automatically. Add HTTP logging to capture request/response metadata, and enrich your logs with the trace ID:

builder.Services.AddHttpLogging(o =>

{

o.LoggingFields = HttpLoggingFields.RequestPath

| HttpLoggingFields.ResponseStatusCode

| HttpLoggingFields.Duration;

});

var app = builder.Build();

app.UseHttpLogging();

// Serilog's Enrich.FromLogContext + the ambient Activity give every

// log line a TraceId you can pivot on across the whole request.

For built-in access logging without Serilog, .NET also ships AddW3CLogging, which writes W3C-formatted request logs directly from the app.

Watch the app pool itself

Some of the most useful monitoring isn't in your code at all — it's the health of the IIS worker process. Three things to keep an eye on:

Recycles. Unexpected app-pool recycles drop in-flight work (and, for Blazor Server, live circuits). Track them and find the trigger with our app pool recycling diagnostic guide.

Memory. Watch private-bytes in the Plesk panel; a steady climb means a leak. Set a memory-based recycle limit as a safety net.

CPU. Sustained high CPU is usually a hot path or a runaway loop — counters plus the high-CPU guide above pinpoint it.

Production logging hygiene

The fastest way to make logging useless is to log too much of the wrong thing. A short discipline list:

Log at Warning+ in production · use structured properties · roll files with retention · split liveness/readiness · alert on /health/ready · keep a queryable SQL sink for warnings and audit events.

🟡 Don't

Log passwords, tokens, full card/PII data · leave stdout logging on permanently · log every Information row to SQL Server · write logs to system folders · ship DetailedErrors to clients in production.

Hosting recommendations

Every tier includes SQL Server 2022 for your log sink and storage for rolling log files — pick the plan that matches your traffic and how much you keep:

ASP.NET Business — $17.49/mo

Production apps with real log volume. 50 GB SSD, 5 SQL Server databases, higher-priority resource scheduling. Most popular tier.

View Business plan →

ASP.NET Professional — $27.49/mo

Multi-app platforms and heavy audit retention. 200 GB SSD, 10 SQL Server databases, highest-priority scheduling.

View Professional plan →

FAQs

Where do ASP.NET Core logs go on IIS?

Wherever you configure them. Your application logs go to the sinks you set up (rolling files in your site's .\logs\ folder, a SQL Server table, or an external APM). Separately, the ASP.NET Core Module can capture stdout to a file via stdoutLogEnabled in web.config, and IIS keeps its own W3C request logs. Use the app sinks as your primary record and stdout only for startup diagnostics.

Can I write logs to SQL Server on shared hosting?

Yes — Adaptive Web Hosting includes SQL Server 2022 on every plan, so the Serilog MSSqlServer sink works out of the box. Keep it at Warning and above so you don't flood the database, and index the log table if it grows.

Does OpenTelemetry work on Windows hosting?

Yes. The OTLP exporter only needs outbound HTTPS to reach a collector or hosted backend (Grafana, Honeycomb, Jaeger, Application Insights). No special server configuration is required — it's all in your app's startup code.

How do I monitor my app pool's memory and CPU?

Use the Plesk monitoring panel for worker-process memory and CPU, dotnet-counters for live runtime metrics, and a memory-based app-pool recycle limit as a safety net. Track recycles too — unexpected ones are a leading indicator of a leak or crash.

Should I use Serilog or the built-in ILogger?

ILogger is the interface you code against in either case. Serilog plugs in behind it to add the file and SQL Server sinks, enrichers, and rolling/retention features that the built-in providers don't offer. Use ILogger everywhere in your code and let Serilog handle delivery.

How do I capture a startup crash that produces HTTP 500.30?

Temporarily set stdoutLogEnabled="true" in web.config, reproduce the failure, and read the captured stdout file — it contains the unhandled startup exception. Then disable it again. Our 500.30 troubleshooting guide walks through the common causes.

Bottom line

Observability on .NET 10 + IIS is three layers working together: structured application logs via ILogger and Serilog, IIS-level diagnostics (stdout, W3C, FREB) for the moments your code can't speak for itself, and OpenTelemetry plus health checks for trends and alerting. Wire them once, keep them disciplined, and the next production incident becomes a quick read instead of a guessing game.

Adaptive Web Hosting gives you the foundation for all of it — Windows Server 2022, IIS 10, SQL Server 2022, generous SSD for logs, and Plesk health monitoring on every plan. See the full .NET 10 LTS hosting guide, harden the app with our .NET 10 security best practices, automate deploys with ASP.NET Core CI/CD, then choose your tier on the ASP.NET hosting plans page or contact us with questions.

Back to Blog