DEV Community

Cover image for 10 Common .NET Logging Mistakes and How to Avoid Them for Maintainable Apps
Mashrul Haque
Mashrul Haque

Posted on • Edited on

10 Common .NET Logging Mistakes and How to Avoid Them for Maintainable Apps

Logging feels simple… until you’re staring at a production incident and your logs are either uselessly noisy or weirdly empty.

Here are 10 .NET logging mistakes that quietly hurt you in production - and how to fix them.

1. Treating Logs as Plain Strings

The mistake

_logger.LogInformation("User " + userId + " purchased " + itemCount + " items");
Enter fullscreen mode Exit fullscreen mode

Everything is one big string, so your logging system can’t query individual fields.

Why it hurts

  • No structured search (e.g. UserId = 123)
  • Hard to build dashboards on numeric fields
  • Formatting drifts over time

Fix

Use structured properties:

_logger.LogInformation(
    "User {UserId} purchased {ItemCount} items",
    userId,
    itemCount);
Enter fullscreen mode Exit fullscreen mode

If you want to filter or graph it later, give it a named property.


2. Using the Same Log Level for Everything

The mistake

Everything is LogInformation or everything is LogError.

Why it hurts

  • Can’t tell what’s actually broken
  • Filtering by severity is useless
  • Alerting becomes noise

Fix

Use levels with intent:

  • Trace – ultra-detailed, usually local only
  • Debug – dev diagnostics
  • Information – key business events
  • Warning – unexpected but not fatal
  • Error – failed operation
  • Critical – system is in real trouble

Example

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

if (!inventoryAvailable)
    _logger.LogWarning("Inventory missing for order {OrderId}", orderId);

try
{
    await _paymentService.ChargeAsync(order);
}
catch (PaymentException ex)
{
    _logger.LogError(ex, "Payment failed for order {OrderId}", orderId);
    throw;
}
Enter fullscreen mode Exit fullscreen mode

3. Spamming Logs Instead of Capturing Signal

The mistake

  • Logging every function entry/exit
  • Logging every item in big loops
  • Serializing massive objects on every request

Why it hurts

  • Log volume (and cost) explodes
  • Performance drops
  • Important lines get buried

Fix

  • Log at boundaries: incoming requests, external calls, DB operations, key decisions, errors
  • Avoid per-item logs in hot loops
  • Use sampling for progress logs:
for (var i = 0; i < items.Count; i++)
{
    if (i % 100 == 0)
        _logger.LogDebug("Processed {Count} items so far", i);

    Process(items[i]);
}
Enter fullscreen mode Exit fullscreen mode

4. Logging Sensitive Data

The mistake

_logger.LogInformation("User {Email} logged in with password {Password}", email, password);
Enter fullscreen mode Exit fullscreen mode

Yes, this happens.

Why it hurts

  • Massive security and compliance risk
  • If logs leak, attackers get passwords, tokens, PII

Fix

  • Maintain a “never log” list: passwords, tokens, card numbers, national IDs, etc.
  • Log identifiers, not secrets:
_logger.LogInformation("User {UserId} logged in", userId);
Enter fullscreen mode Exit fullscreen mode
  • Consider wrappers whose ToString() returns [REDACTED] for secrets
  • Make “no sensitive data in logs” part of your review checklist

5. No Correlation ID or Context

The mistake

Each log line is isolated:

_logger.LogInformation("Started payment");
_logger.LogInformation("Calling gateway");
_logger.LogError("Payment failed");
Enter fullscreen mode Exit fullscreen mode

With multiple services and threads, you can’t follow a single request.

Why it hurts

  • Debugging across services is painful
  • Hard to answer: “What happened to this request?”

Fix

Use scopes with correlation IDs:

using (_logger.BeginScope(new Dictionary<string, object>
{
    ["CorrelationId"] = correlationId,
    ["UserId"] = userId
}))
{
    _logger.LogInformation("Processing payment");
    // everything here includes CorrelationId and UserId
}
Enter fullscreen mode Exit fullscreen mode
  • Generate or accept a CorrelationId per request
  • Propagate it in headers to downstream services
  • Make it a first-class field in your queries/dashboards

6. Mixing Logging Frameworks Instead of Using ILogger<T>

The mistake

  • Some code uses ILogger<T>
  • Some uses NLog directly
  • Some uses Console.WriteLine
  • Some uses Trace.WriteLine

Why it hurts

  • Config and behavior are inconsistent
  • Hard to swap providers (Serilog, NLog, App Insights, etc.)
  • Testing/mocking logging is messy

