Skip to content

Latest commit

 

History

History
164 lines (127 loc) · 7.43 KB

README.md

File metadata and controls

164 lines (127 loc) · 7.43 KB

Logging, an insider guide

Use structured logging. When using .NET / .NET5+ I recommend using the built-in logging providers. If you're using the .NET full framework, the library I recommend is Serilog.

This guide is mostly paraphrasing the excellent writing of Nicholas Blumhardt as I wanted to have everything in a single place.

Contents

High level guidance

  • Don't attach your debugger when developping, logging events should be sufficient. This will allow you to increase the quality of logging over time
  • Use a logging provider that supports structured events
  • Associate log events with a build version (I tend to use InformationalVersion as it doesn't have technical limitations). This comes in handy when tracing back an Exception to a specific version of the code. I also record additional metadata:
    • Application Name (i.e. CatsApi, DogsWorker...): great to restricts events to a single app when querying
    • Environment: local, uat, prod...
    • Host (i.e. RD00155DF084AD): sometimes containers / VMs do go rogue
  • Correlate events across system boundaries (via a CorrelationId for example)
    • That includes queue messages
    • This comes out of the box with Application Insights
  • Use centralized logging, all your systems should be logging in one place
    • Prefer a service rather than something you're hosting yourself
    • If you host yourself, don't use the same storage for your production data and your logs
  • Don't repeat the same property over and over again in a unit of work. Instead use a log scope
// Don't:
_l.LogInformation("[{TransactionId}] - Processing...", transactionId, ...);
// Do:
using(_l.BeginScope(new Dictionary<string, object> {["TransactionId"] = transactionId}))
{
    _l.LogInformation("Processing...", ...);
}
  • Consider retention
    • For audit purpose
    • Typical support incident period
  • Don't log beginning / end of functions / requests
    • Don't use logging for performance timing

Message Template

Fluent Style Guideline - good events use the names of properties as content within the message. This improves readability and makes events more compact.

_l.LogWarning("Disk quota {DiskQuota} MB exceeded by {UserId}", quota, userId);

Sentences vs. fragments - log event messages are fragments, not sentences; avoid a trailing period/full stop when possible.

Templates vs. messages - Log events have a message template associated, not a message. Treating the string parameter to log methods as a message, as in the case below, will hamper your querying abilities.

// Don't:
_l.LogInformation($"Deleted user {userId}");

Instead, always use template properties to include variables in messages:

// Do:
_l.LogInformation("Deleted user {UserId}", userId);

Property Naming - Property names should use PascalCase. Avoid generic property names as they'll be harder to query, a good rule of thumb is that the name should be meaningful when taken outside of context.

Avoid Prefer
{Uri} {PaymentGatewayUri}
{Id} {AppointmentId}
{Revision} {SpaceStationBlueprintRevision}
{Endpoint} {DataProtectionBlobStorageEndpoint}
{Type} {SapMessageType}
{Version} {DesignDocumentVersion}

Event Levels

What do they mean?

  1. Trace - very low-level debugging/internal information (might log Personally Identifiable Information)
    • These will be logged to Application Insights Live Metrics
  2. Debug - low level, control logic, diagnostic information (how and why)
  3. Information - non "internals" information / black box (not how but what)
  4. Warning - possible issues or service/functionality degradation
  5. Error - unexpected failure within the application or connected systems
  6. Critical - critical errors causing complete failure of the application
_l.LogTrace("Calculated {CheckDigit} for {CardNumber}", check, card);
_l.LogDebug("Applied VIP discount for {CustomerId}", customerId);
_l.LogInformation("New {OrderId} placed by {CustomerId}", orderId, customerId);
_l.LogWarning(exception, "Failed to save new {OrderId}, retrying in {SaveOrderRetryDelay} milliseconds", orderId, retryDelay);
_l.LogError("Failed to save new {OrderId}", orderId);
_l.LogCritical("Unhandled exception, application is terminating.");

Exceptions

Log exception instances instead of the Message only. This gives us access to the stack trace and the inner exception(s).

Prefer:

_l.LogError(ex, "Could not authorise payment for order {OrderId}", orderId);

Avoid:

_l.LogError($"Failed to process, error: {ex.Message}");

When catching and swallowing an exception, consider logging the exception instead of a log message only. This will provided us more contact as to what went wrong.

Prefer:

catch (Exception ex)
{
    _logger.LogWarning(ex, "Some action went wrong");
}

Avoid:

catch
{
    _logger.LogWarning("Some action went wrong");
}

References