> ## 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.

# LogBrew로 Flask 로깅과 Flask 예외 처리하기

> Flask 로깅을 설정하고, 처리되지 않은 예외와 요청 트레이스를 수집하고, LogBrew CLI로 저장된 시그널을 하나씩 확인하세요.

`logbrew-flask` 0.1.6 이상을 사용하세요. 이 패키지는 요청 스팬과 처리되지 않은 예외를 수집합니다. 선택 사항인 로깅 핸들러를 쓰면 Python 로그가 요청 트레이스에 연결됩니다. 요청 소요 시간 메트릭도 선택 사항입니다.

이 패키지에는 Python 3.10 이상과 Flask 3.1 이상이 필요합니다.

<Note>
  애플리케이션에는 프로젝트 범위의 서버 수집 키를 사용하세요. CLI를 승인하는 계정 자격 증명은 사용하지 마세요.
</Note>

`<project_id>`, `<trace_id>`, `<issue_id>`를 LogBrew에서 받은 ID로 바꾸세요. 테스트 요청에는 개발용 프로젝트를 사용하세요. 셸 예제는 Bash를 기준으로 합니다.

## 첫 요청 보내기

<Steps>
  <Step title="프로젝트 키 만들기 또는 선택하기">
    [CLI](/ko/guides/cli)로 로그인하세요. 그런 다음 새 프로젝트나 기존 프로젝트를 사용하세요. 두 예제를 모두 실행하지는 마세요. 새 프로젝트라면 수집 키를 소유자만 접근할 수 있는 새 파일에 저장하세요.

    ```bash theme={null}
    install -d -m 700 "$HOME/.logbrew"
    logbrew projects create my-flask-app \
      --runtime flask \
      --environment development \
      --ingest-key-file "$HOME/.logbrew/my-flask-app.ingest" \
      --json
    ```

    기존 프로젝트라면 `logbrew projects --json`에서 프로젝트 ID를 확인하세요. 그런 다음 별도의 서버 키를 만드세요.

    ```bash theme={null}
    logbrew projects keys create "<project_id>" \
      --kind server \
      --label "Flask server" \
      --ingest-key-file "$HOME/.logbrew/my-flask-app.ingest" \
      --json
    ```

    CLI는 수집 키를 소유자 전용 권한으로 저장합니다. 키나 파일 경로는 출력하지 않습니다. 프로덕션에서는 이 로컬 파일 대신 배포 플랫폼의 시크릿 관리 도구를 사용하세요.

    생성 요청의 결과가 불확실하면 같은 명령어를 그대로 다시 실행하세요.
  </Step>

  <Step title="릴리스된 연동 패키지 설치하기">
    애플리케이션의 가상 환경을 만들거나 활성화한 다음 프레임워크 패키지를 설치하세요. 이 패키지는 호환되는 코어 SDK 의존성도 함께 설치합니다.

    ```bash theme={null}
    python3 -m pip install "logbrew-flask>=0.1.6,<0.2"
    ```
  </Step>

  <Step title="배포 정보 설정하기">
    프로젝트 키를 출력하지 않고 로컬에서 불러오세요. 모든 시그널에 유용한 배포 컨텍스트가 담기도록 서비스, 환경, 릴리스를 바뀌지 않는 값으로 설정하세요.

    ```bash theme={null}
    export LOGBREW_SERVER_API_KEY="$(tr -d '\r\n' < "$HOME/.logbrew/my-flask-app.ingest")"
    export LOGBREW_SERVICE_NAME="my-flask-app"
    export LOGBREW_ENVIRONMENT="development"
    export LOGBREW_RELEASE="my-flask-app@1.0.0"
    ```

    `init_logbrew()`는 명시적 인수를 먼저 확인하고, 그다음 Flask 애플리케이션 설정, 마지막으로 이 환경 변수를 확인합니다. 서비스 이름이 설정되지 않았으면 Flask import 이름을 사용합니다.
  </Step>

  <Step title="Flask 한 번만 초기화하기">
    새 앱이라면 예제를 `app.py`로 저장하세요. Flask 애플리케이션을 만든 뒤에 초기화 코드를 추가하세요. 로깅 핸들러는 선택 사항입니다. 이 핸들러를 쓰면 애플리케이션 로그가 활성 요청의 트레이스와 스팬을 공유합니다.

    ```python theme={null}
    import logging

    from flask import Flask
    from logbrew_flask import init_logbrew
    from logbrew_sdk import LogBrewLoggingHandler

    app = Flask(__name__)
    logbrew = init_logbrew(app, capture_request_metrics=True)

    logger = logging.getLogger("my-flask-app")
    logger.setLevel(logging.INFO)
    logger.addHandler(LogBrewLoggingHandler(logbrew.client))


    @app.get("/health")
    def health() -> dict[str, bool]:
        logger.info("health check completed")
        return {"ok": True}
    ```

    `init_logbrew(app)`을 여러 번 호출해도 같은 Flask 확장이 반환됩니다. 요청 훅이나 예외 훅이 중복으로 설치되지 않습니다. 초기화 함수는 로깅 핸들러를 추가하지 않습니다. 앱이 소유한 로거에 핸들러를 한 번만 추가하세요.
  </Step>

  <Step title="요청 1건 실행하기">
    앱을 시작하고 다른 터미널에서 라우트를 호출하세요. 애플리케이션의 모듈 이름이 다르면 명령어의 `app`을 바꾸세요.

    ```bash theme={null}
    flask --app app run
    ```

    ```bash theme={null}
    curl -fsS http://127.0.0.1:5000/health
    ```

    전송은 백그라운드 워커에서 실행되며 응답을 붙잡아 두지 않습니다.
  </Step>

  <Step title="저장된 근거 확인하기">
    정확한 프로젝트와 배포 범위로 조회하세요. 상태 확인 요청의 트레이스와, 예제 로깅 핸들러가 보낸 연결된 로그를 찾으세요.

    ```bash theme={null}
    logbrew read traces \
      --project "<project_id>" \
      --service my-flask-app \
      --environment development \
      --release my-flask-app@1.0.0 \
      --since 1h \
      --json

    logbrew read logs \
      --project "<project_id>" \
      --service my-flask-app \
      --environment development \
      --release my-flask-app@1.0.0 \
      --search "health check completed" \
      --since 1h \
      --json
    ```

    반환된 트레이스 ID를 범위가 정해진 조사 명령어에 넣으세요.

    ```bash theme={null}
    logbrew explain trace "<trace_id>" --json
    ```

    조회 결과가 일치하면 LogBrew가 시그널을 저장했다는 뜻입니다. 로컬 `200` 응답은 Flask가 요청을 처리했다는 사실만 증명합니다.
  </Step>
