skills/doyajin174/structured-logging/SKILL.md
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.
npx skillsauth add aiskillstore/marketplace structured-loggingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
JSON 포맷의 구조화된 로깅을 구현하는 스킬입니다.
"print문 대신 구조화된 로그를 남겨라." "로그는 검색 가능하고, 집계 가능해야 한다."
[2024-01-15 10:30:45] ERROR User login failed for user123
[2024-01-15 10:30:46] INFO Processing request
{
"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
}
| Level | 용도 | 예시 |
|-------|------|------|
| fatal | 시스템 종료 필요 | DB 연결 완전 실패 |
| error | 에러 발생, 복구 가능 | API 호출 실패 |
| warn | 잠재적 문제 | 지연된 응답 |
| info | 주요 이벤트 | 사용자 로그인 성공 |
| debug | 디버깅 정보 | 함수 파라미터 |
| trace | 상세 추적 | 실행 흐름 |
프로덕션: info 이상만
개발: debug 이상
디버깅 시: trace까지
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>;
}
npm install pino pino-pretty
// 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');
npm install winston
// 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(),
}),
],
});
// 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;
}
// 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();
}
// 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
};
// 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;
}
}
// 사용자 활동
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');
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;
}
}
// ❌ 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');
// 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'],
});
// 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),
});
},
},
});
npm install dd-trace
// 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');
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.