> ## Documentation Index
> Fetch the complete documentation index at: https://docs.logbrew.co/llms.txt
> Use this file to discover all available pages before exploring further.

# 구조화된 로깅 모범 사례와 로그 형식

> 구조화된 로깅이 무엇인지 알아보고, 표준 필드를 갖춘 JSON 로그 형식을 고르고, 로그를 검색하기 쉽게 유지하는 로깅 모범 사례를 적용하세요.

LogBrew는 개발자와 코딩 에이전트를 위한 옵저버빌리티 플랫폼입니다. 넉넉한 무료 요금제를 제공하며 카드 등록이 필요 없습니다.

구조화된 로깅은 각 로그 레코드를 자유 텍스트 한 줄이 아니라 이름이 있는 필드로 씁니다. 그러면 프로그램, 사람, 코딩 에이전트가 필드로 레코드를 필터링하고, 개수를 세고, 요청 트레이스에 연결할 수 있습니다.

## 텍스트 로그와 구조화된 로그

이 텍스트 로그에는 아래 구조화된 로그와 같은 정보가 들어 있습니다.

```text theme={null}
2026-11-07 14:21:19 ERROR checkout payment failed for order ord_1001 after 5 s
```

```json theme={null}
{"timestamp": "2026-11-07T14:21:19.773+00:00", "level": "error", "logger": "checkout", "message": "payment failed", "order_id": "ord_1001", "error.type": "TimeoutError", "error.message": "payment provider did not answer in 5 s"}
```

텍스트 로그에서 주문 1건의 실패한 결제를 모두 찾으려면 메시지 변형마다 패턴을 작성해야 합니다. 구조화된 로그에서는 `message`와 `order_id`로 필터링합니다. 메시지는 주문마다 같으므로 개수도 셀 수 있습니다.

## 로그 형식 선택하기

JSON을 사용하고 한 줄에 객체 1개를 쓰세요. 모든 서비스에서 같은 필드 이름을 사용하세요. 이 표는 기본 필드와, OpenTelemetry 로그 데이터 모델과 LogBrew에서 대응하는 이름입니다.

| 필드 | 내용 | OpenTelemetry | LogBrew |
| - | - | - | - |
| `timestamp` | RFC 3339 형식의 UTC 시간(밀리초 포함) | `Timestamp` | 이벤트 `timestamp` |
| `level` | `info`, `warning`, `error`, `critical`처럼 정해진 작은 집합 중 하나 | `SeverityText`, `SeverityNumber` | `level` |
| `message` | 이벤트를 나타내는 고정 텍스트로, 변수 값은 넣지 않습니다 | `Body` | `message` |
| `logger` | 레코드를 쓴 모듈이나 컴포넌트 | 계측 범위 | `logger` |
| `trace_id`, `span_id` | 활성 트레이스와 스팬(시간을 측정한 작업 하나)의 ID | `TraceId`, `SpanId` | `context.trace` |
| `service`, `environment`, `release` | 코드가 실행되는 위치와 버전 | 리소스 속성 | `context.resource` |
| `error.type`, `error.message` | 예외 클래스와 메시지 | `exception.type`, `exception.message` | `metadata` |
| 이벤트 필드 | `order_id`처럼 이 이벤트에 속한 값 | `Attributes` | `metadata` |

