콘텐츠로 이동

OpenTelemetry Java SDK


작성자: 刘锐

OpenTelemetry SDK를 도입하면 핵심 비즈니스 로직을 관측할 수 있습니다. 예를 들어 핵심 비즈니스에 스팬을 설정하여 실제 동작을 통계, 추적, 분석하거나 비즈니스 속성 메트릭을 설정하는 등의 작업이 가능합니다. 이 방법은 어느 정도 침투성을 가집니다.

시작 명령어

java -javaagent:../opentelemetry-javaagent/opentelemetry-javaagent.jar \
-Dotel.traces.exporter=otlp \
-Dotel.exporter.otlp.endpoint=http://192.168.91.11:4317 \
-Dotel.resource.attributes=service.name=demo,version=dev \
-Dotel.metrics.exporter=otlp \
-jar springboot-opentelemetry-otlp-server.jar --client=true
특별 참고 사항

시작 매개변수 exporter에 따라 SDK 방식에서도 해당하는 exporter를 가져와야 하며, 그렇지 않으면 시작에 실패합니다. 예를 들어 시작 매개변수에 otlplogging 두 개의 exporter를 사용한 경우, pom에 해당하는 두 개의 exporter 의존성을 추가해야 합니다.

SDK 관련 의존성을 사용하지 않는 경우, 해당 조정이 필요 없습니다.

의존성 추가

    <dependencies>
        ...
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-exporter-otlp</artifactId>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-extension-annotations</artifactId>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-semconv</artifactId>
            <version>1.21.0-alpha</version>
        </dependency>
        <dependency>
            <groupId>io.opentelemetry</groupId>
            <artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
            <version>1.21.0-alpha</version>
        </dependency>
        ...
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.opentelemetry</groupId>
                <artifactId>opentelemetry-bom</artifactId>
                <version>1.21.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

SDK 생성

권장하지 않음

    @Bean
    public OpenTelemetry openTelemetry() {
        return AutoConfiguredOpenTelemetrySdk.builder()
                .setResultAsGlobal(false)
                .build()
                .getOpenTelemetrySdk();
    }

위 방식은 AutoConfiguredOpenTelemetrySdk가 다시 로드되는 문제를 일으킬 수 있습니다. SDK는 전역 객체 GlobalOpenTelemetry를 제공하여 OpenTelemetry 객체를 가져옵니다.

아래에서는 주로 GlobalOpenTelemetry.get()을 사용하여 OpenTelemetry 객체를 가져옵니다.

트레이스

Tracer 생성

Tracer는 주로 span 객체를 생성하고 가져오는 데 사용됩니다. 참고: Tracer는 일반적으로 구성을 담당하지 않으며, 이는 TracerProvider의 책임입니다. OpenTelemetry 인터페이스는 기본 TracerProvider 구현을 제공합니다.

TracerProvider getTracerProvider();

openTelemetry()를 통해 Tracer 객체를 가져옵니다.

    @Bean
    public Tracer tracer() {
        return GlobalOpenTelemetry.getTracer(appName);
    }

Span 생성

Info

Tracer 외에는 어떤 다른 API도 Span을 생성할 수 없습니다.

Span span = tracer.spanBuilder(spanName).startSpan();

현재 Span 객체 가져오기

현재 span 객체를 가져와 현재 span에 attribute, event 등을 설정할 수 있습니다.

Span span = Span.current();

Attribute 생성

Attribute는 span의 속성으로, 현재 span의 태그입니다.

span.setAttribute(key, value);

하나의 Span은 하나 이상의 인과 관계가 있는 다른 Span에 연결될 수 있습니다. 링크는 배치 작업을 나타내는 데 사용될 수 있습니다. 하나의 Span 초기화가 여러 Span 초기화로 구성되는 경우, 각 Span은 배치에서 처리된 개별 입력 항목을 나타냅니다.

Span child = tracer.spanBuilder(spanName)
        .addLink(span(1))
        .addLink(span(2))
        .addLink(span(3))
        .startSpan();

Event 생성

Span은 0개 이상의 Span 속성을 가진 명명된 이벤트로 주석을 달 수 있습니다. 각 이벤트는 key:value 쌍이며 자동으로 해당 타임스탬프를 포함합니다.

span.addEvent(eventName);

span.addEvent(eventName,Attributes);
참고

recordException은 addEvent의 특수 변형으로, 예외 이벤트를 기록하는 데 사용됩니다.

중첩 Span 생성

setParent(parentSpan)을 사용하여 parentSpan을 설정합니다.

void parent() {
    Span parentSpan = tracer.spanBuilder("parent")
        .startSpan();
    childSpan(parentSpan);
    parentSpan.end();
}
void childSpan(Span parentSpan) {
    Span childSpan = tracer.spanBuilder("childSpan")
        .setParent(parentSpan)
        .startSpan();
    // do stuff
    childSpan.end();
}