</Steps>

## 필요한 시그널 고르기

초기화 함수는 요청 트레이스와 처리되지 않은 예외 수집을 켠 상태로 시작합니다. 요청 메트릭과 Python 로그 전달은 따로 선택합니다.

| 옵션 | 기본값 | 사용하는 경우 |
| - | - | - |
| `capture_successful_requests` | `True` | 완료된 요청마다 스팬 1개가 필요할 때 사용합니다. 이 옵션을 꺼도 상태가 `500` 이상인 응답에는 스팬이 만들어집니다. |
| `capture_exceptions` | `True` | Flask가 처리되지 않았다고 보고한 예외에 대해 유형이 지정된 이슈와 그에 맞는 오류 스팬이 필요할 때 사용합니다. |
| `capture_request_metrics` | `False` | 완료된 요청의 소요 시간 히스토그램이 필요할 때 사용합니다. |
| `LogBrewLoggingHandler` | 연결 안 됨 | 선택한 Python 로그 레코드를 LogBrew 로그로 보내고 싶을 때 사용합니다. |
| `flush_on_response` | `False` | 백그라운드 전송을 쓰려면 기본값을 유지하세요. 응답 경로의 전송을 드러내야 하는 통제된 진단에서만 켜세요. |

기본값으로 시작하세요. 지연 시간을 보고 싶을 때 요청 메트릭을 추가하세요. 로깅 핸들러는 조사에 도움이 되는 로거에만 추가하세요.

