RandomSkunk.StructuredLogging 0.9.2

dotnet add package RandomSkunk.StructuredLogging --version 0.9.2
                    
NuGet\Install-Package RandomSkunk.StructuredLogging -Version 0.9.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RandomSkunk.StructuredLogging" Version="0.9.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RandomSkunk.StructuredLogging" Version="0.9.2" />
                    
Directory.Packages.props
<PackageReference Include="RandomSkunk.StructuredLogging" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RandomSkunk.StructuredLogging --version 0.9.2
                    
#r "nuget: RandomSkunk.StructuredLogging, 0.9.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RandomSkunk.StructuredLogging@0.9.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RandomSkunk.StructuredLogging&version=0.9.2
                    
Install as a Cake Addin
#tool nuget:?package=RandomSkunk.StructuredLogging&version=0.9.2
                    
Install as a Cake Tool

RandomSkunk.StructuredLogging

[NuGet] (https://www.nuget.org/packages/RandomSkunk.StructuredLogging/) License: MIT

Modern, high-performance structured logging extensions for .NET that cleanly separate human-readable messages from machine-readable properties. Stop cluttering your message templates with structured data and start writing logs that are easier to read and query.

Why Choose RandomSkunk.StructuredLogging?

The default logging extension methods from Microsoft.Extensions.Logging force you to embed structured log properties into message templates. This often leads to:

  • Verbose and Unreadable Messages: logger.LogInformation("User {UserId} logged in from {IPAddress}", userId, ipAddress)
  • Performance Overhead: Message template caching can consume memory and CPU.
  • Rigid Structure: You can only log what your template allows.

This library takes a different approach by treating messages and properties as separate concerns, giving you the best of both worlds: clean, readable messages and rich, queryable data.

Features

  • Clean Separation: Keep your log messages for humans and your properties for machines.
  • 🚀 High Performance: A design that avoids message template caching overhead.
  • 📝 Powerful Interpolated Strings: Automatically extract attributes from interpolated strings ($"User {user.Name:<UserName>}") without sacrificing performance. The interpolation only happens if the log level is enabled!
  • 💪 Flexible & Type-Safe: Pass properties using tuples, dictionaries, or arrays with a rich set of overloads.
  • 🔄 Operation Logging: Track operation start/completion logs with shared context, per-operation trace entries, and optional return values/exceptions.

Quick Start

1. Install the Package

dotnet add package RandomSkunk.StructuredLogging

2. Start Logging

Use the extension methods on Microsoft.Extensions.Logging.ILogger.

Basic Logging with Properties

Pass properties as a list of (string, object) tuples. The message remains clean and readable.

logger.Information("User logged in successfully",
    ("UserId", user.Id),
    ("SessionId", sessionId),
    ("LoginTime", DateTime.UtcNow));

Output Log (conceptual JSON):

{
  "Message": "User logged in successfully",
  "UserId": 123,
  "SessionId": "xyz-abc",
  "LoginTime": "2024-01-01T12:00:00Z"
}
Property Extraction from Interpolated Strings

For ultimate convenience, extract properties directly from an interpolated string. The syntax {value:<PropertyName>} captures the value as a property and embeds it in the message.

This is not just a simple string.Format. The library uses a custom interpolated string handler that only evaluates the arguments and formats the string if the log level is enabled.

// The values for username, attemptCount, and clientIp are captured as properties.
logger.Warning($"Failed login attempt for {username:<Username>}",
    ("AttemptCount", attemptCount),
    ("IPAddress", clientIp));

Output Log (conceptual JSON):

{
  "Message": "Failed login attempt for brian",
  "Username": "brian",
  "AttemptCount": 3,
  "IPAddress": "127.0.0.1"
}
Operation Logging

Use LogOperation to automatically write an "operation starting" log when an operation begins, and an "operation complete" log when it ends. Both logs include the same operation parameters.

public int Divide(int dividend, int divisor, int? fallbackValue = null)
{
    using var log = logger.LogOperation(
        $"{typeof(Calculator)}.{nameof(Divide)}",
        dividend,
        divisor,
        fallbackValue);

    if (log.IsNotNull(fallbackValue) && log.Condition(divisor == 0))
    {
        return log.ReturnValue(fallbackValue.Value);
    }

    return log.ReturnValue(dividend / divisor);
}

Output Logs (conceptual JSON):

{
  "Message": "Operation starting: MathUtilities.Calculator.Divide",
  "dividend": 10,
  "divisor": 2,
  "fallbackValue": -2147483648
}
{
  "Message": "Operation complete: MathUtilities.Calculator.Divide",
  "dividend": 10,
  "divisor": 2,
  "fallbackValue": -2147483648,
  "ReturnValue": 5,
  "OperationLog": "[12:34:56.787Z] `fallbackValue != null` is true
[12:34:56.788Z] `divisor == 0` is false
[12:34:56.789Z] Return value is `dividend / divisor`"
}

Performance: Fast and Memory-Efficient

Performance is a core feature. This library is designed to minimize overhead in your application.

Conditional Evaluation

The custom interpolated string handlers are the magic behind the performance. String formatting and method calls inside an interpolated string only occur if the log level is enabled.

// If Debug logging is disabled, CalculateSize() is never called and no string is created.
logger.Debug($"Processing {items.Count:<ItemsCount>} items with total size {CalculateSize(items):<ItemsByteCount>N0} bytes");

No Message Caching

Unlike other libraries, we do not cache message templates. This eliminates memory overhead and performance penalties associated with managing a cache, making it ideal for dynamic log messages.

Advanced Usage

The library is flexible enough to handle any scenario.

Logging with Exceptions and Event IDs

All overloads support standard EventId and Exception arguments.

logger.Error(new EventId(500, "DatabaseError"), exception, "Database connection failed",
    ("ConnectionString", connectionString),
    ("RetryCount", retryCount));

Using Dictionaries for Properties

You can pass properties in any IReadOnlyCollection<KeyValuePair<string, object?>>, including a Dictionary.

var metadata = new Dictionary<string, object?>
{
    ["UserId"] = user.Id,
    ["TenantId"] = tenant.Id,
    ["CorrelationId"] = correlationId
};

logger.Information("Operation completed", metadata);

How It Works

The library extends ILogger with a new set of extension methods for structured event logs and operation logs. These methods use custom [interpolated string handlers] (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/improved-interpolated-strings) to intercept string formatting.

  1. The handler checks if the requested LogLevel is enabled.
  2. If not, it does nothing, and the call is nearly free.
  3. If enabled, it processes the interpolated string, extracting any properties defined with the <Key> syntax.
  4. It then combines all properties and passes them, along with the formatted message, to the underlying ILogger instance.
  5. The library uses an optimized struct-based approach for passing properties.

Compatibility

  • .NET 8.0
  • .NET 9.0
  • .NET 10.0
  • Compatible with all Microsoft.Extensions.Logging providers (OpenTelemetry, Serilog, Console, etc.)

License

This project is licensed under the MIT License.

Copyright (c) 2025-2026 Brian Friesen. All rights reserved.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.9.2 80 4/9/2026
0.9.1 92 4/9/2026
0.9.0 101 2/11/2026