diff --git a/src/DurableTask.Core/Command/OrchestrationCompleteOrchestratorAction.cs b/src/DurableTask.Core/Command/OrchestrationCompleteOrchestratorAction.cs index 56551b13e..54abd5225 100644 --- a/src/DurableTask.Core/Command/OrchestrationCompleteOrchestratorAction.cs +++ b/src/DurableTask.Core/Command/OrchestrationCompleteOrchestratorAction.cs @@ -56,5 +56,10 @@ public class OrchestrationCompleteOrchestratorAction : OrchestratorAction /// Gets a list of events that should be carried over when continuing an orchestration as new. /// public IList CarryoverEvents { get; } = new List(); + + /// + /// Gets a collection of tags associated with the completion action. + /// + public IDictionary Tags { get; } = new Dictionary(); } } \ No newline at end of file diff --git a/src/DurableTask.Core/Logging/EventIds.cs b/src/DurableTask.Core/Logging/EventIds.cs index f7386eb99..f23459c79 100644 --- a/src/DurableTask.Core/Logging/EventIds.cs +++ b/src/DurableTask.Core/Logging/EventIds.cs @@ -69,5 +69,7 @@ static class EventIds public const int RenewOrchestrationWorkItemFailed = 72; public const int OrchestrationDebugTrace = 73; + + public const int OrchestrationCompletedWithWarning = 74; } } diff --git a/src/DurableTask.Core/Logging/LogEvents.cs b/src/DurableTask.Core/Logging/LogEvents.cs index fd626d3be..360528638 100644 --- a/src/DurableTask.Core/Logging/LogEvents.cs +++ b/src/DurableTask.Core/Logging/LogEvents.cs @@ -1098,6 +1098,54 @@ void IEventSourceEvent.WriteEventSource() => } #nullable disable + /// + /// Log event representing a warning associated with an orchestration completing. + /// + internal class OrchestrationCompletedWithWarning : StructuredLogEvent, IEventSourceEvent + { + + public OrchestrationCompletedWithWarning( + OrchestrationInstance instance, + string orchestrationStatus, + string warningMessage) + { + this.InstanceId = instance.InstanceId; + this.ExecutionId = instance.ExecutionId; + this.RuntimeStatus = orchestrationStatus; + this.Details = warningMessage; + } + + [StructuredLogField] + public string InstanceId { get; } + + [StructuredLogField] + public string ExecutionId { get; } + + [StructuredLogField] + public string RuntimeStatus { get; } + + [StructuredLogField] + public string Details { get; } + + public override EventId EventId => new EventId( + EventIds.OrchestrationCompletedWithWarning, + nameof(EventIds.OrchestrationCompletedWithWarning)); + + public override LogLevel Level => LogLevel.Warning; + + protected override string CreateLogMessage() => + $"{this.InstanceId}: Orchestration completed with warning: {this.Details}"; + + void IEventSourceEvent.WriteEventSource() => + StructuredEventSource.Log.OrchestrationCompletedWithWarning( + this.InstanceId, + this.ExecutionId, + this.RuntimeStatus, + this.Details, + Utils.AppName, + Utils.PackageVersion); + } + /// /// Log event representing an orchestration aborted event, which can happen if the host is shutting down. /// diff --git a/src/DurableTask.Core/Logging/LogHelper.cs b/src/DurableTask.Core/Logging/LogHelper.cs index 109c14c42..1ae501b79 100644 --- a/src/DurableTask.Core/Logging/LogHelper.cs +++ b/src/DurableTask.Core/Logging/LogHelper.cs @@ -484,6 +484,23 @@ internal void OrchestrationCompleted( } } + /// + /// Logs a warning associated with an orchestration completing. + /// + /// The orchestration instance of the orchestration. + /// The status of the completed orchestration. + /// The warning message to log. + internal void OrchestrationCompletedWithWarning( + OrchestrationInstance instance, + OrchestrationStatus orchestrationStatus, + string warningMessage) + { + if (this.IsStructuredLoggingEnabled) + { + this.WriteStructuredLog(new LogEvents.OrchestrationCompletedWithWarning(instance, orchestrationStatus.ToString(), warningMessage)); + } + } + /// /// Logs that an orchestration execution was aborted. /// diff --git a/src/DurableTask.Core/Logging/StructuredEventSource.cs b/src/DurableTask.Core/Logging/StructuredEventSource.cs index 162a226f2..47a94b1a4 100644 --- a/src/DurableTask.Core/Logging/StructuredEventSource.cs +++ b/src/DurableTask.Core/Logging/StructuredEventSource.cs @@ -579,6 +579,29 @@ internal void OrchestrationCompleted( } } + [Event(EventIds.OrchestrationCompletedWithWarning, Level = EventLevel.Warning, Version = 1)] + internal void OrchestrationCompletedWithWarning( + string InstanceId, + string ExecutionId, + string RuntimeStatus, + string Details, + string AppName, + string ExtensionVersion) + { + if (this.IsEnabled(EventLevel.Warning)) + { + // TODO: Use WriteEventCore for better performance + this.WriteEvent( + EventIds.OrchestrationCompletedWithWarning, + InstanceId, + ExecutionId, + RuntimeStatus, + Details, + AppName, + ExtensionVersion); + } + } + [Event(EventIds.OrchestrationAborted, Level = EventLevel.Warning, Version = 1)] internal void OrchestrationAborted( string InstanceId, diff --git a/src/DurableTask.Core/OrchestrationTags.cs b/src/DurableTask.Core/OrchestrationTags.cs index 00ff3a2b1..c8fba33a1 100644 --- a/src/DurableTask.Core/OrchestrationTags.cs +++ b/src/DurableTask.Core/OrchestrationTags.cs @@ -56,6 +56,11 @@ public static class OrchestrationTags /// public const string CreateTraceForNewOrchestration = "MS_CreateTrace"; + /// + /// The warning logged when an orchestration completes, if any. + /// + public const string CompleteOrchestrationLogWarning = "MS_CompleteOrchestrationLogWarning"; + /// /// Check whether the given tags contain the fire and forget tag /// diff --git a/src/DurableTask.Core/TaskOrchestrationContext.cs b/src/DurableTask.Core/TaskOrchestrationContext.cs index 0210a10f6..4972e6fcd 100644 --- a/src/DurableTask.Core/TaskOrchestrationContext.cs +++ b/src/DurableTask.Core/TaskOrchestrationContext.cs @@ -721,6 +721,12 @@ public void CompleteOrchestration(string result, string details, OrchestrationSt completedOrchestratorAction.Details = details; completedOrchestratorAction.OrchestrationStatus = orchestrationStatus; completedOrchestratorAction.FailureDetails = failureDetails; + + if (this.continueAsNew != null && orchestrationStatus == OrchestrationStatus.Failed) + { + completedOrchestratorAction.Tags[OrchestrationTags.CompleteOrchestrationLogWarning] = + "Continue as new called for a failed orchestration, orchestration will complete."; + } } completedOrchestratorAction.Id = id; diff --git a/src/DurableTask.Core/TaskOrchestrationDispatcher.cs b/src/DurableTask.Core/TaskOrchestrationDispatcher.cs index c19e5bd80..e11330367 100644 --- a/src/DurableTask.Core/TaskOrchestrationDispatcher.cs +++ b/src/DurableTask.Core/TaskOrchestrationDispatcher.cs @@ -964,6 +964,10 @@ internal static bool ReconcileMessagesWithState(TaskOrchestrationWorkItem workIt runtimeState.AddEvent(executionCompletedEvent); + if (completeOrchestratorAction.Tags.TryGetValue(OrchestrationTags.CompleteOrchestrationLogWarning, out string warningMessage)) + { + this.logHelper.OrchestrationCompletedWithWarning(runtimeState.OrchestrationInstance!, completeOrchestratorAction.OrchestrationStatus, warningMessage); + } this.logHelper.OrchestrationCompleted(runtimeState, completeOrchestratorAction); TraceHelper.TraceInstance( runtimeState.OrchestrationStatus == OrchestrationStatus.Failed ? TraceEventType.Warning : TraceEventType.Information,