Logging & Custom Logging in .NET — ILogger, Serilog, NLog, log4net and Your Own Logger
In short: Trace (very detailed internals, off in production), Debug (developer diagnostics), Information (normal business events: order placed, user logged in), Warning (unexpected but handled: retry, slow dependency), Error (an operation failed — with the exception), Critical (system-wide failure needing immediate attention).
- Configure the minimum level per category so production logs stay useful and affordable.
In short: Logging a message template with named properties ("Order {OrderId} placed", id) so each property is stored as a separate field.
- You can search, filter and aggregate by
OrderIdorTotalacross millions of entries, build dashboards and alerts, and the logger avoids formatting work when the level is disabled. - Interpolated strings lose that structure.
In short: Through Microsoft.Extensions.Logging: inject ILogger<T> (category = class name), call LogInformation/LogError etc. Providers (Console, Debug, EventLog, Application Insights, OpenTelemetry, Serilog/NLog) write the entries;
appsettings.json"Logging" section filters by category and level;- scopes add contextual properties like RequestId and TraceId.
In short: Passwords, tokens/API keys, connection strings, full card numbers/CVV, secrets, and personal data beyond what's needed (Aadhaar/PAN, health data, full addresses) — logs are widely accessible and kept long.
- Mask or redact, log identifiers instead of content, and review log output in code reviews.
In short: ILogger is the abstraction your code depends on; the others are implementations plugged in as providers.
- Serilog: structured logging first, rich sinks and enrichers, code or JSON configuration — the popular choice for modern .NET.
- NLog: mature, XML/JSON config, many targets, good performance, works in Framework and Core. log4net: older, XML-configured, common in legacy .NET Framework apps, limited structured support.
- Built-in providers + OpenTelemetry/App Insights are enough for many cloud apps.
Use a correlation/trace id generated at the edge and propagated in headers (W3C traceparent automatically via Activity/OpenTelemetry, or X-Correlation-ID), include it in every log (scope/enricher), return it to clients in error responses, and send logs from all services to one store where you can filter by that id or open the distributed trace.
In short: Implement ILoggerProvider (creates ILogger instances per category and holds shared resources) and ILogger (IsEnabled, Log, BeginScope), register it with builder.Logging.AddProvider(...).
- Make writing asynchronous and batched through a bounded queue (Channel) so logging never blocks requests, handle sink failures without throwing, and dispose/flush on shutdown.
In short: A way to attach contextual properties to all log entries written within a block (using (logger.BeginScope(...))) — e.g. OrderId, TenantId, CorrelationId.
- ASP.NET Core adds request scopes automatically.
- Providers must support scopes (Serilog's
Enrich.FromLogContext, consoleIncludeScopes).
In short: Application (diagnostic) logs help developers and operators troubleshoot — technical, short retention, can be sampled.
- Audit logs record who did what to which data and when (often with before/after values) for compliance and security — complete, tamper-evident, long retention, stored separately (audit tables, temporal tables, SQL Server Audit, immutable storage).
Use message templates (no interpolation), check IsEnabled before expensive work, use the [LoggerMessage] source generator for hot paths, avoid logging large objects or inside tight loops, set sensible minimum levels per category, use asynchronous/batching sinks, and sample high-volume diagnostic data.
In short: Typically log4net or NLog configured in web.config/separate config, initialised in Application_Start;
- unhandled errors captured in
Application_Error(and MVC exception filters) and logged with the request URL and user; - ELMAH for automatic error capture with a protected viewer;
- Windows Event Log for critical service events;
- health monitoring in older apps.
In short: Keep levels in configuration (appsettings.json, environment variables, Azure App Configuration) with reloadOnChange so ILogger filters update live;
- Serilog supports
LoggingLevelSwitchcontrolled from configuration or an admin endpoint; - NLog reloads
nlog.configautomatically (autoReload="true"). - Raise the level only for the affected category and lower it again afterwards.
In short: Console.WriteLine writes plain text to stdout with no levels, categories, structure or filtering; Debug.WriteLine only works in debug builds with a debugger listener.
- A logging framework adds levels, categories, structured properties, scopes, multiple outputs (files, Seq, App Insights), filtering by configuration, async batching and correlation with traces — essential for production.
In short: Check existing logs around the failure times (filter by error, user, trace id);
- if insufficient, add targeted structured logs (inputs, decisions, external call results) at Information/Debug for that category, temporarily raise its level via configuration without redeploying, correlate with traces and metrics (load, dependency latency), and look for patterns (specific tenant, data, time, server).
- Remove or lower the extra logging afterwards.
In short: Measure which categories produce the most volume;
- raise minimum levels for noisy framework categories (EF commands, ASP.NET routing) and Debug/Trace in production;
- replace per-item logs in loops with summaries;
- sample high-volume telemetry;
- shorten retention for diagnostic logs (keep audit logs separately);
- use metrics instead of logs for counts;
- drop duplicate logging of the same exception at multiple layers.
In short: Logging is synchronous on the request path.
- Make it asynchronous with a bounded in-memory queue and a background writer that batches inserts;
- drop or spill to a local file when the queue is full;
- use a separate logging database or, better, a dedicated log platform (Seq/App Insights/ELK);
- never let logging failures throw into business code.