- Published on
Langfuse SDK 계측 — 무엇이 자동으로 잡히고 무엇을 손으로 넣나
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며 — 계측은 경계를 긋는 일이다
- 시작점 — 키 세 개와 싱글턴 클라이언트
- 방식 하나 — 데코레이터
- 방식 둘 — 컨텍스트 매니저
- 방식 셋 — 컨텍스트를 바꾸지 않는 수동 생성
- 속성을 아래로 흘려보내기 — propagate_attributes
- 프레임워크 통합 — 자동으로 잡히는 범위
- OpenTelemetry와의 관계 — v4가 바꾼 것
- 내보내지 않을 것 걸러 내기
- 짧게 사는 프로세스에서 데이터가 사라지는 이유
- 마치며 — 계측 순서
- 직접 해보기
- 시리즈
- 참고 자료
들어가며 — 계측은 경계를 긋는 일이다
1편에서 데이터 모델을 봤습니다. trace 하나가 observation 트리를 담고, trace 수준 속성이 아래로 전파된다는 구조였습니다. 이제 그 트리를 실제로 만드는 쪽입니다.
계측에서 실수가 나는 지점은 늘 같습니다. 자동으로 잡히는 범위를 과대평가하거나 과소평가하는 것입니다. 과대평가하면 정작 필요한 중간 단계가 비어 있고, 과소평가하면 이미 잡히는 것을 손으로 다시 넣어 트리가 두 겹이 됩니다.
구성과 설정 이름은 2026-08-15에 공식 문서에서 확인했습니다. Langfuse는 버전에 따라 아키텍처가 달라지므로 사용 중인 버전의 문서를 다시 확인하세요. 이 글은 서버 v4와 파이썬 SDK v4를 기준으로 합니다. 버전 정책 문서는 서버 v4에서 파이썬 SDK v4와 JS/TS SDK v5가 정식 지원이라고 밝히고 있습니다.
시작점 — 키 세 개와 싱글턴 클라이언트
시작하기 문서가 모든 통합에 공통으로 요구하는 환경 변수는 세 개입니다.
pip install langfuse
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_BASE_URL="https://cloud.langfuse.com"
LANGFUSE_BASE_URL의 기본값은 EU 리전입니다. 문서는 US, 일본, HIPAA 리전에 각각 별도 호스트가 있다고 명시합니다. 자체 호스팅이라면 여기에 자기 인스턴스 주소를 넣습니다.
클라이언트는 싱글턴입니다. 파이썬 SDK 개요는 애플리케이션 어디서든 get_client()로 접근할 수 있다고 설명합니다. 설정을 코드로 직접 넘기고 싶을 때만 Langfuse() 생성자를 씁니다.
방식 하나 — 데코레이터
가장 적은 코드로 시작하는 방법입니다. 계측 문서는 observe()가 함수의 입력, 출력, 소요 시간, 오류를 자동으로 잡는다고 설명합니다.
from langfuse import observe
@observe()
def my_data_processing_function(data, parameter):
return {"processed_data": data, "status": "ok"}
@observe(name="llm-call", as_type="generation")
async def my_async_llm_call(prompt_text):
return "LLM response"
인자는 네 가지가 문서에 나옵니다. name으로 observation 이름을 바꾸고, as_type으로 span인지 generation인지 정하고, capture_input과 capture_output으로 입출력 수집을 끕니다. 기본값은 둘 다 참입니다.
capture_input을 끄는 경우는 생각보다 자주 옵니다. 함수 인자에 원문 프롬프트나 사용자 개인정보가 들어가는 경로가 그렇습니다. 다만 이건 부분적인 대책이고, 제대로 된 마스킹은 5편에서 다룹니다.
방식 둘 — 컨텍스트 매니저
함수 단위가 아니라 블록 단위로 자르고 싶을 때 씁니다. 문서는 start_as_current_observation()을 활성 OpenTelemetry 컨텍스트를 갱신하면서 observation을 만드는 기본 방법이라고 설명합니다.
from langfuse import get_client, propagate_attributes
langfuse = get_client()
with langfuse.start_as_current_observation(
as_type="span",
name="user-request-pipeline",
input={"user_query": "Tell me a joke"},
) as root_span:
with propagate_attributes(user_id="user_123", session_id="session_abc"):
with langfuse.start_as_current_observation(
as_type="generation",
name="joke-generation",
model="gpt-4o",
) as generation:
generation.update(output="Why did the span cross the road?")
root_span.update(output={"final_joke": "..."})
중첩이 그대로 트리가 됩니다. with 블록의 들여쓰기가 1편에서 그린 부모 자식 관계와 일대일로 대응합니다. 여기서 나온 model="gpt-4o"가 중요합니다. generation으로 만들고 모델 이름을 주어야 비용 축이 생깁니다.
현재 활성 observation을 참조 없이 갱신하는 방법도 있습니다. 문서는 update_current_span()과 update_current_generation()을 이 용도로 안내합니다. 깊은 호출 스택 아래에서 값을 덧붙일 때 유용합니다.
방식 셋 — 컨텍스트를 바꾸지 않는 수동 생성
문서는 활성 컨텍스트를 바꾸지 않고 직접 제어하고 싶을 때 start_observation()을 쓰라고 안내합니다.
from langfuse import get_client
langfuse = get_client()
span = langfuse.start_observation(name="manual-span")
span.update(input="Data for side task")
child = span.start_observation(name="child-span", as_type="generation")
child.end()
span.end()
이 방식이 필요한 곳은 비동기 작업, 백그라운드 태스크, 콜백처럼 시작과 끝이 같은 스코프에 있지 않은 코드입니다. 대신 end()를 부르는 책임이 온전히 사람에게 옵니다. 예외 경로에서 end()가 빠지면 그 observation은 끝나지 않은 채 남습니다.
trace 전체의 입출력을 루트 observation과 별개로 정하고 싶을 때는 set_trace_io() 또는 set_current_trace_io()를 씁니다. 루트가 내부 파이프라인 객체를 다루더라도 trace 목록에는 사용자가 실제로 물어본 문장이 보이게 하는 용도입니다.
속성을 아래로 흘려보내기 — propagate_attributes
1편에서 trace 수준 속성이 아래로 전파된다고 했습니다. 그 전파를 코드에서 켜는 지점이 propagate_attributes()입니다.
from langfuse import propagate_attributes
with propagate_attributes(
user_id="user_123",
session_id="session_abc",
metadata={"experiment": "variant_a"},
version="1.0",
environment="staging",
trace_name="user-workflow",
as_baggage=True,
):
run_pipeline()
문서에 나오는 인자는 위와 같습니다. as_baggage=True는 HTTP 헤더를 통한 서비스 간 전파를 켭니다. 게이트웨이와 워커가 분리된 구조에서 하나의 흐름을 이어 붙이려면 이 옵션이 필요합니다.
여기서 실무적으로 자주 틀리는 지점이 하나 있습니다. 이 값들은 블록 안에서 만들어지는 observation에 적용됩니다. 블록 밖에서 이미 만들어진 observation에는 붙지 않습니다. 그래서 propagate_attributes는 요청을 받은 가장 바깥쪽에서 열어야 합니다.
프레임워크 통합 — 자동으로 잡히는 범위
시작하기 문서가 나열하는 통합 경로는 OpenAI SDK, Vercel AI SDK, LangChain, 네이티브 SDK, OpenTelemetry, 그리고 LlamaIndex와 CrewAI, LiteLLM, AutoGen, Google ADK 같은 프레임워크들입니다. 설치 명령은 통합마다 다릅니다.
# 파이썬: OpenAI SDK 래퍼
pip install langfuse
# 파이썬: LangChain
pip install langfuse langchain-openai
# JS/TS: OpenAI 래퍼
npm install @langfuse/openai
# JS/TS: LangChain
npm install @langfuse/core @langfuse/langchain
# JS/TS: 네이티브 SDK
npm install @langfuse/tracing @langfuse/otel @opentelemetry/sdk-node
OpenAI 통합은 임포트를 바꾸는 것으로 끝납니다.
from langfuse.openai import openai
completion = openai.chat.completions.create(
name="test-chat",
model="gpt-4o",
messages=[{"role": "user", "content": "1 + 1 = "}],
)
import OpenAI from "openai";
import { observeOpenAI } from "@langfuse/openai";
const openai = observeOpenAI(new OpenAI());
이런 통합이 자동으로 잡아 주는 것은 모델 호출 자체입니다. 모델 이름, 프롬프트, 응답, 토큰 사용량, 지연입니다. 잡아 주지 못하는 것은 그 호출들 사이에 있는 여러분의 코드입니다. 어떤 문서를 몇 개 가져왔는지, 리랭킹에서 순서가 어떻게 바뀌었는지, 어떤 분기를 탔는지는 손으로 넣어야 합니다.
경계를 정하는 기준은 단순합니다. 장애 회의에서 "그래서 어느 단계가 문제였나"라는 질문에 답해야 하는 단계는 전부 observation이어야 합니다.
OpenTelemetry와의 관계 — v4가 바꾼 것
파이썬 SDK 개요는 Langfuse SDK가 OpenTelemetry 위에 만들어졌다고 명시합니다. 그리고 버전 정책 문서는 v3에서 v4로 넘어오며 기존 배치 수집 방식을 OpenTelemetry로 교체했다고 밝힙니다. 이건 단순한 내부 구현 변경이 아니라 수집 경로 자체가 달라진 것이므로, v3 시절 자료를 보고 있다면 여기서 어긋납니다.
OpenTelemetry 통합 문서가 밝히는 수집 경로는 이렇습니다.
- 엔드포인트 경로는
/api/public/otel입니다. 클라우드 리전마다 호스트가 다릅니다. - 프로토콜은 HTTP 위의 OTLP이며
HTTP/JSON과HTTP/protobuf를 지원합니다. 문서는 gRPC를 아직 지원하지 않는다고 명시합니다. - 인증 헤더 두 개가 필요합니다.
# 예시: OTLP 익스포터에 넘기는 헤더
OTEL_EXPORTER_OTLP_ENDPOINT="https://cloud.langfuse.com/api/public/otel"
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic ${AUTH_STRING},x-langfuse-ingestion-version=4"
AUTH_STRING은 공개 키와 비밀 키를 base64로 인코딩한 값입니다. 두 번째 헤더인 x-langfuse-ingestion-version은 문서가 v4의 실시간 수집을 켜는 값으로 설명합니다.
속성 매핑 규칙도 문서에 있습니다. model 속성을 가진 스팬은 자동으로 generation 타입 observation이 됩니다. GenAI 시맨틱 규약 쪽에서는 gen_ai.system, gen_ai.request.model, gen_ai.response.model, gen_ai.prompt, gen_ai.completion, gen_ai.usage.*, gen_ai.usage.cost가 매핑됩니다. Langfuse 자체 네임스페이스로는 langfuse.observation.type, langfuse.observation.model.name, langfuse.observation.usage_details, langfuse.trace.metadata.*가 있습니다.
이 매핑을 알면 선택지가 하나 생깁니다. 이미 OpenTelemetry로 계측된 서비스라면 Langfuse SDK를 넣지 않고 익스포터 대상만 추가해도 트레이스가 들어옵니다. 다만 그 경우 스팬이 올바른 속성을 갖도록 직접 챙겨야 합니다.
내보내지 않을 것 걸러 내기
모든 스팬을 Langfuse로 보낼 필요는 없습니다. 이미 OpenTelemetry를 쓰는 애플리케이션이라면 HTTP 서버 스팬이나 데이터베이스 스팬까지 함께 흘러 들어옵니다. 고급 사용 문서는 should_export_span으로 내보낼 스팬을 고르라고 안내합니다.
from langfuse import Langfuse
langfuse = Langfuse(should_export_span=lambda span: True)
문서는 함께 조합할 수 있는 내장 판정 함수로 is_default_export_span, is_langfuse_span, is_genai_span을 소개합니다. 그리고 예전에 쓰던 blocked_instrumentation_scopes 인자는 폐기 예정이며, 거부 규칙은 should_export_span으로 표현하라고 명시합니다.
짧게 사는 프로세스에서 데이터가 사라지는 이유
1편에서 본 대로 Langfuse는 데이터를 로컬에서 배치로 모아 백그라운드로 보냅니다. 요청 경로를 막지 않는 대신, 프로세스가 배치를 비우기 전에 끝나면 그 데이터는 사라집니다.
배치 작업, 서버리스 함수, CLI 스크립트가 전부 여기에 해당합니다. 문서는 flush()를 버퍼에 있는 모든 observation과 score, 미디어 메타데이터를 지금 보내는 동작으로, shutdown()을 플러시에 더해 백그라운드 스레드가 정리될 때까지 기다리는 동작으로 구분합니다.
from langfuse import get_client
langfuse = get_client()
try:
run_batch_job()
finally:
langfuse.flush() # 지금 즉시 전송
langfuse.shutdown() # 백그라운드 스레드 종료까지 대기
장기 실행 서버에서는 신경 쓸 일이 없습니다. 문제가 되는 것은 언제나 짧게 사는 프로세스입니다.
마치며 — 계측 순서
정리하면 순서는 이렇습니다. 먼저 프레임워크 통합으로 모델 호출을 자동으로 잡습니다. 그다음 가장 바깥에서 propagate_attributes를 열어 사용자와 세션, 환경을 붙입니다. 그리고 장애 회의에서 지목될 만한 중간 단계에만 수동 span을 넣습니다. 마지막으로 짧게 사는 실행 경로에 flush()를 답니다.
이 순서를 지키면 트리가 두 겹이 되지 않고, 나중에 답해야 할 질문에 필요한 축이 남습니다. 다음 글에서는 이렇게 만들어진 데이터가 실제로 어디에 어떤 형태로 저장되는지, Langfuse가 왜 ClickHouse를 쓰는지를 봅니다.
직접 해보기
- HTTP 요청 빌더 (cURL 생성기) — OTLP 엔드포인트에 헤더를 붙여 요청을 만들어 보면 인증 형태가 눈에 들어옵니다.
- LLM API 비용 계산기 — generation에 남는 토큰 사용량이 실제 금액으로 얼마인지 먼저 감을 잡아 두세요.
시리즈
참고 자료
- Langfuse 시작하기: https://langfuse.com/docs/observability/get-started
- Langfuse 파이썬 SDK 개요: https://langfuse.com/docs/observability/sdk/python/overview
- Langfuse 파이썬 SDK 계측: https://langfuse.com/docs/observability/sdk/python/instrumentation
- Langfuse 파이썬 SDK 고급 사용: https://langfuse.com/docs/observability/sdk/python/advanced-usage
- Langfuse OpenTelemetry 통합: https://langfuse.com/integrations/native/opentelemetry