なぜロギングが重要か
適切なログは、本番環境での問題特定を数時間から数分に短縮します。逆に不適切なログは、ノイズとなり問題を見つけにくくします。
ログレベルの使い分け
| レベル | 用途 | 例 |
|---|---|---|
| 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 });
構造化ログ
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等が付加
});
機密情報の除外
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: 'secret' }), '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' },
},
});
現場での教訓
- 本番ではINFO以上 - DEBUGログは本番で有効にしない
- requestIdは必須 - 問題追跡に不可欠
- ログローテーション設定 - ディスク枯渇を防ぐ
関連記事
- 監視・可観測性 - メトリクス・トレース
- 分散トランザクション - マイクロサービスのデバッグ
- エラーハンドリング - 例外処理パターン
まとめ
効果的なロギングは、構造化ログ、適切なログレベル、コンテキスト情報の付加が鍵です。機密情報の除外とパフォーマンスにも注意を払いましょう。
← Back to list