ASP.NET Core  

Practical Logging in ASP.NET Core Using Built-In ILogger: Production Scenarios, Patterns, and Case-Study Implementation

Introduction

Logging is one of the most critical components in modern web applications. It helps developers understand what happened, when it happened, and why it happened. Good logging reduces debugging time, improves monitoring, and helps identify performance problems, security attacks, or unexpected failures.

ASP.NET Core includes a built-in logging framework called ILogger that works without requiring any external libraries. It supports logging to the console, debug window, files, Application Insights, cloud logging systems, and custom providers.

This article explains how to use ILogger effectively in real-world enterprise applications, how logging behaves inside different layers (Controllers, Middleware, Services), and how to format logs for production readiness.

Real-World Problem Statement

A large SaaS product started facing occasional failures in production. Users reported intermittent errors, but developers could not find the cause because:

  • Errors were not logged properly

  • Messages were too generic

  • Logs did not include correlation IDs

  • Exceptions were swallowed inside try-catch blocks without context

The engineering team decided to implement structured logging using the built-in ILogger framework.

Result after implementation

  • Root causes became traceable

  • API request patterns became visible

  • Performance bottlenecks were identified

  • Security anomalies were detected early

This case reflects why structured and meaningful logging matters.

How Logging Works in ASP.NET Core

ASP.NET Core logging is built around:

  • ILogger interface

  • ILogger<T> dependency injection

  • Logging providers (Console, Debug, EventLog, Azure, etc.)

  • Log levels (Trace → Critical)

Logging flows through DI, and the framework automatically manages lifecycle and configuration.

Logging Levels Explained

LevelPurpose
TraceVery detailed debugging logs (rarely used in production)
DebugUsed during development for troubleshooting
InformationGeneral flow information such as startup, login, or successful processing
WarningSomething unexpected happened but system continued working
ErrorApplication failure requiring investigation
CriticalSystem-wide or infrastructure-level failure

Best practice: Set different log levels for Development and Production.

Basic Example: Logging in a Controller

[ApiController]
[Route("api/[controller]")]
public class OrderController : ControllerBase
{
    private readonly ILogger<OrderController> _logger;

    public OrderController(ILogger<OrderController> logger)
    {
        _logger = logger;
    }

    [HttpGet("{id}")]
    public IActionResult GetOrder(int id)
    {
        _logger.LogInformation("Fetching order with id {OrderId}", id);

        if (id <= 0)
        {
            _logger.LogWarning("Invalid order id received: {OrderId}", id);
            return BadRequest("Invalid Id");
        }

        return Ok($"Order {id}");
    }
}

Notice structured logging with placeholders such as {OrderId}. This is important for queryable log analytics platforms.

Logging Exceptions the Right Way

Many developers log exceptions incorrectly, such as:

_logger.LogError("Error occurred: " + ex.Message);

Correct approach

catch (Exception ex)
{
    _logger.LogError(ex, "Failed to process order {OrderId}", orderId);
}

This logs:

  • Exception stack trace

  • Message

  • Contextual data

Logging in Service Layer

Logging shouldn't only exist in controllers. It must also exist in business and repository layers.

public class PaymentService
{
    private readonly ILogger<PaymentService> _logger;

    public PaymentService(ILogger<PaymentService> logger)
    {
        _logger = logger;
    }

    public bool ProcessPayment(decimal amount)
    {
        _logger.LogInformation("Processing payment of {Amount}", amount);

        if (amount <= 0)
        {
            _logger.LogWarning("Attempted to process invalid payment value: {Amount}", amount);
            return false;
        }

        return true;
    }
}

Logging from Middleware (Recommended Practice)

public class RequestLoggerMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<RequestLoggerMiddleware> _logger;

    public RequestLoggerMiddleware(RequestDelegate next, ILogger<RequestLoggerMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation("Request: {Method} {Path}", context.Request.Method, context.Request.Path);

        await _next(context);

        _logger.LogInformation("Response Status: {StatusCode}", context.Response.StatusCode);
    }
}

Workflow Diagram

Incoming Request
     |
     V
Middleware (Request Logging)
     |
     V
Controller Logging
     |
     V
Service/Repository Logging
     |
     V
Exception Logging (if needed)
     |
     V
Response + Log Output

Flowchart of Logging Strategy

                ┌──────────────────────┐
                │ Start Application    │
                └───────────┬──────────┘
                            │
                            ▼
              ┌────────────────────────────┐
              │ Log Request Information    │
              └───────────┬───────────────┘
                          │
                          ▼
           ┌──────────────────────────────────┐
           │ Execute Business Logic           │
           └───────────────┬─────────────────┘
                           │
                           ▼
                 ┌────────────────────────┐
                 │ Any Errors Occurred?   │
                 └───────┬───────────────┘
                         │
                Yes      │       No
                         │
                         ▼
        ┌─────────────────────────────┐
        │ Log Error With Stack Trace  │
        └─────────────────────────────┘
                         │
                         ▼
              ┌───────────────────────────┐
              │ Return Response + Logs     │
              └───────────────────────────┘

Writing Logs to Files

Modify appsettings.json:

{"Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }}}

To enable file logging (built-in on Windows hosting):

"Logging": {"AddFile": true}

On Linux/docker environments, logs are typically written to stdout and collected by the hosting system.

Adding Correlation IDs (Highly Recommended)

Correlation IDs allow tracking a single request across the entire application.

Example:

_logger.LogInformation("Request {CorrelationId}", context.TraceIdentifier);

This becomes critical when using distributed systems, queues, or microservices.

Best Practices

  1. Use structured logging, never string concatenation.

  2. Log warnings only when action is required.

  3. Avoid logging sensitive data (passwords, tokens).

  4. Add correlation IDs for tracking.

  5. Use LogInformation for standard flow, not LogError.

  6. Configure different logging levels for staging vs production.

Common Mistakes

MistakeWhy It Is Wrong
Logging everything as errorCreates noise and hides real problems
Logging user passwords or tokensSecurity vulnerability
Logging large objects fullyCreates unnecessary storage cost
Missing contextMakes logs useless in investigation

Final Recommendations

  • Start logging early in development.

  • Design consistent log messages.

  • Use structured logs with placeholders.

  • Integrate logs with monitoring tools when scaling.

Conclusion

The built-in ILogger in ASP.NET Core is powerful enough for most enterprise applications without requiring external dependencies. When implemented with best practices, it provides complete visibility into system behavior, reduces support burden, enables faster incident resolution, and improves long-term maintainability.

Logging is not just a debugging tool — it is an operational asset.