DotnetKit.MetricFlow.AspNetCore
1.0.51
See the version list below for details.
dotnet add package DotnetKit.MetricFlow.AspNetCore --version 1.0.51
NuGet\Install-Package DotnetKit.MetricFlow.AspNetCore -Version 1.0.51
<PackageReference Include="DotnetKit.MetricFlow.AspNetCore" Version="1.0.51" />
<PackageVersion Include="DotnetKit.MetricFlow.AspNetCore" Version="1.0.51" />
<PackageReference Include="DotnetKit.MetricFlow.AspNetCore" />
paket add DotnetKit.MetricFlow.AspNetCore --version 1.0.51
#r "nuget: DotnetKit.MetricFlow.AspNetCore, 1.0.51"
#:package DotnetKit.MetricFlow.AspNetCore@1.0.51
#addin nuget:?package=DotnetKit.MetricFlow.AspNetCore&version=1.0.51
#tool nuget:?package=DotnetKit.MetricFlow.AspNetCore&version=1.0.51
MetricFlow
MetricFlow is a lightweight .NET library designed to simplify the way developers define and track technical and business related metrics (such as counters, timers, throughput, and dimensional breakdowns).
What Can We Do with MetricFlow?
MetricFlow provides domain-oriented observability to monitor an entire application or surgically profile specific portions of your code (methods, background loops, external calls, batch jobs).
It could be used to create an in-memory metrics store with support for OpenTelemetry integration. It can be used in both ASP.NET Core and non-ASP.NET Core applications.
Streamlined Use Cases
| Use Case | Best For | Registration Style | Primary Injections / APIs | Example Project |
|---|---|---|---|---|
| Simple Implementation | Targeted method/loop profiling, CLI jobs, algorithms | Standalone instantiation | new MetricTracker(...) |
BasicConsoleExample & AdvancedConsoleExample |
| DI-Based Implementation | Background workers, daemons, multi-tenant | Standard DI (IServiceCollection) |
IMetricTracker, [FromKeyedServices], IMetricFlow |
AdvancedConsoleWithDIExample |
| Web Implementation | Web APIs, microservices, HTTP routing | ASP.NET Core pipeline | app.UseMetricFlow(), app.MapMetricFlow("/metrics") |
WebApiExample |
| OpenTelemetry & Observability | Prometheus, Grafana, Datadog, OTLP collectors, CLI counters | OpenTelemetry SDK / BCL | .AddMetricFlowInstrumentation(), dotnet-counters |
OpenTelemetryConsoleExample |
1. Targeted Code Profiling & Standalone Tracking (Simple Implementation)
- Goal: Monitor a specific block of code, method, or CLI task with zero ceremony and no DI container.
- When to use: Quick diagnostics, utility tools, benchmarks, batch scripts, and targeted algorithms.
- Key capabilities: Lightweight
using (tracker.Track("Task"))ortracker.TrackAction(...)measuring duration, memory allocations, throughput, and error rates. - Example Projects: BasicConsoleExample & AdvancedConsoleExample
2. Background Daemons, Workers & Multi-Topic Services (DI-Based Implementation)
- Goal: Monitor long-running processes, message queues, and modular services through standard Microsoft DI.
- When to use: Worker Services, hosted daemons (
IHostedService), background consumers, and microservices requiring topic isolation. - Key capabilities:
services.AddMetricFlow(),options.AddTagsEnricher, multi-topic fluent builder (AddMetricTracker), and native keyed resolution ([FromKeyedServices("topic")] IMetricTracker). - Example Project: AdvancedConsoleWithDIExample
3. Full HTTP Request Pipeline & Health Monitoring (Web Implementation)
- Goal: Automatically observe incoming HTTP traffic, endpoint performance, and status codes in web APIs.
- When to use: REST APIs, Minimal APIs, and web apps needing route-level latency distributions and a standardized telemetry endpoint.
- Key capabilities: Turnkey middleware (
app.UseMetricFlow()), dynamic route resolution, HTTP request tag enrichment (AddHttpTagsEnricher), and exposed diagnostic route (app.MapMetricFlow("/metrics")). - Example Project: WebApiExample
4. OpenTelemetry & Cloud Telemetry Ecosystem (Observability Implementation)
- Goal: Seamlessly export domain metrics and scoped tracking to Prometheus, Grafana, Datadog, AWS CloudWatch, and Azure Monitor via OpenTelemetry or inspect live in the terminal using
dotnet-counters. - When to use: Microservices connected to centralized APM systems, cloud platforms, and local CLI diagnostics.
- Key capabilities: Turnkey
.AddMetricFlowInstrumentation(), BCLSystem.Diagnostics.Metricsbridge (Histogram,Counter,UpDownCounter), automatic tag cardinality sanitization, and ambient trace correlation (trace_id,span_id). - Example Project: OpenTelemetryConsoleExample
Features
- Counters: Track execution counts and occurrences of events.
- Timers & Duration: High-precision operation timing via lock-free stopwatch ticks.
- Throughput & Item Tracking: Measure batch sizes, entity counts, and processing rates (items/sec) with
ThroughputCounter. - Dimensional Breakdown & Slicing: Slice and compute operation distributions by business dimensions, tags, or computed rules with
DimensionCounterand built-in cardinality safeguards. - Memory Tracking: Measure per-operation heap allocations with
MemoryCounter. - Exception & Failure Tracking: Capture errors, exceptions, and failure counts with
ExceptionCounter. - System.Diagnostics.Metrics Bridge: Automatic zero-allocation mapping to standard .NET BCL instruments (
Histogram,Counter,UpDownCounter) with cardinality protection. - OpenTelemetry Integration: Turnkey
DotnetKit.MetricFlow.OpenTelemetrypackage with.AddMetricFlowInstrumentation()for exporting to Prometheus, Grafana, Datadog, and OTLP collectors. - CLI Diagnostics: Live real-time inspection in terminal via standard
dotnet-counters monitor. - Metadata and Tags: Add contextual information to metrics for rich analysis and filtering.
- Sampling: Thread-safe sampling control to balance performance and data volume.
- ASP.NET Core Integration: Turnkey middleware, endpoint routing resolution, and metric exposition endpoints.
- Pluggable & Extensible: Fully customizable counter lifecycle (
CounterBase<TState>) and trackers (MetricTrackerBase). - Multi-Targeting: Native support for
.NET 8.0and.NET 10.0.
Getting Started
Prerequisites
- .NET SDK (8.0 or 10.0) installed on your machine
Installation
Install via NuGet package manager:
# Core library
dotnet add package DotnetKit.MetricFlow
# OpenTelemetry integration (optional)
dotnet add package DotnetKit.MetricFlow.OpenTelemetry
# ASP.NET Core integration (optional)
dotnet add package DotnetKit.MetricFlow.AspNetCore
Or clone and build locally:
git clone https://github.com/DotnetKit/MetricFlow.git
cd MetricFlow
dotnet restore
dotnet build
Usage
1. Initialize Tracker
Initialize a tracker and optionally chain throughput, memory, exception, and dimension counters:
using DotnetKit.MetricFlow;
var tracker = new MetricTracker("OrderService", new()
{
["environment"] = "production"
})
.AddThroughputCounter()
.AddMemoryCounter()
.AddExceptionCounter()
.AddDimensionCounter("country");
2. Basic Example (Minimal Setup)
The simplest usage requires zero additional counters or complex configuration—by default, MetricTracker records high-precision execution duration:
using DotnetKit.MetricFlow;
// Initialize tracker (DurationCounter is included by default)
var tracker = new MetricTracker("BasicConsoleTopic", new()
{
["environment"] = "Development"
});
// 1. Scoped tracking with using statement
using (tracker.Track("ProcessOrder"))
{
await Task.Delay(10);
}
// 2. Delegate tracking with TrackAction
tracker.TrackAction("ValidatePayment", () => Thread.Sleep(5));
// 3. Print formatted telemetry
Console.WriteLine(tracker.ToString());
Output:
BasicConsoleTopic
Topic Tags: environment:Development
[Duration] Metric: ProcessOrder
Duration (min, max, avg): 10.50 ms / 12.25 ms / 11.17 ms
Total duration: 55.86 ms
[Duration] Metric: ValidatePayment
Duration (min, max, avg): 5.64 ms / 5.83 ms / 5.70 ms
Total duration: 17.11 ms
3. Tracker Capabilities
Scope Tracking (using)
Measures execution duration until the scope is disposed:
// Explicit metric name (with optional tags)
using (tracker.Track("ProcessOrder", new() { ["order_id"] = "123" }))
{
// work here
}
// Automatic metric name via [CallerMemberName]
void ProcessOrder()
{
using var _ = tracker.Track(); // Metric name is "ProcessOrder"
}
Throughput & Batch Tracking (TrackItems / scope.SetItems)
Track batch or entity processing volume and calculate velocity (items/sec):
// 1. Specify item count upfront via TrackItems
using (tracker.TrackItems("ImportChannels", 500))
{
// Process 500 channels...
}
// 2. Or set dynamic count during / at completion of the operation
using (var scope = tracker.Track("IngestMessages"))
{
var count = await ReadAndProcessBatchAsync();
scope.SetItems(count); // Records processed count for ThroughputCounter
}
// Inspect results
var throughput = tracker.GetThroughputValues("ImportChannels");
// throughput.TotalItems -> 500
// throughput.ItemsPerSecond -> e.g. 2,500 items/sec
Dimensional Breakdown (DimensionCounter / AddDimensionCounter)
Slice and categorize operation counts by business dimensions, tags, composite keys, or custom computed business rules with built-in cardinality safeguards:
// 1. Single dimension tag breakdown with cardinality limit (defaults to 250, overflow into [Other])
tracker.AddDimensionCounter("country", maxUniqueValues: 100);
// 2. Composite multi-tag dimension (e.g. "US / CreditCard", "DE / PayPal")
tracker.AddDimensionCounter(
name: "PaymentChannels",
dimensionKeys: ["country", "payment_method"]);
// 3. Computed business selector / conditional rules (zero custom metric classes needed)
tracker.AddDimensionCounter("CustomerTier", (tags, metadata) =>
{
var amount = metadata?.GetValueOrDefault("amount") ?? 0;
var country = tags?.GetValueOrDefault("country") ?? "Unknown";
if (amount >= 1000) return $"VIP_{country}";
if (amount >= 100) return $"Standard_{country}";
return null; // Return null to skip or mark untracked
});
// Tracking with tags and metadata
using (var scope = tracker.Track("ProcessOrder", new() { ["country"] = "US", ["payment_method"] = "CreditCard" }))
{
scope.SetMetadata("amount", 1500); // Evaluates CustomerTier to "VIP_US"
}
// Inspect snapshots
var countryDim = tracker.GetDimensionValues("ProcessOrder", "country");
var paymentDim = tracker.GetDimensionValues("ProcessOrder", "PaymentChannels");
var tierDim = tracker.GetDimensionValues("ProcessOrder", "CustomerTier");
Delegate Tracking (TrackAction / TrackActionAsync)
Executes an action or task with automatic duration tracking and exception capture:
// Explicit metric name (sync or async, with optional return value)
tracker.TrackAction("ProcessOrder", () => DoWork());
var order = await tracker.TrackActionAsync("FetchOrder", async () => await FetchOrderAsync());
// Automatic metric name via [CallerMemberName]
void ProcessOrder()
{
tracker.TrackAction(() => DoWork()); // Metric name is "ProcessOrder"
}
async Task ProcessOrderAsync()
{
await tracker.TrackActionAsync(async () => await DoWorkAsync());
}
Dependency Injection (Console Apps, Workers & Daemons)
Register MetricFlow in any .NET application using Microsoft.Extensions.DependencyInjection without ASP.NET Core dependencies:
using Microsoft.Extensions.DependencyInjection;
using DotnetKit.MetricFlow;
// Register MetricFlow with topic and optional configuration
services.AddMetricFlow("WorkerDaemon", options =>
{
options.SamplingRate = 1.0;
options.AddTagsEnricher(tags =>
{
tags["env"] = "Production";
});
});
// Inject IMetricTracker or MetricTracker anywhere in your application
public class QueueWorker(IMetricTracker tracker)
{
public async Task ProcessAsync()
{
using (tracker.Track("ProcessMessage"))
{
await HandleMessageAsync();
}
}
}
Multi-Topic Support & Fluent Builder
Register multiple isolated topic trackers in the same application via the fluent builder (AddMetricTracker) and resolve them via the IMetricFlow facade or native keyed injection:
// Fluent builder registration
services.AddMetricFlow("WebApi", options => ...)
.AddMetricTracker("WeatherRadar", options => ...);
// 1. Resolve via IMetricFlow facade
public class IngestionService(IMetricFlow metricFlow)
{
public void Run()
{
var tracker = metricFlow.GetTracker("WeatherRadar");
using var scope = tracker.Track("ScanRadar");
}
}
// 2. Or resolve via native Keyed Services (.NET 8+)
public class RadarWorker([FromKeyedServices("WeatherRadar")] IMetricTracker tracker)
{
// ...
}
ASP.NET Core Integration
Enable automated HTTP request duration, memory allocation, and failure tracking via middleware:
using DotnetKit.MetricFlow.AspNetCore.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Register MetricFlow with optional tag enrichment
builder.Services.AddMetricFlow("WebApiExample", options =>
{
options.EnrichTags = (tags, context) =>
{
if (context.Request.Headers.TryGetValue("X-Tenant-ID", out var tenantId))
{
tags["tenant_id"] = tenantId!;
}
};
});
var app = builder.Build();
// Automated request tracking middleware
app.UseMetricFlow();
// Expose metric snapshot endpoint
app.MapMetricFlow("/metrics");
app.Run();
6. OpenTelemetry & Cloud Telemetry (DotnetKit.MetricFlow.OpenTelemetry)
MetricFlow seamlessly bridges domain metrics and scoped tracking to the standard OpenTelemetry .NET ecosystem. You can configure OpenTelemetry directly using the fluent .WithOpenTelemetry() sub-builder:
using DotnetKit.MetricFlow.OpenTelemetry;
using OpenTelemetry.Metrics;
services.AddMetricFlow("Billing", options => options.AddThroughputCounter())
.WithOpenTelemetry(otel =>
{
otel.WithMetrics(metrics =>
{
// Export to any OpenTelemetry collector
metrics.AddOtlpExporter()
.AddPrometheusExporter();
});
otel.WithTracing();
});
You can also use .AddMetricFlowInstrumentation() directly on an existing MeterProviderBuilder:
Standard BCL Instruments Mapped
| MetricFlow Concept | Instrument Type | Metric Name | Unit | Tags / Attributes |
|---|---|---|---|---|
| DurationCounter | Histogram<double> |
{metricName}.duration |
ms |
operation, status ("ok"/"error"), sanitized tags |
| Execution Counts | Counter<long> |
{metricName}.total |
{operations} |
operation, status ("ok"/"error"), sanitized tags |
| Throughput / Items | Counter<long> |
{metricName}.items |
{items} |
operation, sanitized tags |
| ExceptionCounter | Counter<long> |
{metricName}.exceptions |
{exceptions} |
operation, exception.type, sanitized tags |
| In-Flight / Concurrency | UpDownCounter<long> |
{metricName}.active |
{operations} |
operation, sanitized tags |
Ambient Distributed Tracing Correlation
Enrich scope tags with the ambient OpenTelemetry Activity.Current trace and span IDs:
using DotnetKit.MetricFlow.OpenTelemetry;
var tags = new Dictionary<string, string> { ["region"] = "eu" }.WithTraceContext();
using (tracker.Track("ProcessOrder", tags))
{
// Metric measurements now carry trace_id and span_id attributes
}
7. Live Terminal Diagnostics (dotnet-counters)
Because MetricFlow is backed directly by System.Diagnostics.Metrics.Meter, you can inspect active metrics in real time in your terminal without configuring any external collector:
# Monitor live operations for topic "OrderProcessingService"
dotnet-counters monitor -p <PID> --counters DotnetKit.MetricFlow.OrderProcessingService
# Or monitor across all MetricFlow topics
dotnet-counters monitor -p <PID> --counters DotnetKit.MetricFlow
Under the Hood: System.Diagnostics Bridge
MetricFlow connects domain tracking with the .NET runtime using a lightweight, built-in bridge to the standard BCL System.Diagnostics.Metrics APIs:
Decoupled, Zero-Dependency Core:
- The core
DotnetKit.MetricFlowlibrary has no external dependency on the OpenTelemetry SDK. - It creates standard BCL
System.Diagnostics.Metrics.Meterinstances per topic viaIMetricMeterBridgeandMetricFlowMeterRegistry. - External exporters (Prometheus, OTLP, Datadog) or the
DotnetKit.MetricFlow.OpenTelemetrypackage plug into these native .NET meters without requiring custom adapters.
- The core
Hot-Path Lifecycle Dispatch:
- Operation Start (
In): Atomically increments an in-flight concurrency gauge (UpDownCounter<long>named{metricName}.active). - Operation Completion (
Out/Dispose):- Records latency in milliseconds into a
Histogram<double>({metricName}.duration). - Increments the total execution count (
Counter<long>named{metricName}.total) tagged with operation status ("ok"or"error"). - If items/records were processed (via
TrackItemsorscope.SetItems), records batch volume into aCounter<long>({metricName}.items). - If an exception was thrown, records the error into a
Counter<long>({metricName}.exceptions) with theexception.typeattribute. - Decrements the in-flight concurrency gauge.
- Records latency in milliseconds into a
- Operation Start (
Tag Cardinality Guard (
TagCardinalityGuard):- High-cardinality tags (such as user IDs or order numbers) can quickly cause metric explosion and memory leaks in downstream time-series databases.
- The bridge tracks distinct values per tag key and clamps any values exceeding
MaxUniqueTagValues(default100, configurable viaoptions.ConfigureMeters(...)) into a safe fallback bucket ([Other]).
Dual-Mode Telemetry:
- Streaming BCL Metrics: Emitted immediately to any registered
MeterListener,dotnet-counters, or OpenTelemetryMeterProvider. - Aggregated Local Snapshots: Maintained concurrently in memory for local logging, diagnostics, health checks, or the
/metricsendpoint (tracker.ToString()).
- Streaming BCL Metrics: Emitted immediately to any registered
Examples
- BasicConsoleExample: Simplest implementation demonstrating minimal tracker setup and duration measurement with zero optional counters.
- AdvancedConsoleExample: Full multi-counter demonstration including duration, throughput (items/sec and batch sizing), memory allocation, exceptions, and delegate tracking.
- AdvancedConsoleWithDIExample: Standard Microsoft DI integration demonstrating fluent builder (
AddMetricTracker),AddTagsEnricher, multi-topic tracking, keyed services ([FromKeyedServices]), worker pipelines, and programmatic telemetry queries. - OpenTelemetryConsoleExample: Complete OpenTelemetry integration demonstrating
.AddMetricFlowInstrumentation(), raw console metric export, cardinality protection, batch items throughput, and trace correlation. - WebApiExample: Demonstrates ASP.NET Core integration, middleware, and
/metricsendpoint. - CustomCounters: Demonstrates extension capabilities by implementing custom counters and trackers.
Run the examples:
# Basic console example (minimal setup)
dotnet run --project examples/BasicConsoleExample
# Multi-counter console example (advanced: duration, throughput, memory, exceptions)
dotnet run --project examples/AdvancedConsoleExample
# Dependency injection console example (DI, AddTagsEnricher, keyed services)
dotnet run --project examples/AdvancedConsoleWithDIExample
# OpenTelemetry console example (OpenTelemetry SDK, BCL bridge, console exporter)
dotnet run --project examples/OpenTelemetryConsoleExample
# ASP.NET Core Web API example
dotnet run --project examples/WebApiExample
Extension Capabilities (Custom Counters & Trackers)
MetricFlow is designed to be extensible. You can implement custom counters by deriving from CounterBase<TState> and pre-configure trackers by inheriting from MetricTrackerBase.
See the examples in examples/CustomCounters:
1. Custom Counter (UtcDurationCounter)
Inherit from CounterBase<TState> to track state during operation lifecycle (OnIn / OnOut):
using DotnetKit.MetricFlow.Abstractions;
using DotnetKit.MetricFlow.Counters;
namespace CustomCounters;
/// <summary>
/// Counter based on UTC time using state token.
/// </summary>
public class UtcDurationCounter(string name = "UtcDuration") : CounterBase<long>(name)
{
private readonly DurationCounter _inner = new(name);
public override long OnIn(in InContext context)
{
if (!IsEnabled) return 0;
return DateTimeOffset.UtcNow.Ticks;
}
public override void OnOut(long state, in OutContext context)
{
if (!IsEnabled) return;
TimeSpan elapsed = TimeSpan.Zero;
if (state > 0)
{
elapsed = TimeSpan.FromTicks(DateTimeOffset.UtcNow.Ticks - state);
}
_inner.OnOut(state, new OutContext(context.MetricName, context.Failed, context.Exception, elapsed, context.Tags, context.Metadata, context.UtcTimestamp));
}
public override IMetricSnapshot? GetSnapshot(string metricName) => _inner.GetSnapshot(metricName);
public override IEnumerable<IMetricSnapshot> GetAllSnapshots() => _inner.GetAllSnapshots();
public override void Reset() => _inner.Reset();
}
2. Custom Tracker (CustomMetricTrackerWithUtcCounter)
Inherit from MetricTrackerBase to provide a domain-specific or pre-configured tracker with custom counters:
using DotnetKit.MetricFlow.Abstractions;
namespace CustomCounters;
/// <summary>
/// Custom metric tracker implementation with default UtcDurationCounter
/// </summary>
public class CustomMetricTrackerWithUtcCounter : MetricTrackerBase
{
public CustomMetricTrackerWithUtcCounter(
string topic,
IReadOnlyDictionary<string, string>? topicTags = null,
double? samplingRate = 1.0)
: base(topic, topicTags, samplingRate)
{
RegisterCounter(new UtcDurationCounter());
}
}
Roadmap
See ROADMAP.md for the development roadmap, upcoming milestones, and architectural improvements.
Changelog
See CHANGELOG.md for a detailed history of changes, releases, and fixes.
| Product | Versions 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 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. |
-
net10.0
- DotnetKit.MetricFlow (>= 1.0.51)
-
net8.0
- DotnetKit.MetricFlow (>= 1.0.51)
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 |
|---|---|---|
| 1.0.52-preview.0.2 | 21 | 10/9/2026 |
| 1.0.52-preview.0.1 | 47 | 10/4/2026 |
| 1.0.51 | 92 | 9/29/2026 |
| 1.0.5-preview.0.2 | 58 | 9/27/2026 |
| 1.0.5-preview.0.1 | 53 | 9/27/2026 |
| 1.0.4 | 92 | 9/22/2026 |
| 1.0.3-preview.0.2 | 55 | 9/20/2026 |
| 1.0.3-preview.0.1 | 58 | 9/19/2026 |
| 1.0.2 | 89 | 9/18/2026 |
| 1.0.0-preview.0.10 | 52 | 9/18/2026 |
| 1.0.0-preview.0.9 | 58 | 9/18/2026 |
| 1.0.0-preview.0.8 | 61 | 9/18/2026 |
| 1.0.0-preview.0.7 | 53 | 9/18/2026 |