Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Dapr.Workflow/Worker/Internal/ReplaySafeLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace Dapr.Workflow.Worker.Internal;
/// </summary>
internal sealed class ReplaySafeLogger(ILogger innerLogger, Func<bool> isReplaying) : ILogger
{
private readonly ILogger _innerLogger = innerLogger ?? throw new ArgumentNullException(nameof(innerLogger));
internal readonly ILogger _innerLogger = innerLogger ?? throw new ArgumentNullException(nameof(innerLogger));
private readonly Func<bool> _isReplaying = isReplaying ?? throw new ArgumentNullException(nameof(isReplaying));

public IDisposable? BeginScope<TState>(TState state) where TState : notnull => _innerLogger.BeginScope(state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ internal sealed class WorkflowOrchestrationContext : WorkflowContext
private readonly SortedDictionary<int, OrchestratorAction> _pendingActions = [];
private readonly IWorkflowSerializer _workflowSerializer;
private readonly ILogger<WorkflowOrchestrationContext> _logger;
private readonly ILoggerFactory _loggerFactory;
// Maps runtime sub-orchestration created EventId -> parent action/task id (our local task id).
private readonly Dictionary<int, int> _subOrchestrationCreatedEventIdToParentTaskId = [];
// Maps child instance id -> parent action/task id (our local task id).
Expand Down Expand Up @@ -75,6 +76,7 @@ public WorkflowOrchestrationContext(string name, string instanceId, DateTime cur
IWorkflowSerializer workflowSerializer, ILoggerFactory loggerFactory, WorkflowVersionTracker versionTracker, string? appId = null)
{
_workflowSerializer = workflowSerializer;
_loggerFactory = loggerFactory;
_logger = loggerFactory.CreateLogger<WorkflowOrchestrationContext>() ??
throw new ArgumentNullException(nameof(loggerFactory));
_instanceGuid = Guid.TryParse(instanceId, out var guid) ? guid : Guid.NewGuid();
Expand Down Expand Up @@ -339,13 +341,16 @@ public override Guid NewGuid()
}

/// <inheritdoc />
public override ILogger CreateReplaySafeLogger(string categoryName) => new ReplaySafeLogger(_logger, () => IsReplaying);
public override ILogger CreateReplaySafeLogger(string categoryName) =>
new ReplaySafeLogger(_loggerFactory.CreateLogger(categoryName), () => IsReplaying);

/// <inheritdoc />
public override ILogger CreateReplaySafeLogger(Type type) => CreateReplaySafeLogger(type.FullName ?? type.Name);
public override ILogger CreateReplaySafeLogger(Type type) =>
new ReplaySafeLogger(_loggerFactory.CreateLogger(type), () => IsReplaying);

/// <inheritdoc />
public override ILogger CreateReplaySafeLogger<T>() => CreateReplaySafeLogger(typeof(T));
public override ILogger CreateReplaySafeLogger<T>() =>
new ReplaySafeLogger(_loggerFactory.CreateLogger<T>(), () => IsReplaying);

private Task<T> HandleHistoryMatch<T>(string name, HistoryEvent e, int taskId)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Dapr.Workflow.Serialization;
using Dapr.Workflow.Versioning;
using Dapr.Workflow.Worker.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using JsonException = System.Text.Json.JsonException;

Expand Down Expand Up @@ -795,6 +796,84 @@ public void CreateReplaySafeLogger_ShouldReturnLoggerThatIsDisabledDuringReplay(
Assert.False(context.IsReplaying);
Assert.True(logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information));
}

[Fact]
public void CreateReplaySafeLogger_StringOverload_ShouldReturnReplaySafeLogger()
{
var serializer = new JsonWorkflowSerializer(new JsonSerializerOptions(JsonSerializerDefaults.Web));
var tracker = new WorkflowVersionTracker([]);

var context = new WorkflowOrchestrationContext(
name: "wf",
instanceId: "i",
currentUtcDateTime: new DateTime(2025, 01, 01, 0, 0, 0, DateTimeKind.Utc),
workflowSerializer: serializer,
loggerFactory: NullLoggerFactory.Instance,
versionTracker: tracker);

const string categoryName = "test";
var logger = context.CreateReplaySafeLogger(categoryName);

Assert.NotNull(logger);
Assert.IsType<ReplaySafeLogger>(logger);

// Unfortunately, this is as far as we can take this since this creates a NullLogger and it doesn't expose the
// category name
}

[Fact]
public void CreateReplaySafeLogger_TypeOverload_ShouldReturnReplaySafeLogger()
{
var serializer = new JsonWorkflowSerializer(new JsonSerializerOptions(JsonSerializerDefaults.Web));
var tracker = new WorkflowVersionTracker([]);
var recordingFactory = new RecordingLoggerFactory();

var context = new WorkflowOrchestrationContext(
name: "wf",
instanceId: "i",
currentUtcDateTime: new DateTime(2025, 01, 01, 0, 0, 0, DateTimeKind.Utc),
workflowSerializer: serializer,
loggerFactory: recordingFactory,
versionTracker: tracker);

var logger = context.CreateReplaySafeLogger(typeof(MyExampleType));

Assert.NotNull(logger);

var replaySafeLogger = Assert.IsType<ReplaySafeLogger>(logger);

// The inner logger's category name must match the type passed to CreateReplaySafeLogger
var innerLogger = Assert.IsType<RecordingLogger>(replaySafeLogger._innerLogger);
var expectedCategoryName = typeof(MyExampleType).FullName!.Replace('+', '.');
Assert.Equal(expectedCategoryName, innerLogger.CategoryName);
}

[Fact]
public void CreateReplaySafeLogger_GenericOverload_ShouldReturnReplaySafeLogger()
{
var serializer = new JsonWorkflowSerializer(new JsonSerializerOptions(JsonSerializerDefaults.Web));
var tracker = new WorkflowVersionTracker([]);
var recordingFactory = new RecordingLoggerFactory();

var context = new WorkflowOrchestrationContext(
name: "wf",
instanceId: "i",
currentUtcDateTime: new DateTime(2025, 01, 01, 0, 0, 0, DateTimeKind.Utc),
workflowSerializer: serializer,
loggerFactory: NullLoggerFactory.Instance,
versionTracker: tracker);

var logger = context.CreateReplaySafeLogger<MyExampleType>();

Assert.NotNull(logger);

var replaySafeLogger = Assert.IsType<ReplaySafeLogger>(logger);

// CreateLogger<T>() returns a Logger<T> wrapper — verify the generic type argument is correct
var innerLoggerType = replaySafeLogger._innerLogger.GetType();
Assert.True(innerLoggerType.IsGenericType, "Inner logger should be a generic type");
Assert.Equal(typeof(MyExampleType), innerLoggerType.GetGenericArguments()[0]);
}

[Fact]
public void ContinueAsNew_ShouldAddCompleteOrchestrationAction_WithCarryoverEvents_WhenPreserveUnprocessedEventsIsTrue()
Expand Down Expand Up @@ -1156,6 +1235,56 @@ public void CreateReplaySafeLogger_TypeAndGenericOverloads_ShouldBehaveLikeCateg
Assert.True(genericLogger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Information));
}