OpenTelemetry 심각도 숫자는 1부터 24까지입니다. `INFO`는 9, `WARN`은 13, `ERROR`는 17, `FATAL`은 21에서 시작합니다. LogBrew는 OTLP 로그를 바로 받습니다. [OpenTelemetry 데이터 보내기](https://docs.logbrew.co/ko/guides/opentelemetry.md)를 참고하세요.

## 로깅 모범 사례

### 메시지 고정하기

`"payment failed"`라고 쓰고 주문 ID는 필드에 넣으세요. 메시지가 고정되면 이벤트 유형 1개로 묶고, 세고, 알림을 설정할 수 있습니다.

### 필드마다 이름 하나만 쓰기

`order_id`와 `orderId` 중 하나를 한 번 정하고 모든 서비스에서 사용하세요. `http.response.status_code`처럼 흔한 필드에는 OpenTelemetry 시맨틱 규칙의 이름을 사용하세요.

### 필요한 대응에 따라 레벨 고르기

`error`는 작업이 실패했다는 뜻입니다. `warning`은 프로그램이 복구했다는 뜻입니다. `info`는 정상 작업을 확인합니다. `debug`는 문제를 찾는 동안에만 사용하세요.

### 시간은 UTC로 쓰기

`2026-11-07T14:21:19.773+00:00`처럼 밀리초와 오프셋이 있는 RFC 3339를 사용하세요. UTC와 밀리초가 없으면 시간대가 다른 레코드나 같은 초에 쓴 레코드가 잘못된 순서로 정렬됩니다.

### 트레이스 ID와 스팬 ID 추가하기

이 ID가 있으면 요청 1개의 로그를 여러 서비스에 걸쳐 모두 읽을 수 있습니다.

### 서비스, 환경, 릴리스는 한 번만 설정하기

호출할 때마다가 아니라 시작할 때 설정하세요. 릴리스가 없으면 어떤 배포가 변화를 일으켰는지 알 수 없습니다.

### 오류는 처리하는 곳에서 한 번만 기록하기

오류 유형과 메시지를 추가하세요. 같은 오류를 계층마다 다시 기록하지 마세요.

### 시크릿과 개인정보 빼기

비밀번호, 토큰, 쿠키, 요청 본문, 쿼리 문자열이 있는 전체 URL을 기록하지 마세요. 로거의 가리기 기능을 사용하세요. [로그 마스킹](https://docs.logbrew.co/ko/guides/safe-capture.md)을 참고하세요.

### 고정된 필드 이름 사용하기

`{"ord_1001": "failed"}`처럼 값을 필드 이름으로 쓰지 마세요. 그러면 새 값마다 필드가 새로 생기고, 필터 1개로 모든 주문을 찾을 수 없습니다.

### 프로덕션에서는 표준 출력에 JSON 쓰기

읽기 쉬운 형식은 개발 환경에서만 사용하세요.

## Python의 구조화된 로깅

표준 `logging` 모듈에는 JSON 포매터가 없습니다. 이 포매터는 기본 필드와 `extra={"fields": {...}}`의 값을 씁니다.

```python theme={null}
import json
import logging
from datetime import datetime, timezone


class JsonFormatter(logging.Formatter):
    def format(self, record):
        entry = {
            "timestamp": datetime.fromtimestamp(record.created, timezone.utc).isoformat(
                timespec="milliseconds"
            ),
            "level": record.levelname.lower(),
            "logger": record.name,
            "message": record.getMessage(),
            **getattr(record, "fields", {}),
        }
        if record.exc_info:
            entry["error.type"] = record.exc_info[0].__name__
            entry["error.message"] = str(record.exc_info[1])
        return json.dumps(entry)


handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
logging.basicConfig(level=logging.INFO, handlers=[handler])
logger = logging.getLogger("checkout")

logger.info("order created", extra={"fields": {"order_id": "ord_1001", "total_cents": 4200}})
try:
    raise TimeoutError("payment provider did not answer in 5 s")
except TimeoutError:
    logger.error("payment failed", exc_info=True, extra={"fields": {"order_id": "ord_1001"}})
```

```text theme={null}
{"timestamp": "2026-11-07T14:21:19.772+00:00", "level": "info", "logger": "checkout", "message": "order created", "order_id": "ord_1001", "total_cents": 4200}
{"timestamp": "2026-11-07T14:21:19.773+00:00", "level": "error", "logger": "checkout", "message": "payment failed", "order_id": "ord_1001", "error.type": "TimeoutError", "error.message": "payment provider did not answer in 5 s"}
```

같은 레코드를 LogBrew로 보내려면 `LogBrewLoggingHandler`를 추가하세요. 이 핸들러는 메시지, 레벨, 로거를 필드로 보내고, `extra`의 원시 값을 메타데이터로 보냅니다. [Python 로깅](https://docs.logbrew.co/ko/guides/python-logging.md)을 참고하세요.

## Node.js의 구조화된 로깅

Pino는 기본적으로 레코드마다 JSON 1줄을 씁니다. Winston은 `winston.format.json()`으로 JSON을 씁니다. 둘 다 호출에서 받은 필드와 자식 로거의 필드를 유지합니다. 두 설정 방법과 Express 로그의 트레이스 ID는 [Node.js 로깅](https://docs.logbrew.co/ko/guides/node-logging.md)을 참고하세요.

## LogBrew에서 구조화된 로그 검색하기

LogBrew는 각 로그를 메시지, 레벨, 로거, 메타데이터, 트레이스와 함께 저장합니다. CLI에서 이 필드로 필터링하세요.

```bash theme={null}
logbrew read logs --severity error --since 24h --json
logbrew read logs --search "payment failed" --service checkout-api --since 24h --json
logbrew read logs --trace "<trace_id>" --since 24h --json
```

API는 500 이상인 숫자처럼 타입이 있는 메타데이터 값으로도 필터링할 수 있습니다. [쿼리 필터](https://docs.logbrew.co/ko/reference/telemetry-api-filters.md)를 참고하세요.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.