CK.Metrics 0.3.3

dotnet add package CK.Metrics --version 0.3.3
                    
NuGet\Install-Package CK.Metrics -Version 0.3.3
                    
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="CK.Metrics" Version="0.3.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CK.Metrics" Version="0.3.3" />
                    
Directory.Packages.props
<PackageReference Include="CK.Metrics" />
                    
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 CK.Metrics --version 0.3.3
                    
#r "nuget: CK.Metrics, 0.3.3"
                    
#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 CK.Metrics@0.3.3
                    
#: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=CK.Metrics&version=0.3.3
                    
Install as a Cake Addin
#tool nuget:?package=CK.Metrics&version=0.3.3
                    
Install as a Cake Tool

CK-Metrics

Supports System.Diagnostics.Metrics with a different approach than other metrics collectors and handlers like the ones in Microsoft.Extensions.Diagnostics.Abstractions.

Key features:

  • DI-free: No dependency injection required. Configuration is static and thread-safe.
  • Automatic capture: Uses a global MeterListener to capture all System.Diagnostics.Metrics instruments.
  • Strict validation: Enforces OpenTelemetry-compatible naming conventions.
  • CK.Monitoring integration: Metrics flow through the GrandOutput pipeline for persistence and consumption.

This library is on the collector side of the Telemetry world. It can handle Meters and Instruments defined in external assemblies like the .Net standard ones but we impose some constraints that are stricter than the OpenTelemetry framework suggests.

Quick Start

Creating Metrics

CK.Metrics automatically captures all System.Diagnostics.Metrics instruments via a global MeterListener. You use the standard .NET Metrics API to create meters and instruments:

using System.Diagnostics.Metrics;
using CK.Metrics;

// Create a Meter (namespace-like naming required, see Restrictions section)
using var meter = new Meter("MyApp.Orders", "1.0");

// Create instruments
var ordersCompleted = meter.CreateCounter<int>("orders.completed", "orders", "Number of completed orders");
var itemsInCart = meter.CreateGauge<int>("cart.items");
var orderDuration = meter.CreateHistogram<double>("order.duration", "ms");

Enabling Instruments

Instruments are disabled by default. You can enable them in two ways:

Option 1: Inline with DefaultConfigure() (recommended for producer-owned instruments)

var counter = meter.CreateCounter<int>("orders.completed")
    .DefaultConfigure(InstrumentConfiguration.BasicEnabled);

counter.Add(1); // Measurement is recorded

The DefaultConfigure() extension method is fluent and applies the configuration immediately if no existing matching configuration exists. It works with all instrument types: Counter<T>, UpDownCounter<T>, Gauge<T>, Histogram<T>, and their observable variants.

Option 2: Global configuration (for external or multiple instruments)

var config = new MetricsConfiguration();
config.AutoObservableTimer = 50; // Collect observables every 50ms
config.Configurations.Add((new InstrumentMatcher("MyApp.*"), InstrumentConfiguration.BasicEnabled));
DotNetMetrics.ApplyConfiguration(config);

Recording Measurements

Use the standard .NET APIs for recording:

// Counters: Add values (cumulative)
counter.Add(1);
counter.Add(5, new KeyValuePair<string, object?>("region", "us_east"));

// Gauges: Record point-in-time values
gauge.Record(42);

// Histograms: Record distributions
histogram.Record(123.45);

Observable Instruments

For instruments that pull values on demand:

var memoryGauge = meter.CreateObservableGauge<long>(
    "process.memory.used",
    () => GC.GetTotalMemory(false),
    "bytes"
).DefaultConfigure(InstrumentConfiguration.BasicEnabled);

// Enable automatic periodic collection
var config = new MetricsConfiguration { AutoObservableTimer = 100 }; // every 100ms
DotNetMetrics.ApplyConfiguration(config);

How It Works

CK.Metrics sets up a global MeterListener at static initialization that:

  1. Captures all Meters and Instruments as they are created via System.Diagnostics.Metrics
  2. Validates naming against strict OpenTelemetry-compatible patterns (see Restrictions)
  3. Logs metrics events through ActivityMonitor.StaticLogger using two tags:
    • MetricsTag ("Metrics"): for actual metric entries (meter/instrument creation, measurements)
    • MetricsInternalTag ("MetricsInternal"): for internal/diagnostic logging with a default LogFilter.Monitor filter (groups: Trace, lines: Warn) to reduce noise
  4. Applies configurations to enable/disable instruments based on pattern matching

Metric Log Format

Metrics are logged as structured text entries with these prefixes:

Prefix Meaning
+Meter: New meter created
-Meter: Meter disposed
+Instrument: Instrument published
+IConfig: Configuration changed
M: Measurement recorded

These entries flow through the CK.Monitoring pipeline and can be processed by handlers like MetricsLogHandler.

Configuration

The following static methods of the DotNetMetrics class are thread-safe and configure the metrics for the whole system:

public static DotNetMetricsInfo GetConfiguration();
public static Task<DotNetMetricsInfo> GetConfigurationAsync();
public static void ApplyConfiguration( MetricsConfiguration configuration, bool waitForApplication = false );
public static Task<DotNetMetricsInfo> ApplyAndGetConfigurationAsync( MetricsConfiguration configuration );

Getting the current System state and configurations

The DotNetMetricsInfo returned by GetConfiguration or GetConfigurationAsync exposes the Meters and Instruments and the configuration of the timer that collects the measures of the ObservableInstrument:

public sealed class DotNetMetricsInfo
{
    /// <summary>
    /// Gets the timer delay that collects the <see cref="ObservableInstrument{T}"/>
    /// measures.
    /// </summary>
    public int AutoObservableTimer => _autoObservableTimer;

    /// <summary>
    /// Gets the instruments and their configuration grouped by their <see cref="FullInstrumentInfo.MeterInfo"/>.
    /// </summary>
    public IReadOnlyList<FullInstrumentInfo> Instruments => _instruments;
}

The FullInstrumentInfo exposes:

  • The MeterInfo that is an immutable capture of the .Net Meter object.
  • The InstrumentInfo that is an immutable capture of the .Net Instrument object.
  • The InstrumentConfiguration currently applies to the instrument (also immutable).
  • It also exposes a string FullName that is the "Meter's name/Instrument's name".

Currently InstrumentConfiguration only contains a bool Enabled property but it is planned to extend it.

Applying configurations

Meters and Instruments are created by code. The configuration can only enable or disable them (at least for now). The MetricsConfiguration provided to the the static ApplyConfiguration contains "rules" to select instruments and the associated configuration to be applied as well as a potential update of the AutoObservableTimer timer delay.

public class MetricsConfiguration
{
    /// <summary>
    /// Gets or sets the timer delay that collects the <see cref="ObservableInstrument{T}"/>
    /// measures. Defaults to null (leaves the current value unchaged).
    /// <para>
    /// When not null, this is normalized to 0 (the default, auto collection is disabled by default)
    /// or to a value between 50 ms and 3_600_000 ms (one hour).
    /// </para>
    /// </summary>
    public int? AutoObservableTimer { get; set; }

    /// <summary>
    /// Gets the ordered list of configurations to apply from most precise one to more general one.
    /// </summary>
    public IList<(InstrumentMatcher,InstrumentConfiguration)> Configurations => _configurations;
}

The "rules" are the InstrumentMatcher that are very simple immutable objects that have a wildcard NamePattern applied to the FullInstrumentInfo.FullName. This uses a very basic projection to a regular expression:

  • ? Maps to any single character ('.' in RegEx syntax).
  • * Maps to the lazy '.*?' pattern.
  • ** Maps to the greedy '.*' pattern.

Because "*" or "**" matches every instrument. To enable all the instruments and collect the ObservableInstrument each 50 ms:

var c = new MetricsConfiguration();
c.AutoObservableTimer = 50;
c.Configurations.Add( (new InstrumentMatcher( "*" ), new InstrumentConfiguration( true )) );
DotNetMetrics.ApplyConfiguration( c );

Configuration Priority

Rules are matched in order. Put specific rules before general ones:

var config = new MetricsConfiguration();
// Disable .NET internal metrics
config.Configurations.Add((new InstrumentMatcher("System.**"), InstrumentConfiguration.BasicDisabled));
// Disable debug metrics
config.Configurations.Add((new InstrumentMatcher("MyApp.Debug.*"), InstrumentConfiguration.BasicDisabled));
// Enable all other app metrics
config.Configurations.Add((new InstrumentMatcher("MyApp.*"), InstrumentConfiguration.BasicEnabled));
DotNetMetrics.ApplyConfiguration(config);

For more complex configurations, the IncludeTags and ExcludeTags can be used.

public sealed class InstrumentMatcher
{
    public string NamePattern { get; }

    /// <summary>
    /// Gets the required tags that must appear in <see cref="MeterInfo.Tags"/> or <see cref="InstrumentInfo.Tags"/>.
    /// Since a <c>null</c> is not welcome (but accepted) in tag values, a <c>null</c> value matches any tag value.
    /// </summary>
    public ImmutableArray<KeyValuePair<string, object?>> IncludeTags { get; }

