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.
Table of Contents
Section titled “Table of Contents”Overview
Section titled “Overview”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
Getting Started
Section titled “Getting Started”To use traces in your project:
- Add a reference to
Cratis.Fundamentals - Add a reference to
Cratis.Metrics.Roslynif you want source-generated span methods - Register your services with
AddBindingsByConvention()orAddSelfBindings() - Inject
IActivitySource<T>into the service that creates spans
var services = new ServiceCollection();
services.AddBindingsByConvention();For keyed named activity source configuration, see Named Registration.
Core Concepts
Section titled “Core Concepts”Activity sources
Section titled “Activity sources”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.
Activity scopes
Section titled “Activity scopes”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);Ambient span relationships
Section titled “Ambient span relationships”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);Using Source Generation
Section titled “Using Source Generation”The Cratis.Metrics.Roslyn package can implement your span methods at compile time.
- Create a partial class
- Add
static partialmethods that returnIActivityScope<T> - Decorate them with
[Span] - 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_casetags - returns an
ActivityScope<T>that stops the activity on disposal
Examples
Section titled “Examples”Creating spans in a service
Section titled “Creating spans in a service”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);Working with the underlying activity
Section titled “Working with the underlying activity”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);Best Practices
Section titled “Best Practices”- Always use
usingwhen you create anIActivityScope<T> - Keep span names stable so dashboards and queries do not drift
- Use low-cardinality tags unless you explicitly need high-cardinality diagnostics
- Create nested spans for meaningful sub-operations, not every line of code
- Prefer extension-method span declarations so call sites read as
_activitySource.ProcessOrder(...) - Keep trace declarations together in a single partial class per area