Skip to content
2 changes: 2 additions & 0 deletions src/DurableTask.Core/Logging/EventIds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,7 @@ static class EventIds
public const int RenewOrchestrationWorkItemStarting = 70;
public const int RenewOrchestrationWorkItemCompleted = 71;
public const int RenewOrchestrationWorkItemFailed = 72;

public const int OrchestrationDebugTrace = 73;
Comment on lines +66 to +67
Copy link
Collaborator Author

@davidmrdavid davidmrdavid Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seemed to me that we were missing a general "debug trace" for logs like this one, so I decided to create one. It did not seem appropriate to create a very specific new category just for this log, so instead I created a more general one. I think this is a good idea considering that, as we recently learned with the entity logs, adding new EventIDs is expensive / can take a long time to deploy in Antares, so I wanted to make sure we have access to more general logging methods.

}
}
40 changes: 40 additions & 0 deletions src/DurableTask.Core/Logging/LogEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1629,5 +1629,45 @@ void IEventSourceEvent.WriteEventSource() =>
Utils.AppName,
Utils.PackageVersion);
}

internal class OrchestrationDebugTrace : StructuredLogEvent, IEventSourceEvent
{
public OrchestrationDebugTrace(string instanceId, string executionId, string details)
{
this.InstanceId = instanceId;
this.ExecutionId = executionId;
this.Details = details;
}

[StructuredLogField]
public string InstanceId { get; }

[StructuredLogField]
public string ExecutionId { get; }

[StructuredLogField]
public string Name { get; }

[StructuredLogField]
public string Details { get; }

public override EventId EventId => new EventId(
EventIds.OrchestrationDebugTrace,
nameof(EventIds.OrchestrationDebugTrace));

public override LogLevel Level => LogLevel.Debug;

protected override string CreateLogMessage() =>
$"{this.InstanceId}: Orchestration Debug Trace: {this.Details}";

void IEventSourceEvent.WriteEventSource() =>
StructuredEventSource.Log.OrchestrationDebugTrace(
this.InstanceId,
this.ExecutionId,
this.Details,
Utils.AppName,
Utils.PackageVersion);
}

}
}
8 changes: 8 additions & 0 deletions src/DurableTask.Core/Logging/LogHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,14 @@ internal void RenewActivityMessageFailed(TaskActivityWorkItem workItem, Exceptio
}
#endregion

internal void OrchestrationDebugTrace(string instanceId, string executionId, string details)
{
if (this.IsStructuredLoggingEnabled)
{
this.WriteStructuredLog(new LogEvents.OrchestrationDebugTrace(instanceId, executionId, details));
}
}

void WriteStructuredLog(ILogEvent logEvent, Exception? exception = null)
{
this.log?.LogDurableEvent(logEvent, exception);
Expand Down
20 changes: 20 additions & 0 deletions src/DurableTask.Core/Logging/StructuredEventSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -865,5 +865,25 @@ internal void RenewOrchestrationWorkItemFailed(
ExtensionVersion);
}
}

[Event(EventIds.OrchestrationDebugTrace, Level = EventLevel.Verbose, Version = 1)]
internal void OrchestrationDebugTrace(
string InstanceId,
string ExecutionId,
string Details,
string AppName,
string ExtensionVersion)
{
if (this.IsEnabled(EventLevel.Verbose))
{
this.WriteEvent(
EventIds.OrchestrationDebugTrace,
InstanceId,
ExecutionId,
Details,
AppName,
ExtensionVersion);
}
}
}
}
40 changes: 34 additions & 6 deletions src/DurableTask.Core/OrchestrationRuntimeState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace DurableTask.Core
using System.Diagnostics;
using DurableTask.Core.Common;
using DurableTask.Core.History;
using DurableTask.Core.Logging;
using DurableTask.Core.Tracing;