    /// <summary>
    /// Gets the tags that must not appear in <see cref="MeterInfo.Tags"/> or <see cref="InstrumentInfo.Tags"/>.
    /// See <see cref="IncludeTags"/>.
    /// </summary>
    public ImmutableArray<KeyValuePair<string, object?>> ExcludeTags { get; }

Restrictions

A fundamental restriction is the type of the measures that can be collected by the .Net Instrument<T> and ObservableInstrument<T>: a measure can be byte, short, int, long, float, double and decimal.

Meter

The meter's name must be a namespace-like simple identifier that cannot be longer than the static int DotNetMetrics.MeterNameLengthLimit that defaults to 255 charaters. This can be programmatically changed if needed. It is checked by ^[a-zA-Z][_a-zA-Z0-9]*(\.[_a-zA-Z0-9]*)*$ regular expression.

Its version, when not null or empty must be a non negative integer or a standard 2, 3 or 4 parts Version.

The meter's description can be any string (of any length).

Instrument

The instrument's name follows https://opentelemetry.io/docs/specs/otel/metrics/api/#instrument-name-syntax. It cannot be longer than 255 charaters. It is checked by ^[a-zA-Z][-_\./a-zA-Z0-9]*$ regular expression.

The instrument's unit (when defined) must be at most 63 characters and can contain any Unicode characters. See https://opentelemetry.io/docs/specs/otel/metrics/api/#instrument-unit

Tags (Attributes)

By defaut, a maximum of 128 tags per meter or instrument is allowed. This can be programmatically changed by the static int DotNetMetrics.AttributeCountLimit property.

For tag's key (attribute names for OpenTelemetry) we enforce the OpenTelemetry Semantic Conventions and restricts keys to not be longer than the static int DotNetMetrics.AttributeNameLengthLimit that defaults to 255 charaters. This can be programmatically changed if needed. It is checked by ^[a-z][a-z0-9]*((\.|_)[a-z][a-z0-9]*)*$ regular expression.

Only long, double, bool and string (or array of them) are allowed in Tag values (see OpenTelemetry's attributes). However, strings cannot be longer than the static int DotNetMetrics.AttributeValueLengthLimit that defaults to 1023 charaters. This can be programmatically changed if needed.

Notes:

  • A null is allowed as a tag's value and in an array of strings but a warning is emitted when a null value is found.
  • Tags' value of type int are automatically converted to long (in the MeterInfo).

Monitoring Integration

CK.Metrics logs metrics through ActivityMonitor.StaticLogger. To persist and process these metrics, use the companion packages:

  • CK.Monitoring.Metrics: Provides MetricsLogHandler for the GrandOutput pipeline that writes metrics to FasterLog.
  • CK.AppIdentity.Monitoring.Metrics: Full integration with FasterLog lifecycle management and IMetricsConsumer interface.

Full Stack Example

// 1. Configure GrandOutput with metrics handler
await using var go = GrandOutput.EnsureActiveDefault(new GrandOutputConfiguration
{
    Handlers = { new MetricsLogHandlerConfiguration() }
});

// 2. Enable metrics collection
DotNetMetrics.ApplyConfiguration(new MetricsConfiguration
{
    AutoObservableTimer = 50,
    Configurations = { (new InstrumentMatcher("*"), InstrumentConfiguration.BasicEnabled) }
});

// 3. Create and use metrics
using var meter = new Meter("MyApp.Service", "1.0");
var counter = meter.CreateCounter<int>("requests.total")
    .DefaultConfigure(InstrumentConfiguration.BasicEnabled);
counter.Add(1);
// Metrics flow through GrandOutput -> MetricsLogHandler -> FasterLog
Package Description
CK.Monitoring.Metrics GrandOutput handler that writes metrics to FasterLog
CK.AppIdentity.Monitoring.Metrics AppIdentity integration with FasterLog management and MetricsConsumerBase
CK.AppIdentity.Monitoring.Metrics.Csv CSV export consumer for metrics analysis
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 was computed.  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 was computed.  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 (1)

Showing the top 1 NuGet packages that depend on CK.Metrics:

Package Downloads
CK.Monitoring.Metrics

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.3 168 1/26/2026
0.3.2 157 1/26/2026
0.3.1 157 1/25/2026
0.3.0 168 1/24/2026
0.2.0 243 11/14/2025
0.1.1 134 9/27/2025
0.1.0 196 9/11/2025