기본적으로 초기화 함수는 HTTP 전송 계층을 직접 관리합니다. 각 이벤트를 큐에 넣고, 큐에 이벤트가 1개라도 들어오면 백그라운드 워커가 바로 전송을 시작합니다. 전송은 응답을 붙잡아 두지 않습니다. 전송이 실패해도 Flask 응답은 깨지지 않습니다.

`flush_on_response=True`를 쓰면 큐에 있는 이벤트를 응답 경로에서 보냅니다. 이 옵션과 함께 `raise_flush_errors=True`를 쓰면 전송 오류가 예외로 발생합니다. 두 옵션은 통제된 진단에서만 사용하세요.

## 로그 내용 제어하기

필요한 메시지를 남기는 로거에 `LogBrewLoggingHandler`를 1개 추가하세요. 이 핸들러는 앱의 콘솔 핸들러나 파일 핸들러를 대체하지 않습니다. Flask나 Python logging 모듈을 패치하지도 않습니다. 같은 핸들러를 두 번 추가하면 로그가 중복됩니다. 앱이 루트 로거도 사용한다면 두 번째 핸들러를 붙이기 전에 전파 설정을 확인하세요.

요청 안에서 남긴 로그는 활성 트레이스와 스팬을 이어받습니다. 요청 밖에서 남긴 로그에는 요청 트레이스가 없습니다.

핸들러는 포맷된 메시지, 로거 이름, LogBrew 레벨을 보냅니다. 소스 파일 이름, 모듈, 함수, 줄, 스레드 이름, 프로세스 이름도 보내지만 소스 전체 경로는 보내지 않습니다. `extra={...}`에 넣은 기본 타입 값은 메타데이터가 됩니다. 사용자가 보내는 요청에서 로그를 남기기 전에 이 값을 검토하세요.

Python `DEBUG` 레코드는 LogBrew `info`가 됩니다. `WARNING`은 `warning`이 되고, `CRITICAL`은 `critical`이 됩니다. 원래 Python 레벨 이름과 숫자는 메타데이터에 남습니다. 레코드를 더 적게 보내려면 `LogBrewLoggingHandler(logbrew.client, level=logging.WARNING)`처럼 핸들러에 별도 레벨을 지정하세요.

콘솔과 LogBrew가 같은 레코드를 받을 수 있습니다. 예를 들어 Flask 앱을 만들기 전에 콘솔을 설정하세요.

```python theme={null}
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
)
```

콘솔은 포맷된 텍스트를 받습니다. LogBrew는 메시지, 레벨, 로거, 메타데이터를 각각 별도 필드로 저장합니다.

`exc_info`가 있는 로그 레코드에는 예외 유형과 메시지가 들어갑니다. 전체 예외 텍스트는 핸들러에 `include_exception_text=True`가 설정된 경우에만 전송됩니다. 이 설정은 자동 예외 수집과 별개입니다. 자동 예외 수집은 예외 메시지를 절대 보내지 않습니다. 로그 메시지나 예외 메시지에 자격 증명, 개인정보, 요청 본문을 넣지 마세요.

## 연동 패키지가 수집하는 항목