Baggage 사용법

Baggage는 전체 트레이스에 걸쳐 전파될 수 있으며, 사용자 ID, 사용자 이름 등과 같은 전역 계측에 적합하여 비즈니스 데이터를 추적할 수 있습니다.

gateway 메서드에서 Baggage 설정

// Baggage 사용법, 여기서 set
    Baggage.current().toBuilder().put("app.username", "gateway").build().makeCurrent();
    logger.info("gateway set baggage[app.username] value: gateway");

resource 메서드에서 Baggage 가져오기

 // Baggage 사용법, 여기서 get
    String baggage = Baggage.current().getEntryValue("app.username");
    logger.info("resource get baggage[app.username] value: {}", baggage);

알려진 traceId와 spanId를 사용하여 새 Span 구성

Tracer의 span 생성은 setParent(context) 메서드를 제공하여 사용자 정의 span에 대한 부모 span을 구성할 수 있도록 합니다.

tracer.spanBuilder(spanName).setParent(context)

여기서 setParentContext 매개변수를 전달해야 하므로 컨텍스트를 구성해야 합니다.

OpenTelemetry SDK는 SpanContext를 생성하기 위한 create 메서드 하나만 제공하며, 여기서 traceId와 spanId를 사용자 정의할 수 있습니다.

SpanContext create(String traceIdHex, String spanIdHex, TraceFlags traceFlags, TraceState traceState)

SpanContext는 Span의 일부를 나타내며, 직렬화 가능해야 하고 분산 컨텍스트를 따라 전파되어야 합니다. SpanContext는 불변입니다.

OpenTelemetry SpanContextW3C TraceContext 사양을 준수합니다. 여기에는 TraceId와 SpanId라는 두 개의 식별자, 일반적인 TraceFlags 세트, 시스템별 TraceState가 포함됩니다.

  1. TraceId 유효한 TraceId는 16바이트 배열이며, 최소한 하나의 0이 아닌 바이트가 있어야 합니다.

  2. SpanId 유효한 SpanId는 8바이트 배열이며, 최소한 하나의 0이 아닌 바이트가 있어야 합니다.

  3. TraceFlags 해당 트레이스의 세부 정보를 포함합니다. TraceFlags는 모든 트레이스에 영향을 미칩니다. 현재 버전에서 정의된 Flags는 sampled뿐입니다.

  4. TraceState KV 쌍 배열을 통해 식별되는 특정 트레이스 식별 데이터를 전달합니다. TraceState는 여러 추적 시스템이 동일한 트레이스에 참여할 수 있도록 합니다. 전체 정의는 W3C Trace Context specification을 참조하세요.

이 API는 SpanContext를 생성하는 메서드를 구현해야 합니다. 이 메서드들은 SpanContext를 생성하는 유일한 메서드여야 합니다. 이 기능은 API에서 완전히 구현되어야 하며 재정의될 수 없어야 합니다.

그러나 SpanContext는 Context가 아니므로 한 번의 변환이 더 필요합니다.

private Context withSpanContext(SpanContext spanContext, Context context) {
    return context.with(Span.wrap(spanContext));
}

전체 코드는 다음과 같습니다.

    /***
     * @Description 알려진 traceId와 spanId를 사용하여 새 span을 구성합니다.
     * @Param [spanName, traceId, spanId]
     * @return java.lang.String
     **/
    @GetMapping("/customSpanByTraceIdAndSpanId")
    @ResponseBody
    public String customSpanByTraceIdAndSpanId(String spanName,String traceId,String spanId){
        assert StringUtils.isEmpty(spanName):"spanName은 비워둘 수 없습니다.";
        assert StringUtils.isEmpty(traceId):"traceId는 비워둘 수 없습니다.";
        assert StringUtils.isEmpty(spanId):"spanId는 비워둘 수 없습니다.";
        Context context =
                withSpanContext(
                        SpanContext.create(
                                traceId, spanId, TraceFlags.getSampled(), TraceState.getDefault()),
                        Context.current());
        Span span = tracer.spanBuilder(spanName)
                .setParent(context)
                .startSpan();
        span.setAttribute("attribute.a2", "some value");
        span.setAttribute("func","attr");
        span.setAttribute("app","otel3");
        span.end();
        return buildTraceUrl(span.getSpanContext().getTraceId());
    }

    private Context withSpanContext(SpanContext spanContext, Context context) {
        return context.with(Span.wrap(spanContext));
    }
참고

현재 테스트 방식에 따라 요청 자체가 새로운 trace 정보를 생성합니다. 새로 구성된 span은 전달된 매개변수를 기반으로 구성됩니다.

