Observability extensions

Operation names

WithOperationName adds forward-scoped metadata. Place it before consumers such as logging or timeout so those registrations capture the name.

ITaskScheduler named = flow
    .WithOperationName("orders.persist")
    .WithLogging(logger);

await named.Enqueue(token => PersistAsync(token));

static Task PersistAsync(CancellationToken token) => Task.CompletedTask;

Later annotations never change middleware that is already registered. Replacing an annotation type affects subsequent registrations and the final operation only, so immutable branches can carry independent metadata.

Microsoft logging

Install the integration package:

dotnet add package TaskFlow.Microsoft.Extensions.Logging
ITaskScheduler logged = flow
    .WithOperationName("imports.run")
    .WithLogging(logger, options =>
    {
        options.EnqueuedLogLevel = LogLevel.Debug;
        options.StartedLogLevel = LogLevel.Information;
        options.SucceededLogLevel = LogLevel.Information;
        options.FailedLogLevel = LogLevel.Error;
        options.FinishedLogLevel = LogLevel.Debug;
    });

await logged.Enqueue(token => ImportAsync(token));

static Task ImportAsync(CancellationToken token) => Task.CompletedTask;

The decorator can emit enqueued, started, cancellation-requested, succeeded or failed, and finished events. Every level defaults to Trace; set a level to LogLevel.None to disable that event. Events include an increasing operation ID, optional name, duration where applicable, and the failure exception.

The cancellation event reports a request, not the final outcome. A delegate can ignore cancellation and complete successfully. Logging never suppresses the operation exception.

Interception

Intercept supports custom operation lifecycle behavior. A synchronous interceptor is a struct copied for each operation and implements callbacks in this order:

  1. OnBefore;
  2. the operation;
  3. either OnSuccess or OnError; and
  4. OnFinally.

An asynchronous interceptor uses IAsyncTaskSchedulerInterceptor to create one IAsyncTaskInterceptor per operation. Every returned ValueTask is awaited before the lifecycle advances.

Callbacks run inside the selected scheduler context. A callback failure faults the returned operation task; error or finalization callback failures can replace the original operation failure. Use interception when this replacement behavior and lifecycle control are intentional. Prefer OnError or WithLogging for simpler observation.

Ownership

Annotations, interception, and logging are scheduler decorators. They neither own nor dispose the underlying flow. Keep the original ITaskFlow and dispose it at the component boundary.

Middleware ordering

TaskFlow registrations have distinct phases. Enqueue middleware handles admission, timeout, and cancellation before the terminal scheduler. Execution middleware surrounds the operation on the selected scheduler context. Completion middleware processes the resulting success or failure in registration order. An execution failure therefore reaches interception error/finally callbacks before OnError completion callbacks.