| 시그널 | 기본 동작 |
| - | - |
| 요청 트레이스 | 메서드, Flask 라우트 템플릿, 상태 코드, 소요 시간을 담은 서버 스팬 1개입니다. |
| 예외 이슈 | 제목 `<METHOD> <route template> failed`, 처리되지 않은 예외 유형, 고정된 `Unhandled exception` 요약, `flask.middleware` 메커니즘, 처리 여부, 정제된 프레임 최대 32개입니다. |
| 애플리케이션 로그 | `LogBrewLoggingHandler`로 직접 켭니다. 요청 안에서 남긴 로그는 활성 트레이스와 스팬을 이어받습니다. |
| 요청 메트릭 | `capture_request_metrics=True`로 직접 켭니다. `Duration of one completed server request.`라는 설명이 붙은 `http.server.duration` delta 히스토그램을 보냅니다. |
| 들어오는 트레이스 | 유효한 W3C `traceparent`를 새 자식 스팬으로 이어 갑니다. |

스팬 이름은 HTTP 메서드와 일치한 라우트 템플릿으로 정해집니다. 예를 들어 `/orders/100`과 `/orders/200`은 모두 `GET /orders/<int:order_id>`가 됩니다. 일치하는 라우트가 없는 요청에는 고정된 `<unmatched>` 레이블이 붙습니다. `traceparent`가 없거나 유효하지 않으면 새 트레이스가 시작됩니다.

스팬 메타데이터에는 메서드, 라우트 템플릿, 엔드포인트, 상태 코드, 소요 시간이 들어갑니다. 구체적인 경로나 쿼리 문자열은 들어가지 않습니다.

0.1.6 이상에서는 요청 스팬이 Flask 디스패치 전체를 포함합니다. before-request 훅, 오류 핸들러, teardown이 요청 트레이스를 공유합니다. 스트리밍 응답의 스팬은 본문 전송이 끝나거나 닫힐 때 끝납니다.

### 예외 수집 테스트하기

`4xx`나 `5xx` 상태는 요청 스팬에 나타납니다. 상태만으로는 예외가 발생했다고 증명할 수 없습니다. 실패를 진단하기 전에 트레이스와 이슈를 모두 읽으세요. LogBrew는 Flask 오류 핸들러를 대체하거나 응답 상태를 바꾸지 않습니다.

개발용 프로젝트에서 임시 라우트를 추가하세요. 한 번 호출하세요. 배포하기 전에 삭제하세요.

```python theme={null}
@app.get("/logbrew-test-error")
def handle_test_error() -> None:
    raise RuntimeError("local capture check")
```

이 요청은 상태 `500`을 반환합니다. 새 이슈와 요청 메트릭을 찾으세요.

```bash theme={null}
logbrew read issues --project "<project_id>" --since 1h --json
logbrew explain issue "<issue_id>" --json

logbrew explain metric http.server.duration \
  --project "<project_id>" \
  --service my-flask-app \
  --environment development \
  --release my-flask-app@1.0.0 \
  --since 1h \
  --json
```

이슈의 서비스, 환경, 릴리스, 예외 유형, 요청 트레이스를 확인하세요. 이슈에 `local capture check` 텍스트가 들어 있으면 안 됩니다.

### 아웃바운드 작업을 요청에 연결하기

