Attribute Reference
[Instrument]
[AttributeUsage(AttributeTargets.Interface)]
public sealed class InstrumentAttribute : Attribute
{
public string ActivitySource { get; }
public InstrumentAttribute(string activitySource);
}
Placement: Interface only.
Effect: Triggers the source generator. The generator emits a sealed proxy class named {TypeName}Instrumented (leading I stripped) in the same namespace as the interface.
activitySource: The name used for both the static ActivitySource and the static Meter field in the generated proxy. Typically a dotted component name: "MyApp.Orders", "ZeroAlloc.EventSourcing".
[Instrument("MyApp.Payments")]
public interface IPaymentGateway { ... }
// Emits: PaymentGatewayInstrumented : IPaymentGateway
// ActivitySource name: "MyApp.Payments"
// Meter name: "MyApp.Payments"
[Trace]
[AttributeUsage(AttributeTargets.Method)]
public sealed class TraceAttribute : Attribute
{
public string Name { get; }
public TraceAttribute(string name);
}
Placement: Interface method.
Effect: Wraps the method body in an Activity span.
- Span is started with
ActivitySource.StartActivity("name")before the call. - Span is stopped automatically via
using(disposed infinally). - On exception:
activity?.SetStatus(ActivityStatusCode.Error, ex.Message)then rethrow.
[Trace("payment.charge")]
ValueTask<ChargeResult> ChargeAsync(ChargeRequest request, CancellationToken ct);
Generated:
using var _activity = _activitySource.StartActivity("payment.charge");
try { ... }
catch (Exception _ex)
{
_activity?.SetStatus(ActivityStatusCode.Error, _ex.Message);
throw;
}
[Count]
[AttributeUsage(AttributeTargets.Method)]
public sealed class CountAttribute : Attribute
{
public string Metric { get; }
public CountAttribute(string metric);
}
Placement: Interface method.
Effect: Increments a Counter<long> by 1 after a successful (non-throwing) call only.
The counter field is a static field on the proxy — one per unique metric name across all methods. If two methods share the same metric name, only one Counter<long> field is emitted.
[Count("payments.charged")]
ValueTask<ChargeResult> ChargeAsync(ChargeRequest request, CancellationToken ct);
Generated field + increment:
private static readonly Counter<long> _payments_charged =
_meter.CreateCounter<long>("payments.charged");
// In the method body (success path only):
_payments_charged.Add(1);
[Histogram]
[AttributeUsage(AttributeTargets.Method)]
public sealed class HistogramAttribute : Attribute
{
public string Metric { get; }
public HistogramAttribute(string metric);
}
Placement: Interface method.
Effect: Records the elapsed time in milliseconds in a Histogram<double> on every call — including when the method throws.
Uses Stopwatch.GetTimestamp() before the call and Stopwatch.GetElapsedTime(ts).TotalMilliseconds after, so the measurement includes the full method duration regardless of outcome.
[Histogram("payment.charge_ms")]
ValueTask<ChargeResult> ChargeAsync(ChargeRequest request, CancellationToken ct);
Generated field + recording:
private static readonly Histogram<double> _payment_charge_ms =
_meter.CreateHistogram<double>("payment.charge_ms");
// In the method body:
var _sw = Stopwatch.GetTimestamp();
try
{
var _result = await _inner.ChargeAsync(request, ct);
_payment_charge_ms.Record(Stopwatch.GetElapsedTime(_sw).TotalMilliseconds);
return _result;
}
catch (Exception _ex)
{
_payment_charge_ms.Record(Stopwatch.GetElapsedTime(_sw).TotalMilliseconds);
throw;
}
Combining Attributes
All four attributes can appear on the same method:
[Instrument("MyApp.Payments")]
public interface IPaymentGateway
{
[Trace("payment.charge")]
[Count("payments.charged")]
[Histogram("payment.charge_ms")]
ValueTask<ChargeResult> ChargeAsync(ChargeRequest request, CancellationToken ct);
}
The generated code records the span, the histogram (on both success and failure), and the counter (on success only).
Methods Without Attributes
Methods with no [Trace], [Count], or [Histogram] annotation are passed through to the inner implementation without any wrapping — no try/catch, no timing, no span. They are still correctly proxied.