Les orchestrations de fonctions durables sont implémentées dans du code et peuvent utiliser les fonctionnalités de gestion des erreurs intégrées dans le langage de programmation. Vous n’avez pas vraiment besoin d’apprendre de nouveaux concepts pour ajouter la gestion des erreurs et les compensations dans vos orchestrations. Toutefois, vous devez tenir compte de certains comportements.
Remarque
La version 4 du modèle de programmation Node.js pour Azure Functions est en disponibilité générale. Le modèle v4 est conçu pour offrir une expérience plus flexible et intuitive pour les développeurs JavaScript et TypeScript. Pour plus d’informations sur les différences entre v3 et v4, consultez le guide de migration.
Dans les extraits de code suivants, JavaScript (PM4) désigne le modèle de programmation v4, la nouvelle expérience.
Erreurs dans les fonctions d’activité et les sous-orchestrations
Dans Durable Functions, les exceptions non gérées levées dans les fonctions d’activité ou les sous-orchestrations sont transmises à la fonction d’orchestrateur en utilisant des types d’exception standardisés.
Par exemple, considérez la fonction d’orchestrateur suivante qui effectue un transfert de fonds entre deux comptes :
Dans Durable Functions C# in-process, les exceptions non gérées sont levées en tant que FunctionFailedException.
Le message d’exception identifie généralement les fonctions d’activité ou les sous-orchestrations qui ont provoqué l’échec. Pour accéder à des informations d’erreur plus détaillées, inspectez la InnerException propriété.
[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
});
}
}
Remarque
Les exemples C# précédents portent sur Durable Functions 2.x. Pour Durable Functions 1.x, vous devez utiliser DurableOrchestrationContext au lieu de IDurableOrchestrationContext. Pour en savoir plus sur les différences entre les versions, consultez l’article Versions de Durable Functions.
Dans Durable Functions C# Isolated, les exceptions non gérées sont exposées en tant que TaskFailedException.
Le message d’exception identifie généralement les fonctions d’activité ou les sous-orchestrations qui ont provoqué l’échec. Pour accéder à des informations d’erreur plus détaillées, inspectez la propriété FailureDetails .
[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
});
}
}
Remarque
- Le message d’exception identifie généralement les fonctions d’activité ou les sous-orchestrations qui ont provoqué l’échec. Pour accéder à des informations d’erreur plus détaillées, inspectez la
FailureDetails propriété.
- Par défaut,
FailureDetails inclut le type d’erreur, le message d’erreur, la trace de pile et toutes les exceptions internes imbriquées (chacune représentée en tant qu’objet récursif FailureDetails ). Si vous souhaitez inclure des propriétés d’exception supplémentaires dans la sortie d’échec, consultez 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)
Par défaut, les applets de commande dans PowerShell ne déclenchent pas d’exceptions qui peuvent être interceptées à l’aide de blocs try/catch. Vous avez deux options pour modifier ce comportement :
- Utilisez l’indicateur
-ErrorAction Stop lors de l’appel de cmdlets, telles que Invoke-DurableActivity.
- Définissez la variable de préférence
$ErrorActionPreference sur "Stop" dans la fonction d’orchestrateur avant d’appeler des cmdlets.
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 }
}
Pour plus d’informations sur la gestion des erreurs dans PowerShell, consultez la documentation PowerShell 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();
}
}
Si le premier appel à la fonction CreditAccount échoue, la fonction d’orchestrateur compense en recréditant les fonds sur le compte source.
Erreurs dans les fonctions d’entité
Le comportement de gestion des exceptions pour les fonctions d’entité diffère en fonction du modèle d’hébergement Durable Functions :
Dans Durable Functions avec C# in-process, les types d’exceptions d’origine générées par les fonctions d’entité sont directement retournés à l’orchestrateur.
[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;
}
}
Dans Durable Functions C# isolé, les exceptions sont exposées à l’orchestrateur sous la forme d’un EntityOperationFailedException. Pour accéder aux détails de l’exception d’origine, inspectez sa propriété FailureDetails.
[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
Actuellement, les fonctions d’entité ne sont pas prises en charge dans PowerShell.
Actuellement, les fonctions d’entité ne sont pas prises en charge dans Java.
Nouvelle tentative automatique en cas d’échec
Lorsque vous appelez les fonctions d’activité ou les fonctions de sous-orchestration, vous pouvez spécifier une stratégie de nouvelle tentative automatique. L’exemple suivant tente d’appeler une fonction jusqu’à trois fois, et attend 5 secondes avant chaque nouvelle tentative :
[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);
// ...
}
Remarque
Les exemples C# précédents portent sur Durable Functions 2.x. Pour Durable Functions 1.x, vous devez utiliser DurableOrchestrationContext au lieu de IDurableOrchestrationContext. Pour en savoir plus sur les différences entre les versions, consultez l’article Versions de Durable Functions.
[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();
// ...
}
L'appel de fonction de l'activité de l'exemple précédent utilise un paramètre pour configurer une stratégie de nouvelles tentatives automatiques. Il existe plusieurs options de personnalisation de la stratégie de nouvelle tentative automatique :
-
Nombre maximal de tentatives: le nombre maximal de tentatives. Si la valeur est 1, aucune nouvelle tentative ne se produit.
-
Intervalle avant première nouvelle tentative : temps d’attente avant la première nouvelle tentative.
-
Coefficient d’interruption : coefficient permettant de déterminer le taux d’augmentation de l’interruption. La valeur par défaut est de 1.
-
Intervalle maximal entre deux nouvelles tentatives : durée maximale de l’attente entre deux nouvelles tentatives.
-
Délai de nouvelle tentative : temps que passe le système à effectuer de nouvelles tentatives. Par défaut, le système effectue ces nouvelles tentatives indéfiniment.
Descripteurs de nouvelle tentative personnalisés
Quand vous utilisez .NET ou Java, vous avez également la possibilité d’implémenter des descripteurs de nouvelle tentative dans le code. Cela est utile lorsque les politiques de réessai déclaratives ne sont pas suffisamment expressives. Pour les langages qui ne prennent pas en charge les descripteurs de nouvelle tentative personnalisés, vous avez toujours la possibilité d’implémenter des stratégies de nouvelle tentative à l’aide de boucles, de gestion des exceptions et de retardateurs pour injecter des retards entre les nouvelles tentatives.
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 ne prend actuellement pas en charge les descripteurs de nouvelle tentative personnalisés. Toutefois, vous avez toujours la possibilité d’implémenter la logique de nouvelle tentative directement dans la fonction orchestrator à l’aide de boucles, de gestion des exceptions et de retardateurs pour injecter des retards entre les nouvelles tentatives.
JavaScript ne prend actuellement pas en charge les descripteurs de nouvelle tentative personnalisés. Toutefois, vous avez toujours la possibilité d’implémenter la logique de nouvelle tentative directement dans la fonction orchestrator à l’aide de boucles, de gestion des exceptions et de retardateurs pour injecter des retards entre les nouvelles tentatives.
Python ne prend actuellement pas en charge les descripteurs de nouvelle tentative personnalisés. Toutefois, vous avez toujours la possibilité d’implémenter la logique de nouvelle tentative directement dans la fonction orchestrator à l’aide de boucles, de gestion des exceptions et de retardateurs pour injecter des retards entre les nouvelles tentatives.
PowerShell ne prend actuellement pas en charge les descripteurs de nouvelle tentative personnalisés. Toutefois, vous avez toujours la possibilité d’implémenter la logique de nouvelle tentative directement dans la fonction orchestrator à l’aide de boucles, de gestion des exceptions et de retardateurs pour injecter des retards entre les nouvelles tentatives.
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...
}
Délais d’expiration des fonctions
Vous souhaiterez peut-être abandonner un appel de fonction au sein d’une fonction d’orchestrateur s’il prend trop de temps. Actuellement, la méthode adéquate pour cela consiste à créer un retardateur durable avec un sélecteur de tâche « any », comme dans l’exemple suivant :
[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;
}
}
}
Remarque
Les exemples C# précédents portent sur Durable Functions 2.x. Pour Durable Functions 1.x, vous devez utiliser DurableOrchestrationContext au lieu de IDurableOrchestrationContext. Pour en savoir plus sur les différences entre les versions, consultez l’article Versions de Durable Functions.
[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;
}
}
Remarque
Ce mécanisme ne termine pas réellement l’exécution de la fonction d’activité en cours. Il permet simplement à la fonction de l’orchestrateur d’ignorer le résultat et de continuer. Pour plus d’informations, voir la documentation Minuteurs.
Exceptions non prises en charge
Si une fonction d’orchestration échoue avec une exception non prise en charge, les détails de cette dernière sont enregistrés et l’instance s’exécute avec un état Failed.
Inclure des propriétés d’exception personnalisées pour FailureDetails (isolé (.NET))
Lors de l’exécution de flux de travail Durable Task dans le modèle isolé .NET, les échecs de tâche sont automatiquement sérialisés dans un objet FailureDetails. Par défaut, cet objet inclut des champs standard tels que :
- ErrorType : nom du type d’exception
- Message : message d’exception
- StackTrace : piste d'exécution sérialisée
- InnerFailure : objet FailureDetails imbriqué pour les exceptions internes récursives
À compter de Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0, vous pouvez étendre ce comportement en implémentant un IExceptionPropertiesProvider (défini dans le package Microsoft.DurableTask.Worker à partir de la version 1.16.1). Ce fournisseur définit les types d’exception et les propriétés qui doivent être inclus dans le dictionnaire FailureDetails.Properties.
Remarque
- Cette fonctionnalité est disponible uniquement dans .NET Isolé . La prise en charge de Java sera ajoutée dans une prochaine version.
- Vérifiez que vous utilisez Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0 ou version ultérieure.
- Vérifiez que vous utilisez Microsoft.DurableTask.Worker v1.16.1 ou version ultérieure.
Implémenter un fournisseur de propriétés d’exception
Implémentez un IExceptionPropertiesProvider personnalisé pour extraire et retourner les propriétés sélectionnées pour les exceptions dont vous vous souciez. Le dictionnaire retourné sera sérialisé dans le champ Propriétés de FailureDetails lorsqu’un type d’exception correspondant sera levé.
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
};
}
}
Inscrire le fournisseur
Inscrivez votre IExceptionPropertiesProvider personnalisé dans votre hôte de travailleur isolé .NET, généralement dans 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();
Une fois inscrite, toute exception qui correspond à l’un des types gérés inclut automatiquement les propriétés configurées dans ses FailureDetails.
Exemple de sortie FailureDetails
Lorsqu’une exception se produit qui correspond à la configuration de votre fournisseur, l’orchestration reçoit une structure FailureDetails sérialisée comme suit :
{
"errorType": "TaskFailedException",
"message": "Activity failed with an exception.",
"stackTrace": "...",
"innerFailure": {
"errorType": "ArgumentOutOfRangeException",
"message": "Specified argument was out of range.",
"properties": {
"ParamName": "count",
"ActualValue": 42
}
}
}
Étapes suivantes