Skip to content

Traces

The Cratis Fundamentals library provides a typed tracing API on top of .NET activities. You work with IActivitySource<T> and IActivityScope<T> instead of raw ActivitySource and Activity, and you can generate spans from partial methods with the Cratis.Metrics.Roslyn package.

The tracing system gives you:

  • Typed activity sources through IActivitySource<T>
  • Disposable activity scopes through IActivityScope<T>
  • Source-generated spans with [Span]
  • Automatic tags from method parameters
  • Ambient parent-child relationships through Activity.Current

To use traces in your project:

  1. Add a reference to Cratis.Fundamentals
  2. Add a reference to Cratis.Metrics.Roslyn if you want source-generated span methods
  3. Register your services with AddBindingsByConvention() or AddSelfBindings()
  4. Inject IActivitySource<T> into the service that creates spans
var services = new ServiceCollection();
services.AddBindingsByConvention();

For keyed named activity source configuration, see Named Registration.

An activity source is the typed entry point for creating spans:

public interface IActivitySource<T>
{
ActivitySource ActualSource { get; }
}

When you resolve IActivitySource<T> from dependency injection, Fundamentals creates an ActivitySource named after T.

An activity scope wraps the underlying activity and stops it when you dispose it:

public interface IActivityScope<T> : IDisposable
{
Activity? Activity { get; }
}

Use using so the activity stops even when an exception is thrown:

using var scope = _activitySource.ProcessOrder(orderId, customerId);

ActivitySource.StartActivity() automatically uses Activity.Current as the parent. That means nested generated spans form a hierarchy without passing parent spans around manually.

using var orderScope = _activitySource.ProcessOrder(order.Id, order.CustomerId);
using var validationScope = _activitySource.ValidateOrder(order.Id);

The Cratis.Metrics.Roslyn package can implement your span methods at compile time.

  1. Create a partial class
  2. Add static partial methods that return IActivityScope<T>
  3. Decorate them with [Span]
  4. Use this IActivitySource<T> as the first parameter so the generated spans become extension methods
using Cratis.Traces;
using System.Diagnostics;
namespace MyApplication.Orders;
public static partial class OrderTraces
{
[Span("order.process", ActivityKind.Server)]
internal static partial IActivityScope<OrderService> ProcessOrder(
this IActivitySource<OrderService> source,
string orderId,
string customerId);
[Span("order.validate")]
internal static partial IActivityScope<OrderService> ValidateOrder(
this IActivitySource<OrderService> source,
string orderId);
}

The generator:

  • starts the activity with the configured name and kind
  • converts additional parameter names to snake_case tags
  • returns an ActivityScope<T> that stops the activity on disposal
using Cratis.Traces;
namespace MyApplication.Orders;
public class OrderService(IActivitySource<OrderService> activitySource)
{
readonly IActivitySource<OrderService> _activitySource = activitySource;
public async Task Process(Order order)
{
using var processScope = _activitySource.ProcessOrder(order.Id, order.CustomerId);
await Validate(order);
await Save(order);
}
async Task Validate(Order order)
{
using var validationScope = _activitySource.ValidateOrder(order.Id);
await Task.CompletedTask;
}
async Task Save(Order order)
{
await Task.CompletedTask;
}
}
public record Order(string Id, string CustomerId);

If you need to add extra tags or status details, use the wrapped activity directly:

using var scope = _activitySource.ProcessOrder(order.Id, order.CustomerId);
scope.Activity?.SetTag("retry_count", retryCount);
scope.Activity?.SetStatus(ActivityStatusCode.Ok);
  1. Always use using when you create an IActivityScope<T>
  2. Keep span names stable so dashboards and queries do not drift
  3. Use low-cardinality tags unless you explicitly need high-cardinality diagnostics
  4. Create nested spans for meaningful sub-operations, not every line of code
  5. Prefer extension-method span declarations so call sites read as _activitySource.ProcessOrder(...)
  6. Keep trace declarations together in a single partial class per area