/// <summary>
Expand Down Expand Up @@ -184,6 +185,14 @@ public OrchestrationStatus OrchestrationStatus
this.Events.Count == 1 && this.Events[0].EventType == EventType.OrchestratorStarted ||
this.ExecutionStartedEvent != null;

/// <summary>
/// Gets or sets a LogHelper instance that can be used to log messages.
/// </summary>
/// <remarks>
/// Ideally, this would be set in the constructor but that would require a larger refactoring.
/// </remarks>
internal LogHelper? LogHelper { get; set; } = null;

/// <summary>
/// Adds a new history event to the Events list and NewEvents list
/// </summary>
Expand Down Expand Up @@ -260,14 +269,33 @@ void SetMarkerEvents(HistoryEvent historyEvent)
}
else if (historyEvent is ExecutionCompletedEvent completedEvent)
{
if (ExecutionCompletedEvent != null)
if (ExecutionCompletedEvent == null)
{
throw new InvalidOperationException(
"Multiple ExecutionCompletedEvent found, potential corruption in state storage");
ExecutionCompletedEvent = completedEvent;
orchestrationStatus = completedEvent.OrchestrationStatus;
}
else
{
// It's not generally expected to receive multiple execution completed events for a given orchestrator, but it's possible under certain race conditions.
// For example: when an orchestrator is signaled to terminate at the same time as it attempts to continue-as-new.
var log = $"Received new {completedEvent.GetType().Name} event despite the orchestration being already in the {orchestrationStatus} state.";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of this log change. You're saying we received an event but you're not saying what we're doing with it. The previous log made it clear that we're ignoring the new event.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cgillum: I think there might be some confusion here. The log grows to include whether we decided to ignore the event later on. We don't always ignore the new event, as seen in the snippet below:

log += "Discarding previous 'ExecutionCompletedEvent' as termination is forceful.";
ExecutionCompletedEvent = completedEvent;
orchestrationStatus = completedEvent.OrchestrationStatus;
}
else
{
// otherwise, we ignore the new event.
log += "Discarding new 'ExecutionCompletedEvent'.";
}

So it's a log message that's created in two parts. First we capture that we're in a situation where we have to make a decision of which ExecutionCompletedEvent to preserve, and we include which one we decide to preserve (the new one or the previous one).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. In that case it's fine, but I think you need to add a space between the period in the first half of the log and the start of the next sentence.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addressed in latest commit: c23d3cc


if (orchestrationStatus == OrchestrationStatus.ContinuedAsNew && completedEvent.OrchestrationStatus == OrchestrationStatus.Terminated)
{
// If the orchestration planned to continue-as-new but termination is requested, we transition to the terminated state.
// This is because termination should be considered to be forceful.
log += " Discarding previous 'ExecutionCompletedEvent' as termination is forceful.";
ExecutionCompletedEvent = completedEvent;
orchestrationStatus = completedEvent.OrchestrationStatus;
}
else
{
// otherwise, we ignore the new event.
log += " Discarding new 'ExecutionCompletedEvent'.";
}

LogHelper?.OrchestrationDebugTrace(this.OrchestrationInstance?.InstanceId ?? "", this.OrchestrationInstance?.ExecutionId ?? "", log);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not find a pre-existing loghelper method that fit this case.

}

ExecutionCompletedEvent = completedEvent;
orchestrationStatus = completedEvent.OrchestrationStatus;
}
else if (historyEvent is ExecutionSuspendedEvent)
{
Expand Down
1 change: 1 addition & 0 deletions src/DurableTask.Core/TaskOrchestrationDispatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ protected async Task<bool> OnProcessWorkItemAsync(TaskOrchestrationWorkItem work
IList<HistoryEvent>? carryOverEvents = null;
string? carryOverStatus = null;

workItem.OrchestrationRuntimeState.LogHelper = this.logHelper;
OrchestrationRuntimeState runtimeState = workItem.OrchestrationRuntimeState;

runtimeState.AddEvent(new OrchestratorStartedEvent(-1));
Expand Down