Open13

【Java(Kotlin)編】OTel のドキュメントを読む

柾樹柾樹

Spring Boot starter

API&SDKを順番に読み進めてもわかりづらいので、Spring Boot のゼロコード計装をさきにやる。

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/

柾樹柾樹

Getting started

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/getting-started/

以下を追加することで、自動的に OTel の計装がされていた

dependencies {
	// otel まわりで必要なパッケージ
	implementation(platform(SpringBootPlugin.BOM_COORDINATES))
	implementation(platform("io.opentelemetry:opentelemetry-bom:1.44.0"))
	implementation(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom:2.10.0"))
	implementation("io.opentelemetry.instrumentation:opentelemetry-spring-boot-starter")
}

src/main/kotlin/com/example/opentelemetry_zero_code_instrumentation_practice/Controller.kt
@RestController
class Controller {
    @GetMapping("/ping")
    fun ping(): String {
        return "pong"
    }
}

柾樹柾樹

Extending instrumentations with the API

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/api/

Span

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/api/#span

span の切り方。

package com.example.opentelemetry_zero_code_instrumentation_practice

import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.api.trace.Tracer
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class Controller(openTelemetry: OpenTelemetry) {
    private val tracer: Tracer = openTelemetry.getTracer("application")

    @GetMapping("/ping")
    fun ping(): String {
        this.pingSpan()
        return "pong"
    }

    fun pingSpan() {
        val span = tracer.spanBuilder("pingSpan").startSpan()
        span.end()
        return
    }
}

Meter

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/api/#meter

Java API & SDK で実装するためスキップ

柾樹柾樹

SDK configuration

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/sdk-configuration/

General configuration

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/sdk-configuration/#general-configuration

build.gradle.kts
dependencies {
	// 中略

	// 用途
	// Propagator Distribution(tracecontext、b3)に必要
	// - https://opentelemetry.io/docs/specs/otel/context/api-propagators/#propagators-distribution
	// 変更点
	// - application.yaml(application.properties)に otel.propagators=tracecontext,b3 を追加するとき
	implementation("io.opentelemetry:opentelemetry-extension-trace-propagators")
}

src/main/resources/application.yaml
spring:
  application:
    name: opentelemetry-zero-code-instrumentation-practice
otel:
  propagators:
    - tracecontext
    - b3

Overriding Resource Attributes

Resource Attribute の上書き。スキップ。

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/sdk-configuration/#overriding-resource-attributes

Disable the OpenTelemetry Starter

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/sdk-configuration/#disable-the-opentelemetry-starter

OTEL の SDK を無効化する。これを設定するとシグナルが送信されなくなる。

Programmatic configuration

Exclude actuator endpoints from tracing

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/sdk-configuration/#exclude-actuator-endpoints-from-tracing

Sampler の設定。これを設定すると、トレースがサンプリングされなくなる。
ドキュメントのサンプルでは、actuator を drop する。

build.gradle.kts
dependencies {
   // 中略

	// 名前
	// - opentelemetry-samplers
	// Maven Repository の URL
	// - https://mvnrepository.com/artifact/io.opentelemetry.contrib/opentelemetry-samplers
	// 用途
	// - サンプリングのカスタマイズに必要
	// - このリポジトリでは、actuator の drop に使用する
	implementation("io.opentelemetry.contrib:opentelemetry-samplers:1.33.0-alpha")
	implementation("org.springframework.boot:spring-boot-starter-actuator")
}

src/main/kotlin/com/example/opentelemetry_zero_code_instrumentation_practice/FilterPaths.kt
package com.example.opentelemetry_zero_code_instrumentation_practice

import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.contrib.sampler.RuleBasedRoutingSampler
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider
import io.opentelemetry.semconv.UrlAttributes
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class FilterPaths {

    // actuator パスをフィルタリングするカスタマイズ
    @Bean
    fun otelCustomizer(): AutoConfigurationCustomizerProvider {
        return AutoConfigurationCustomizerProvider { p ->
            p.addSamplerCustomizer { fallback, config ->
                RuleBasedRoutingSampler.builder(SpanKind.SERVER, fallback)
                    .drop(UrlAttributes.URL_PATH, "^/actuator")
                    .build()
            }
        }
    }
}

たとえば、↓のような actuator が表示されなくなる。

Configure the exporter programmatically

OTLP exporter に設定を追加する。
今回の例だと、認証用の設定を追加する。
戦術の otelCustomizer を使い回すと起動しなくなるので注意。

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/sdk-configuration/#configure-the-exporter-programmatically

package com.example.opentelemetry_zero_code_instrumentation_practice

import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration
class CustomAuth {
    @Bean
    fun otelCustomizer(): AutoConfigurationCustomizerProvider {
        return AutoConfigurationCustomizerProvider { p ->
            p.addSpanExporterCustomizer { exporter, _ ->
                if (exporter is OtlpHttpSpanExporter) {
                    exporter.toBuilder()
                        .setHeaders { headers() }
                        .build()
                } else {
                    exporter
                }
            }
        }
    }

    private fun headers(): Map<String, String> {
        return mapOf("Authorization" to "Bearer ${refreshToken()}")
    }

    private fun refreshToken(): String {
        // e.g. read the token from a kubernetes secret
        return "token"
    }
}

Resource Providers

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/sdk-configuration/#resource-providers

OpenTelemetry Starter を利用すると、以下の Resource が自動的に付与される。

  • Distribution Resource Provider
    • telemetry.distro.name
    • telemetry.distro.version

下記の項目は、所定の箇所に設定(application.yaml、build.gradle.kts、etc...)することで、反映される。

  • Spring Resource Provider
    • service.name
    • service.version

application.yaml の設定例

application.yaml
// 略
otel:
  // 略
  resource:
    attributes:
      deployment.environment: dev
      service:
        name: zero-code-instrumentation-practice
        version: 0.0.1

画像は反映後の例。

Service name

Resource の Service の反映の優先順位について記載。

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/sdk-configuration/#service-name

柾樹柾樹

Annotations

https://opentelemetry.io/ja/docs/zero-code/java/spring-boot-starter/annotations/

WithSpan アノテーションを利用すると、AOP で Span を生成できる。

src/main/kotlin/com/example/opentelemetry_zero_code_instrumentation_practice/TracedClass.kt
package com.example.opentelemetry_zero_code_instrumentation_practice

import io.opentelemetry.api.trace.Span
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.instrumentation.annotations.SpanAttribute
import io.opentelemetry.instrumentation.annotations.WithSpan
import org.springframework.stereotype.Component

@Component
class TracedClass {

    @WithSpan
    fun tracedMethod() {}

    @WithSpan(value = "TracedClass span name")
    fun tracedMethodWithSpanName() {
        val currentSpan = Span.current()
        currentSpan.addEvent("ADD EVENT TO tracedMethodWithName SPAN");
        currentSpan.setAttribute("isTestAttribute", true);
    }

    @WithSpan(kind = SpanKind.CLIENT)
    fun tracedMethodWithoutAnnotation() {}

    @WithSpan
    fun tracedMethodWithAttribute(@SpanAttribute("attributeName") parameter: String) {}
}

build.gradle.kts
dependencies {
    // 中略

	// 名前
	// - spring-boot-starter-aop
	// Maven Repository の URL
	// - https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop
	// 用途
	// - アノテーションを利用したトレースに必要
	// - AOP であるため、アノテーションを付与しただけでは動作せず、Constructor Injection などでインスタンス化する必要がある
	implementation("org.springframework.boot:spring-boot-starter-aop")
}

ただし、AOP で動作するため、DI しなければ利用できないことに注意。

src/main/kotlin/com/example/opentelemetry_zero_code_instrumentation_practice/Controller.kt
package com.example.opentelemetry_zero_code_instrumentation_practice

import io.opentelemetry.api.OpenTelemetry
import io.opentelemetry.api.trace.Tracer
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class Controller(
    openTelemetry: OpenTelemetry,
    private val tracedClass: TracedClass
) {
    private val tracer: Tracer = openTelemetry.getTracer("application")

    @GetMapping("/ping")
    fun ping(): String {
        this.pingSpan()
        return "pong"
    }

    fun pingSpan() {
        val span = tracer.spanBuilder("pingSpan").startSpan()
        span.end()
        return
    }

    @GetMapping("/ping2")
    fun ping2(): String {
        tracedClass.tracedMethod()
        tracedClass.tracedMethodWithSpanName()
        tracedClass.tracedMethodWithoutAnnotation()
        tracedClass.tracedMethodWithAttribute("attributeValue")
        return "pong2"
    }
}