Skip to main content

Request Observers

IRequestObserver hooks into the request lifecycle — started, completed, and failed — without modifying handler code.

Interface

public interface IRequestObserver
{
Task OnStarted(ObservabilityContext context, CancellationToken ct);
Task OnCompleted(ObservabilityContext context, CancellationToken ct);
Task OnFailed(ObservabilityContext context, CancellationToken ct);
}

public class ObservabilityContext
{
public string RequestName { get; }
public string OperationId { get; } // Guid.NewGuid().ToString()
public TimeSpan Duration { get; }
public bool IsSuccess { get; }
public Exception? Exception { get; }
public Dictionary<string, object?> Tags { get; }
}

Implementing an Observer

Logging Observer

public class LoggingObserver : IRequestObserver
{
private readonly ILogger<LoggingObserver> _logger;

public LoggingObserver(ILogger<LoggingObserver> logger)
{
_logger = logger;
}

public Task OnStarted(ObservabilityContext context, CancellationToken ct)
{
_logger.LogDebug("Starting {Request} [{OperationId}]",
context.RequestName, context.OperationId);
return Task.CompletedTask;
}

public Task OnCompleted(ObservabilityContext context, CancellationToken ct)
{
_logger.LogInformation(
"Completed {Request} in {Duration}ms [{OperationId}]",
context.RequestName,
context.Duration.TotalMilliseconds,
context.OperationId);
return Task.CompletedTask;
}

public Task OnFailed(ObservabilityContext context, CancellationToken ct)
{
_logger.LogError(context.Exception,
"Failed {Request} after {Duration}ms [{OperationId}]",
context.RequestName,
context.Duration.TotalMilliseconds,
context.OperationId);
return Task.CompletedTask;
}
}

Alerting Observer

public class SlowRequestAlertObserver : IRequestObserver
{
private static readonly TimeSpan SlowThreshold = TimeSpan.FromSeconds(2);
private readonly IAlertService _alerts;

public SlowRequestAlertObserver(IAlertService alerts)
{
_alerts = alerts;
}

public Task OnStarted(ObservabilityContext context, CancellationToken ct) => Task.CompletedTask;

public async Task OnCompleted(ObservabilityContext context, CancellationToken ct)
{
if (context.Duration > SlowThreshold)
{
await _alerts.SendSlowRequestAlertAsync(
context.RequestName,
context.Duration,
ct);
}
}

public Task OnFailed(ObservabilityContext context, CancellationToken ct) => Task.CompletedTask;
}

Registration

Multiple observers can be registered — all run for every request:

builder.Services.AddObservability();

// Register observers
builder.Services.AddTransient<IRequestObserver, LoggingObserver>();
builder.Services.AddTransient<IRequestObserver, SlowRequestAlertObserver>();
note

If one observer throws an exception, the others still run. All exceptions are collected into an AggregateException. Design observers to be resilient — wrap their logic in try/catch if they call external systems.

Built-in: ConsoleLoggingObserver

For development, use the built-in console observer:

builder.Services.AddObservability(config =>
{
config.UseObserver<ConsoleLoggingObserver>();
});