다음 링크를 통해 결과를 확인할 수 있습니다.

http://localhost:8080/customSpanByTraceIdAndSpanId?spanName=tSpan&traceId=24baeeddfbb35fceaf4c18e7cae58fe1&spanId=ff1955b4f0eacc4f

메트릭

OpenTelemetry는 메트릭 관련 작업을 위한 API도 제공합니다.

Span은 애플리케이션에 대한 상세 정보를 제공하지만 생성된 데이터는 시스템의 부하에 비례합니다. 반면 메트릭은 개별 측정값을 집계하여 시스템 부하의 함수로 일정한 데이터를 생성합니다. 집계는 저수준 문제를 진단하는 데 필요한 세부 정보는 부족하지만, 추세 식별을 돕고 애플리케이션 런타임 텔레메트리를 제공하여 Span을 보완합니다.

메트릭 API는 다양한 계측기를 정의합니다. 계측기는 측정값을 기록하며, 이 측정값은 메트릭 SDK에 의해 집계되고 최종적으로 프로세스 외부로 내보내집니다. 계측기에는 동기식과 비동기식이 있습니다. 동기식 계측기는 측정값을 기록합니다. 비동기식 계측기는 콜백을 등록하며, 수집 시마다 한 번 호출되어 해당 시점의 측정값을 기록합니다. 다음 계측기를 사용할 수 있습니다.

  1. LongCounter/DoubleCounter: 양수 값만 기록하며, 동기식 및 비동기식 옵션이 있습니다. 네트워크를 통해 전송된 바이트 수와 같은 것을 계산하는 데 유용합니다. 기본적으로 카운터 측정값은 항상 증가하는 단조 합계로 집계됩니다.

  2. LongUpDownCounter/DoubleUpDownCounter: 양수 및 음수 값을 기록하며, 동기식 및 비동기식 옵션이 있습니다. 큐 크기와 같이 증가 및 감소하는 것을 계산하는 데 유용합니다. 기본적으로 업다운 카운터 측정값은 비단조 합계로 집계됩니다.

  3. LongGauge/DoubleGauge: 비동기 콜백으로 순간 값을 측정합니다. CPU 사용률 백분율과 같이 속성 간에 병합할 수 없는 값을 기록하는 데 유용합니다. 기본적으로 게이지 측정값은 게이지로 집계됩니다.

  4. LongHistogram/DoubleHistogram: 히스토그램 분포 분석에 가장 유용한 측정값을 기록합니다. 비동기 옵션은 사용할 수 없습니다. HTTP 서버가 요청을 처리하는 데 소요된 시간 등을 기록하는 데 유용합니다. 기본적으로 히스토그램 측정값은 명시적 버킷 히스토그램으로 집계됩니다.

Meter 객체 가져오기

API는 Meter 인터페이스를 정의합니다. 이 인터페이스는 계측기 생성자 집합과 원자적 방식으로 측정값을 일괄 가져오는 도구로 구성됩니다. Meter는 MeterProvider의 getMeter(name) 메서드를 통해 새 인스턴스를 생성할 수 있습니다. MeterProvider는 일반적으로 싱글톤으로 사용됩니다. 해당 구현은 전역적으로 유일한 MeterProvider 구현이어야 합니다. Meter 객체를 통해 다양한 유형의 메트릭을 구성할 수 있습니다.

    @Bean
    public Meter meter() {
        return GlobalOpenTelemetry.getMeter(appName);
    }

여기서는 MeterProvider의 세부 사항을 건너뜁니다. 주된 이유는 OpenTelemetry 인터페이스가 MeterProvider의 기본 noop 구현을 제공하기 때문입니다.

    default MeterProvider getMeterProvider() {
        return MeterProvider.noop();
    }

gauge 유형의 메트릭 구성

    meter.gaugeBuilder("connections")
        .setDescription("현재 Socket.io 연결 수")
        .setUnit("1")
        .buildWithCallback(
                result -> {
                    System.out.println("metrics");
                    for (int i = 1; i < 4; i++) {
                        result.record(
                                i,
                                Attributes.of(
                                        AttributeKey.stringKey("id"),
                                        "a" + i));
                    }
                });

buildWithCallback은 콜백 함수로, 비동기 API를 지원하고 필요에 따라 메트릭 데이터를 수집하는 추가 도구입니다. 일정 간격으로 데이터를 수집하며, 기본값은 1분에 한 번입니다.

관련 문서

OpenTelemetry 트레이스 데이터 수집

springboot-opentelemetry-otlp-server

opentelemetry api

opentelemetry java

opentelemetry 파라미터 구성

문서 평가

이 페이지가 도움이 되었나요?