private sealed class MyExampleType
{
}

private sealed class RecordingLoggerFactory : ILoggerFactory
{
public string? LastCategoryName { get; private set; }
public ILogger? LastCreatedLogger { get; private set; }

public void AddProvider(ILoggerProvider provider) { }

public void Reset()
{
LastCategoryName = null;
LastCreatedLogger = null;
}

public ILogger CreateLogger(string categoryName)
{
LastCategoryName = categoryName;
LastCreatedLogger = new RecordingLogger(categoryName);
return LastCreatedLogger;
}

public void Dispose() { }
}

private sealed class RecordingLogger(string categoryName) : ILogger
{
public string CategoryName { get; } = categoryName;

public IDisposable? BeginScope<TState>(TState state) where TState : notnull => NullScope.Instance;
public bool IsEnabled(LogLevel logLevel) => true;

public void Log<TState>(
LogLevel logLevel,
EventId eventId,
TState state,
Exception? exception,
Func<TState, Exception?, string> formatter)
{
}

private sealed class NullScope : IDisposable
{
public static readonly NullScope Instance = new();
public void Dispose() { }
}
}

private sealed class AlwaysEnabledLoggerFactory : Microsoft.Extensions.Logging.ILoggerFactory
{
public void AddProvider(Microsoft.Extensions.Logging.ILoggerProvider provider) { }
Expand Down
Loading