Python SDK에는 `requests`, `urllib`, `httpx`, `aiohttp`, 데이터베이스, 캐시, 큐 작업을 위해 앱이 직접 호출하는 헬퍼가 있습니다. 각 헬퍼는 활성 요청 트레이스의 자식 스팬을 만듭니다. SDK는 이 라이브러리를 전역으로 패치하지 않습니다. 예제는 [`logbrew-flask` 패키지](https://pypi.org/project/logbrew-flask/)에 있습니다.

각 헬퍼에 바뀌지 않는 작업 이름과 라우트 템플릿 또는 구문 템플릿을 지정하세요. 이 이름에 레코드 ID, 고객 값, SQL 바인드 값, 메시지 페이로드를 넣지 마세요. 자식 스팬은 요청에 중요한 의존성에만 추가하세요. 작은 호출마다 스팬을 만들면 트레이스를 읽기 어려워집니다. 아웃바운드 HTTP 헬퍼는 W3C `traceparent` 헤더 1개를 추가합니다. 전체 URL, 쿼리 문자열, 본문, 쿠키, 임의 헤더는 수집하지 않습니다. 데이터베이스, 캐시, 큐 헬퍼는 바인드 값, 결과, 캐시 값, 큐 페이로드를 수집하지 않습니다.

## 개인정보 보호와 프로세스 수명 주기

Flask 자동 수집은 구체적인 요청 경로, 쿼리 문자열, 요청 본문과 응답 본문, 쿠키, 임의 헤더, 원시 `traceparent` 값, baggage, tracestate를 제외합니다.

자동 예외 수집은 `str(error)`를 절대 읽거나 보내지 않습니다. 고정된 요약 `Unhandled exception`을 사용하고, 예외 체인의 각 메시지를 가려짐으로 표시합니다. 프레임에는 파일 이름(경로 제외)과 범위가 제한된 코드 식별 정보가 들어갑니다. 원시 트레이스백 텍스트, 소스 코드, 지역 변수, 절대 경로는 들어가지 않습니다.

앱과 LogBrew 클라이언트는 각 워커 프로세스 안에서 만드세요. Gunicorn의 기본 동작인 워커별 앱 로딩은 문제없이 작동합니다. 부모 프로세스에서 만든 클라이언트와 함께 Gunicorn `--preload`를 쓰지 마세요. uWSGI에서는 `--lazy-apps` 같은 워커별 앱 로딩을 사용하세요. Python 클라이언트는 프로세스 fork 이후의 재사용을 거부합니다.

프로세스 관리자의 워커별 종료 훅에서 `logbrew.client.shutdown()`을 호출하세요. 이 함수는 큐에 남은 이벤트를 보내고 클라이언트를 닫습니다. 요청마다 호출하지 마세요. 종료 후 클라이언트는 그 워커의 새 이벤트를 거부합니다.

워커가 큐에 남은 마지막 이벤트를 보내기 전에 응답이 끝날 수 있습니다. 배포나 축소 중에는 정상 종료를 거쳐야 이 이벤트를 마지막으로 한 번 더 전송할 수 있습니다.

## 설정 문제 해결하기

| 증상 | 확인할 점 | 다음 조치 |
| - | - | - |
| `LOGBREW_SERVER_API_KEY is required` | 초기화 함수가 비어 있지 않은 프로젝트 키를 받지 못했습니다. | Flask 설정, 명시적 인수, 환경 변수 중 하나로 프로젝트 범위의 서버 키를 설정하세요. |
| 트레이스가 나타나지 않음 | 프로젝트, 서비스, 환경, 릴리스, 시간 필터가 맞지 않습니다. | `logbrew doctor --project "<project_id>" --json`을 실행한 다음, 범위를 정한 트레이스 조회를 다시 실행하세요. |
| 트레이스는 나타나지만 로그는 나타나지 않음 | 요청 계측은 Python 로깅 핸들러를 자동으로 연결하지 않습니다. | 앱이 소유한 로거에 `LogBrewLoggingHandler`를 1개 추가하세요. |
| 요청은 작동하지만 종료 중 마지막 이벤트가 빠짐 | 백그라운드 워커가 남은 이벤트를 다 보내기 전에 프로세스가 종료됩니다. | 워커 종료 훅에서 `logbrew.client.shutdown()`을 호출하세요. |
| 워커가 `process_ownership_error`를 보고함 | 워커 프로세스가 fork되기 전에 클라이언트가 만들어졌습니다. | 워커마다 앱을 로드하고, 그 프로세스 안에서 새 클라이언트를 만드세요. |

계정 접근, 키, 조회 확인 오류는 [문제 해결](/ko/guides/troubleshooting)을 참고하세요. 다른 런타임은 [SDK 연동](/ko/guides/sdk-ingestion)을 참고하세요.


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