yodogawa 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.windsurf/templates/documentation-rules.md +143 -0
- package/.windsurf/templates/project/01-requirements/01-system-overview.md +49 -0
- package/.windsurf/templates/project/01-requirements/02-features-implemented.md +73 -0
- package/.windsurf/templates/project/01-requirements/03-features-planned.md +75 -0
- package/.windsurf/templates/project/01-requirements/04-non-functional-requirements.md +115 -0
- package/.windsurf/templates/project/01-requirements/05-user-stories.md +124 -0
- package/.windsurf/templates/project/02-behavior/01-scenarios.md +406 -0
- package/.windsurf/templates/project/03-domain/01-domain-model.md +338 -0
- package/.windsurf/templates/project/03-domain/02-ubiquitous-language.md +153 -0
- package/.windsurf/templates/project/04-design/01-tech-stack.md +360 -0
- package/.windsurf/templates/project/04-design/02-repository-structure.md +390 -0
- package/.windsurf/templates/project/04-design/03-screen-design.md +586 -0
- package/.windsurf/templates/project/04-design/04-data-model.md +211 -0
- package/.windsurf/templates/project/04-design/05-api-spec.md +221 -0
- package/.windsurf/templates/project/04-design/06-architecture.md +183 -0
- package/.windsurf/templates/project/04-design/07-infrastructure.md +180 -0
- package/.windsurf/templates/tasks/task-template/a-definition.md +143 -0
- package/.windsurf/templates/tasks/task-template/b-research.md +185 -0
- package/.windsurf/templates/tasks/task-template/c-implementation.md +197 -0
- package/.windsurf/workflows/a-001-SetupDocStructure.md +165 -0
- package/.windsurf/workflows/a-002-InitializeProject.md +229 -0
- package/.windsurf/workflows/a-003-CreateScenarios.md +130 -0
- package/.windsurf/workflows/a-004-DefineDomainModel.md +133 -0
- package/.windsurf/workflows/a-005-CreateDomainDiagram.md +114 -0
- package/.windsurf/workflows/a-006-ReviewRequirementsDomain.md +132 -0
- package/.windsurf/workflows/a-007-DefineTechStack.md +121 -0
- package/.windsurf/workflows/a-008-DefineRepositoryStructure.md +118 -0
- package/.windsurf/workflows/a-009-DefineScreenDesign.md +121 -0
- package/.windsurf/workflows/a-010-DefineDataModel.md +125 -0
- package/.windsurf/workflows/a-011-DefineAPISpec.md +123 -0
- package/.windsurf/workflows/a-012-DefineArchitecture.md +119 -0
- package/.windsurf/workflows/a-013-DefineInfrastructure.md +120 -0
- package/.windsurf/workflows/a-014-ReviewDesign.md +122 -0
- package/.windsurf/workflows/b-001-CreateTaskDirectory.md +71 -0
- package/.windsurf/workflows/b-002-CreateTaskDefinition.md +165 -0
- package/.windsurf/workflows/b-003-CreateTaskResearch.md +412 -0
- package/.windsurf/workflows/b-004-CreateTaskImplementation.md +97 -0
- package/.windsurf/workflows/b-005-ReviewTask.md +312 -0
- package/.windsurf/workflows/c-001-ImplementTask.md +493 -0
- package/.windsurf/workflows/c-002-UpdateDocumentation.md +797 -0
- package/.windsurf/workflows/x-Accessibility-Check.md +469 -0
- package/.windsurf/workflows/x-Bundle-Optimize.md +386 -0
- package/.windsurf/workflows/x-CI-FixFailure.md +636 -0
- package/.windsurf/workflows/x-CI-Setup.md +641 -0
- package/.windsurf/workflows/x-Code-Refactor.md +71 -0
- package/.windsurf/workflows/x-Code-ResearchAndReview.md +78 -0
- package/.windsurf/workflows/x-Component-Create.md +359 -0
- package/.windsurf/workflows/x-Context-CatchUp.md +63 -0
- package/.windsurf/workflows/x-Database-Seed.md +300 -0
- package/.windsurf/workflows/x-Dependencies-Update.md +315 -0
- package/.windsurf/workflows/x-DevEnvironment-Setup.md +437 -0
- package/.windsurf/workflows/x-Logging-Add.md +682 -0
- package/.windsurf/workflows/x-Migration-Create.md +354 -0
- package/.windsurf/workflows/x-Problem-RootCauseAnalysis.md +65 -0
- package/.windsurf/workflows/x-Repository-Push.md +375 -0
- package/.windsurf/workflows/x-Repository-PushToGithub.md +72 -0
- package/.windsurf/workflows/x-Requirements-Clarify.md +61 -0
- package/.windsurf/workflows/z-CreateWorkflow.md +77 -0
- package/README.md +280 -0
- package/bin/cli.js +74 -0
- package/package.json +28 -0
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 構造化ログを実装し、本番環境での問題調査とモニタリングを効率化するワークフロー
|
|
3
|
+
auto_execution_mode: 1
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Logging-Add (x-Logging-Add)
|
|
7
|
+
|
|
8
|
+
## 目的
|
|
9
|
+
|
|
10
|
+
- アプリケーションに構造化ログを実装し、本番環境での問題調査を効率化する。
|
|
11
|
+
- ログレベル(DEBUG, INFO, WARN, ERROR)を適切に使い分ける。
|
|
12
|
+
- 機密情報(パスワード、トークン、個人情報)をログに出力しない。
|
|
13
|
+
- ログ集約サービス(CloudWatch, Datadog, Sentry)との統合を実現する。
|
|
14
|
+
- リクエスト ID によるトレーサビリティを確保する。
|
|
15
|
+
|
|
16
|
+
## 前提
|
|
17
|
+
|
|
18
|
+
- アプリケーションが動作している(Node.js, Python, Ruby など)。
|
|
19
|
+
- ログライブラリが利用可能(Winston, Pino, Python logging, など)。
|
|
20
|
+
- ログ出力先が決まっている(ファイル、標準出力、ログ集約サービス)。
|
|
21
|
+
- Git リポジトリが初期化されている。
|
|
22
|
+
|
|
23
|
+
## 手順
|
|
24
|
+
|
|
25
|
+
### 1. ログ戦略の決定
|
|
26
|
+
|
|
27
|
+
**ログフォーマットの選択**:
|
|
28
|
+
- **構造化ログ(JSON)**: 推奨。ログ集約サービスで検索・分析しやすい
|
|
29
|
+
- **テキストログ**: シンプルだが検索・分析が困難
|
|
30
|
+
- **ハイブリッド**: 開発環境では可読性優先、本番環境では JSON
|
|
31
|
+
|
|
32
|
+
**ログレベルの設計**:
|
|
33
|
+
- **ERROR**: エラー発生時(例外、失敗)
|
|
34
|
+
- **WARN**: 警告(非推奨 API 使用、リトライ)
|
|
35
|
+
- **INFO**: 重要なイベント(ユーザーログイン、支払い完了)
|
|
36
|
+
- **DEBUG**: デバッグ情報(SQL クエリ、API レスポンス)
|
|
37
|
+
- **TRACE**: 詳細なトレース(関数呼び出し)
|
|
38
|
+
|
|
39
|
+
**ログ出力先の決定**:
|
|
40
|
+
- **標準出力**: Docker/Kubernetes 環境推奨
|
|
41
|
+
- **ファイル**: ローテーション設定が必要
|
|
42
|
+
- **ログ集約サービス**: CloudWatch, Datadog, Sentry, Loggly
|
|
43
|
+
|
|
44
|
+
### 2. ログライブラリのインストール
|
|
45
|
+
|
|
46
|
+
**Node.js**:
|
|
47
|
+
```bash
|
|
48
|
+
# Winston (人気、多機能)
|
|
49
|
+
npm install winston
|
|
50
|
+
|
|
51
|
+
# Pino (高速、軽量)
|
|
52
|
+
npm install pino
|
|
53
|
+
|
|
54
|
+
# Morgan (Express 用 HTTP ログ)
|
|
55
|
+
npm install morgan
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Python**:
|
|
59
|
+
```bash
|
|
60
|
+
# 標準ライブラリの logging を使用
|
|
61
|
+
# または
|
|
62
|
+
pip install structlog # 構造化ログ
|
|
63
|
+
pip install python-json-logger # JSON フォーマット
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Ruby (Rails)**:
|
|
67
|
+
```bash
|
|
68
|
+
# 標準の Logger を使用
|
|
69
|
+
# または
|
|
70
|
+
gem install lograge # Rails のログを構造化
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3. ロガーの設定
|
|
74
|
+
|
|
75
|
+
#### 3.1. Node.js + Winston
|
|
76
|
+
|
|
77
|
+
**logger.ts**:
|
|
78
|
+
```typescript
|
|
79
|
+
import winston from 'winston';
|
|
80
|
+
|
|
81
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
82
|
+
|
|
83
|
+
const logger = winston.createLogger({
|
|
84
|
+
level: process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug'),
|
|
85
|
+
format: winston.format.combine(
|
|
86
|
+
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
87
|
+
winston.format.errors({ stack: true }),
|
|
88
|
+
winston.format.metadata(),
|
|
89
|
+
isProduction
|
|
90
|
+
? winston.format.json() // 本番: JSON
|
|
91
|
+
: winston.format.combine( // 開発: 可読性優先
|
|
92
|
+
winston.format.colorize(),
|
|
93
|
+
winston.format.printf(({ level, message, timestamp, ...meta }) => {
|
|
94
|
+
return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`;
|
|
95
|
+
})
|
|
96
|
+
)
|
|
97
|
+
),
|
|
98
|
+
transports: [
|
|
99
|
+
new winston.transports.Console(),
|
|
100
|
+
// ファイル出力(オプション)
|
|
101
|
+
...(isProduction ? [
|
|
102
|
+
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
|
|
103
|
+
new winston.transports.File({ filename: 'logs/combined.log' }),
|
|
104
|
+
] : []),
|
|
105
|
+
],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
export default logger;
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### 3.2. Node.js + Pino
|
|
112
|
+
|
|
113
|
+
**logger.ts**:
|
|
114
|
+
```typescript
|
|
115
|
+
import pino from 'pino';
|
|
116
|
+
|
|
117
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
118
|
+
|
|
119
|
+
const logger = pino({
|
|
120
|
+
level: process.env.LOG_LEVEL || (isProduction ? 'info' : 'debug'),
|
|
121
|
+
transport: !isProduction ? {
|
|
122
|
+
target: 'pino-pretty',
|
|
123
|
+
options: {
|
|
124
|
+
colorize: true,
|
|
125
|
+
translateTime: 'SYS:standard',
|
|
126
|
+
ignore: 'pid,hostname',
|
|
127
|
+
},
|
|
128
|
+
} : undefined,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export default logger;
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### 3.3. Python + structlog
|
|
135
|
+
|
|
136
|
+
**logger.py**:
|
|
137
|
+
```python
|
|
138
|
+
import structlog
|
|
139
|
+
import logging
|
|
140
|
+
import sys
|
|
141
|
+
|
|
142
|
+
def setup_logging():
|
|
143
|
+
logging.basicConfig(
|
|
144
|
+
format="%(message)s",
|
|
145
|
+
stream=sys.stdout,
|
|
146
|
+
level=logging.INFO,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
structlog.configure(
|
|
150
|
+
processors=[
|
|
151
|
+
structlog.stdlib.filter_by_level,
|
|
152
|
+
structlog.stdlib.add_logger_name,
|
|
153
|
+
structlog.stdlib.add_log_level,
|
|
154
|
+
structlog.stdlib.PositionalArgumentsFormatter(),
|
|
155
|
+
structlog.processors.TimeStamper(fmt="iso"),
|
|
156
|
+
structlog.processors.StackInfoRenderer(),
|
|
157
|
+
structlog.processors.format_exc_info,
|
|
158
|
+
structlog.processors.UnicodeDecoder(),
|
|
159
|
+
structlog.processors.JSONRenderer() # JSON 出力
|
|
160
|
+
],
|
|
161
|
+
context_class=dict,
|
|
162
|
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
|
163
|
+
cache_logger_on_first_use=True,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
logger = structlog.get_logger()
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 4. リクエスト ID の追加
|
|
170
|
+
|
|
171
|
+
リクエスト ID を各ログエントリに追加することで、分散システムでのトレーサビリティを確保します。
|
|
172
|
+
|
|
173
|
+
#### 4.1. Web フレームワークのミドルウェア実装
|
|
174
|
+
|
|
175
|
+
**Express (Node.js) の例**:
|
|
176
|
+
|
|
177
|
+
**requestLogger.ts**:
|
|
178
|
+
```typescript
|
|
179
|
+
import { Request, Response, NextFunction } from 'express';
|
|
180
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
181
|
+
import logger from './logger';
|
|
182
|
+
|
|
183
|
+
export function requestLoggerMiddleware(req: Request, res: Response, next: NextFunction) {
|
|
184
|
+
const requestId = req.headers['x-request-id'] as string || uuidv4();
|
|
185
|
+
req.requestId = requestId;
|
|
186
|
+
res.setHeader('X-Request-ID', requestId);
|
|
187
|
+
|
|
188
|
+
// リクエストログ
|
|
189
|
+
logger.info('Incoming request', {
|
|
190
|
+
requestId,
|
|
191
|
+
method: req.method,
|
|
192
|
+
url: req.url,
|
|
193
|
+
userAgent: req.headers['user-agent'],
|
|
194
|
+
ip: req.ip,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const startTime = Date.now();
|
|
198
|
+
|
|
199
|
+
res.on('finish', () => {
|
|
200
|
+
const duration = Date.now() - startTime;
|
|
201
|
+
logger.info('Request completed', {
|
|
202
|
+
requestId,
|
|
203
|
+
method: req.method,
|
|
204
|
+
url: req.url,
|
|
205
|
+
statusCode: res.statusCode,
|
|
206
|
+
duration: `${duration}ms`,
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
next();
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**app.ts**:
|
|
215
|
+
```typescript
|
|
216
|
+
import express from 'express';
|
|
217
|
+
import { requestLoggerMiddleware } from './middleware/requestLogger';
|
|
218
|
+
|
|
219
|
+
const app = express();
|
|
220
|
+
|
|
221
|
+
app.use(requestLoggerMiddleware);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### 4.2. Django ミドルウェア
|
|
225
|
+
|
|
226
|
+
**middleware.py**:
|
|
227
|
+
```python
|
|
228
|
+
import uuid
|
|
229
|
+
import time
|
|
230
|
+
import structlog
|
|
231
|
+
|
|
232
|
+
logger = structlog.get_logger()
|
|
233
|
+
|
|
234
|
+
class RequestLoggingMiddleware:
|
|
235
|
+
def __init__(self, get_response):
|
|
236
|
+
self.get_response = get_response
|
|
237
|
+
|
|
238
|
+
def __call__(self, request):
|
|
239
|
+
request_id = request.headers.get('X-Request-ID', str(uuid.uuid4()))
|
|
240
|
+
request.request_id = request_id
|
|
241
|
+
|
|
242
|
+
logger.info(
|
|
243
|
+
"incoming_request",
|
|
244
|
+
request_id=request_id,
|
|
245
|
+
method=request.method,
|
|
246
|
+
path=request.path,
|
|
247
|
+
user_agent=request.META.get('HTTP_USER_AGENT'),
|
|
248
|
+
ip=request.META.get('REMOTE_ADDR'),
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
start_time = time.time()
|
|
252
|
+
response = self.get_response(request)
|
|
253
|
+
duration = (time.time() - start_time) * 1000
|
|
254
|
+
|
|
255
|
+
logger.info(
|
|
256
|
+
"request_completed",
|
|
257
|
+
request_id=request_id,
|
|
258
|
+
method=request.method,
|
|
259
|
+
path=request.path,
|
|
260
|
+
status_code=response.status_code,
|
|
261
|
+
duration_ms=round(duration, 2),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
response['X-Request-ID'] = request_id
|
|
265
|
+
return response
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**settings.py**:
|
|
269
|
+
```python
|
|
270
|
+
MIDDLEWARE = [
|
|
271
|
+
'myapp.middleware.RequestLoggingMiddleware',
|
|
272
|
+
# ...
|
|
273
|
+
]
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### 5. ログの追加
|
|
277
|
+
|
|
278
|
+
#### 5.1. エラーログ
|
|
279
|
+
|
|
280
|
+
**Node.js**:
|
|
281
|
+
```typescript
|
|
282
|
+
import logger from './logger';
|
|
283
|
+
|
|
284
|
+
try {
|
|
285
|
+
// 処理
|
|
286
|
+
} catch (error) {
|
|
287
|
+
logger.error('Failed to process payment', {
|
|
288
|
+
error: error.message,
|
|
289
|
+
stack: error.stack,
|
|
290
|
+
userId: user.id,
|
|
291
|
+
orderId: order.id,
|
|
292
|
+
});
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Python**:
|
|
298
|
+
```python
|
|
299
|
+
from logger import logger
|
|
300
|
+
|
|
301
|
+
try:
|
|
302
|
+
# 処理
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.error(
|
|
305
|
+
"failed_to_process_payment",
|
|
306
|
+
error=str(e),
|
|
307
|
+
user_id=user.id,
|
|
308
|
+
order_id=order.id,
|
|
309
|
+
exc_info=True, # スタックトレースを含める
|
|
310
|
+
)
|
|
311
|
+
raise
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### 5.2. 情報ログ
|
|
315
|
+
|
|
316
|
+
**Node.js**:
|
|
317
|
+
```typescript
|
|
318
|
+
logger.info('User logged in', {
|
|
319
|
+
userId: user.id,
|
|
320
|
+
email: user.email,
|
|
321
|
+
loginMethod: 'password',
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
logger.info('Payment completed', {
|
|
325
|
+
orderId: order.id,
|
|
326
|
+
amount: order.amount,
|
|
327
|
+
currency: order.currency,
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Python**:
|
|
332
|
+
```python
|
|
333
|
+
logger.info(
|
|
334
|
+
"user_logged_in",
|
|
335
|
+
user_id=user.id,
|
|
336
|
+
email=user.email,
|
|
337
|
+
login_method="password",
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
logger.info(
|
|
341
|
+
"payment_completed",
|
|
342
|
+
order_id=order.id,
|
|
343
|
+
amount=order.amount,
|
|
344
|
+
currency=order.currency,
|
|
345
|
+
)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### 5.3. デバッグログ
|
|
349
|
+
|
|
350
|
+
**Node.js**:
|
|
351
|
+
```typescript
|
|
352
|
+
logger.debug('Database query executed', {
|
|
353
|
+
query: 'SELECT * FROM users WHERE id = ?',
|
|
354
|
+
params: [userId],
|
|
355
|
+
duration: '12ms',
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
logger.debug('API response', {
|
|
359
|
+
endpoint: '/api/users',
|
|
360
|
+
statusCode: 200,
|
|
361
|
+
responseTime: '45ms',
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### 6. 機密情報の除外
|
|
366
|
+
|
|
367
|
+
ログに機密情報が含まれないよう、適切なマスキング処理を実装します。
|
|
368
|
+
|
|
369
|
+
**除外すべき情報**:
|
|
370
|
+
- パスワード
|
|
371
|
+
- API キー / トークン
|
|
372
|
+
- クレジットカード番号
|
|
373
|
+
- 社会保障番号
|
|
374
|
+
- 個人情報(住所、電話番号)
|
|
375
|
+
|
|
376
|
+
**logger.ts(フィルター追加)**:
|
|
377
|
+
```typescript
|
|
378
|
+
import winston from 'winston';
|
|
379
|
+
|
|
380
|
+
const sensitiveKeys = ['password', 'token', 'apiKey', 'creditCard', 'ssn'];
|
|
381
|
+
|
|
382
|
+
const maskSensitiveData = winston.format((info) => {
|
|
383
|
+
const maskValue = (obj: any) => {
|
|
384
|
+
if (typeof obj !== 'object' || obj === null) return obj;
|
|
385
|
+
|
|
386
|
+
for (const key in obj) {
|
|
387
|
+
if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk.toLowerCase()))) {
|
|
388
|
+
obj[key] = '***REDACTED***';
|
|
389
|
+
} else if (typeof obj[key] === 'object') {
|
|
390
|
+
maskValue(obj[key]);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return obj;
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
return maskValue(info);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const logger = winston.createLogger({
|
|
400
|
+
format: winston.format.combine(
|
|
401
|
+
maskSensitiveData(),
|
|
402
|
+
winston.format.timestamp(),
|
|
403
|
+
winston.format.json()
|
|
404
|
+
),
|
|
405
|
+
// ...
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**使用例**:
|
|
410
|
+
```typescript
|
|
411
|
+
logger.info('User registration', {
|
|
412
|
+
email: 'user@example.com',
|
|
413
|
+
password: 'secret123', // ***REDACTED*** として出力される
|
|
414
|
+
});
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### 7. ログローテーション
|
|
418
|
+
|
|
419
|
+
ログファイルが肥大化しないよう、ローテーション設定を行います。
|
|
420
|
+
|
|
421
|
+
**主要なログライブラリでのローテーション設定例**:
|
|
422
|
+
|
|
423
|
+
**Winston + daily-rotate-file (Node.js)**:
|
|
424
|
+
```bash
|
|
425
|
+
npm install winston-daily-rotate-file
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
import winston from 'winston';
|
|
430
|
+
import DailyRotateFile from 'winston-daily-rotate-file';
|
|
431
|
+
|
|
432
|
+
const logger = winston.createLogger({
|
|
433
|
+
transports: [
|
|
434
|
+
new DailyRotateFile({
|
|
435
|
+
filename: 'logs/application-%DATE%.log',
|
|
436
|
+
datePattern: 'YYYY-MM-DD',
|
|
437
|
+
maxSize: '20m',
|
|
438
|
+
maxFiles: '14d', // 14日間保持
|
|
439
|
+
}),
|
|
440
|
+
],
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**Linux logrotate** (`/etc/logrotate.d/myapp`):
|
|
445
|
+
```
|
|
446
|
+
/var/log/myapp/*.log {
|
|
447
|
+
daily
|
|
448
|
+
rotate 14
|
|
449
|
+
compress
|
|
450
|
+
delaycompress
|
|
451
|
+
missingok
|
|
452
|
+
notifempty
|
|
453
|
+
create 0640 www-data www-data
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### 8. ログ集約サービスとの統合
|
|
458
|
+
|
|
459
|
+
本番環境では、ログ集約サービスを使用して複数サーバーのログを一元管理します。
|
|
460
|
+
|
|
461
|
+
**主要なログ集約サービス**: AWS CloudWatch, Datadog, Sentry, Loggly, Splunk
|
|
462
|
+
|
|
463
|
+
#### 8.1. AWS CloudWatch との統合
|
|
464
|
+
|
|
465
|
+
**Node.js**:
|
|
466
|
+
```bash
|
|
467
|
+
npm install winston-cloudwatch
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import CloudWatchTransport from 'winston-cloudwatch';
|
|
472
|
+
|
|
473
|
+
const logger = winston.createLogger({
|
|
474
|
+
transports: [
|
|
475
|
+
new CloudWatchTransport({
|
|
476
|
+
logGroupName: '/aws/lambda/my-app',
|
|
477
|
+
logStreamName: () => {
|
|
478
|
+
const date = new Date().toISOString().split('T')[0];
|
|
479
|
+
return `${date}-${process.env.NODE_ENV}`;
|
|
480
|
+
},
|
|
481
|
+
awsRegion: 'us-east-1',
|
|
482
|
+
}),
|
|
483
|
+
],
|
|
484
|
+
});
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
#### 8.2. Datadog
|
|
488
|
+
|
|
489
|
+
**Node.js**:
|
|
490
|
+
```bash
|
|
491
|
+
npm install dd-trace
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
```typescript
|
|
495
|
+
import tracer from 'dd-trace';
|
|
496
|
+
tracer.init({
|
|
497
|
+
logInjection: true, // ログに trace ID を自動追加
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
import logger from './logger';
|
|
501
|
+
|
|
502
|
+
logger.info('User action', {
|
|
503
|
+
userId: user.id,
|
|
504
|
+
action: 'purchase',
|
|
505
|
+
});
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
#### 8.3. Sentry(エラートラッキング)
|
|
509
|
+
|
|
510
|
+
**Node.js**:
|
|
511
|
+
```bash
|
|
512
|
+
npm install @sentry/node
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
import * as Sentry from '@sentry/node';
|
|
517
|
+
|
|
518
|
+
Sentry.init({
|
|
519
|
+
dsn: process.env.SENTRY_DSN,
|
|
520
|
+
environment: process.env.NODE_ENV,
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
// エラーを Sentry に送信
|
|
524
|
+
try {
|
|
525
|
+
// 処理
|
|
526
|
+
} catch (error) {
|
|
527
|
+
Sentry.captureException(error);
|
|
528
|
+
logger.error('Error occurred', { error: error.message });
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### 9. パフォーマンスメトリクスの追加
|
|
534
|
+
|
|
535
|
+
パフォーマンス分析のため、実行時間やレスポンスタイムをログに記録します。
|
|
536
|
+
|
|
537
|
+
**汎用的な実装パターン(関数実行時間の記録)**:
|
|
538
|
+
|
|
539
|
+
**TypeScript/JavaScript の例**:
|
|
540
|
+
```typescript
|
|
541
|
+
import logger from './logger';
|
|
542
|
+
|
|
543
|
+
function withTiming<T>(fn: () => T, operationName: string): T {
|
|
544
|
+
const start = Date.now();
|
|
545
|
+
try {
|
|
546
|
+
const result = fn();
|
|
547
|
+
const duration = Date.now() - start;
|
|
548
|
+
logger.debug('Operation completed', {
|
|
549
|
+
operation: operationName,
|
|
550
|
+
duration: `${duration}ms`,
|
|
551
|
+
status: 'success',
|
|
552
|
+
});
|
|
553
|
+
return result;
|
|
554
|
+
} catch (error) {
|
|
555
|
+
const duration = Date.now() - start;
|
|
556
|
+
logger.error('Operation failed', {
|
|
557
|
+
operation: operationName,
|
|
558
|
+
duration: `${duration}ms`,
|
|
559
|
+
status: 'error',
|
|
560
|
+
error: error.message,
|
|
561
|
+
});
|
|
562
|
+
throw error;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// 使用例
|
|
567
|
+
const users = withTiming(() => {
|
|
568
|
+
return db.query('SELECT * FROM users');
|
|
569
|
+
}, 'database.query.users');
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### 10. ログの動作確認
|
|
573
|
+
|
|
574
|
+
**ローカル環境でのログ確認方法**:
|
|
575
|
+
```bash
|
|
576
|
+
# ファイル出力の場合
|
|
577
|
+
tail -f logs/combined.log
|
|
578
|
+
|
|
579
|
+
# Docker の場合
|
|
580
|
+
docker logs -f <container_name>
|
|
581
|
+
|
|
582
|
+
# JSON ログを見やすく表示
|
|
583
|
+
tail -f logs/combined.log | jq .
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
**確認すべき項目**:
|
|
587
|
+
- ログレベルが適切に設定されているか
|
|
588
|
+
- リクエスト ID が各ログエントリに含まれているか
|
|
589
|
+
- 機密情報が適切にマスキングされているか
|
|
590
|
+
- JSON 形式が正しいか(本番環境の場合)
|
|
591
|
+
- タイムスタンプが正しく記録されているか
|
|
592
|
+
|
|
593
|
+
### 11. テストの実行
|
|
594
|
+
|
|
595
|
+
既存のテストスイートを実行し、ログ機能が既存の機能に影響を与えていないことを確認します。
|
|
596
|
+
|
|
597
|
+
```bash
|
|
598
|
+
# 例: npm/yarn/pnpm
|
|
599
|
+
npm test
|
|
600
|
+
|
|
601
|
+
# 例: pytest
|
|
602
|
+
pytest
|
|
603
|
+
|
|
604
|
+
# 例: Rails
|
|
605
|
+
bundle exec rspec
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### 12. Git コミット
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
git add src/utils/logger.ts
|
|
612
|
+
git add src/middleware/requestLogger.ts
|
|
613
|
+
git add src/
|
|
614
|
+
|
|
615
|
+
git commit -m "feat: add structured logging with Winston
|
|
616
|
+
|
|
617
|
+
- Add Winston logger with JSON format for production
|
|
618
|
+
- Add request logging middleware with request ID
|
|
619
|
+
- Mask sensitive data (password, token, apiKey)
|
|
620
|
+
- Add log rotation with daily-rotate-file
|
|
621
|
+
- Integrate with CloudWatch for centralized logging
|
|
622
|
+
- Add performance metrics logging
|
|
623
|
+
|
|
624
|
+
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
|
625
|
+
|
|
626
|
+
Co-Authored-By: Claude <noreply@anthropic.com>"
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
## 完了条件
|
|
630
|
+
|
|
631
|
+
- ログライブラリがインストールされている
|
|
632
|
+
- ロガーが設定され、構造化ログが出力される
|
|
633
|
+
- リクエスト ID が追加され、トレーサビリティが確保されている
|
|
634
|
+
- 機密情報がマスキングされている
|
|
635
|
+
- ログレベル(ERROR, WARN, INFO, DEBUG)が適切に使い分けられている
|
|
636
|
+
- ログローテーションが設定されている(必要に応じて)
|
|
637
|
+
- ログ集約サービスとの統合が完了している(必要に応じて)
|
|
638
|
+
- すべてのテストが通る
|
|
639
|
+
- ログが期待通りに出力される
|
|
640
|
+
|
|
641
|
+
## エスカレーション
|
|
642
|
+
|
|
643
|
+
- **ログが出力されない**:
|
|
644
|
+
- 「ログが出力されません。以下を確認してください:」
|
|
645
|
+
- ログレベルが適切か(DEBUG ログが INFO レベルで実行されていないか)
|
|
646
|
+
- ロガーが正しくインポートされているか
|
|
647
|
+
- 環境変数 `LOG_LEVEL` が設定されているか
|
|
648
|
+
- ファイル出力の場合、ディレクトリのアクセス権限
|
|
649
|
+
|
|
650
|
+
- **機密情報が漏洩している**:
|
|
651
|
+
- 「ログに機密情報が含まれています。以下を実施してください:」
|
|
652
|
+
- マスキングフィルターを追加
|
|
653
|
+
- 機密情報のキーワードリストを更新
|
|
654
|
+
- 既存ログを削除またはローテーション
|
|
655
|
+
- セキュリティチームに報告
|
|
656
|
+
|
|
657
|
+
- **ログが多すぎる / パフォーマンス影響**:
|
|
658
|
+
- 「ログ出力がパフォーマンスに影響しています。以下を検討してください:」
|
|
659
|
+
- ログレベルを INFO 以上に制限
|
|
660
|
+
- 非同期ログライブラリを使用(Pino など)
|
|
661
|
+
- サンプリング(1% のリクエストのみログ)
|
|
662
|
+
- 不要なログを削除
|
|
663
|
+
|
|
664
|
+
- **ログ集約サービスのコストが高い**:
|
|
665
|
+
- 「ログ集約サービスのコストが高騰しています。以下を検討してください:」
|
|
666
|
+
- ログレベルの見直し
|
|
667
|
+
- ログのサンプリング
|
|
668
|
+
- 保持期間の短縮
|
|
669
|
+
- ログ圧縮の有効化
|
|
670
|
+
|
|
671
|
+
## ベストプラクティス
|
|
672
|
+
|
|
673
|
+
- **構造化ログ(JSON)を使用**: 検索・分析が容易
|
|
674
|
+
- **リクエスト ID を追加**: トレーサビリティの確保
|
|
675
|
+
- **ログレベルを適切に使い分け**: ERROR は即座に対応、INFO は重要なイベントのみ
|
|
676
|
+
- **機密情報を絶対に出力しない**: GDPR / PCI-DSS 違反に注意
|
|
677
|
+
- **環境による出力形式の切り替え**: 開発環境では可読性優先、本番環境では JSON
|
|
678
|
+
- **エラーにはスタックトレースを含める**: 問題調査の効率化
|
|
679
|
+
- **ログローテーションを設定**: ディスク容量の枯渇を防ぐ
|
|
680
|
+
- **ログ集約サービスの活用**: 分散システムでのログ統合
|
|
681
|
+
- **パフォーマンスメトリクスを追加**: レスポンスタイム、DB クエリ時間
|
|
682
|
+
- **ログレベルを環境変数で制御**: 本番環境で動的に変更可能に
|