Durable Function-Orchestrierungen sind im Code implementiert und können die integrierten Funktionen zur Fehlerbehandlung der Programmiersprache nutzen. Daher gibt es eigentlich keine neuen Konzepte, die Sie bei der Einbindung der Fehlerbehandlung und Kompensierung in Ihre Orchestrierungen beachten müssen. Es gibt jedoch einige wenige Verhaltensweisen, die Sie kennen sollten:
Hinweis
Version 4 des Node.js-Programmiermodells für Azure Functions ist allgemein verfügbar. Das neue Modell V4 ist für ein flexibleres und intuitiveres Benutzererlebnis für JavaScript- und TypeScript-Entwickler konzipiert. Erfahren Sie mehr über die Unterschiede zwischen v3 und v4 in der Migrationsanleitung.
In den folgenden Codeschnipseln steht JavaScript (PM4) für das Programmiermodell V4, das neue Benutzererlebnis.
Fehler in Aktivitätsfunktionen und Suborchestrierungen
In dauerhaften Funktionen werden unbehandelte Ausnahmen, die innerhalb von Aktivitätsfunktionen oder Unterorchestrierungen ausgelöst werden, mithilfe standardisierter Ausnahmetypen zurück zur Orchestratorfunktion gemarshallt.
Betrachten Sie beispielsweise die folgende Orchestratorfunktion, die eine Überweisung zwischen zwei Konten durchführt:
In Durable Functions C# in-process werden unbehandelte Ausnahmen als FunctionFailedException ausgelöst.
Die Ausnahmemeldung identifiziert in der Regel, welche Aktivitätsfunktionen oder Sub-Orchestrierungen den Fehler verursacht haben. Um auf detailliertere Fehlerinformationen zuzugreifen, überprüfen Sie die InnerException Eigenschaft.
[FunctionName("TransferFunds")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var transferDetails = context.GetInput<TransferOperation>();
await context.CallActivityAsync("DebitAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
try
{
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.DestinationAccount,
Amount = transferDetails.Amount
});
}
catch (FunctionFailedException)
{
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
}
}
Hinweis
Die vorherigen C#-Beispiele gelten für Durable Functions 2.x. Für Durable Functions 1.x müssen Sie DurableOrchestrationContext anstelle von IDurableOrchestrationContext verwenden. Weitere Informationen zu den Unterschieden zwischen den Versionen finden Sie im Artikel Durable Functions-Versionen.
In Durable Functions C# Isolated werden unbehandelte Ausnahmen als TaskFailedException behandelt.
Die Ausnahmemeldung identifiziert in der Regel, welche Aktivitätsfunktionen oder Sub-Orchestrierungen den Fehler verursacht haben. Um auf detailliertere Fehlerinformationen zuzugreifen, überprüfen Sie die FailureDetails-Eigenschaft .
[FunctionName("TransferFunds")]
public static async Task Run(
[OrchestrationTrigger] TaskOrchestrationContext context, TransferOperation transferDetails)
{
await context.CallActivityAsync("DebitAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
try
{
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.DestinationAccount,
Amount = transferDetails.Amount
});
}
catch (TaskFailedException)
{
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
}
}
Hinweis
- Die Ausnahmemeldung identifiziert in der Regel, welche Aktivitätsfunktionen oder Sub-Orchestrierungen den Fehler verursacht haben. Um auf detailliertere Fehlerinformationen zuzugreifen, überprüfen Sie die
FailureDetails Eigenschaft.
- Standardmäßig enthält
FailureDetails den Fehlertyp, die Fehlermeldung, die Stapelspur und alle geschachtelten inneren Ausnahmen, wobei jede als rekursives FailureDetails-Objekt dargestellt wird. Wenn Sie zusätzliche Ausnahmeeigenschaften in die Fehlerausgabe einschließen möchten, lesen Sie Include Custom Exception Properties for FailureDetails (.NET Isolated).
const df = require("durable-functions");
module.exports = df.orchestrator(function* (context) {
const transferDetails = context.df.getInput();
yield context.df.callActivity("DebitAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
try {
yield context.df.callActivity("CreditAccount", {
account: transferDetails.destinationAccount,
amount: transferDetails.amount,
});
} catch (error) {
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
yield context.df.callActivity("CreditAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
}
})
const df = require("durable-functions");
df.app.orchestration("transferFunds", function* (context) {
const transferDetails = context.df.getInput();
yield context.df.callActivity("debitAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
try {
yield context.df.callActivity("creditAccount", {
account: transferDetails.destinationAccount,
amount: transferDetails.amount,
});
} catch (error) {
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
yield context.df.callActivity("creditAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
}
});
import azure.functions as func
import azure.durable_functions as df
def orchestrator_function(context: df.DurableOrchestrationContext):
transfer_details = context.get_input()
yield context.call_activity('DebitAccount', {
'account': transfer_details['sourceAccount'],
'amount' : transfer_details['amount']
})
try:
yield context.call_activity('CreditAccount', {
'account': transfer_details['destinationAccount'],
'amount': transfer_details['amount'],
})
except:
yield context.call_activity('CreditAccount', {
'account': transfer_details['sourceAccount'],
'amount': transfer_details['amount']
})
main = df.Orchestrator.create(orchestrator_function)
Standardmäßig lösen Cmdlets in PowerShell keine Ausnahmen aus, die mithilfe von Try/Catch-Blöcken abgefangen werden können. Sie haben zwei Optionen zum Ändern dieses Verhaltens:
- Verwenden Sie das Flag
-ErrorAction Stop beim Aufrufen von Cmdlets, z. B Invoke-DurableActivity.
- Legen Sie die Einstellungsvariable
$ErrorActionPreference vor dem Aufrufen von Cmdlets in der Orchestratorfunktion auf "Stop" fest.
param($Context)
$ErrorActionPreference = "Stop"
$transferDetails = $Context.Input
Invoke-DurableActivity -FunctionName 'DebitAccount' -Input @{ account = transferDetails.sourceAccount; amount = transferDetails.amount }
try {
Invoke-DurableActivity -FunctionName 'CreditAccount' -Input @{ account = transferDetails.destinationAccount; amount = transferDetails.amount }
} catch {
Invoke-DurableActivity -FunctionName 'CreditAccount' -Input @{ account = transferDetails.sourceAccount; amount = transferDetails.amount }
}
Weitere Informationen zur Fehlerbehandlung in PowerShell finden Sie in der PowerShell-Dokumentation Try-Catch-Finally.
@FunctionName("TransferFunds")
public void transferFunds(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
TransferOperation transfer = ctx.getInput(TransferOperation.class);
ctx.callActivity(
"DebitAccount",
new OperationArgs(transfer.sourceAccount, transfer.amount)).await();
try {
ctx.callActivity(
"CreditAccount",
new OperationArgs(transfer.destinationAccount, transfer.amount)).await();
} catch (TaskFailedException ex) {
// Refund the source account on failure
ctx.callActivity(
"CreditAccount",
new OperationArgs(transfer.sourceAccount, transfer.amount)).await();
}
}
Wenn der erste Funktionsaufruf von CreditAccount fehlschlägt, wird dies durch die Orchestratorfunktion kompensiert, indem die Gelder auf das Quellkonto zurücküberwiesen werden.
Fehler in Entitätsfunktionen
Das Verhalten der Ausnahmebehandlung für Entitätsfunktionen unterscheidet sich je nach dem Hostingmodell für dauerhafte Funktionen:
Bei dauerhaften Funktionen, die C#-In-Process verwenden, werden ursprüngliche Ausnahmetypen, die von Entitätsfunktionen ausgelöst werden, direkt an den Orchestrator zurückgegeben.
[FunctionName("Function1")]
public static async Task<string> RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
try
{
var entityId = new EntityId(nameof(Counter), "myCounter");
await context.CallEntityAsync(entityId, "Add", 1);
}
catch (Exception ex)
{
// The exception type will be InvalidOperationException with the message "this is an entity exception".
}
return string.Empty;
}
[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
switch (ctx.OperationName.ToLowerInvariant())
{
case "add":
throw new InvalidOperationException("this is an entity exception");
case "get":
ctx.Return(ctx.GetState<int>());
break;
}
}
In Durable Functions C# Isolated werden Ausnahmen dem Orchestrator als EntityOperationFailedException übermittelt. Um auf die ursprünglichen Ausnahmedetails zuzugreifen, überprüfen Sie deren FailureDetails Eigenschaft.
[Function(nameof(MyOrchestrator))]
public static async Task<List<string>> MyOrchestrator(
[Microsoft.Azure.Functions.Worker.OrchestrationTrigger] TaskOrchestrationContext context)
{
var entityId = new Microsoft.DurableTask.Entities.EntityInstanceId(nameof(Counter), "myCounter");
try
{
await context.Entities.CallEntityAsync(entityId, "Add", 1);
}
catch (EntityOperationFailedException ex)
{
// Add your error handling
}
return new List<string>();
}
df.app.orchestration("counterOrchestration", function* (context) {
const entityId = new df.EntityId(counterEntityName, "myCounter");
try {
const currentValue = yield context.df.callEntity(entityId, "get");
if (currentValue < 10) {
yield context.df.callEntity(entityId, "add", 1);
}
} catch (err) {
context.log(`Entity call failed: ${err.message ?? err}`);
}
});
df.app.orchestration("counterOrchestration", function* (context) {
const entityId = new df.EntityId(counterEntityName, "myCounter");
try {
const currentValue = yield context.df.callEntity(entityId, "get");
if (currentValue < 10) {
yield context.df.callEntity(entityId, "add", 1);
}
} catch (err) {
context.log(`Entity call failed: ${err.message ?? err}`);
}
});
@myApp.orchestration_trigger(context_name="context")
def run_orchestrator(context):
try:
entityId = df.EntityId("Counter", "myCounter")
yield context.call_entity(entityId, "get")
return "finished"
except Exception as e:
# Add your error handling
Entitätsfunktionen werden derzeit in PowerShell nicht unterstützt.
Entitätsfunktionen werden derzeit in Java nicht unterstützt.
Automatische Wiederholung bei einem Fehler
Wenn Sie Aktivitätsfunktionen oder untergeordnete Orchestrierungsfunktionen aufrufen, können Sie eine Richtlinie für automatische Wiederholungen angeben. Im folgenden Beispiel wird versucht, eine Funktion bis zu 3-mal mit je 5 Sekunden Wartezeit zwischen den einzelnen Wiederholungsversuchen aufzurufen:
[FunctionName("TimerOrchestratorWithRetry")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(5),
maxNumberOfAttempts: 3);
await context.CallActivityWithRetryAsync("FlakyFunction", retryOptions, null);
// ...
}
Hinweis
Die vorherigen C#-Beispiele gelten für Durable Functions 2.x. Für Durable Functions 1.x müssen Sie DurableOrchestrationContext anstelle von IDurableOrchestrationContext verwenden. Weitere Informationen zu den Unterschieden zwischen den Versionen finden Sie im Artikel Durable Functions-Versionen.
[FunctionName("TimerOrchestratorWithRetry")]
public static async Task Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
var options = TaskOptions.FromRetryPolicy(new RetryPolicy(
maxNumberOfAttempts: 3,
firstRetryInterval: TimeSpan.FromSeconds(5)));
await context.CallActivityAsync("FlakyFunction", options: options);
// ...
}
const df = require("durable-functions");
module.exports = df.orchestrator(function*(context) {
const firstRetryIntervalInMilliseconds = 5000;
const maxNumberOfAttempts = 3;
const retryOptions =
new df.RetryOptions(firstRetryIntervalInMilliseconds, maxNumberOfAttempts);
yield context.df.callActivityWithRetry("FlakyFunction", retryOptions);
// ...
});
const df = require("durable-functions");
df.app.orchestration("callActivityWithRetry", function* (context) {
const firstRetryIntervalInMilliseconds = 5000;
const maxNumberOfAttempts = 3;
const retryOptions = new df.RetryOptions(firstRetryIntervalInMilliseconds, maxNumberOfAttempts);
yield context.df.callActivityWithRetry("flakyFunction", retryOptions);
// ...
});
import azure.functions as func
import azure.durable_functions as df
def orchestrator_function(context: df.DurableOrchestrationContext):
first_retry_interval_in_milliseconds = 5000
max_number_of_attempts = 3
retry_options = df.RetryOptions(first_retry_interval_in_milliseconds, max_number_of_attempts)
yield context.call_activity_with_retry('FlakyFunction', retry_options)
main = df.Orchestrator.create(orchestrator_function)
param($Context)
$retryOptions = New-DurableRetryOptions `
-FirstRetryInterval (New-TimeSpan -Seconds 5) `
-MaxNumberOfAttempts 3
Invoke-DurableActivity -FunctionName 'FlakyFunction' -RetryOptions $retryOptions
@FunctionName("TimerOrchestratorWithRetry")
public void timerOrchestratorWithRetry(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
final int maxAttempts = 3;
final Duration firstRetryInterval = Duration.ofSeconds(5);
RetryPolicy policy = new RetryPolicy(maxAttempts, firstRetryInterval);
TaskOptions options = new TaskOptions(policy);
ctx.callActivity("FlakeyFunction", options).await();
// ...
}
Der Aktivitätsfunktionsaufruf im vorherigen Beispiel nimmt einen Parameter zum Konfigurieren einer automatischen Wiederholungsrichtlinie. Es stehen mehrere Optionen zur Verfügung, um die automatische Wiederholungsrichtlinie anzupassen:
-
Maximale Anzahl von Versuchen: Die maximale Anzahl von Versuchen. Wenn 1 festgelegt ist, wird kein Wiederholungsversuch durchgeführt.
-
First retry interval (Erstes Wiederholungsintervall): Die Zeitspanne bis zum Ablauf des ersten Wiederholungsversuchs.
-
Backoff-Koeffizient: Der Koeffizient, der verwendet wird, um den Anstieg der Backoff-Intervalle zu bestimmen. Der Standardwert lautet 1.
-
Max retry interval (Maximales Wiederholungsintervall): die maximale Zeitspanne zwischen den Wiederholungsversuchen.
-
Retry timeout (Timeout wiederholen): die maximale Zeitspanne für das Ausführen von Wiederholungsversuchen. Das Standardverhalten ist das Wiederholen auf unbestimmte Zeit.
Benutzerdefinierte Wiederholungshandler
Wenn Sie .NET oder Java verwenden, haben Sie auch die Möglichkeit, Wiederholungshandler im Code zu implementieren. Dies ist nützlich, wenn deklarative Wiederholungsrichtlinien nicht ausdrucksstark genug sind. Bei Sprachen, die keine benutzerdefinierten Wiederholungshandler unterstützen, haben Sie weiterhin die Möglichkeit, Wiederholungsrichtlinien mithilfe von Schleifen, Ausnahmebehandlung und Timern zum Einfügen von Verzögerungen zwischen Wiederholungsversuchen zu implementieren.
RetryOptions retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(5),
maxNumberOfAttempts: int.MaxValue)
{
Handle = exception =>
{
// True to handle and try again, false to not handle and throw.
if (exception is TaskFailedException failure)
{
// Exceptions from TaskActivities are always this type. Inspect the
// inner Exception to get more details.
}
return false;
};
}
await ctx.CallActivityWithRetryAsync("FlakeyActivity", retryOptions, null);
TaskOptions retryOptions = TaskOptions.FromRetryHandler(retryContext =>
{
// Don't retry anything that derives from ApplicationException
if (retryContext.LastFailure.IsCausedBy<ApplicationException>())
{
return false;
}
// Quit after N attempts
return retryContext.LastAttemptNumber < 3;
});
try
{
await ctx.CallActivityAsync("FlakeyActivity", options: retryOptions);
}
catch (TaskFailedException)
{
// Case when the retry handler returns false...
}
JavaScript unterstützt derzeit keine benutzerdefinierten Wiederholungshandler. Sie haben jedoch weiterhin die Möglichkeit, die Wiederholungslogik mithilfe von Schleifen, Ausnahmebehandlung und Timern zum Einfügen von Verzögerungen zwischen Wiederholungsversuchen direkt in der Orchestratorfunktion zu implementieren.
JavaScript unterstützt derzeit keine benutzerdefinierten Wiederholungshandler. Sie haben jedoch weiterhin die Möglichkeit, die Wiederholungslogik mithilfe von Schleifen, Ausnahmebehandlung und Timern zum Einfügen von Verzögerungen zwischen Wiederholungsversuchen direkt in der Orchestratorfunktion zu implementieren.
Python unterstützt derzeit keine benutzerdefinierten Wiederholungshandler. Sie haben jedoch weiterhin die Möglichkeit, die Wiederholungslogik mithilfe von Schleifen, Ausnahmebehandlung und Timern zum Einfügen von Verzögerungen zwischen Wiederholungsversuchen direkt in der Orchestratorfunktion zu implementieren.
PowerShell unterstützt derzeit keine benutzerdefinierten Wiederholungshandler. Sie haben jedoch weiterhin die Möglichkeit, die Wiederholungslogik mithilfe von Schleifen, Ausnahmebehandlung und Timern zum Einfügen von Verzögerungen zwischen Wiederholungsversuchen direkt in der Orchestratorfunktion zu implementieren.
RetryHandler retryHandler = retryCtx -> {
// Don't retry anything that derives from RuntimeException
if (retryCtx.getLastFailure().isCausedBy(RuntimeException.class)) {
return false;
}
// Quit after N attempts
return retryCtx.getLastAttemptNumber() < 3;
};
TaskOptions options = new TaskOptions(retryHandler);
try {
ctx.callActivity("FlakeyActivity", options).await();
} catch (TaskFailedException ex) {
// Case when the retry handler returns false...
}
Funktion-Timeouts
Vielleicht möchten Sie einen Funktionsaufruf innerhalb einer Orchestratorfunktion verwerfen, wenn der Vorgang zu lange dauert. Die richtige Vorgehensweise dafür ist das Erstellen eines permanenten Timers mit einem any-Taskselektor, wie im folgenden Beispiel gezeigt:
[FunctionName("TimerOrchestrator")]
public static async Task<bool> Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
TimeSpan timeout = TimeSpan.FromSeconds(30);
DateTime deadline = context.CurrentUtcDateTime.Add(timeout);
using (var cts = new CancellationTokenSource())
{
Task activityTask = context.CallActivityAsync("FlakyFunction");
Task timeoutTask = context.CreateTimer(deadline, cts.Token);
Task winner = await Task.WhenAny(activityTask, timeoutTask);
if (winner == activityTask)
{
// success case
cts.Cancel();
return true;
}
else
{
// timeout case
return false;
}
}
}
Hinweis
Die vorherigen C#-Beispiele gelten für Durable Functions 2.x. Für Durable Functions 1.x müssen Sie DurableOrchestrationContext anstelle von IDurableOrchestrationContext verwenden. Weitere Informationen zu den Unterschieden zwischen den Versionen finden Sie im Artikel Durable Functions-Versionen.
[Function("TimerOrchestrator")]
public static async Task<bool> Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
TimeSpan timeout = TimeSpan.FromSeconds(30);
DateTime deadline = context.CurrentUtcDateTime.Add(timeout);
using (var cts = new CancellationTokenSource())
{
Task activityTask = context.CallActivityAsync("FlakyFunction");
Task timeoutTask = context.CreateTimer(deadline, cts.Token);
Task winner = await Task.WhenAny(activityTask, timeoutTask);
if (winner == activityTask)
{
// success case
cts.Cancel();
return true;
}
else
{
// timeout case
return false;
}
}
}
const df = require("durable-functions");
const moment = require("moment");
module.exports = df.orchestrator(function*(context) {
const deadline = moment.utc(context.df.currentUtcDateTime).add(30, "s");
const activityTask = context.df.callActivity("FlakyFunction");
const timeoutTask = context.df.createTimer(deadline.toDate());
const winner = yield context.df.Task.any([activityTask, timeoutTask]);
if (winner === activityTask) {
// success case
timeoutTask.cancel();
return true;
} else {
// timeout case
return false;
}
});
const df = require("durable-functions");
const { DateTime } = require("luxon");
df.app.orchestration("timerOrchestrator", function* (context) {
const deadline = DateTime.fromJSDate(context.df.currentUtcDateTime).plus({ seconds: 30 });
const activityTask = context.df.callActivity("flakyFunction");
const timeoutTask = context.df.createTimer(deadline.toJSDate());
const winner = yield context.df.Task.any([activityTask, timeoutTask]);
if (winner === activityTask) {
// success case
timeoutTask.cancel();
return true;
} else {
// timeout case
return false;
}
});
import azure.functions as func
import azure.durable_functions as df
from datetime import datetime, timedelta
def orchestrator_function(context: df.DurableOrchestrationContext):
deadline = context.current_utc_datetime + timedelta(seconds = 30)
activity_task = context.call_activity('FlakyFunction')
timeout_task = context.create_timer(deadline)
winner = yield context.task_any(activity_task, timeout_task)
if winner == activity_task:
timeout_task.cancel()
return True
else:
return False
main = df.Orchestrator.create(orchestrator_function)
param($Context)
$expiryTime = New-TimeSpan -Seconds 30
$activityTask = Invoke-DurableActivity -FunctionName 'FlakyFunction'-NoWait
$timerTask = Start-DurableTimer -Duration $expiryTime -NoWait
$winner = Wait-DurableTask -Task @($activityTask, $timerTask) -NoWait
if ($winner -eq $activityTask) {
Stop-DurableTimerTask -Task $timerTask
return $True
}
else {
return $False
}
@FunctionName("TimerOrchestrator")
public boolean timerOrchestrator(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
Task<Void> activityTask = ctx.callActivity("SlowFunction");
Task<Void> timeoutTask = ctx.createTimer(Duration.ofMinutes(30));
Task<?> winner = ctx.anyOf(activityTask, timeoutTask).await();
if (winner == activityTask) {
// success case
return true;
} else {
// timeout case
return false;
}
}
Hinweis
Dieser Mechanismus beendet die Ausführung der laufenden Aktivitätsfunktion nicht tatsächlich. Stattdessen lässt er zu, dass die Orchestratorfunktion das Ergebnis ignoriert und fortfährt. Weitere Informationen finden Sie in der Dokumentation zu Timern.
Nicht behandelte Ausnahmen
Fällt eine Orchestratorfunktion mit einer nicht behandelten Ausnahme aus, werden die Details der Ausnahme protokolliert, und die Instanz wird mit einem Failed-Status abgeschlossen.
Benutzerdefinierte Ausnahmeeigenschaften für FailureDetails (.NET Isolated) einschließen
Wenn Sie dauerhafte Aufgabenworkflows im isolierten .NET-Modell ausführen, werden Vorgangsfehler automatisch in ein FailureDetails-Objekt serialisiert. Standardmäßig enthält dieses Objekt Standardfelder wie:
- ErrorType – der Name des Ausnahmetyps
- Nachricht – Die Ausnahmemeldung
- StackTrace – die serialisierte Stapelablaufverfolgung
- InnerFailure – ein geschachteltes FailureDetails-Objekt für rekursive innere Ausnahmen
Ab Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0 können Sie dieses Verhalten erweitern, indem Sie einen IExceptionPropertiesProvider implementieren (definiert im Microsoft.DurableTask.Worker ab v1.16.1-Paket). Dieser Anbieter definiert, welche Ausnahmetypen und welche ihrer Eigenschaften im Wörterbuch FailureDetails.Properties enthalten sein sollen.
Hinweis
- Dieses Feature ist nur in .NET Isolated verfügbar. Unterstützung für Java wird in einer zukünftigen Version hinzugefügt.
- Stellen Sie sicher, dass Sie Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0 oder höher verwenden.
- Stellen Sie sicher, dass Sie Microsoft.DurableTask.Worker v1.16.1 oder höher verwenden.
Implementieren eines Ausnahmeeigenschaften-Providers
Implementieren Sie einen benutzerdefinierten IExceptionPropertiesProvider, um ausgewählte Eigenschaften für die gewünschten Ausnahmen zu extrahieren und zurückzugeben. Das zurückgegebene Wörterbuch wird beim Auslösen eines übereinstimmenden Ausnahmetyps in das Feld „Eigenschaften“ von FailureDetails serialisiert.
using Microsoft.DurableTask.Worker;
public class CustomExceptionPropertiesProvider : IExceptionPropertiesProvider
{
public IDictionary<string, object?>? GetExceptionProperties(Exception exception)
{
return exception switch
{
ArgumentOutOfRangeException e => new Dictionary<string, object?>
{
["ParamName"] = e.ParamName,
["ActualValue"] = e.ActualValue
},
InvalidOperationException e => new Dictionary<string, object?>
{
["CustomHint"] = "Invalid operation occurred",
["TimestampUtc"] = DateTime.UtcNow
},
_ => null // Other exception types not handled
};
}
}
Registrieren des Anbieters
Registrieren Sie Ihren benutzerdefinierten IExceptionPropertiesProvider in Ihrem .NET Isolated Worker-Host, in der Regel in Program.cs:
using Microsoft.DurableTask.Worker;
using Microsoft.Extensions.DependencyInjection;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(builder =>
{
// Register custom exception properties provider
builder.Services.AddSingleton<IExceptionPropertiesProvider, CustomExceptionPropertiesProvider>();
})
.Build();
host.Run();
Nach der Registrierung enthalten alle Ausnahmen, die mit einem der behandelten Typen übereinstimmen, automatisch die konfigurierten Eigenschaften in den FailureDetails.
Beispiel für eine FailureDetails-Ausgabe
Wenn eine Ausnahme auftritt, die der Konfiguration Ihres Anbieters entspricht, empfängt die Orchestrierung eine serialisierte FailureDetails-Struktur wie folgt:
{
"errorType": "TaskFailedException",
"message": "Activity failed with an exception.",
"stackTrace": "...",
"innerFailure": {
"errorType": "ArgumentOutOfRangeException",
"message": "Specified argument was out of range.",
"properties": {
"ParamName": "count",
"ActualValue": 42
}
}
}
Nächste Schritte