Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ZeroAlloc.Telemetry

NuGet Build License: MIT AOT GitHub Sponsors

Source-generated OpenTelemetry instrumentation for .NET — Activity spans and Meter instruments without reflection, params object[] boxing, or runtime attribute inspection. Native AOT safe.

Add [Instrument] to any interface and the generator emits a sealed proxy class that wraps your implementation and records spans and metrics per method. Swap it in via DI without changing application code.

Install

dotnet add package ZeroAlloc.Telemetry

The package bundles both the attribute assembly and the generator — no separate install.

Quick Start

using ZeroAlloc.Telemetry;

// 1. Annotate your interface
[Instrument("MyApp.Orders")]
public interface IOrderService
{
    [Trace("order.create")]
    [Count("orders.created")]
    ValueTask<OrderId> CreateOrderAsync(CreateOrderCommand cmd, CancellationToken ct);

    [Trace("order.get")]
    [Histogram("order.get_ms")]
    ValueTask<Order> GetOrderAsync(OrderId id, CancellationToken ct);
}

// 2. The generator emits OrderServiceInstrumented : IOrderService automatically.

// 3. Wire up in DI
services.AddSingleton<OrderServiceImpl>();
services.AddSingleton<IOrderService>(sp =>
    new OrderServiceInstrumented(sp.GetRequiredService<OrderServiceImpl>()));

No OpenTelemetry SDK dependency is required. ActivitySource and Meter are BCL types (net8.0+). Add exporters (OTLP, Console, Prometheus) in your application startup separately.

What Gets Generated

For the CreateOrderAsync method above, the generator emits:

// <auto-generated />
internal sealed class OrderServiceInstrumented : IOrderService
{
    private static readonly ActivitySource _activitySource = new("MyApp.Orders");
    private static readonly Meter _meter = new("MyApp.Orders");
    private static readonly Counter<long> _orders_created =
        _meter.CreateCounter<long>("orders.created");

    private readonly IOrderService _inner;
    public OrderServiceInstrumented(IOrderService inner) => _inner = inner;

    public async ValueTask<OrderId> CreateOrderAsync(CreateOrderCommand cmd, CancellationToken ct)
    {
        using var _activity = _activitySource.StartActivity("order.create");
        try
        {
            var _result = await _inner.CreateOrderAsync(cmd, ct);
            _orders_created.Add(1);
            return _result;
        }
        catch (Exception _ex)
        {
            _activity?.SetStatus(ActivityStatusCode.Error, _ex.Message);
            throw;
        }
    }
    // ...
}

Performance

Head-to-head vs hand-written ActivitySource + Meter instrumentation (no-listeners profile, .NET 10.0.7, BenchmarkDotNet v0.15.4):

Method Time Allocated
Direct call (no instrumentation) 87 ns 72 B
Hand-written ActivitySource + Counter + Histogram 201 ns 72 B
ZA.Telemetry generated proxy 201 ns 72 B

ZA's generated code is at parity with hand-written instrumentation (within measurement noise). The value isn't faster instrumentation — it's eliminating the boilerplate so every [Trace] / [Count] / [Histogram]-annotated method gets the same try/finally pattern, with zero risk of forgetting to dispose an Activity or skipping a metric.

Full methodology: docs/performance.md.

Instruments

Attribute Instrument Recorded when
[Trace("name")] ActivitySource.StartActivity("name") Every call — stopped in finally, Error status on exception
[Count("metric")] Counter<long>.Add(1) After a successful (non-throwing) call only
[Histogram("metric")] Histogram<double>.Record(ms) Every call including on exception

Packages

Package Description TFM
ZeroAlloc.Telemetry Attributes + bundled generator net8.0;net9.0;net10.0
ZeroAlloc.Telemetry.Generator Roslyn source generator (standalone) netstandard2.0

The generator is bundled inside ZeroAlloc.Telemetry as an analyzer. Install ZeroAlloc.Telemetry.Generator separately only if you need the generator without the attribute assembly.

AOT Safety

Generated proxies use no reflection, no params object[], and no runtime type inspection. T is closed at generation time — the generated ActivitySource and Meter field initializers are plain constructor calls. Fully compatible with <PublishAot>true</PublishAot>.

Documentation

Full docs at telemetry.zeroalloc.net.

Page Description
Getting Started Install and instrument your first interface
Attribute Reference [Instrument], [Trace], [Count], [Histogram], [TraceTag], [TraceTagFromResult], [TraceTagConstant]
Source Generator What the generator emits — input/output examples
Testing Assert spans and metrics with BCL listeners
AOT & Trimming Native AOT compatibility

License

MIT

About

Source-generated OpenTelemetry instrumentation for .NET — Activity spans and Meter instruments without reflection or allocation. Native AOT safe.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages