- 追加された行はこの色です。
- 削除された行はこの色です。
#author("2020-09-23T07:33:04+00:00","","")
#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)
queueItem := invokeReq.Data["queueItem"].(string)
printInfo("queue message: %+v", queueItem)
dequeueCount := fmt.Sprintf("%v",invokeReq.Metadata["DequeueCount"])
if dequeueCount == "1" {
SENDGRID_API_KEY := getEnv("SENDGRID_API_KEY", "")
MAIL_FROM_ADDRESS := getEnv("MAIL_FROM_ADDRESS", "")
MAIL_TO_ADDRESS := getEnv("MAIL_TO_ADDRESS", "")
queueItem := invokeReq.Data["queueItem"].(string)
// 見やすいように過剰エスケープを調整
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)
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)
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)
}
// 見やすいように過剰エスケープを調整
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>)