Discord webhookでエラー通知基盤を作る最短コース
個人開発のモニタリング基盤、月コスト0円で構築する最短コース

個人開発で複数の自動化Workerを並走させていると、必ず「気づかないうちに止まっていた」が起きます。GramShift Desktop、ai-pick 記事自動生成 Worker、ai-pick 記事自動生成、W2 note 自動投稿、と私が運用している6つのWorkerは、すべて Discord webhook で1チャネル #gramshift-alerts に異常通知を集約しています。月コストは0円、運用は朝1分のDiscord確認だけ。この記事ではその構成の最小実装と、運用で必要になる3つの拡張ポイントを公開します。
Discord webhook 取得
Discord アプリで通知用チャネルを開き、設定 → 連携サービス → ウェブフック → 新しいウェブフックで作成します。URL は https://discord.com/api/webhooks/.../... 形式で、これに POST するだけで指定チャネルにメッセージが投稿されます。アカウント認証もAPI キーも不要、URLが認証情報そのものです。
最小実装 (Node.js)
シンプルな通知関数はこれだけで十分です。node-fetch や Node 18 以降の標準 fetch を使います。
// lib/discord-notify.mjs
import { config as loadDotenv } from 'dotenv';
loadDotenv();
const WEBHOOK_URL = process.env.DISCORD_WEBHOOK_URL;
export async function notify(level, message, extra = {}) {
if (!WEBHOOK_URL) {
console.warn('DISCORD_WEBHOOK_URL not set');
return;
}
const emoji = { info: 'ℹ️', warn: '⚠️', error: '🚨' }[level] || 'ℹ️';
const color = { info: 0x3b82f6, warn: 0xf59e0b, error: 0xef4444 }[level] || 0x6b7280;
const content = `${emoji} **[${level.toUpperCase()}]** ${message}`;
const embeds = Object.keys(extra).length > 0 ? [{
color,
fields: Object.entries(extra).map(([name, value]) => ({
name,
value: String(value).slice(0, 1024),
inline: false
})),
timestamp: new Date().toISOString()
}] : [];
try {
await fetch(WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content, embeds })
});
} catch (e) {
console.error('Discord notify failed:', e.message);
}
}
使い方はawait notify('error', 'Stripe webhook 失敗', { event_id: id, error: e.message })のように呼ぶだけ。エラー時もアプリ本体は止めないために try-catch でログ出力に留めています。
拡張1: 同一エラーの抑制 (notifyOnce)
同じエラーが大量に発生してアラート嵐になる問題への対策です。同じキーのアラートは5分に1回までに制限します。
const recentAlerts = new Map(); // key -> last sent timestamp
export async function notifyOnce(level, message, key, extra = {}) {
const now = Date.now();
const last = recentAlerts.get(key) || 0;
if (now - last < 5 * 60 * 1000) return;
recentAlerts.set(key, now);
await notify(level, message, extra);
}
notifyOnce('error', 'API rate limit', 'api_rate_limit', {...}) のように呼べば、同じ key の通知は 5分間に1通だけ届きます。私の運用でこれを入れた前後で、Discord アラート量が約 1/10 に減りました。
拡張2: サイレント時間 (深夜帯抑制)
深夜にアラートが鳴っても対応できません。深夜帯 (23時-7時) のアラートは「重大エラー」のみ即時送信、warn/info は朝のサマリーに回します。
const morningSummary = [];
export async function notifySmart(level, message, extra = {}) {
const hour = new Date().getHours();
const isQuiet = hour >= 23 || hour < 7;
if (isQuiet && level !== 'error') {
morningSummary.push({ level, message, extra, t: new Date() });
return;
}
await notify(level, message, extra);
}
export async function flushMorningSummary() {
if (morningSummary.length === 0) return;
const text = morningSummary.map(s =>
`[${s.level}] ${s.message} (${s.t.toLocaleTimeString('ja-JP')})`
).join('\n');
await notify('info', `朝のサマリー (${morningSummary.length}件)`, { details: text });
morningSummary.length = 0;
}
flushMorningSummary は別途 7:00 の Windows タスクで呼び出します。これにより、夜中のWorker失敗を朝起きた時に1通でまとめて確認できます。
拡張3: pm2 との連携
VPS の Fastify サーバーは pm2 で常駐させていますが、メモリリークや OOM Killer による再起動の瞬間を Discord で検知したい。pm2 のイベントを ecosystem.config.js でフックします。
// ecosystem.config.js
module.exports = {
apps: [{
name: 'gramshift',
script: './server.mjs',
max_memory_restart: '700M', // 700MB超で再起動
cron_restart: '0 4 * * *', // 毎日04:00に予防的再起動
autorestart: true,
error_file: './logs/error.log',
out_file: './logs/out.log',
listen_timeout: 10000,
}]
};
そしてserver.mjsの起動時に Discord に「pm2 から起動された」と通知させます。これで再起動の頻度が可視化されます。月3-5回の再起動が観測されたら、メモリリークの根本原因を調査する流れです。
1チャネル運用と多チャネル分散
個人開発の規模では「すべてのアラートを1チャネル」が運用負荷最小です。複数チャネルに分けると「重要な通知を別チャネルで見逃す」リスクが上がります。私は #gramshift-alerts 1本に集約し、notifyOnce + サイレント時間で十分整理できています。
運用が大規模化したら、Webhook URL を複数発行して error / warn / info で分けるのもありです。ただし最初はシンプル構成から始めるのが鉄則です。
関連する自動化Worker 並走運用は 自動化レシピカテゴリ、Worker 全体設計は AIワークフローカテゴリ にまとめています。