Azure Functions Host をローカルデバッグする

# なぜやってみようと思ったか
なんとなく。これをやることでどんないいことがあるのかも不明です。何か良いことがあればここをアップデートします。

# Azure Functions の構成要素
Azure Functions チョットデキル方の資料によると、ランタイムは以下のように構成されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/80bb21a7-7392-1892-93e7-6b014e54d201.png)

https://www.slideshare.net/TsuyoshiUshio/azure-functions-98577634


#####ホスト (Script Host) 
全体を管理する親玉のプロセスです。これがないと始まらない。今回見るところです。
Azure ポータルから Function App を作ると App Service という PaaS 基盤にあらかじめインストールされ、Windows であれば w3wp.exe、Linux の場合は dotnet プロセスで実行されます。リポジトリはここで、.NET で開発されています。
https://github.com/Azure/azure-functions-host

#####言語ワーカー (Language Worker)
作成した関数を実際に処理するプロセスです。ホストと gRPC で通信します。Node.js は node.exe、Java は java、.NET の場合は Functions のランタイムのバージョンや OS によって異なりますが、w3wp.exe または dotnet です。

#####トリガーとバインド (Trigger & Bindings)
トリガー = Blob ストレージへのファイルアップロード、Queue ストレージへのメッセージの到着、HTTP リクエストの受信、タイマーによる定期実行など、関数を実行するための様々な条件
バインド = トリガーを条件として入出力する先の何か。タイマートリガーで定期的に関数が起動したときに Blob ストレージのコンテンツを読む (入力バインド) するとか、Cosmos DB に出力する (出力バインド) とか。

トリガーとバインドのリポジトリは一か所にまとまっていないけど、Queue や Blob はここにあるはず。(Microsoft.Azure.WebJobs.Extensions.Storage)
https://github.com/Azure/azure-webjobs-sdk

トリガーとバインドについては以下のサンプルシナリオを見ていただいた方がわかりやすです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/b210ec88-226f-1540-d72c-8e5ffbaa104c.png)

https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-triggers-bindings?tabs=csharp

# ローカルデバッグするために必要な環境
Windows 10 で Visual Studio 2019 を使った Azure Functions の開発環境が整っていれば大丈夫でした。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-develop-vs

基本的には Windows で Visual Studio 2019 を使うことが推奨されているようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/0fdaac3a-0105-0b62-ffe2-dcbb36da12ad.png)

https://github.com/Azure/azure-functions-host/blob/dev/CONTRIBUTING.md#pre-reqs-for-developing


# やってみる
以下に記載されている流れに沿ってやっていきます。
https://github.com/Azure/azure-functions-host/blob/dev/CONTRIBUTING.md#running-locally-visual-studio-v2

## 1. リポジトリからソースコードを取ってくる
任意のフォルダーでこれを実行

```
git clone https://github.com/Azure/azure-functions-host.git
```


## 2. Visual Studio 2019 でプロジェクトを開く
azure-functions-host というフォルダーの中にある WebJobs.Script.sln を開きます。
src の中に複数のプロジェクトがありますが、このあと WebJobs.Script.WebHost というプロジェクトをデバッグ実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/871c17b7-e10a-1e15-05fc-a5b39db7b545.png)

## 3. 環境変数を追加する
ソリューションエクスプローラーで WebJobs.Script.WebHost のプロジェクトを選択 -> デバッグ -> WebJobs.Script.WebHost のデバッグ プロパティ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/6476370b-2a4b-e702-dbe0-42d5652ce7e2.png)

"AzureWebJobsScriptRoot" という名前、名前を関数のソースコードを配置する場所を指定します。(ここでは例として D:\AzureWebJobsScriptRoot を指定しました)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/3e8d1770-f45b-d7fb-feff-4718b1c61cd2.png)

## 3. 関数を作成する
先ほどの環境変数 AzureWebJobsScriptRoot の値として設定した D:\AzureWebJobsScriptRoot (任意のパス) に関数のソースコードを作成します。言語は何でもよいですが、今回は Javascript を用います。

```
func init
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/72fe84da-8153-8cfd-7438-70777a713cc3.png)

```
func new -l javascript -t httptrigger -n <任意の名前>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/06481239-3fe5-7778-501d-80ef6ff3c296.png)

ここまでのコマンドを実行することでいくつかの json や js ファイルができるので、これを Visual Studio Code (記事を書くうえで Function Host か関数をちがいがわかりやすいようエディタを分けた) で開きます。


function.json で関数を実行するためのアクセスレベル authLevel を anonymous に変更します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/e60d3678-0c4c-ab39-2e93-d4b797426655.png)

index.js はここで変更しませんが、以下のような英語のメッセージを 200 OK で返すだけのシンプルな関数です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/87057d0b-6511-6dba-050d-da95fe6feb5f.png)

