yotiky Tech Blog

とあるエンジニアの備忘録

Azure Functions で FunctionExceptionFilter を使って例外を処理する

目次

検証環境

  • Azure Functions v3

概要

Azure Functions で使えるフィルターには、FunctionInvocationFilterFunctionExceptionFilter がある。

それぞれ Function のクラス毎にインターフェイスとして実装する IFunctionInvocationFilterIFunctionExceptionFilter、クラスに属性として付与する汎用的な抽象クラス FunctionExceptionFilterAttributeFunctionInvocationFilterAttribute が存在する。

FunctionInvocationFilter は関数の実行前、実行後に処理を追加することができる。

FunctionExceptionFilter は未処理例外が発生した場合の処理を追加することができる。

いずれのインターフェイス、抽象クラスも プレビューという名の Obsolete でマークされており、アップデートにより破壊的な変更が入る可能性があることには注意が必要である。(すでに5年くらいプレビューっぽい)

実装

FunctionExceptionFilter

IFunctionExceptionFilter

インターフェイスを使って関数クラス自体に実装する場合は次の通り。

この場合 DI で Storage などのクライアントのインスタンスを取得していると、ハンドラーメソッド内でそれらを使えるので、例外が起きた時にログ以外にテーブルやキューに情報を登録することができる。

public class ErrorFunction : IFunctionExceptionFilter
{
    private readonly NanikanoClient _client;
    public ErrorFunction(NanikanoClient client)
    {
        _client = client;
    }

    [FunctionName("ErrorFunction")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");
    
        throw new System.Exception("Exception test.");
    
        return new OkObjectResult("OK");
    }

    public Task OnExceptionAsync(FunctionExceptionContext exceptionContext, CancellationToken cancellationToken)
    {
        exceptionContext.Logger.LogError($"OnExceptionAsync called.");

        // Table や Queue などにエラー情報を登録できる
        // _client.Add("エラーが発生したョ");

        return Task.CompletedTask;
    }
}

FunctionExceptionFilterAttribute

FunctionExceptionFilterAttribute は抽象クラスなので継承したクラスを定義する。 こっちは DI ができそうにないのでログ出力が基本になりそう。

public class ErrorHandlerAttribute : FunctionExceptionFilterAttribute
{
    public override Task OnExceptionAsync(FunctionExceptionContext exceptionContext, CancellationToken cancellationToken)
    {
        exceptionContext.Logger.LogError($"ErrorHandler called. {exceptionContext.FunctionName}");

        return Task.CompletedTask;
    }
}

利用する場合は Function のクラスに属性として付ける。

[ErrorHandler]
public class ErrorFunction
{
    // 省略
}

FunctionInvocationFilter

IFunctionInvocationFilter

インターフェイスを使って関数クラス自体に実装する場合は次の通り。

こちらも Exception 同様、Function のメンバ変数にアクセスできるので自由度が高い。

public class ErrorFunction : IFunctionInvocationFilter
{
    private readonly NanikanoClient _client;
    public ErrorFunction(NanikanoClient client)
    {
        _client = client;
    }

    [FunctionName("ErrorFunction")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");

        throw new System.Exception("Exception test.");

        return new OkObjectResult("OK");
    }

    public Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancellationToken)
    {
        executingContext.Logger.LogInformation($"OnExecutingAsync called.");
        // _client.Add("実行されたョ");
        return Task.CompletedTask;
    }

    public Task OnExecutedAsync(FunctionExecutedContext executedContext, CancellationToken cancellationToken)
    {
        executedContext.Logger.LogInformation($"OnExecutedAsync called.");
        // _client.Add("Doneしたョ");
        return Task.CompletedTask;
    }
}

FunctionInvocationFilterAttribute

FunctionInvocationFilterAttribute は抽象クラスなので継承したクラスを定義する。 Exception との違いは、Context からサービスを取得できるので DI で登録したクラスのインスタンスなどを使うことができる。そのため、自由度高めで汎用的な処理を行える。

public class AspectFilterAttribute : FunctionInvocationFilterAttribute
{
    public override Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancellationToken)
    {
        executingContext.Logger.LogInformation($"AspectFilter.OnExecutingAsync called. {executingContext.FunctionName}");

        var req = executingContext.Arguments["req"] as HttpRequest;
        var svc = req.HttpContext.RequestServices;
        var client = svc.GetService(typeof(NanikanoClient)) as NanikanoClient;
        client.Add("取り出せるョ");

        return base.OnExecutingAsync(executingContext, cancellationToken);
    }
    public override Task OnExecutedAsync(FunctionExecutedContext executedContext, CancellationToken cancellationToken)
    {
        executedContext.Logger.LogInformation($"AspectFilter.OnExecutedAsync called. {executedContext.FunctionName}");

        return base.OnExecutedAsync(executedContext, cancellationToken);
    }
}

利用する場合は Function のクラスに属性として付ける。

[AspectFilter]
public class ErrorFunction
{
    // 省略
}

実行結果

これまでの機能をすべて実装した状態で Function を呼び出した結果は次の通り。

[2021-03-16T10:38:16.851Z] OnExecutingAsync called.
[2021-03-16T10:38:16.852Z] AspectFilter.OnExecutingAsync called. ErrorFunction
[2021-03-16T10:38:16.855Z] C# HTTP trigger function processed a request.
[2021-03-16T10:38:16.871Z] AspectFilter.OnExecutedAsync called. ErrorFunction
[2021-03-16T10:38:16.872Z] OnExecutedAsync called.
[2021-03-16T10:38:16.918Z] OnExceptionAsync called.
[2021-03-16T10:38:16.920Z] ErrorHandler called. ErrorFunction

f:id:yotiky:20210316194021p:plain

実行順序は次のようになる。

  1. 関数の OnExecutingAsync
  2. フィルタの OnExecutingAsync
  3. (例外発生)
  4. フィルタの OnExecutedAsync
  5. 関数の OnExecutedAsync
  6. 関数の OnExceptionAsync
  7. フィルタの OnExceptionAsync

参考