運用中のAPIでエラーが出たときどうやって検知してSlackに通知しよう 〜概要編〜

# はじめに
これは [KWC Advent Calendar 2022](https://qiita.com/advent-calendar/2022/kwc) の記事です。

はじめまして、（株）KDDIウェブコミュニケーションズ でバックエンドエンジニアをしています[chihiros](https://github.com/chihiros)と言います。2021年11月にKWCへ入社し、熊本や福岡などからフルリモートで日々業務を行っています。

この記事では本番稼働中のプログラムがエラーを出したときに、どうやったらエラーの存在に気づけるのかについて書いていきます。

1日目の記事、[試用期間中に会社の主力事業のAWS環境をゼロからTerraform化した](https://hikarun-sre.hatenablog.jp/entry/2022/12/08) の中にも書かれてあるように、弊社ではビジネス向けにレンタルサーバーを提供しています。その中で一部の社内システムにはAWSやAzureと言ったクラウドを使っています。

https://hikarun-sre.hatenablog.jp/entry/2022/12/08

**\*紹介\***
こちらは内容は概要編になっておりまして、実装編はこちらになります。（明日公開です！！）
リンクは該当記事が公開され次第、修正いたします。

https://qiita.com/yusuke_K11/items/494947c4df1b46791431

# 目次
1. [前提条件](#前提条件)
1. [最終目標](#最終目標)
1. [どこでSlackにメッセージを送信するべきなのか](#どこでSlackにメッセージを送信するべきなのか)
1. [システムの構成](#システムの構成)
1. [いざ実装へ](#いざ実装へ)
1. [まとめ](#まとめ)

# 前提条件
私の主な業務はAPIの開発でして言語はGoを使っています。APIはAWS ECS上で動作しています。
構成としてはこんな感じです。（画像はimageです）
ログは全てCloudWatch Logsに保存されています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674343/d834fe5b-af02-85d0-4957-7dd909132f4a.png">

弊社では、一部の社内サービスをオンプレからクラウドにリプレイスしており、バックエンドとしてはAPIの種類や機能を増やしたり、既存のシステムとの通信を行うために社内ライブラリの開発をGoでしています。
クラウドのシステム環境はインフラ・アプリ含め絶賛構築中でして、これからDevOpsの環境が整っていきます。

本題に戻りますが、ここからが運用中のAPIが出したエラーをどうやって検知するかのお話です。

# 最終目標
:::note info
1. チャットツールはSlackを使っているので、アラートはSlackに飛ばしたい
1. AWS ECS上で動いているAPI内部でエラーが発生した時にSlackへ通知したい
1. エラーでなくとも、特定のログなどをSlackへ送れたら嬉しい
:::

# どこでSlackにメッセージを送信するべきなのか
上で掲げた最終目標を達成するためには、まず、Slackにメッセージを送る機能をどこかに実装する必要があります。

- ここで言う「**どこかに**」とはメッセージ送信の機能をプログラム内部に組み込むか、外部に実装するかのことです
    1. **プログラム内部に+α**でメッセージ送信の**機能を組み込む場合**
        - エラー処理の中で`Webhook`を叩いたり、`Slack SDK`を使ってメッセージ送信してあげたら良さそう
    1. **プラグラム外部**にメッセージ送信の**機能を作る場合**
        1. プログラムが吐いた`ログ`を見て、場合によってはメッセージを送信してあげるとか
        1. 特定のエラーに限り`メトリクス`を送るようにしてみるとか
            1. DatadogやGrafanaなどを使っていく予定があれば相性良さそう

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674343/0647948d-e600-8edf-c680-7166057ee576.png">

### 1. プログラム内部に+αで組み込む場合
- エラー処理の中で`Webhook`を叩いたり、`Slack SDK`を使ってメッセージ送信してあげる
    - メリット
        - 特に無い気がする
        - 強いて言えば、通知にリアルタイム性があるぐらい？
    - デメリット
        - メッセージの`内容`や`メンション`などちょっとした変更をするだけでも、プログラム全体をデプロイし直す必要がある
        - プログラムの主機能 +α(メッセージ送信) となるため、プラグラム自体の機能が増えてしまい出来ることが多くなってしまう
        - ↑ 個人的にはAPI自体で出来ることは最低限にしてあげたい（何でも出来るから何でもやろうのモノリシック化を避けていきたい）


やるとしたら、コードはこんな感じになるんだと思います。
とにかくWebhookURLにPOSTでデータを送ればメッセージが届きます。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674343/45add2fe-eed9-e49b-4e49-0778dce00627.png" width=60%>

```go:main.go
func SendSlackMessage(msg string) {
	url := "https://hooks.slack.com/services/XXXXXXXX/YYYYYYY/ZZZZZZZZZZ"
	values := map[string]string{"text": msg}
	jsonValue, _ := json.Marshal(values)
	http.Post(url, "application/json", bytes.NewBuffer(jsonValue))
}

func main() {
	SendSlackMessage("これはGoから送信したメッセージです")
}
// ----------------------------------------------------
func main() {
    conn, err := sql.Open("postgres", "")
	if err != nil {
		SendSlackMessage("DBのコネクション取得に失敗しているよ！！")
		return
	}
	defer conn.Close()
}
```

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674343/4136beea-c82a-f083-51d8-1a1e80e8286f.png" width=50%>

```sh:cURLの場合
curl --request POST \
  --url https://hooks.slack.com/services/XXXXXXXX/YYYYYYY/ZZZZZZZZZZ \
  --header 'content-type: application/json' \
  --data '{"text": "これはcURLで送信したメッセージです"}'
```


### 2. プラグラム外部にメッセージ送信の機能を作る場合
- 外部に独立したメッセージ送信の機能を作る場合
    - メリット
        - 機能の分離・明確化が行える
            - アラートの通知はAWS ChatbotやLambdaに任せることが出来る
        - 通知の機能が独立していることでメンテナンスのしやすさが上がる
        - 複数のシステムから使うことが出来る
    - デメリット
        - 実装の仕方次第ではエラーが出てから通知までにタイムラグが出ることも
            - 実際に作ってみてタイムラグが許容出来る範囲か確認した方がいいかも

### 結局どっちにしたの？
最終的には、Slackに通知する機能をAPIの外部に実装しました。

ただ、APIの開発を行い始めたときは、機能の実装を優先したため、APIに+αでSlack通知の仕組みを入れていました。しかし、APIが2,3個と増えていくについれて管理が手間になってきたので、社内の共通ライブラリを作ったタイミングでSlack通知の機能を外部に出しました。

### CloudWatch Logs、 ログを見るか？ メトリクスを見るか？
AWS ECSで動いているAPIのログはCloudWatch Logsに溜まっているため、ここを見てメッセージを送るようにしました。

次に問題となってくるのは`ログ` `メトリクス`のどっちを見るのかです。
このどっちを見るべきなのか答えを出せていません。すいません。 orz

今回、Slackへ送りたいアラートの内容としてはAPI内部で発生したエラーのため、
**`ログを見て`Slackへ通知を送るように実装しました。**

今後、Datadogなどが環境に入って来たらOpenMetrics形式でメトリクスを出す仕組みを作ろうと思います。

# システムの構成
最終的にはSREの協力を得ながら次のような構成になりました。
1. APIのログはCloudWatch Logsに溜まっていきます
1. CloudWatch　Logsでは、`Subscription filter`を使い特定の文字が含まれるログを検知します
    1. 今回は`_SLACK_NOTIFY`をトリガーにしています
1. 特定の文字があった場合は、そのログがeventとしてLambdaへ送られるようにしています
1. Lambdaの中でeventを中身を見て、必要な処理をしてからSlackにメッセージを送信します

AWSには追加料金なしで使えるChatbotがありますが、今回はメッセージの内容をeventの中身に応じて変えたかったためLambdaを使っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/674343/1e8f55f1-5964-62de-3886-996b92dc7e90.png)


# いざ実装へ
概要の説明だけでここまで長くなってしまったので、実装はこちらをご覧ください。

https://qiita.com/yusuke_K11/items/494947c4df1b46791431

# まとめ
今回は `CloudWatch Logs`+`Subscription filter`+`Lambda`を使って、Slackへメッセージを送信する機能をAPIの外部に作りました。
ただ、これはシステムのスケールや用件に応じてはAPI内部に入れても良いと思っています。
みなさんの環境に応じて色々と試してみてください。

# 宣伝
[KWC Advent Calendar 2022](https://qiita.com/advent-calendar/2022/kwc)はまだ、続きますので良ければ他の記事もご覧ください。