#author("2020-09-23T05:19:39+00:00","","")
#mynavi(Azureメモ)
#setlinebreak(on);

#html(){{
<style>
.images img { border: 1px solid #333; }
</style>
}}

* 概要 [#r5ca587b]
#html(<div class="pl10">)

Azure Blobトリガーから起動される関数アプリがコケた時にメール通知を行う方法について記載する。

[[Azure Functions の異常を検知する]] では、Azure Monitor を使用してメール通知を行ったが、Azure Monitor ではメールの本文を指定して送信する事ができない為、
ここでは有害キュー(webjobs-blobtrigger-poison)にトリガーを設定して関数アプリからメール送信を行う方法について記載する。

また、関数アプリの言語は従量課金プランが可能な Node.js を使用している。

[補足]
- Blobトリガーで起動される関数では何回目のリトライか等が判断できない為、有害キューにメッセージが出力されたタイミングで起動する別の関数を作成している。
- 正常終了させてしまうとキューからメッセージが削除されてしまう為、わざとエラーにしている。

※複数(5回のリトライ回数分)の通知が来ても良いなら ログの出力形式だけ決めておいて Azure Monitor から通知しても良いと思う。

#html(<div class="images">)
&ref(azure_blob_trigger_error_notice.png,nolink);
#html(</div>)

#html(</div>)

* 目次 [#c3ab24a8]
#contents
- 関連
-- [[Azure Functions を Go で書く]]
-- [[Azure Functions のログを参照する]]
-- [[Azure Functions の異常を検知する]]
-- [[Azure Blobトリガーで起動される関数をリトライで再利用する]]
-- [[Azureからのメール送信(SendGird使用)]]

* サンプル関数 [#z575ec59]
#html(<div>)

#html(){{
<div id="tabs1">
  <ul>
    <li><a href="#tabs1-1">host.json</a></li>
    <li><a href="#tabs1-2">local.settings.json</a></li>
    <li><a href="#tabs1-3">package.json</a></li>
    <li><a href="#tabs1-4">MyNoticeFunction/function.json</a></li>
    <li><a href="#tabs1-5">MyNoticeFunction/index.js</a></li>

  </ul>
}}

// START tabs1-1
#html(<div id="tabs1-1">)
#mycode2(){{
{
    "version": "2.0",
    "extensions": {
        "queues": {
            "maxPollingInterval": "00:00:10",
            "visibilityTimeout" : "00:00:00",
            "batchSize": 16, 
            "maxDequeueCount": 1,
            "newBatchThreshold": 8
        }   
    },  
    "extensionBundle": {
        "id": "Microsoft.Azure.Functions.ExtensionBundle",
        "version": "[1.*, 2.0.0)"
    }   
}
}}
#html(</div>)
// END tabs1-1

// START tabs1-2
#html(<div id="tabs1-2">)
#mycode2(){{
{
    "IsEncrypted": false,
    "Values": {
      "AzureWebJobsStorage": "UseDevelopmentStorage=true",
      "SENDGRID_APIKEY": "SendGridのAPIキー",
      "MAIL_FROM_ADDRESS": "送信元アドレス",
      "MAIL_TO_ADDRESS": "送信先アドレス"
    }   
}
}}
#html(</div>)
// END tabs1-2

// START tabs1-3
#html(<div id="tabs1-3">)
#mycode2(){{
{
  "name": "sample_sendgrid_nodejs",
  "version": "1.0.0",
  "main": "myfunc.js",
  "dependencies": {
    "@sendgrid/mail": "^7.2.6"
  }
}
}}
#html(</div>)
// END tabs1-3

// START tabs1-4
#html(<div id="tabs1-4">)
#mycode2(){{
{
    "disabled": false,
    "bindings": [
        {   
            "type": "queueTrigger",
            "direction": "in",
            "name": "queueItem",
            "queueName": "webjobs-blobtrigger-poison"
        }   
    ]   
}
}}
#html(</div>)
// END tabs1-4