Fix

Standardize on Microsoft.Extensions.Logging in app code:

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

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

    public void DoWork()
    {
        _logger.LogInformation("Doing work");
    }
}
Enter fullscreen mode Exit fullscreen mode

Configure the actual provider (Serilog, etc.) at startup only:

Host.CreateDefaultBuilder(args)
    .UseSerilog((context, services, configuration) =>
    {
        configuration
            .ReadFrom.Configuration(context.Configuration)
            .ReadFrom.Services(services);
    });
Enter fullscreen mode Exit fullscreen mode

App code = abstraction. Startup = implementation.


7. Same Logging Config in Every Environment

The mistake

Dev, test, prod — all using the same log levels and sinks.

Why it hurts

  • Dev: too noisy, hard to see real issues
  • Prod: too noisy, too expensive, or not verbose enough when you need it

Fix

Use appsettings.*.json:

// Development
"Logging": {
  "LogLevel": {
    "Default": "Debug",
    "Microsoft": "Information"
  }
}
Enter fullscreen mode Exit fullscreen mode
// Production
"Logging": {
  "LogLevel": {
    "Default": "Information",
    "Microsoft": "Warning"
  }
}
Enter fullscreen mode Exit fullscreen mode

And override via environment variables when you need temporary extra verbosity:

Logging__LogLevel__MyAppNamespace=Debug
Enter fullscreen mode Exit fullscreen mode

8. Logging Exceptions Poorly (or Twice)

The mistakes

Logging only the message:

catch (Exception ex)
{
    _logger.LogError("Something failed: " + ex.Message);
}
Enter fullscreen mode Exit fullscreen mode

Logging in multiple layers for the same exception:

// Repo
catch (Exception ex)
{
    _logger.LogError(ex, "Failed in repository");
    throw;
}

// Service
catch (Exception ex)
{
    _logger.LogError(ex, "Failed in service");
    throw;
}
Enter fullscreen mode Exit fullscreen mode

Why it hurts

  • Missing stack traces
  • Duplicate error logs, inflated dashboards

Fix

  • Always pass the exception:
catch (Exception ex)
{
    _logger.LogError(ex, "Failed to process order {OrderId}", orderId);
    throw;
}
Enter fullscreen mode Exit fullscreen mode
  • Decide where errors get logged (usually at the edges: request handlers, background workers)
  • Consider global exception handling (middleware) to centralize error logging

9. Ignoring Performance Cost of Logging

The mistake

_logger.LogDebug("Result: " + JsonSerializer.Serialize(bigObject));
Enter fullscreen mode Exit fullscreen mode

Even if Debug is off, the string concatenation and serialization still happen.

Why it hurts

  • Wasted CPU
  • Extra allocations and GC pressure
  • Slower hot paths

Fix

  • Use structured templates:
_logger.LogDebug("Result: {@Result}", bigObject);
Enter fullscreen mode Exit fullscreen mode
  • Guard expensive logs:
if (_logger.IsEnabled(LogLevel.Debug))
{
    _logger.LogDebug("Detailed result: {@Result}", bigObject);
}
Enter fullscreen mode Exit fullscreen mode
  • Avoid logging giant payloads by default; log summaries instead

10. Having Logs but No Logging Strategy

The mistake

Every dev logs in their own style. There’s no shared idea of:

  • What to log
  • At which level
  • Where to look during incidents

Why it hurts

  • Inconsistent logs are harder to read
  • New devs repeat old mistakes
  • Incidents take longer to debug

Fix

Create a lightweight team logging guide:

  • What to log: requests, external calls, key domain events, warnings, errors
  • Property names: UserId, OrderId, CorrelationId (pick a standard)
  • Levels: examples of what counts as Information vs Warning vs Error
  • Where logs go: link to your log system and common queries

Review and update it after real incidents.


Quick .NET Logging Checklist

Before you ship:

  • [ ] Using structured properties, not string concatenation
  • [ ] Levels (Info/Warn/Error) actually mean something
  • [ ] No passwords/tokens/PII in logs
  • [ ] Correlation ID included where it matters
  • [ ] Environment-specific log levels configured
  • [ ] Exceptions logged once, with stack trace

About me

I’m a Systems Architect who is passionate about distributed systems, .Net clean code, logging, performance, and production debugging.

  • 🧑‍💻 Check out my projects on GitHub: mashrulhaque
  • 👉 Follow me here on dev.to for more .NET posts

Top comments (0)