DEV Community

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

Posted 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)