미들웨어
Sophonz Python SDK로 Flask, FastAPI, Django의 요청 스팬 보강하기 — baggage와 session.id, 클라이언트 IP, user-agent, 사용자 지정 속성, 에러 핸들러.
SDK는 Flask, FastAPI(및 모든 Starlette 앱), Django용 요청 헬퍼를 제공합니다 — sophonzExpressMiddleware와 setupExpressErrorHandler의 Python 버전입니다. 서버 스팬은 각 프레임워크의 OpenTelemetry 계측이 만들고, 헬퍼는 요청의 모든 스팬에 baggage, 클라이언트 IP, user agent, 사용자 속성을 복사하며 라우트에서 빠져나간 예외를 기록합니다.
각 헬퍼는 별도 모듈에 있으며, 그 모듈을 사용할 때만 프레임워크를 import합니다. 프레임워크 계측이 설치되도록 해당 extra(sophonz-opentelemetry[flask], [fastapi], [django])를 설치하세요.
Flask
from flask import Flask
from sophonz.opentelemetry import init
from sophonz.opentelemetry.flask import setup_flask_error_handler, sophonz_flask_middleware
init(service="checkout-api")
app = Flask(__name__)
sophonz_flask_middleware(app)
setup_flask_error_handler(app)
@app.get("/")
def index():
return "ok"sophonz_flask_middleware는 before_request 훅을 등록합니다. 앱이 아직 계측되지 않았다면 FlaskInstrumentor로 계측도 하므로, init()이 앱 생성 전후 어느 쪽에 실행되어도 동작합니다(건너뛰려면 instrument=False).
함수로 요청별 속성 지정 — Flask request를 인자로 받습니다.
sophonz_flask_middleware(
app,
attributes=lambda request: {"enduser.id": request.headers.get("x-user-id", "")},
)FastAPI
from fastapi import FastAPI
from sophonz.opentelemetry import init
from sophonz.opentelemetry.fastapi import setup_fastapi_error_handler, sophonz_fastapi_middleware
init(service="checkout-api")
app = FastAPI()
sophonz_fastapi_middleware(app)
setup_fastapi_error_handler(app)
@app.get("/")
async def index():
return {"status": "ok"}sophonz_fastapi_middleware는 순수 ASGI 미들웨어인 SophonzASGIMiddleware를 추가하고(스트리밍 응답과 백그라운드 작업에 영향 없음), 필요하면 FastAPIInstrumentor로 앱을 계측합니다. attributes 함수는 ASGI scope를 받습니다. 이 미들웨어는 애플리케이션의 lifespan 종료가 끝날 때 텔레메트리도 flush합니다. 정상 종료를 참고하세요.
두 헬퍼는 미들웨어를 가장 바깥에 두는 add_middleware 대신 가장 안쪽에 덧붙입니다. 그래서 CORSMiddleware 등 다른 미들웨어와의 호출 순서와 상관없이 보강과 예외 기록이 OpenTelemetry 서버 스팬 안에서 실행됩니다. 앱이 요청을 받기 시작하기 전에 호출하세요.
일반 Starlette 앱에서는 같은 헬퍼를 instrument=False로 쓰고, 서버 스팬은 Starlette 계측(opentelemetry-instrumentation-starlette)으로 만드세요.
from starlette.applications import Starlette
from sophonz.opentelemetry.fastapi import setup_fastapi_error_handler, sophonz_fastapi_middleware
app = Starlette(routes=routes)
sophonz_fastapi_middleware(app, instrument=False)
setup_fastapi_error_handler(app)Django
MIDDLEWARE에 SophonzMiddleware를 추가하세요. WSGI와 ASGI 모두에서 동작합니다. 서버 스팬을 만드는 Django 계측의 미들웨어가 목록 맨 위에 삽입되므로, SophonzMiddleware는 그 아래 어디에 두어도 됩니다. 마지막에 두기를 권장합니다. 그러면 REMOTE_ADDR을 바꾸는 미들웨어가 실행된 뒤에 값을 읽고, Django가 process_exception을 아래에서 위로 호출하므로 다른 미들웨어가 예외를 응답으로 바꾸기 전에 뷰의 예외를 봅니다.
# settings.py
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
# ...
"sophonz.opentelemetry.django.SophonzMiddleware",
]
# 선택
SOPHONZ_MIDDLEWARE = {
"capture_client_ip": True,
"capture_user_agent": True,
"capture_baggage": True,
# dict, 또는 HttpRequest를 받는 함수의 점 표기 경로
"attributes": "shop.telemetry.request_attributes",
}SophonzMiddleware.process_exception은 뷰에서 발생한 예외를 기록합니다. Django 계측은 init()이 로드하며(먼저 DJANGO_SETTINGS_MODULE을 설정하세요), 필요하면 sophonz.opentelemetry.django.instrument_django()로 직접 계측할 수 있습니다. SOPHONZ_MIDDLEWARE 설정은 Django가 미들웨어를 만들 때 한 번 읽습니다.
NOTE — 프레임워크를 로드하기 전에 SDK를 초기화하세요
계측이 프레임워크와 데이터베이스 드라이버를 패치할 수 있도록, 이들을 import하기 전에 init()을 호출하세요. Django는 DJANGO_SETTINGS_MODULE을 설정한 뒤 manage.py, wsgi.py, asgi.py에서 호출합니다. 예제를 참고하세요.
옵션
sophonz_flask_middleware, sophonz_fastapi_middleware / SophonzASGIMiddleware, Django의 SOPHONZ_MIDDLEWARE는 같은 옵션을 받습니다.
| 옵션 | 타입 | 기본값 | 설명 |
|---|---|---|---|
capture_baggage | bool | True | 모든 baggage 항목을 baggage. 접두사를 붙여 요청의 스팬에 복사합니다. |
capture_client_ip | bool | True | 클라이언트 IP를 http.client.ip로 캡처합니다(피어 주소. X-Forwarded-For를 신뢰하려면 프레임워크의 프록시 설정을 사용하세요). |
capture_user_agent | bool | True | user-agent 헤더를 http.user_agent로 캡처합니다. |
attributes | dict | Callable[[request], dict] | — | 추가 속성 — 정적 dict 또는 프레임워크 요청(Flask request, ASGI scope, Django HttpRequest)을 받는 함수. |
instrument | bool | True | Flask와 FastAPI 전용: 앱이 계측되지 않았다면 계측합니다. |
속성은 서버 스팬에 적용되고, set_trace_attributes()를 통해 요청을 처리하는 동안 시작되는 모든 스팬(데이터베이스와 HTTP 클라이언트 스팬 포함)에도 적용됩니다. 스팬에 이미 있는 속성은 덮어쓰지 않습니다.
NOTE — 텔레메트리는 요청을 깨뜨리지 않습니다
보강 작업은 try/except 안에서 실행됩니다. attributes 함수가 예외를 던지는 등 어떤 이유로든 실패해도 요청은 정상적으로 계속됩니다. 이때 헬퍼는 그 요청에 속성을 하나도 붙이지 않으며, baggage는 SDK 자체가 계속 복사합니다.
Baggage와 session.id
미들웨어뿐 아니라 SDK 자체가 모든 W3C baggage 항목을 모든 스팬에 baggage.<key>로 복사합니다. Sophonz 브라우저 SDK가 보내는 session.id 항목은 session.id로도 복사되므로, 브라우저 세션과 그 세션이 일으킨 서버·데이터베이스 스팬을 하나의 속성으로 조회할 수 있습니다. 다른 키는 promote_baggage_keys로 승격할 수 있습니다.
init(service="checkout-api", promote_baggage_keys=["session.id", "tenant.id"])CAUTION — Baggage는 다음 서비스로 전달됩니다
Baggage는 앱이 호출하는 모든 다운스트림 서비스로 전파됩니다. 민감한 데이터를 넣지 마세요.
에러 핸들러
앱을 만든 뒤 에러 핸들러를 등록하세요. 라우트에서 빠져나간 예외(HTTP 500이 되는 예외)를 요청 스팬의 exception 이벤트로 기록하고 스팬 상태를 ERROR로 설정합니다. 처리된 에러(FastAPI의 HTTPException, Flask의 abort(404))는 기록하지 않으며, Django의 SophonzMiddleware는 Http404, PermissionDenied, BadRequest, SuspiciousOperation을 건너뜁니다.
프레임워크 계측은 이미 서버 스팬에서 빠져나간 예외를 기록합니다. 핸들러는 이를 감지해 exception.mechanism(flask, asgi, django)만 추가하고 같은 예외를 두 번 기록하지 않으며, 계측이 없을 때는 직접 기록합니다.
NOTE — Django와 Http404
Django 계측은 SophonzMiddleware와 별개로, 뷰가 던진 예외를 Http404까지 모두 서버 스팬에 기록하고 ERROR로 표시합니다. 이것이 문제라면 Http404를 던지는 대신 404 응답을 반환하세요.
error_log_capture(기본값 켜짐)가 켜져 있으면 실패한 스팬은 트레이스와 연결된 에러 로그 레코드도 하나 만듭니다.
수동 기록
처리한 에러는 어디서든 record_exception으로 기록하세요.
from sophonz.opentelemetry import record_exception
try:
sync_inventory()
except Exception as exc:
record_exception(exc, attributes={"job.name": "nightly-sync"})현재 스팬에, 또는 span=을 주면 그 스팬에 기록합니다. 컨텍스트에 스팬이 없으면 예외 타입 이름으로 짧은 스팬을 만듭니다.