Logging

January 23, 2026 · View on GitHub

The Durable Task Framework provides structured logging for observability and debugging. This guide covers logging configuration and best practices.

Log Sources

DTFx emits logs from these sources:

SourceDescription
DurableTask.CoreCore framework operations
DurableTask.AzureStorageAzure Storage provider
DurableTask.ServiceBusService Bus provider
DurableTask.AzureManagedBackendDurable Task Scheduler

Configuring Logging

With Microsoft.Extensions.Logging

using Microsoft.Extensions.Logging;

var loggerFactory = LoggerFactory.Create(builder =>
{
    builder
        .SetMinimumLevel(LogLevel.Information)
        .AddConsole()
        .AddFilter("DurableTask.Core", LogLevel.Debug);
});

// Configure provider-specific logging (e.g., Azure Storage)
var settings = new AzureStorageOrchestrationServiceSettings
{
    TaskHubName = "MyTaskHub",
    StorageAccountClientProvider = new StorageAccountClientProvider(
        "mystorageaccount", 
        new DefaultAzureCredential()),
    LoggerFactory = loggerFactory, // Provider logs
};
var service = new AzureStorageOrchestrationService(settings);

// Pass to worker and client
var worker = new TaskHubWorker(service, loggerFactory);
var client = new TaskHubClient(service, loggerFactory: loggerFactory);

Note

Pass the ILoggerFactory to all three locations (provider settings, worker, and client) for complete log coverage. Provider-specific logs include message delivery times, partition operations, and other backend details useful for debugging.

With Serilog

using Serilog;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .MinimumLevel.Override("DurableTask.Core", Serilog.Events.LogEventLevel.Debug)
    .WriteTo.Console()
    .CreateLogger();

var loggerFactory = new LoggerFactory().AddSerilog();
var worker = new TaskHubWorker(service, loggerFactory);

ASP.NET Core Integration

// In Program.cs
builder.Logging.AddFilter("DurableTask.Core", LogLevel.Debug);

Log Events

Orchestration Events

Event IDLevelDescription
40InformationScheduling orchestration
43InformationWaiting for orchestration
49InformationOrchestration completed
51InformationExecuting orchestration logic
52InformationOrchestration executed (scheduled operations)

Activity Events

Event IDLevelDescription
46InformationScheduling activity
60InformationStarting activity
61InformationActivity completed

Worker Events

Event IDLevelDescription
10InformationWorker starting
11InformationWorker started
12InformationWorker stopping
13InformationWorker stopped

Example Log Output

