エラーをログサーバーに送信する|JavaScript

本番環境ではエラーが発生しても開発者のコンソールには表示されません。ユーザーの環境で起きたエラーを把握するには、エラー情報をサーバーに送信する仕組みが必要です。

基本的な送信方法

fetch を使ってエラー情報を JSON で送信します。

function reportError(error) {
  const payload = {
    message: error.message,
    stack: error.stack,
    url: location.href,
    userAgent: navigator.userAgent,
    timestamp: new Date().toISOString()
  };
  
  fetch('/api/errors', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload)
  }).catch(() => {
    // エラー送信自体が失敗しても握りつぶす
  });
}

エラー送信の失敗で別のエラーが発生しないよう、catch で握りつぶすのがポイントです。

sendBeacon を使う

ページ遷移やタブを閉じる直前でも確実に送信したい場合は、navigator.sendBeacon を使います。

function reportError(error) {
  const payload = JSON.stringify({
    message: error.message,
    stack: error.stack,
    url: location.href
  });
  
  navigator.sendBeacon('/api/errors', payload);
}

sendBeacon はブラウザがバックグラウンドで送信を完了するため、ユーザー体験を阻害しません。

fetch

レスポンスを待てる。細かい制御が可能。ページ離脱時に送信が中断されることがある。

sendBeacon

レスポンスは受け取れない。ページ離脱時も確実に送信される。

グローバルエラーハンドラと組み合わせる

window.onerror と unhandledrejection の両方でエラーを捕捉し、送信します。

// 同期エラー
window.onerror = function(message, source, lineno, colno, error) {
  reportError({
    type: 'onerror',
    message,
    source,
    lineno,
    colno,
    stack: error?.stack
  });
  return false;
};

// Promise のエラー
window.addEventListener('unhandledrejection', event => {
  reportError({
    type: 'unhandledrejection',
    message: event.reason?.message,
    stack: event.reason?.stack
  });
});

送信するべき情報

デバッグに役立つ情報をできるだけ含めます。

エラー情報

message, stack, name など Error オブジェクトのプロパティ

発生場所

URL、行番号、列番号、ソースファイル名

環境情報

ブラウザ、OS、画面サイズ、言語設定

コンテキスト

ログインユーザー ID、セッション ID、直前の操作履歴

function collectErrorContext(error) {
  return {
    // エラー情報
    message: error.message,
    stack: error.stack,
    name: error.name,
    
    // 発生場所
    url: location.href,
    referrer: document.referrer,
    
    // 環境情報
    userAgent: navigator.userAgent,
    language: navigator.language,
    screenSize: `${screen.width}x${screen.height}`,
    viewportSize: `${window.innerWidth}x${window.innerHeight}`,
    
    // タイムスタンプ
    timestamp: new Date().toISOString(),
    timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
  };
}

エラーの重複排除

同じエラーが短時間に大量に送信されるのを防ぎます。

const reportedErrors = new Set();

function reportError(error) {
  const key = `${error.message}:${error.stack?.slice(0, 200)}`;
  
  if (reportedErrors.has(key)) {
    return; // 既に報告済み
  }
  
  reportedErrors.add(key);
  
  // 5分後に再報告可能にする
  setTimeout(() => reportedErrors.delete(key), 5 * 60 * 1000);
  
  sendToServer(collectErrorContext(error));
}

サンプリング

トラフィックが多いサイトでは、一部のエラーのみを送信するサンプリングを行うこともあります。

function reportError(error) {
  // 10% のエラーのみ送信
  if (Math.random() > 0.1) {
    return;
  }
  
  sendToServer(collectErrorContext(error));
}

エラー追跡サービスの活用

自前で実装する代わりに、専用のサービスを使う方法もあります。

Sentry
Bugsnag
Rollbar
LogRocket
New Relic

これらのサービスは、スクリプトを読み込むだけで自動的にエラーを収集し、ダッシュボードで分析できます。ソースマップのアップロードにも対応しており、難読化されたコードのスタックトレースも元のソースに変換されます。

サーバーサイドでの処理

送信されたエラーはサーバーで受け取り、データベースに保存したり、通知を送ったりします。

// Express の例
app.post('/api/errors', express.json(), (req, res) => {
  const errorData = req.body;
  
  // データベースに保存
  db.errors.insert(errorData);
  
  // 重大なエラーは Slack に通知
  if (errorData.message.includes('Critical')) {
    notifySlack(errorData);
  }
  
  res.status(204).end();
});

エラーログを定期的に分析することで、ユーザーが遭遇している問題を把握し、サービスの品質向上につなげられます。