我有一个Azure函数应用程序(NET8.0,隔离模式,核心工具版本4.0.5801,函数运行时版本:4.34.1.22669)。在撰写此问题时,所有软件包都是最新的。 有许多编排和活动功能,以及一个启动调用链的“主”编排器。该协调器还应该能够捕获异常并提供原始错误消息和堆栈跟踪。演示代码:
[Function(nameof(MainOrchestrator))]
public static async Task MainOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context, FunctionContext executionContext)
{
string[] cities = ["Seattle", "London"];
try
{
foreach (var city in cities)
{
await context.CallSubOrchestratorAsync(nameof(CityOrchestrator), city);
}
}
catch (TaskFailedException ex)
{
// inspect the exception here
}
}
[Function(nameof(CityOrchestrator))]
public static async Task CityOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context, string city, FunctionContext executionContext)
{
string[] postfixes = ["a", "b"];
foreach (var postfix in postfixes)
{
await context.CallActivityAsync(nameof(SayHello), input: $"{city}-{postfix}");
}
}
[Function(nameof(SayHello))]
public static async Task SayHello([ActivityTrigger] string cityName, FunctionContext executionContext)
{
await Task.Delay(100);
if (cityName == "London-b") throw new Exception("Fire in London!");
}
[Function("Orchestration_HttpStart")]
public static async Task<HttpResponseData> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext)
{
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
nameof(MainOrchestrator));
return await client.CreateCheckStatusResponseAsync(req, instanceId);
}
错误发生在“SayHello”活动中。如果我在“CityOrchestrator”中捕获它,那么我可以从“FailureDetails”属性中获取我需要的内容。但如果我在主协调器中这样做,那么失败详细信息会向我显示消息“任务‘SayHello’(#1)因未处理的异常而失败:伦敦着火了!” (好吧,它接近原始)以及与执行框架相关的堆栈跟踪:
在 Microsoft.DurableTask.Worker.Shims.TaskOrchestrationContextWrapper.CallActivityAsync[T](TaskName 名称、对象输入、TaskOptions 选项) 在C中的AzureBackground.Orchestration.CityOrchestrator(TaskOrchestrationContext上下文,字符串城市,FunctionContextexecutionContext):b_projekt \ DociaBackground \ AzureBackgroundTasks \ AzureBackground \ Orchestration.cs:第42行 在 C: b_projekt\DociaBackground\AzureBackgroundTasks\AzureBackground\obj\Debug 中的 AzureBackground.DirectFunctionExecutor.ExecuteAsync(FunctionContext context) et8.0\Microsoft.Azure.Functions.Worker.Sdk.Generators\Microsoft.Azure.Functions.Worker.Sdk.Generators.FunctionExecutorGenerator\GenerateFunctionExecutor.g.cs:第 70 行 在 D: _work \s\src\DotNetWorker.Core\OutputBindings\OutputBindingsMiddleware.cs 中的 Microsoft.Azure.Functions.Worker.OutputBindings.OutputBindingsMiddleware.Invoke(FunctionContext context,FunctionExecutionDelegate next):第 13 行 在 /mnt/vss/work/1/s/extensions/Worker.Extensions.Http.AspNetCore/src/ 中的 Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.FunctionsHttpProxyingMiddleware.Invoke(FunctionContext context,FunctionExecutionDelegate next) FunctionsMiddleware/FunctionsHttpProxyingMiddleware.cs:第 38 行 在 //src/Worker.Extensions.DurableTask/FunctionsOrchestrator.cs 中的 Microsoft.Azure.Functions.Worker.Extensions.DurableTask.FunctionsOrchestrator.EnsureSynchronousExecution(FunctionContext functionContext、FunctionExecutionDelegate next、FunctionsOrchestrationContext OrchestrationContext):第 81 行 在 /_/src/Worker.Extensions.DurableTask/FunctionsOrchestrator.cs 中的 Microsoft.Azure.Functions.Worker.Extensions.DurableTask.FunctionsOrchestrator.RunAsync(TaskOrchestrationContext 上下文,对象输入):第 51 行 在 Microsoft.DurableTask.Worker.Shims.TaskOrchestrationShim.Execute(OrchestrationContext innerContext,String rawInput)
原始堆栈跟踪对于业务逻辑非常重要,我应该能够以某种方式在代码中获取它。我怎样才能做到这一点? 预先感谢您!
由于无法以优雅的方式解决问题,我开发了一个基于中间件类的解决方案。不是美丽,而是解决我的问题。我分享给遇到同样问题的人。 首先我在Program.cs中添加一个用于活动函数的中间件:
var host = new HostBuilder()
.ConfigureFunctionsWebApplication(app =>
{
app.UseWhen<ErrorMiddleware>(context =>
{
var triggerType = context.FunctionDefinition.InputBindings.Values.First(a => a.Type.EndsWith("Trigger")).Type;
return triggerType == "activityTrigger";
});
})
.ConfigureServices(services =>
{
services.AddSingleton<IErrorStorage>(s =>
{
return new ErrorStorageTable(Environment.GetEnvironmentVariable("AzureWebJobsStorage"), "MyErrorRegistry");
});
});
中间件捕获异常并将其详细信息存储到配置的存储(在我的例子中为 Azure 表存储)中,并使用唯一的相关 ID。然后根据此 Id 构建一个标记,并使用该标记作为消息抛出另一个错误。
internal class ErrorMiddleware : IFunctionsWorkerMiddleware
{
private readonly ILogger<ErrorMiddleware> _logger;
private readonly IErrorStorage _errorStorage;
public ErrorMiddleware(ILogger<ErrorMiddleware> logger, IErrorStorage errorStorage)
{
_logger = logger;
_errorStorage = errorStorage;
}
public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "Exception in {n}", context.FunctionDefinition.Name);
// Registering exception
ErrorDetails details = ErrorDetails.FromException(ex);
ErrorCorrelationId id = ErrorCorrelationId.NewId();
await _errorStorage.RegisterErrorAsync(id, details);
throw new Exception(ExceptionParser.CreateMarker(id), ex);
}
}
}
最后,主编排中有一个 try-catch 块。如果出现异常,其消息可能包含带有 Id 的相关标记。在这种情况下,我可以从存储中读取错误详细信息。
try
{
job.ReturnValue =
await context.CallSubOrchestratorAsync<string?>(taskName, input: job);
}
catch (Exception ex)
{
if(ExceptionParser.TryGetCorrelationId(ex, out var correlationId))
{
job.ErrorCorrelationId = correlationId;
}
else
{
job.ErrorDetails = ErrorDetails.FromException(ex);
}
}
我确信我的解决方案并不完美,但这总比没有好。希望它能为其他人节省一些时间。