Diagnostics and guards

Chronicler.Diagnostics is an independent package for host-facing diagnostics and exception guards. It targets netstandard2.1 and net10.0 without depending on Chronicler.Core, JSON, MemoryPack, or an engine. Standard and Lean consumers use the same package.

dotnet add package Chronicler.Diagnostics

Configure a channel

DiagnosticChannel starts disabled with a no-op sink. The host chooses the minimum level and output destination:

using System;
using Chronicler.Diagnostics;

var channel = new DiagnosticChannel("MyLibrary")
{
    MinimumLevel = DiagnosticLevel.Warning,
    Sink = WriteDiagnostic
};

int capacity = 128;
channel.Warn($"Buffer capacity reached {capacity}.", source: "Buffer");

static void WriteDiagnostic(in DiagnosticEvent diagnostic)
    => Console.WriteLine($"[{diagnostic.Level}] {diagnostic.Channel}: {diagnostic.Message}");

Info, Warn, Error, and dynamic-level Log accept both strings and custom interpolated-string handlers. With C# 10 or later, interpolated calls select the handler overload and skip formatted expressions when disabled. String arguments are already formatted when the helper receives them. When a source is omitted, both paths derive it from caller file and method information.

Older-language callers

C# 9 callers can use the same helper names through the string overloads. Their interpolation expressions run before the call, even when that level is disabled. Use an explicit level check to avoid that work:

if (channel.IsEnabled(DiagnosticLevel.Warning))
    channel.Warn($"Buffer capacity reached {capacity}.", source: "Buffer");

The libraries are distributed as compiled assemblies. The netstandard2.1 implementation uses C# 11; callers do not need to compile its source syntax. The .NET 10 implementation and tests use C# 14. Host runtime, AOT, and linker support also depend on the consuming application's toolchain; validate those environments as part of your integration.

Use a library-owned facade

DiagnosticLogger is an optional base for a library-owned facade. It provides normal/debug channels and configurable formatting and output. Its normal channel starts at Warning; debug logging is disabled. Its default output writes errors to stderr and other levels to stdout.

Keep library-specific channel names and facades with their owning library. For example, SwiftCollections retains SwiftCollectionDiagnostics.Shared, and GridForge retains GridForgeLogger. Diagnostics do not become recorded state or contribute to record hashes. Keep simulation decisions out of logging callbacks and interpolation expressions.

Guard invalid inputs

ChronicleGuard offers common argument, range, state, disposal, and key guards. It preserves parameter names and routes exceptions through separate throw methods. Its interpolated overloads build messages only when the guard fails:

int capacity = 128;
int requested = 256;
ChronicleGuard.ThrowIfArgumentOutOfRange(
    requested > capacity, requested, nameof(requested),
    $"Requested {requested} entries; capacity is {capacity}.");

The handler overloads support the same lazy-message behavior on both target frameworks when the caller uses C# 10 or later. Older callers can use the string guard overloads and pass explicit parameter names where needed, for example ChronicleGuard.ThrowIfNull(settings, nameof(settings)). A preformatted string guard message is evaluated before the guard runs. The netstandard assembly carries the public compiler attributes needed by downstream handler APIs.

See the migration guide when upgrading from SwiftCollections.Diagnostics.