Tracing 서비스
Tracing 서비스 (alpha)
Tracing 서비스는 Backstage 백엔드 플러그인에서 애플리케이션 수준 OpenTelemetry 트레이스 스팬을 내보내기 위한 통합 인터페이스를 제공해요. 각 플러그인의 스팬을 OpenTelemetry Instrumentation Scope로 자동 범위를 지정하고, 스팬 수명주기(자동 종료, 예외 기록, 오류 상태)를 감싸서 플러그인이 그 보일러플레이트를 작성하지 않아도 되게 하며, HTTP 요청이나 BackstageCredentials가 제공되면 인증된 주체(principal)의 신원으로 스팬을 투명하게 보강해요.
출처: 문서
본문
Tracing 서비스는 Backstage 백엔드 플러그인에서 애플리케이션 수준 OpenTelemetry 트레이스 스팬을 내보내기 위한 통합 인터페이스를 제공해요. 각 플러그인의 스팬을 OpenTelemetry Instrumentation Scope로 자동 범위를 지정하고, 스팬 수명주기(자동 종료, 예외 기록, 오류 상태)를 감싸서 플러그인이 그 보일러플레이트를 작성하지 않아도 되게 하며, HTTP 요청이나 BackstageCredentials가 제공되면 인증된 주체(principal)의 신원으로 스팬을 투명하게 보강해요.
참고
이 서비스는 현재 alpha 단계이며 @backstage/backend-plugin-api/alpha에서 가져와요. API는 향후 릴리스에서 변경될 수 있어요.
OpenTelemetry 설정하기
Tracing 서비스는 OpenTelemetry SDK를 직접 구성하지 않아요. exporter, sampler, 리소스 속성 등을 포함해 OpenTelemetry Node SDK를 Backstage 백엔드를 시작하기 전에 직접 초기화해야 해요. 자세한 내용은 튜토리얼을 참고하세요.
OpenTelemetry 자동 계측과의 관계
Tracing 서비스는 자동 계측(auto-instrumentation)을 대체하기보다 보완해요. 자동 계측은 인바운드 HTTP 요청, 아웃바운드 HTTP 호출, 데이터베이스 쿼리 같은 인프라 수준 스팬을 표준 HTTP/DB 속성까지 포함해 자동으로 캡처해요. Tracing 서비스는 플러그인이 만들 수 있는 애플리케이션 수준 스팬과, 그 인프라 작업에 붙이고 싶은 하위 스팬을 위한 것이에요.
HTTP 스팬은 자동 계측되므로 일반적으로 Tracing 서비스 스팬에 http.* 속성을 직접 설정하면 안 돼요. 부모 HTTP 스팬이 이미 그 속성을 담고 있기 때문이에요. 여러분이 만드는 스팬은 같은 트레이스 안에서 그 HTTP 스팬의 자식이 돼요.
서비스 사용하기
Tracing 서비스는 alpha API이므로 서비스 참조는 coreServices 대신 @backstage/backend-plugin-api/alpha에서 가져와요.
import { createBackendPlugin } from '@backstage/backend-plugin-api';
import { tracingServiceRef } from '@backstage/backend-plugin-api/alpha';
createBackendPlugin({
pluginId: 'todos',
register(env) {
env.registerInit({
deps: { tracing: tracingServiceRef },
async init({ tracing }) {
// ... wire up your routes/handlers, holding onto `tracing` ...
const result = await tracing.startActiveSpan(
'process-todo',
async span => {
span.setAttribute('todo.category', 'personal');
// ...do the work...
return computeResult();
},
);
},
});
},
});
startActiveSpan(name, fn, options?)는 fn을 새 활성 스팬 안에서 실행해요. 스팬은 fn이 resolve되면 자동으로 끝나고, 오류가 던져지면 예외가 기록되고, error.type이 오류의 name에서 설정되며, 스팬 상태는 ERROR로 설정돼요. 그런 것 때문에 try/catch/finally를 작성할 필요가 없어요.
이 서비스를 통해 내보내진 모든 스팬은 backstage.plugin.id(pluginMetadata.getId()와 일치)를 통해 호출 플러그인에 자동으로 귀속돼요. Tracing 백엔드는 OpenTelemetry 계측 범위를 조사하지 않고도 이 값을 사용해 특정 플러그인의 모든 활동을 필터링할 수 있어요. 스팬이 논리적으로 다른 플러그인이 소유한 작업을 나타낸다면(예: 다른 플러그인의 코드로 디스패치하는 래퍼), 콜백 안에서 span.setAttribute('backstage.plugin.id', 'other-plugin')을 호출해 재귀속할 수 있어요.
스팬 옵션(Span Options)
startActiveSpan의 세 번째 인자는 선택적 옵션 객체예요.
| Property | Type | Description | |
|---|---|---|---|
attributes |
TracingServiceAttributes |
Attributes to attach to the span at creation time. | |
kind |
TracingServiceSpanKind |
Span kind. Defaults to OpenTelemetry's internal. See Span Kinds. |
|
credentials |
BackstageCredentials |
Authenticated principal source — adds principal-derived attributes to the span. | |
request |
Request |
HTTP request to extract credentials from (used only for principal extraction, not HTTP attribution). |
스팬 종류(Span Kinds)
| Kind | Use Case | |
|---|---|---|
'internal' |
Default. Internal application work — e.g. processing pipelines, scheduled tasks. | |
'server' |
Protocol-level inbound request handlers (e.g. an MCP tools/call server). |
|
'client' |
Outbound calls. Usually auto-instrumented at the HTTP / RPC client layer instead. | |
'producer' |
Sending a message to a queue or stream. | |
'consumer' |
Receiving a message from a queue or stream. |
대부분의 Backstage 애플리케이션 수준 스팬은 internal이에요. kind를 설정하지 않으면 OpenTelemetry의 기본값이 적용돼요.
콜백 안에서 속성과 상태 설정하기
콜백은 추가 속성이나 상태를 설정할 수 있는 스팬 객체를 받아요.
await tracing.startActiveSpan('refresh-entity', async span => {
const entity = await fetchEntity(ref);
span.setAttribute('catalog.entity.kind', entity.kind);
if (entity.spec.deprecated) {
span.setStatus({ code: 'error', message: 'entity is deprecated' });
}
});
스팬 객체는 다음을 노출해요.
| Method | Description | |
|---|---|---|
setAttribute(key, value) |
Set a single attribute. Value is a primitive or array of primitives. | |
setStatus({ code, message }) |
Set the span status. code is 'ok', 'error', or 'unset'. |
컨텍스트 전파(Context Propagation)
tracing 서비스는 @opentelemetry/api의 해당 네임스페이스를 반영하는 두 개의 하위 객체를 노출해요.
tracing.context: 컨텍스트 관리를 위한 것(active,with)tracing.propagation: 컨텍스트 전파를 위한 것(extract,getBaggage,getActiveBaggage)
플러그인이 요청을 처리할 때 호출자의 컨텍스트를 실행하는 작업에 자동으로 연결해주지 않는 전송이나 프레임워크(예: 메시지 큐 컨슈머에서 디스패치된 핸들러, 또는 Express 미들웨어 체인 밖에서 사용자 코드를 다시 진입하는 MCP streamable HTTP 전송 같은 타사 전송)를 사용한다면, 인바운드 요청의 헤더에서 트레이스 부모와 baggage를 직접 추출하고 그 컨텍스트를 활성 상태로 두고 핸들러를 실행하세요.
router.post('/', async (req, res) => {
const ctx = tracing.propagation.extract(
tracing.context.active(),
req.headers,
);
await tracing.context.with(ctx, () =>
transport.handleRequest(req, res, req.body),
);
});
propagation.extract는 헤더 모양의 레코드(Record<string, string | string[] | undefined>)에서 읽으므로, 헤더의 어떤 소스든(Express의 req.headers, Node.js http.IncomingMessage, 직렬화된 헤더를 담은 페이로드 필드) 같은 방식으로 동작해요.
콜백 안에서 생성된 스팬은(startActiveSpan의 것 포함) 전파된 트레이스의 자식이 되고 전파된 baggage에 접근할 수 있어요.
propagation.extract와 context.active가 반환하는 컨텍스트는 불투명한 핸들이에요. 소비자는 그것을 API에 다시 전달하지만 내부를 들여다보지는 않아요.
Baggage 읽기
현재 활성 컨텍스트에서 baggage 항목을 읽으려면 propagation.getActiveBaggage()를 사용하세요. 이는 호출자가 baggage로 전파한 요청 ID, 테넌트 식별자, 피처 플래그 컨텍스트 같은 호출자 설정 메타데이터를 내 스팬으로 전달하는 데 유용해요. baggage는 평평한 항목 목록으로 노출되며, 관심 있는 키를 찾으려면 반복 순회하세요.
const baggage = tracing.propagation.getActiveBaggage();
for (const [key, entry] of baggage?.getAllEntries() ?? []) {
if (key === 'app.tenant.id') {
span.setAttribute('app.tenant.id', entry.value);
}
}
이미 특정 컨텍스트 핸들을 들고 있을 때(propagation.extract가 반환한 것 같은)는 propagation.getBaggage(ctx)를 사용해 컨텍스트를 먼저 활성화하지 않고 baggage를 읽을 수 있어요.
반환된 객체는 다음을 노출해요.
| Method | Description | |
|---|---|---|
getAllEntries() |
Returns all entries as [key, { value }][]. |
두 호출 모두 baggage가 없으면 undefined를 반환해요. 단일 키 조회는 의도적으로 제공되지 않아요. baggage는 호출자 메타데이터를 스팬이나 지표로 연결하기 위한 것이지, 범용 키-값 저장소가 아니에요.
주체 보강(Principal Enrichment)
credentials나 request를 제공하면 서비스가 주체에서 파생된 속성을 스팬에 추가해요.
- 주체가 있으면
backstage.principal.type이 항상 설정돼요('user','service','none'). Backstage 특유의 확장이에요. enduser.id는 백엔드 수준에서backend.tracing.capture.endUser가 활성화된 경우에만 설정돼요. 사용자 주체의 경우 사용자 엔티티 ref(예:user:default/alice)이고, 서비스 주체의 경우 서비스 subject(예:external:my-service)예요.
credentials와 request가 모두 제공되면 credentials가 우선해요. 서비스는 요청에서 추출하지 않아요. request는 자격 증명 추출에만 사용되며 다른 스팬 속성에는 영향을 주지 않아요.
async ({ credentials }) => {
await tracing.startActiveSpan(
'process-tool-call',
async span => {
// ... span automatically has backstage.principal.type, and (if enabled)
// enduser.id matching the credentials' principal ...
},
{ credentials },
);
};
인증된 최종 사용자 캡처
backend.tracing.capture.endUser 플래그는 Tracing Service 스팬이 인증된 주체의 신원을 enduser.id로 포함할지 제어해요. 기본값은 false라서 신원은 기본적으로 내보내지지 않아요.
app-config.yaml
backend:
tracing:
capture:
endUser: true # defaults to false
이는 이 서비스를 통해 스팬을 만드는 모든 플러그인이 따르는 백엔드 전역 구성이에요.
플러그인별 Tracer 구성
각 플러그인은 backstage-plugin-<pluginId>라는 tracer를 자동으로 받아요. 운영자는 코드 변경 없이 특정 플러그인의 OpenTelemetry Instrumentation Scope를 덮어쓸 수 있어요.
app-config.yaml
backend:
tracing:
plugin:
catalog:
tracer:
name: 'custom-catalog-tracer'
version: '2.0.0'
schemaUrl: 'https://example.com/schema'
| Property | Type | Default | Description | |
|---|---|---|---|---|
name |
string |
backstage-plugin-<pluginId> |
Name of the OpenTelemetry tracer | |
version |
string |
— | Version string for the tracer | |
schemaUrl |
string |
— | Schema URL for the tracer |
팁
대부분의 플러그인은 이 중 어떤 것도 필요하지 않아요. 기본값은 구성 없이도 각 플러그인의 스팬을 고유하게 귀속시키도록 설계되어 있어요.