info: DurableTask.Core[10] Durable task hub worker is starting
info: DurableTask.Core[40] Scheduling orchestration 'MyOrchestration' with instance ID = 'abc123'
info: DurableTask.Core[51] abc123: Executing 'MyOrchestration' orchestration logic
info: DurableTask.Core[46] abc123: Scheduling activity [MyActivity#0]
info: DurableTask.Core[60] abc123: Starting task activity [MyActivity#0]
info: DurableTask.Core[61] abc123: Task activity [MyActivity#0] completed successfully
info: DurableTask.Core[49] abc123: Orchestration completed with status 'Completed'

Logging in Orchestrations

Using IsReplaying

Avoid duplicate logs during replay. Note that DTFx orchestrations do not support constructor-based dependency injection. Use a static logger or pass a logger factory through your object creator:

public class MyOrchestration : TaskOrchestration<string, string>
{
    // Use a static logger or configure via ObjectCreator
    private static readonly ILogger Logger = LoggerFactory
        .Create(builder => builder.AddConsole())
        .CreateLogger<MyOrchestration>();
    
    public override async Task<string> RunTask(
        OrchestrationContext context, 
        string input)
    {
        // Only log during actual execution, not replay
        if (!context.IsReplaying)
        {
            Logger.LogInformation(
                "Processing orchestration {InstanceId} with input {Input}",
                context.OrchestrationInstance.InstanceId,
                input);
        }
        
        var result = await context.ScheduleTask<string>(typeof(MyActivity), input);
        
        if (!context.IsReplaying)
        {
            Logger.LogInformation(
                "Orchestration {InstanceId} completed with result {Result}",
                context.OrchestrationInstance.InstanceId,
                result);
        }
        
        return result;
    }
}

Structured Logging Best Practices

Include relevant context in log messages:

// ✅ Good - structured with context
_logger.LogInformation(
    "Processing order {OrderId} for customer {CustomerId} in orchestration {InstanceId}",
    input.OrderId,
    input.CustomerId,
    context.OrchestrationInstance.InstanceId);

// ❌ Bad - string concatenation, no structure
_logger.LogInformation(
    "Processing order " + input.OrderId + " for customer " + input.CustomerId);

Logging in Activities

Activities don't have replay concerns, so log freely. Like orchestrations, DTFx activities do not support constructor-based dependency injection. Use a static logger or configure via a custom ObjectCreator:

public class MyActivity : AsyncTaskActivity<string, string>
{
    // Use a static logger or configure via ObjectCreator
    private static readonly ILogger Logger = LoggerFactory
        .Create(builder => builder.AddConsole())
        .CreateLogger<MyActivity>();
    
    protected override async Task<string> ExecuteAsync(
        TaskContext context, 
        string input)
    {
        Logger.LogInformation(
            "Starting activity for orchestration {InstanceId}",
            context.OrchestrationInstance.InstanceId);
        
        try
        {
            var result = await DoWorkAsync(input);
            
            Logger.LogInformation(
                "Activity completed for orchestration {InstanceId}",
                context.OrchestrationInstance.InstanceId);
            
            return result;
        }
        catch (Exception ex)
        {
            Logger.LogError(ex,
                "Activity failed for orchestration {InstanceId}",
                context.OrchestrationInstance.InstanceId);
            throw;
        }
    }
    
    private Task<string> DoWorkAsync(string input) => Task.FromResult(input);
}

Log Correlation

Correlation IDs

Include correlation IDs for end-to-end tracing:

public override async Task<string> RunTask(
    OrchestrationContext context, 
    OrderInput input)
{
    using (Logger.BeginScope(new Dictionary<string, object>
    {
        ["InstanceId"] = context.OrchestrationInstance.InstanceId,
        ["OrderId"] = input.OrderId,
        ["CorrelationId"] = input.CorrelationId
    }))
    {
        if (!context.IsReplaying)
        {
            Logger.LogInformation("Starting order processing");
        }
        
        // ... orchestration logic
    }
}

Distributed Tracing Integration

For trace correlation, see Distributed Tracing.

Log Levels

Recommended log level configuration:

EnvironmentDurableTask.CoreProvider
DevelopmentDebugDebug
TestingDebugInformation
ProductionInformationWarning

Configuration Example

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "DurableTask.Core": "Information",
      "DurableTask.AzureStorage": "Warning"
    }
  }
}

Filtering Noisy Logs

Some operations generate many logs. Filter as needed:

builder.Logging
    .AddFilter("DurableTask.Core", LogLevel.Information)
    // Reduce noise from Azure Storage provider
    .AddFilter("DurableTask.AzureStorage", LogLevel.Warning);

Diagnostic Logging

For troubleshooting, enable debug logging:

builder.Logging
    .SetMinimumLevel(LogLevel.Debug)
    .AddFilter("DurableTask", LogLevel.Debug);

This reveals:

  • Message processing details
  • Partition lease operations
  • History loading/saving
  • Timer scheduling

Logging with Middleware

For cross-cutting logging concerns without modifying orchestration or activity code, use middleware. This approach lets you intercept all executions in one place:

var worker = new TaskHubWorker(orchestrationService, loggerFactory);

// Add orchestration logging middleware
worker.AddOrchestrationDispatcherMiddleware(async (context, next) =>
{
    var instance = context.GetProperty<OrchestrationInstance>();
    var runtimeState = context.GetProperty<OrchestrationRuntimeState>();
    
    logger.LogInformation("Orchestration {Name} ({InstanceId}) starting", 
        runtimeState?.Name, instance?.InstanceId);
    
    var stopwatch = Stopwatch.StartNew();
    try
    {
        await next();
        logger.LogInformation("Orchestration {Name} ({InstanceId}) completed in {ElapsedMs}ms", 
            runtimeState?.Name, instance?.InstanceId, stopwatch.ElapsedMilliseconds);
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "Orchestration {Name} ({InstanceId}) failed", 
            runtimeState?.Name, instance?.InstanceId);
        throw;
    }
});

// Add activity logging middleware
worker.AddActivityDispatcherMiddleware(async (context, next) =>
{
    var scheduledEvent = context.GetProperty<TaskScheduledEvent>();
    var instance = context.GetProperty<OrchestrationInstance>();
    
    logger.LogInformation("Activity {ActivityName} starting for {InstanceId}", 
        scheduledEvent?.Name, instance?.InstanceId);
    
    await next();
});

See Middleware for complete examples.

Event Source Logging

In addition to ILogger, DTFx also emits logs via Event Source, which is used by platforms like Azure Functions and Azure App Service for automatic telemetry collection. Event Source logging is always enabled and captures additional correlation details.

For advanced Event Source configuration, including provider GUIDs and structured logging details, see the source documentation.

Next Steps