ログとは?ロギング設計と運用の基本

中級 | 16分 で読める | 2025.01.10

公式ドキュメント

ログとは

ログとは、アプリやサーバーで起きた出来事を記録した情報です。ユーザー登録、エラー発生、API呼び出し、決済完了、ログイン失敗など、あとから状況を調べるために残します。

初心者は、ログを「プログラムの行動記録」と考えると理解しやすいです。

たとえば、画面に「エラーが発生しました」とだけ表示されても、原因は分かりません。しかしログに「どのユーザーが、どのAPIで、どんなエラーになったか」が残っていれば、調査できます。

用語役割
ログ起きた出来事の記録
ロギングログを出力すること
ログレベル重要度ごとの分類
構造化ログ検索しやすい形に整えたログ

なぜロギングが重要か

適切なログは、本番環境での問題特定を数時間から数分に短縮します。逆に不適切なログは、ノイズとなり問題を見つけにくくします。

ログレベルの使い分け

レベル用途
ERROR即座の対応が必要データベース接続失敗、API障害
WARN潜在的な問題リトライ発生、非推奨機能使用
INFO重要なビジネスイベントユーザー登録、決済完了
DEBUG開発時のデバッグ情報変数の値、処理フロー
TRACE詳細なトレース情報メソッド呼び出し詳細
// 適切なログレベル使用例
logger.error('Payment failed', { orderId, error: err.message });
logger.warn('Retry attempt', { attempt: 3, maxRetries: 5 });
logger.info('User registered', { userId, plan: 'premium' });
logger.debug('Processing order items', { items });

構造化ログ

ポイント: ログレベルの使い分けが最も重要です。ERRORは「今すぐ対応が必要」、WARNは「放置すると問題になる可能性」、INFOは「正常な業務イベント」と明確に区別しましょう。

JSONログフォーマット

import pino from 'pino';

const logger = pino({
 level: process.env.LOG_LEVEL || 'info',
 formatters: {
 level: (label) => ({ level: label }),
 },
 timestamp: pino.stdTimeFunctions.isoTime,
});

// 出力例
// {"level":"info","time":"2025-01-10T12:00:00.000Z","msg":"User login","userId":"123","ip":"192.168.1.1"}

logger.info({ userId: '123', ip: '192.168.1.1' }, 'User login');

コンテキスト情報の付加

// リクエストコンテキストを自動付加
function createRequestLogger(req: Request) {
 return logger.child({
 requestId: req.headers['x-request-id'],
 userId: req.user?.id,
 path: req.path,
 method: req.method,
 });
}

// 使用
app.use((req, res, next) => {
 req.log = createRequestLogger(req);
 next();
});

app.get('/orders', (req, res) => {
 req.log.info('Fetching orders'); // 自動的にrequestId等が付加
});

実践メモ: pinoやwinstonなどの構造化ログライブラリを使えば、child loggerでリクエストコンテキストを自動付加でき、ログの追跡が格段に楽になります。

機密情報の除外

const sensitiveFields = ['password', 'token', 'creditCard', 'ssn'];

function sanitizeLogData(data: Record<string, any>): Record<string, any> {
 const sanitized = { ...data };

 for (const field of sensitiveFields) {
 if (field in sanitized) {
 sanitized[field] = '[REDACTED]';
 }
 }

 // ネストされたオブジェクトも処理
 for (const [key, value] of Object.entries(sanitized)) {
 if (typeof value === 'object' && value !== null) {
 sanitized[key] = sanitizeLogData(value);
 }
 }

 return sanitized;
}

// 使用
logger.info(sanitizeLogData({ userId: '123', password: 'YOUR_PASSWORD' }), 'Login attempt');
// 出力: {"userId":"123","password":"[REDACTED]","msg":"Login attempt"}

注意: パスワード、トークン、クレジットカード番号などの機密情報がログに出力されると、重大なセキュリティインシデントにつながります。ログ出力前に必ずサニタイズ処理を行ってください。

分散トレーシング連携

import { trace, context } from '@opentelemetry/api';

function getTraceContext() {
 const span = trace.getSpan(context.active());
 if (!span) return {};

 const spanContext = span.spanContext();
 return {
 traceId: spanContext.traceId,
 spanId: spanContext.spanId,
 };
}

// ログに自動的にトレース情報を付加
const tracingLogger = logger.child(getTraceContext());

エラーログのベストプラクティス

// 悪い例
try {
 await processPayment(order);
} catch (error) {
 console.log('Error'); // 情報不足
}

// 良い例
try {
 await processPayment(order);
} catch (error) {
 logger.error({
 err: error,
 orderId: order.id,
 amount: order.amount,
 paymentMethod: order.paymentMethod,
 stack: error.stack,
 }, 'Payment processing failed');

 // エラー種別に応じた処理
 if (error instanceof PaymentDeclinedError) {
 logger.warn({ orderId: order.id }, 'Payment declined by provider');
 }
}

ログ集約アーキテクチャ

flowchart LR
 subgraph Apps["アプリケーション"]
 A1["Service A"]
 A2["Service B"]
 A3["Service C"]
 end

 subgraph Collect["収集層"]
 FB["Fluentd/Fluent Bit"]
 end

 subgraph Store["保存層"]
 ES["Elasticsearch"]
 end

 subgraph View["可視化層"]
 KB["Kibana"]
 end

 Apps --> FB --> ES --> KB

パフォーマンス考慮

高頻度ログはサンプリングで出力量を制御し、パフォーマンスへの影響を最小限に抑えましょう。

// 高頻度ログはサンプリング
let requestCount = 0;
const SAMPLE_RATE = 100;

app.use((req, res, next) => {
 requestCount++;
 if (requestCount % SAMPLE_RATE === 0) {
 logger.debug({ path: req.path }, 'Request sample');
 }
 next();
});

// 非同期ログ出力
const logger = pino({
 transport: {
 target: 'pino/file',
 options: { destination: '/var/log/app.log' },
 },
});

現場での教訓

  1. 本番ではINFO以上 - DEBUGログは本番で有効にしない
  2. requestIdは必須 - 問題追跡に不可欠
  3. ログローテーション設定 - ディスク枯渇を防ぐ

関連記事

まとめ

効果的なロギングは、構造化ログ、適切なログレベル、コンテキスト情報の付加が鍵です。機密情報の除外とパフォーマンスにも注意を払いましょう。

参考リソース

← 一覧に戻る
PR
PR
PR
PR