## 4. デバッグ実行する
Visual Studio 2019 に戻り、ソリューションエクスプローラーで WebJobs.Script.WebHost のプロジェクトを選択した状態で、デバッグ実行（緑の再生ボタン）をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/1908890a-3810-e466-28de-6d9bf73d68b8.png)

デバッグ実行するとコンソールに出てくる https://localhost:(ポート番号) の URL をブラウザに入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/30c9cf99-34bd-e6a4-f1a3-c08efd4d4d2f.png)

URL の最後に /api/hello のように関数が動作するパスを入力すると、たしかに関数に記載されていた英語のメッセージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/a2bd23f4-800f-8c6b-ae11-18af323ed4d8.png)


ここまでで基本的な操作は終わりです。

## 5. 関数内でエラーが発生した場合の挙動を確認する

デバッグ実行中に画面の下の方にある "例外設定" のウィンドウを開き、Common Language Runtime Exceptions のチェックをオンにします。
そうすると、デバッグ実行中に何か例外が発生したときにそこで止まります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/210c206f-b62d-6d81-6029-d022b6cc1246.png)


一度 Visual Studio 2019 のデバッグ実行を停止します。
Visual Studio Code で開いた index.js に Error() という例外をスローするようにソースコードを書き換えます。


```index.js
module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    err();

    const name = (req.query.name || (req.body && req.body.name));
    const responseMessage = name
        ? "Hello, " + name + ". This HTTP triggered function executed successfully."
        : "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.";

    context.res = {
        // status: 200, /* Defaults to 200 */
        body: responseMessage
    };
}

function err(){
    throw new Error();
 }
```

そしてもう一度 Visual Studio 2019 でデバッグ実行すると .System.IO.FileNotFoundException が発生しますが、実際には存在しないファイル .vs フォルダーの function.json の読み込みしようとしていることが原因です。
動作に影響はないのでこれは無視 (続行(ボタンを押下)) します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/17748286-f47a-734a-385e-59a7e9c463c7.png)

次に出たのは  Microsoft.Azure.WebJobs.Script.Workers.Rpc.RpcException の例外です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/dc57d765-7be8-a733-2f15-9efe178cb97b.png)

"詳細のコピー" を押下すると以下のような例外の情報をコピーできます。

>
Microsoft.Azure.WebJobs.Script.Workers.Rpc.RpcException
  HResult=0x80131500
  Message=Result: Failure
Exception: Error
Stack: Error
    at err (D:\AzureWebJobsScriptRoot\hello\index.js:18:11)
    at module.exports (D:\AzureWebJobsScriptRoot\hello\index.js:4:5)
    at WorkerChannel.invocationRequest (D:\Source\azure-functions-host\src\WebJobs.Script.WebHost\bin\Debug\netcoreapp3.1\workers\node\worker-bundle.js:18675:26)
    at ClientDuplexStream.<anonymous> (D:\Source\azure-functions-host\src\WebJobs.Script.WebHost\bin\Debug\netcoreapp3.1\workers\node\worker-bundle.js:18455:30)
    at ClientDuplexStream.emit (events.js:315:20)
    at addChunk (_stream_readable.js:295:12)
    at readableAddChunk (_stream_readable.js:271:9)
    at ClientDuplexStream.Readable.push (_stream_readable.js:212:10)
    at Object.onReceiveMessage (D:\Source\azure-functions-host\src\WebJobs.Script.WebHost\bin\Debug\netcoreapp3.1\workers\node\worker-bundle.js:23010:19)
    at InterceptingListener.module.exports.InterceptingListener.recvMessageWithContext (D:\Source\azure-functions-host\src\WebJobs.Script.WebHost\bin\Debug\netcoreapp3.1\workers\node\worker-bundle.js:22320:19)
  Source=System.Private.CoreLib
  スタック トレース:
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at Microsoft.Azure.WebJobs.Script.Description.WorkerFunctionInvoker.<InvokeCore>d__8.MoveNext() in D:\Source\azure-functions-host\src\WebJobs.Script\Description\Workers\WorkerFunctionInvoker.cs:line 93

上記のスタックトレースからは WorkerFunctionInvoker.cs の WorkerFunctionInvoker の処理の延長で (恐らく) RCP 通信をして Node.js の関数を呼び出したが、index.js で L18 で Error という例外が発生したということを読み取れます。
ソースコードの行数ともしっかり対応しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320008/edbdddbb-e839-f379-1cb5-70e126026c1d.png)


# わかったこと
ローカルでデバッグ実行するだけなら意外と簡単だったかも。
関数で例外が発生したときは Function Host でその例外のスタックトレースまで見ることができる。
.NET Core のアプリの例外の詳細から Node.js の例外やスタックトレースまで見れることに驚き！