// START tabs1-5
#html(<div id="tabs1-5">)
#mycode2(){{
module.exports = async function (context, message) {

    context.log('queue item:', message);

    // Dequeue1回目の時のみメール通知
    if (context.bindingData.dequeueCount === 1) {

        SENDGRID_APIKEY   = process.env.SENDGRID_APIKEY
        MAIL_FROM_ADDRESS = process.env.MAIL_FROM_ADDRESS
        MAIL_TO_ADDRESS   = process.env.MAIL_TO_ADDRESS

        const sgMail = require('@sendgrid/mail');
        sgMail.setApiKey(SENDGRID_APIKEY);
        const msg = { 
            to: MAIL_TO_ADDRESS,
            from: MAIL_FROM_ADDRESS,
            subject: 'サンプルメール',
            text: 'Node.js からのメール送信テスト' + message,
            html: '<strong>Blobトリガーの処理失敗</strong><br />' +
                  '<div style="border:1px solid #333; padding:10px; display: inline-block;">' + message + '</div>',
        };  
        await (async () => {
          try {
            await sgMail.send(msg);
            context.log('Mail sended.');
          } catch (error) {
            context.log.error("[MAILSEND ERROR]", error, error.response.body)
          }   
        })();
    }   

    context.log('DequeueCount: ', context.bindingData.dequeueCount);
    // 成功させるとキューからメッセージが削除されてしまう為、あえて失敗させる。
    //context.done();
    throw new Error('The message is not deleted because it is only a notification');
};
}}
#html(</div>)
// END tabs1-5

#html(</div>)
// END tabs1

#html(<script>$(function() { $("#tabs1").tabs(); });</script>)

#html(</div>)

* おまけ [#w8c20169]
#html(<div class="pl10">)

blobトリガー用の関数アプリに含めてしまう場合(Go)の例

#mycode2(){{
import (
     :
    "github.com/sendgrid/sendgrid-go"
    "github.com/sendgrid/sendgrid-go/helpers/mail"
)

:

/**
 * エラーのメール通知.
 */
func blobErrorHandler(w http.ResponseWriter, r *http.Request) {

    var invokeReq InvokeRequest
    d := json.NewDecoder(r.Body)
    decodeErr := d.Decode(&invokeReq)
    if decodeErr != nil {
        http.Error(w, decodeErr.Error(), http.StatusBadRequest)
        return
    }   
    printInfo("queue metadata: %v", invokeReq.Metadata)

    queueItem := invokeReq.Data["queueItem"]
    printInfo("queue message: %+v", queueItem)

    SENDGRID_API_KEY  := getEnv("SENDGRID_API_KEY", "") 
    MAIL_FROM_ADDRESS := getEnv("MAIL_FROM_ADDRESS", "") 
    MAIL_TO_ADDRESS   := getEnv("MAIL_TO_ADDRESS", "") 

    from := mail.NewEmail(MAIL_FROM_ADDRESS, MAIL_FROM_ADDRESS)
    subject := "Blobトリガーでエラーが発生しています"
    to := mail.NewEmail(MAIL_TO_ADDRESS, MAIL_TO_ADDRESS)
    plainTextContent := fmt.Sprintf("有害キューに出力された情報\n%s", queueItem)
    htmlContent := fmt.Sprintf("<strong>%s</strong><pre style=\"border: 1px solid #333; padding: 10px;\">%v</pre>",
        "有害キューに出力された情報", queueItem)
    message := mail.NewSingleEmail(from, subject, to, plainTextContent, htmlContent)
    client := sendgrid.NewSendClient(SENDGRID_API_KEY)
    response, err := client.Send(message)
    if err != nil {
        printError("[MAILSEND ERROR] %v", err)
    } else if (response.StatusCode == 202) {
        printInfo("[MAILSEND SUCCESS]")
    } else {
        printError("[MAILSEND ERROR] status: %v, body: %v, header: %v", response.StatusCode, response.Body, response.Headers)
    }   

    // 正常終了してしまうとキューから消えてしまうので、わざとエラーにしておく。
    //invokeResponse := InvokeResponse{Logs: []string{"Received blob trigger error."} }
    //js, err := json.Marshal(invokeResponse)
    //if err != nil {
    //  http.Error(w, err.Error(), http.StatusInternalServerError)
    //  return
    //} 
    //w.Header().Set("Content-Type", "application/json")
    //w.Write(js)
    http.Error(w, "process blobErrorHandler.", http.StatusInternalServerError)
}

 :

func main() {
    httpInvokerPort, exists := os.LookupEnv("FUNCTIONS_HTTPWORKER_PORT")
    if exists {
        printInfo("FUNCTIONS_HTTPWORKER_PORT: " + httpInvokerPort)
    }
    mux := http.NewServeMux()
    mux.HandleFunc("/BlobTrigger", blobTriggerHandler)
    mux.HandleFunc("/RerunAll"   , rerunAllHandler)
    mux.HandleFunc("/ErrorNotice", blobErrorHandler)   // 追加
    log.Println("Go server Listening...on httpInvokerPort:", httpInvokerPort)
    log.Fatal(http.ListenAndServe(":"+httpInvokerPort, mux))
}

}}


#html(</div>)

トップ   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS