agentsclimarketplace

Structured logging

Skill aiskillstore/marketplace/skills/doyajin174/structured-logging

Security-audited skills for Claude, Codex & Claude Code. One-click install, quality verified.

Install
npx -y skills add aiskillstore/marketplace --skill structured-logging

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.

What its author says it does

Copied from the file, not written here

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.

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

9.3 KB, as published. Nobody here has run it

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)

{
  "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까지

필수 로그 필드

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 (권장 - 고성능)

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');

Winston

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(),
    }),
  ],
});

Request Context

Request ID 전파

// 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 (권장)

// 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

// 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 요청 로깅

// 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'],
});

Log Aggregation 연동

ELK Stack (Elasticsearch)

// 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

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');

Checklist

설정

  • 구조화된 로깅 라이브러리 설치 (Pino/Winston)
  • 로그 레벨 환경변수 설정
  • 기본 필드 (service, environment) 설정
  • Request ID 미들웨어 적용
  • 민감 정보 redaction 설정

로깅 표준

  • JSON 포맷 사용
  • 적절한 로그 레벨 사용
  • 비즈니스 이벤트 로깅
  • 에러에 스택 트레이스 포함
  • 성능 측정 로깅

운영

  • 로그 집계 시스템 연동
  • 로그 기반 알림 설정
  • 로그 보관 정책 수립

References

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.