#author("2020-09-23T10:51:37+00:00","","") #mynavi(Azureメモ) #setlinebreak(on); #TODO(可視性タイムアウトの問題(すぐにリランできない)) #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">ErrorNotice/function.json</a></li> <li><a href="#tabs1-5">ErrorNotice/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) dequeueCount := fmt.Sprintf("%v",invokeReq.Metadata["DequeueCount"]) if dequeueCount == "1" { queueItem := invokeReq.Data["queueItem"].(string) SENDGRID_API_KEY := getEnv("SENDGRID_API_KEY", "") MAIL_FROM_ADDRESS := getEnv("MAIL_FROM_ADDRESS", "") MAIL_TO_ADDRESS := getEnv("MAIL_TO_ADDRESS", "") // 見やすいように過剰エスケープを調整 queueItem = strings.TrimRight(queueItem, "\"") queueItem = strings.TrimLeft(queueItem, "\"") queueItem = strings.Replace(queueItem, "\\n", "\n", -1) queueItem = strings.Replace(queueItem, "\\\"", "\"", -1) queueItem = strings.Replace(queueItem, "\\\\\"", "", -1) printInfo("queue message(Value): %v", queueItem) from := mail.NewEmail("トリガー異常監視", MAIL_FROM_ADDRESS) subject := "Blobトリガーでエラーが発生しています" to := mail.NewEmail(MAIL_TO_ADDRESS, MAIL_TO_ADDRESS) plainTextContent := fmt.Sprintf("有害キューに出力された情報:\n%s", queueItem) htmlContent := fmt.Sprintf("有害キューに出力された情報:<br /><pre style=\"border: 1px solid #333; padding: 10px;\">%s</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 class="images">) &ref(azure_blob_trigger_error_notice_mail.png,nolink); #html(</div>) #html(</div>)