概要(エラー通知は「落ちたことを人間に伝える仕組み」)
エラー通知は、
「プログラムがコケた瞬間に、人間がちゃんと気づけるようにする仕組み」
です。
例外ログは「証拠を残す」もので、
エラー通知は「誰かに知らせる」ものです。
自動化が進んでくると、
- 夜中にバッチが落ちていた
- 誰も気づかなくて、朝まで放置
- データが更新されていないのに業務が進んでしまった
みたいな事故が簡単に起こります。
だから、「落ちたら分かる」をコードに埋め込んでおくことが、
自動化を本番運用に耐えさせるための必須条件になってきます。
まず考えるべきこと(誰に・何で・いつ知らせるのか)
誰に通知するのか(宛先の設計)
最初に決めるべきは「宛先」です。
自分だけにメールが来ればいいのか、
チームの Slack に飛ばしたいのか、
運用担当のグループアドレスに送りたいのか。
ここが曖昧なまま実装を始めると、
- 誰にも届いていない通知をせっせと送り続ける
- 見ていないメールボックスが通知で溢れる
といった意味のない「自己満通知」になります。
「誰が」「どのタイミングで」「どのチャネルを見ているか」を、
一旦自分の頭の中か紙に書き出してみてください。
何で通知するのか(チャネルの選択)
典型的な手段は、だいたいこのあたりです。
メール(Gmail、会社のメール)
チャット(Slack、Teams、Discord など)
監視ツール(Zabbix、Datadog など。ここは今回は深追いしない)
プログラミング初心者が最初に手を出しやすいのは「メール」か「Slack Webhook」です。
メールはどの環境でも通じるし、
Slack はチームで開いていることが多いのでリアルタイム性が高い。
どちらにも共通しているのは、
- 宛先や API URL などは、ソースコードにベタ書きしない
- 環境変数や設定ファイルから読むようにする
という点です。これも後で触れます。
いつ通知するのか(トリガーの設計)
「全部のエラーで通知する」と考えると、すぐに破綻します。
細かい警告レベルまで全部通知すると、
人間が慣れてしまって「またか」で終わり、誰も見なくなります。
現実的には、次のような考え方で線を引きます。
バッチ全体が止まるような致命的エラー
→ 必ず通知する(CRITICAL / ERROR レベル)
一部のレコードだけ失敗したが、全体は続いている
→ ログには残すが、通知は「一定件数以上」などの条件付きにする
タイムアウトや一時的エラーでリトライして最終的に成功した
→ 通知は不要で、ログにだけ残す
この線引きを決めてから、どこで「エラー通知関数」を呼ぶかを考えると、実装がブレません。
例1:メールでエラー通知する(smtplib を使う基本形)
最小の「バッチ異常終了メール」のイメージ
まずは、「バッチ全体が落ちたら自分にメールする」という超シンプルな例から。
Python 標準の smtplib を使う方法です。
ここでは構造が分かればいいので、Gmail を送信元にするイメージで書きます(実際に使うときはアプリパスワードやセキュリティ設定が必要です)。
import smtplib
from email.message import EmailMessage
import logging
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
LOG_FILE = BASE_DIR / "batch.log"
def setup_logging():
logging.basicConfig(
filename=LOG_FILE,
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s - %(message)s"
)
def send_error_mail(subject: str, body: str):
msg = EmailMessage()
msg["Subject"] = subject
msg["From"] = "from@example.com"
msg["To"] = "to@example.com"
msg.set_content(body)
with smtplib.SMTP("smtp.example.com", 587) as smtp:
smtp.starttls()
smtp.login("from@example.com", "PASSWORD")
smtp.send_message(msg)
def main():
logging.info("バッチ開始")
# ここにメイン処理
1 / 0 # わざとエラー
logging.info("バッチ正常終了")
if __name__ == "__main__":
setup_logging()
try:
main()
except Exception:
logging.exception("バッチで予期しない例外が発生しました")
subject = "[ERROR] 日次バッチ異常終了"
body = "日次バッチでエラーが発生しました。ログを確認してください。"
try:
send_error_mail(subject, body)
except Exception as notify_err:
logging.exception(f"エラー通知の送信にも失敗しました: {notify_err}")
raise
Pythonこの例で押さえておきたいポイントを、少し深掘りします。
1つ目は、「エラー通知はトップレベルの try/except で行っている」ことです。main() の中で何が起きても、最終的にはここで捕まり、ログ+メール通知が行われます。
2つ目は、「通知の失敗自体も例外になる可能性がある」ことを考慮している点です。send_error_mail 自体もネットワークや認証などで失敗しうるので、その例外も logging.exception で記録し、最悪メールは飛ばなくてもログには残るようにしています。
3つ目は、「メール本文はシンプルにしておき、詳細はログを見る設計」にしていることです。
長文をメールに全部書こうとせず、「落ちたことが分かればよい」「詳しくは log を見て」という線引きで十分なことが多いです。
メールに「どのバッチがいつ落ちたか」を含める
もう少し実用的にするなら、件名や本文に最低限の文脈情報を含めます。
例えば、日付やバッチ名、環境名(dev / prod)などです。
from datetime import datetime
def notify_batch_error():
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
subject = f"[ERROR][prod] 日次売上バッチ異常終了 ({now})"
body = f"日次売上バッチが異常終了しました。\n発生時刻: {now}\nログファイル: {LOG_FILE}"
send_error_mail(subject, body)
Pythonこの程度でも、「どの環境のどのバッチが、いつ落ちたか」がメールだけで把握できます。
例2:Slack Webhook でエラー通知する(よくある現場パターン)
Slack Webhook の基本構造
Slack の Incoming Webhook を使うと、
決まった URL に POST するだけで特定チャンネルにメッセージを送れます。
設定は Slack の管理画面側で行う必要がありますが、
Python 側から見ると単なる HTTP POST です。
import requests
import logging
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX"
def send_slack_error(text: str):
payload = {"text": text}
resp = requests.post(SLACK_WEBHOOK_URL, json=payload, timeout=5)
try:
resp.raise_for_status()
except Exception as e:
logging.exception(f"Slack通知の送信に失敗: {e}")
Pythonこれをトップレベルのエラー通知に組み込みます。
def main():
logging.info("バッチ開始")
# メイン処理
1 / 0
logging.info("バッチ正常終了")
if __name__ == "__main__":
setup_logging()
try:
main()
except Exception:
logging.exception("バッチで予期せぬ例外")
try:
send_slack_error("[ERROR] 日次バッチ異常終了。ログを確認してください。")
except Exception as notify_err:
logging.exception(f"Slack通知でもエラー: {notify_err}")
raise
Pythonこのパターンも、メール版と考え方は同じです。
致命的エラーが起きたときに、
Slack の特定チャンネルに「落ちたよ」と一言飛ばすだけで、
誰かがすぐに気づけるようになります。
Slack メッセージに最低限含めたい情報
Slack はテキストなので、シンプルに書けます。
例えば、
- [ERROR] or [WARN] などのレベル
- バッチ名
- 環境名(dev / staging / prod)
- 発生時刻
あたりがあると、パッと見で判別しやすいです。
from datetime import datetime
def build_slack_message(batch_name: str):
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return f":warning: [ERROR][prod] {batch_name} が異常終了しました。\n発生時刻: {now}\n詳細はログを確認してください。"
Python実際のスタックトレースやエラー詳細はログに任せ、
Slack では「気づかせる」ことに専念するのがポイントです。
重要ポイント:エラー通知の「範囲」と「頻度」を決める
すべての例外で通知すると「通知疲れ」になる
エラー通知は、やろうと思えば
- API のリトライ失敗1回ごと
- ファイル1件の処理失敗ごと
に飛ばすこともできますが、これはほぼ確実に失敗パターンです。
通知が多すぎると、人間はすぐにこうなります。
- 最初はちゃんと見る
- そのうち「どうせまただろう」とスルーする
- 本当に重大な通知も埋もれてしまう
これを避けるために、
- バッチ全体が落ちたときだけ通知
- 1バッチ中の失敗件数が一定以上になったときだけ通知
- 同じ種類のエラーが短時間に連発したときだけ通知
といったルールを導入します。
「致命的なエラー」と「許容される失敗」の線引き
設計として、一度考えておいてほしいのが次の問いです。
この処理では、何が起きたら「人間が今すぐ気づくべき」なのか?
逆に、何が起きても「ログだけ残せばOK」なのか?
例えば、
日次バッチ全体が途中で止まった
→ 即通知(CRITICAL)
1ファイルだけ読めなかった(ログの1行が壊れていた)
→ ログに残す。通知は不要
API の一時的なエラーだが、リトライで最終的に成功した
→ ログに残す。通知は不要
API の失敗が 10件を超えた
→ 何かおかしいので Warning 通知
この線引きが決まっていれば、
「どの例外ハンドラで通知を呼ぶか」がはっきりします。
設計で気をつけるべきこと(セキュリティと堅牢性)
認証情報や URL をソースにベタ書きしない
メールのログイン情報や、Slack の Webhook URL を
ソースコードにそのまま書くのは、セキュリティ的に危険です。
最低限、
環境変数(os.environ)から読む
設定ファイル(.env や config.json)に外出しする
のどちらかはやっておいたほうがいいです。
import os
SMTP_USER = os.environ.get("SMTP_USER")
SMTP_PASS = os.environ.get("SMTP_PASS")
SLACK_WEBHOOK_URL = os.environ.get("SLACK_WEBHOOK_URL")
Pythonこうしておけば、コードを Git に上げたり共有したりするときも、
認証情報が漏れにくくなります。
通知処理自体が本体を巻き込んで落ちないようにする
エラー通知は、「失敗のときにだけ呼ばれる処理」です。
ここでさらに例外が出ると、本来のエラー原因を覆い隠してしまうことがあります。
なので、
通知処理の中でも try/except を入れて、
通知失敗はログにだけ残し、元の例外の流れを壊さない
ように設計します。
先ほどの例でいうと、
try:
send_slack_error(...)
except Exception as notify_err:
logging.exception(f"エラー通知の送信にも失敗: {notify_err}")
Pythonといった形です。
「エラー通知も完璧でなければならない」と思う必要はありません。
「最低でも元のエラー原因がログに残る」ことを優先してください。
まとめ(エラー通知は「動かしっぱなしにするための最後の一手」)
Python 自動化におけるエラー通知のポイントを整理すると、こうなります。
誰に・何で・いつ通知するのか(宛先・チャネル・トリガー)を先に決める。
トップレベルの try/except で致命的例外を捕まえ、例外ログとエラー通知(メール・Slackなど)をセットで行う。
通知内容には「どのバッチが」「どの環境で」「いつ落ちたか」を最低限含め、詳細はログに任せる。
全ての例外で通知せず、「致命的なエラー」や「異常な頻度で発生している失敗」に絞ることで、通知疲れを防ぐ。
通知処理自体も安全側に書き、認証情報やURLは環境変数などに外出ししてセキュリティを確保する。
エラー通知が入ると、プログラムは「動いている間だけ頼れるツール」から、
「落ちてもすぐ気づける、運用に耐えるツール」に変わります。
