structured-logging by aiskillstore
Implement JSON-based structured logging for observability. Use when setting up logging, debugging production issues, or preparing for log aggregation (ELK, Datadog). Covers log levels, context, and best practices.
Content & Writing
85 Stars
2 Forks
Updated Jan 19, 2026, 04:39 AM
Why Use This
This skill provides specialized capabilities for aiskillstore's codebase.
Use Cases
- Developing new features in the aiskillstore repository
- Refactoring existing code to follow aiskillstore standards
- Understanding and working with aiskillstore's codebase structure
Install Guide
2 steps- 1
Skip this step if Ananke is already installed.
- 2
Skill Snapshot
Auto scan of skill assets. Informational only.
Valid SKILL.md
Checks against SKILL.md specification
Source & Community
Skill Stats
SKILL.md 452 Lines
Total Files 1
Total Size 0 B
License MIT
---
name: structured-logging
description: Implement JSON-based structured logging for observability. Use when setting up logging, debugging production issues, or preparing for log aggregation (ELK, Datadog). Covers log levels, context, and best practices.
allowed-tools: Read, Glob, Grep, Edit, Write, Bash
license: MIT
metadata:
author: antigravity-team
version: "1.0"
---
# Structured Logging
JSON 포맷의 구조화된 로깅을 구현하는 스킬입니다.
## Core Principle
> **"print문 대신 구조화된 로그를 남겨라."**
> **"로그는 검색 가능하고, 집계 가능해야 한다."**
## 왜 Structured Logging인가?
### ❌ 일반 텍스트 로그
```
[2024-01-15 10:30:45] ERROR User login failed for user123
[2024-01-15 10:30:46] INFO Processing request
```
- 파싱 어려움
- 필터링/검색 제한
- 컨텍스트 손실
### ✅ 구조화된 로그 (JSON)
```json
{
"timestamp": "2024-01-15T10:30:45.123Z",
"level": "error",
"message": "User login failed",
"userId": "user123",
"errorCode": "AUTH_INVALID_PASSWORD",
"requestId": "req-abc-123",
"duration": 45
}
```
- 쉬운 파싱/검색
- 필드별 필터링
- 풍부한 컨텍스트
## Log Levels
| Level | 용도 | 예시 |
|-------|------|------|
| `fatal` | 시스템 종료 필요 | DB 연결 완전 실패 |
| `error` | 에러 발생, 복구 가능 | API 호출 실패 |
| `warn` | 잠재적 문제 | 지연된 응답 |
| `info` | 주요 이벤트 | 사용자 로그인 성공 |
| `debug` | 디버깅 정보 | 함수 파라미터 |
| `trace` | 상세 추적 | 실행 흐름 |
### 프로덕션 로그 레벨
```
프로덕션: info 이상만
개발: debug 이상
디버깅 시: trace까지
```
## 필수 로그 필드
```typescript
interface LogEntry {
// 필수
timestamp: string; // ISO 8601
level: string; // error, warn, info, debug
message: string; // 사람이 읽을 수 있는 메시지
// 권장
requestId?: string; // 요청 추적
userId?: string; // 사용자 식별
service?: string; // 서비스명
environment?: string; // prod, staging, dev
// 상황별
error?: {
name: string;
message: string;
stack?: string;
};
duration?: number; // ms
metadata?: Record<string, unknown>;
}
```
## Node.js 구현
### Pino (권장 - 고성능)
```bash
npm install pino pino-pretty
```
```typescript
// lib/logger.ts
import pino from 'pino';
export const logger = pino({
level: process.env.LOG_LEVEL || 'info',
// 기본 필드
base: {
service: 'my-app',
environment: process.env.NODE_ENV,
},
// 타임스탬프 포맷
timestamp: pino.stdTimeFunctions.isoTime,
// 개발 환경: pretty print
transport: process.env.NODE_ENV === 'development'
? { target: 'pino-pretty' }
: undefined,
});
// 사용
logger.info({ userId: '123' }, 'User logged in');
logger.error({ error, requestId }, 'Request failed');
```
### Winston
```bash
npm install winston
```
```typescript
// lib/logger.ts
import winston from 'winston';
export const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: {
service: 'my-app',
environment: process.env.NODE_ENV,
},
transports: [
new winston.transports.Console({
format: process.env.NODE_ENV === 'development'
? winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
: winston.format.json(),
}),
],
});
```
## Request Context
### Request ID 전파
```typescript
// middleware/requestId.ts
import { randomUUID } from 'crypto';
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const requestId = request.headers.get('x-request-id') || randomUUID();
const response = NextResponse.next();
response.headers.set('x-request-id', requestId);
return response;
}
```
### AsyncLocalStorage (권장)
```typescript
// lib/context.ts
import { AsyncLocalStorage } from 'async_hooks';
interface RequestContext {
requestId: string;
userId?: string;
startTime: number;
}
export const asyncLocalStorage = new AsyncLocalStorage<RequestContext>();
// 미들웨어에서 설정
export function withContext<T>(context: RequestContext, fn: () => T): T {
return asyncLocalStorage.run(context, fn);
}
// 로거에서 사용
export function getContext(): RequestContext | undefined {
return asyncLocalStorage.getStore();
}
```
### Context-aware Logger
```typescript
// lib/logger.ts
import pino from 'pino';
import { getContext } from './context';
const baseLogger = pino({ /* config */ });
export const logger = {
info: (obj: object, msg?: string) => {
const ctx = getContext();
baseLogger.info({ ...obj, ...ctx }, msg);
},
error: (obj: object, msg?: string) => {
const ctx = getContext();
baseLogger.error({ ...obj, ...ctx }, msg);
},
// ... other levels
};
```
## 로깅 패턴
### API 요청 로깅
```typescript
// middleware/logging.ts
export async function loggingMiddleware(req: Request, handler: Function) {
const startTime = Date.now();
const requestId = randomUUID();
logger.info({
requestId,
method: req.method,
url: req.url,
userAgent: req.headers.get('user-agent'),
}, 'Request started');
try {
const response = await handler(req);
logger.info({
requestId,
statusCode: response.status,
duration: Date.now() - startTime,
}, 'Request completed');
return response;
} catch (error) {
logger.error({
requestId,
error: {
name: error.name,
message: error.message,
stack: error.stack,
},
duration: Date.now() - startTime,
}, 'Request failed');
throw error;
}
}
```
### 비즈니스 이벤트 로깅
```typescript
// 사용자 활동
logger.info({
event: 'user.login',
userId,
method: 'google_oauth',
ip: request.ip,
}, 'User logged in');
// 결제
logger.info({
event: 'payment.success',
userId,
amount: 9900,
currency: 'KRW',
paymentId,
}, 'Payment completed');
// 에러
logger.error({
event: 'payment.failed',
userId,
amount: 9900,
errorCode: 'CARD_DECLINED',
paymentId,
}, 'Payment failed');
```
### 성능 로깅
```typescript
async function fetchData() {
const startTime = Date.now();
try {
const result = await db.query(/* ... */);
logger.info({
operation: 'db.query',
table: 'users',
duration: Date.now() - startTime,
rowCount: result.length,
}, 'Database query completed');
return result;
} catch (error) {
logger.error({
operation: 'db.query',
table: 'users',
duration: Date.now() - startTime,
error: error.message,
}, 'Database query failed');
throw error;
}
}
```
## 금지 패턴
```typescript
// ❌ BAD: 민감 정보 로깅
logger.info({ password, creditCard, ssn }, 'User data');
// ❌ BAD: 과도한 로깅 (성능 저하)
for (const item of items) {
logger.debug({ item }, 'Processing item'); // 수천 번 호출
}
// ❌ BAD: 구조화되지 않은 로그
logger.info(`User ${userId} logged in at ${timestamp}`);
// ✅ GOOD: 구조화된 로그
logger.info({ userId, timestamp }, 'User logged in');
```
## 민감 정보 제거
```typescript
// lib/logger.ts
const sensitiveFields = ['password', 'token', 'apiKey', 'creditCard'];
function redactSensitiveData(obj: object): object {
const redacted = { ...obj };
for (const key of Object.keys(redacted)) {
if (sensitiveFields.some(f => key.toLowerCase().includes(f))) {
redacted[key] = '[REDACTED]';
}
}
return redacted;
}
// Pino redact 옵션
const logger = pino({
redact: ['password', 'creditCard', '*.token', 'headers.authorization'],
});
```
## Log Aggregation 연동
### ELK Stack (Elasticsearch)
```typescript
// filebeat.yml에서 JSON 파싱
// 또는 직접 Elasticsearch로 전송
import { Client } from '@elastic/elasticsearch';
const esClient = new Client({ node: 'http://localhost:9200' });
const esTransport = new winston.transports.Stream({
stream: {
write: async (log: string) => {
await esClient.index({
index: 'app-logs',
document: JSON.parse(log),
});
},
},
});
```
### Datadog
```bash
npm install dd-trace
```
```typescript
// tracer.ts
import tracer from 'dd-trace';
tracer.init({
service: 'my-app',
env: process.env.NODE_ENV,
});
// 로그에 trace ID 포함
logger.info({
dd: {
trace_id: tracer.scope().active()?.context().toTraceId(),
span_id: tracer.scope().active()?.context().toSpanId(),
},
}, 'Event with trace');
```
## Checklist
### 설정
- [ ] 구조화된 로깅 라이브러리 설치 (Pino/Winston)
- [ ] 로그 레벨 환경변수 설정
- [ ] 기본 필드 (service, environment) 설정
- [ ] Request ID 미들웨어 적용
- [ ] 민감 정보 redaction 설정
### 로깅 표준
- [ ] JSON 포맷 사용
- [ ] 적절한 로그 레벨 사용
- [ ] 비즈니스 이벤트 로깅
- [ ] 에러에 스택 트레이스 포함
- [ ] 성능 측정 로깅
### 운영
- [ ] 로그 집계 시스템 연동
- [ ] 로그 기반 알림 설정
- [ ] 로그 보관 정책 수립
## References
- [Pino](https://getpino.io/)
- [Winston](https://github.com/winstonjs/winston)
- [12-Factor App Logs](https://12factor.net/logs)
Name Size