100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
.NET

Logging in .NET

How .NET Core's ILogger abstraction, log levels, structured logging, and scopes work together, and how providers like Serilog plug in.

Building BlocksIntermediate9 min readJul 10, 2026
Analogies

The ILogger Abstraction

Logging in .NET Core is built around a provider-agnostic abstraction: application code takes an ILogger<T> via constructor injection and calls methods like LogInformation, LogWarning, or LogError, without knowing or caring whether those messages end up in the console, a file, Application Insights, or an ELK stack. The generic type parameter T stamps each log entry with a category name (typically the fully qualified class name), which lets you filter and configure verbosity per-namespace in appsettings.json without touching a single line of application code.

🏏

Cricket analogy: This is like a team's official scorer recording every ball into a standard scorebook format without needing to know whether TV broadcasters or the press will read it later, the same way ILogger<T> stays unchanged regardless of which provider reads the logs.

Log Levels and Structured Logging

ILogger defines six ordered levels — Trace, Debug, Information, Warning, Error, and Critical — and appsettings.json's Logging:LogLevel section controls the minimum level emitted per category, so you might run "Default": "Information" broadly but "Microsoft.EntityFrameworkCore": "Warning" to silence noisy SQL logging. Crucially, .NET's logging is structured, not string-concatenated: calling logger.LogInformation("Order {OrderId} shipped to {City}", orderId, city) preserves OrderId and City as separate, queryable fields in providers that support structured sinks like Seq or Application Insights, rather than baking them into an opaque message string the way string interpolation would.

🏏

Cricket analogy: This is like classifying commentary into tiers — routine ball updates (Information), a rain-delay alert (Warning), an injury stoppage (Error) — each escalated differently, while recording the exact over number and batsman name as separate structured fields mirrors structured logging with named placeholders.

csharp
public class OrderService(ILogger<OrderService> logger)
{
    public void Ship(int orderId, string city)
    {
        logger.LogInformation("Order {OrderId} shipped to {City}", orderId, city);

        if (city == "Unknown")
        {
            logger.LogWarning("Order {OrderId} shipped to an unresolved city", orderId);
        }
    }
}

Scopes and Third-Party Providers

BeginScope lets you attach contextual data, such as a request's CorrelationId, to every log entry written within a using block, so a single downstream ILogger call automatically carries context set much higher up the call stack without threading a parameter through every method signature. While the built-in Console and Debug providers are fine for local development, production systems typically swap in Serilog or NLog as the logging provider (via AddSerilog or AddNLog), gaining structured sinks, enrichers that automatically attach machine name or environment, and log-shipping to aggregation platforms like Seq, Elasticsearch, or Datadog, all while application code keeps calling the same unchanged ILogger<T> API.

🏏

Cricket analogy: This is like a broadcast director tagging an entire session's coverage with a persistent 'Day 3, Session 2' label that automatically stamps every subsequent camera cut without the commentator repeating it each time, mirroring how BeginScope attaches context to every log entry within its block automatically.

Serilog's message template syntax, logger.LogInformation("Order {OrderId} shipped", orderId), is the same syntax the built-in Microsoft.Extensions.Logging abstraction uses, which is why Serilog can be dropped in as a provider with almost no changes to existing application logging calls.

Never log entire request or response bodies, connection strings, or PII like raw email addresses at Information level in production — structured sinks make logs highly searchable, which also makes over-logged sensitive data a serious compliance and security liability. Redact or omit sensitive fields before logging.

  • ILogger<T> is a provider-agnostic abstraction; application code doesn't know or care where log entries ultimately go.
  • The generic type parameter T supplies the log category, usually the fully qualified class name, enabling per-namespace filtering.
  • Log levels (Trace, Debug, Information, Warning, Error, Critical) are configured per category in appsettings.json's Logging:LogLevel section.
  • Structured logging with message templates like "Order {OrderId} shipped" keeps values as queryable fields, not flattened strings.
  • BeginScope attaches ambient context to every log entry written within its block, without threading parameters manually.
  • Serilog and NLog are common production providers, adding structured sinks, enrichers, and log-shipping while keeping the same ILogger<T> API.
  • Never log sensitive data like PII or secrets at broadly visible log levels in production.

Practice what you learned

Was this page helpful?

Topics covered

#NET#NETCoreStudyNotes#MicrosoftTechnologies#LoggingInNET#Logging#ILogger#Abstraction#Log#StudyNotes#SkillVeris