Kora облачно ориентированный серверный фреймворк написанный на Java для написания Java / Kotlin приложений с упором на производительность, эффективность, прозрачность сделанный выходцами из Т-Банк / Тинькофф

Kora is a cloud-oriented server-side Java framework for writing Java / Kotlin applications with a focus on performance, efficiency and transparency

Skip to content

Tracing

Module for collecting application trace according to [OpenTelemetry] standard(https://opentelemetry.io/docs/what-is-opentelemetry/) and export trace by gRPC in OTLP format.

For a step-by-step walkthrough before the reference details, see Observability.

gRPC

Module allows trace collection using gRPC protocol by means of GrpcSender.

Зависимость build.gradle:

implementation "ru.tinkoff.kora:opentelemetry-tracing-exporter-grpc"

Модуль:

@KoraApp
public interface Application extends OpentelemetryGrpcExporterModule { }

Зависимость build.gradle.kts:

implementation("ru.tinkoff.kora:opentelemetry-tracing-exporter-grpc")

Модуль:

@KoraApp
interface Application : OpentelemetryGrpcExporterModule

HTTP

Module allows to collect trace using HTTP protocol by means of HttpSender.

Зависимость build.gradle:

implementation "ru.tinkoff.kora:opentelemetry-tracing-exporter-http"

Модуль:

@KoraApp
public interface Application extends OpentelemetryHttpExporterModule { }

Зависимость build.gradle.kts:

implementation("ru.tinkoff.kora:opentelemetry-tracing-exporter-http")

Модуль:

@KoraApp
interface Application : OpentelemetryHttpExporterModule

Configuration

endpoint is the only a required field, attributes from the attributes field will be sent with each span.

Parameters described in the OpentelemetryGrpcExporterConfig/OpentelemetryHttpExporterConfig and OpentelemetryResourceConfig classes:

tracing {
  exporter {
    endpoint = "http://localhost:4317" //(1)!
    connectTimeout = "60s" //(2)!
    exportTimeout = "3s" //(3)!
    scheduleDelay = "2s" //(4)!
    maxExportBatchSize = 512 //(5)!
    maxQueueSize = 2048 //(6)!
    batchExportTimeout = "30s" //(7)!
    compression = "gzip" //(8)!
    exportUnsampledSpans = false //(9)!
    retry {
      maxAttempts = 5 //(10)!
      initialBackoff = "1s" //(11)!
      maxBackoff = "5s" //(12)!
      backoffMultiplier = 1.5 //(13)!
    }
  }
  attributes { //(14)!
    "service.name" = "example-service"
    "service.namespace" = "kora"
  }
}
  1. URL from OpenTelemetry service collector (mandatory)
  2. Time to wait for connection to exporter
  3. Maximum time to wait for telemetry processing by collector
  4. Time between exporting telemetry to the collector
  5. Maximum number of telemetry within one export
  6. Maximum queue size of unsent telemetry
  7. Maximum waiting time for export
  8. Telemetry compression mechanism when exporting
  9. Whether to export unsampled telemetry
  10. Maximum number of export attempts
  11. Initial value of waiting time before next export attempt
  12. Maximum wait value before next export attempt
  13. Waiting delay value multiplier
  14. Additional telemetry attributes

Translated with DeepL.com (free version)

tracing:
  exporter:
    endpoint: http://localhost:4317 #(1)!
    connectTimeout: 60s #(2)!
    exportTimeout: 3s #(3)!
    scheduleDelay: 2s #(4)!
    maxExportBatchSize: 512 #(5)!
    maxQueueSize: 2048 #(6)!
    batchExportTimeout: 30s #(7)!
    compression: gzip #(8)!
    exportUnsampledSpans: false #(9)!
    retry:
      maxAttempts: 5 #(10)!
      initialBackoff: 1s #(11)!
      maxBackoff: 5s #(12)!
      backoffMultiplier: 1.5 #(13)!
  attributes: #(14)!
    service.name: example-service
    service.namespace: kora
  1. URL from OpenTelemetry service collector (mandatory)
  2. Time to wait for connection to exporter
  3. Maximum time to wait for telemetry processing by collector
  4. Time between exporting telemetry to the collector
  5. Maximum number of telemetry within one export
  6. Maximum queue size of unsent telemetry
  7. Maximum waiting time for export
  8. Telemetry compression mechanism when exporting
  9. Whether to export unsampled telemetry
  10. Maximum number of export attempts
  11. Initial value of waiting time before next export attempt
  12. Maximum wait value before next export attempt
  13. Waiting delay value multiplier
  14. Additional telemetry attributes

Trace collection configuration parameters are described in modules that include trace collection, e.g. HTTP server, HTTP client, etc.

Tracing context

Obtain the current tracing Span, you can use the getSpan method in OpentelemetryContext:

var span = OpentelemetryContext.getSpan();
val span = OpentelemetryContext.getSpan();

Obtain the current trace ID, you can use the getTraceId() method in OpentelemetryContext:

var traceId = OpentelemetryContext.getTraceId();
val traceId = OpentelemetryContext.getTraceId();

Tracing sync

In addition to automatically created spans, you can use the Tracer object from the dependency container. You can create a span with the current one in parent as follows:

@Component
public final class MyService {

    private final io.opentelemetry.api.trace.Tracer tracer;

    public MyService(Tracer tracer) {
        this.tracer = tracer;
    }

    public String doTraceWork() {
        var ctx = ru.tinkoff.kora.common.Context.current();
        var otctx = OpentelemetryContext.get(ctx);
        var span = tracer.spanBuilder("myOperation")
                .setParent(otctx.getContext())
                .startSpan();

        OpentelemetryContext.set(ctx, otctx.add(span));
        try {
            var result = doWork();
            span.setStatus(StatusCode.OK);
            return result;
        } catch (Exception e) {
            span.recordException(e);
            span.setStatus(StatusCode.ERROR, e.getMessage());
            throw e;
        } finally {
            span.end();
            OpentelemetryContext.set(ctx, otctx);
        }
    }

    public String doWork() {
        // do some work
    }
}
@Component
class MyService(private val tracer: io.opentelemetry.api.trace.Tracer) {

    fun doTraceWork(): String {
        val ctx = ru.tinkoff.kora.common.Context.current()
        val otctx = OpentelemetryContext.get(ctx)
        val span = tracer.spanBuilder("myOperation")
            .setParent(otctx.getContext())
            .startSpan()

        OpentelemetryContext.set(ctx, otctx.add(span))
        try {
            val result = doWork()
            span.setStatus(StatusCode.OK)
            return result
        } catch (e: Exception) {
            span.recordException(e)
            span.setStatus(StatusCode.ERROR, e.message)
            throw e
        } finally {
            span.end()
            OpentelemetryContext.set(ctx, otctx)
        }
    }

    fun doWork(): String = // do some work
}

Асинхронная трассировка

In addition to spans automatically created by the framework, you can use the Tracer object from the container to create your own traces. The main challenge lies in correctly propagating the context Fork to another execution thread to ensure the trace works properly.

To create a trace for asynchronous code inherited from the current parent context, you can do the following:

Example is shown for the CompletableStage asynchronous approach:

@Component
public final class MyService {

    private final io.opentelemetry.api.trace.Tracer tracer;

    public MyService(Tracer tracer) {
        this.tracer = tracer;
    }

    public CompletionStage<String> doTraceWork() {
        var ctx = ru.tinkoff.kora.common.Context.current().fork();
        var otctx = OpentelemetryContext.get(ctx);
        var span = tracer.spanBuilder("myOperation")
                .setParent(otctx.getContext())
                .startSpan();

        return CompletableFuture.supplyAsync(() -> {
                    OpentelemetryContext.set(ctx, otctx.add(span));
                    var result = doWork();
                    return result;
                })
                .whenComplete((r, e) -> {
                    if (e != null) {
                        span.recordException(e);
                        span.setStatus(StatusCode.ERROR, e.getMessage());
                    } else {
                        span.setStatus(StatusCode.OK);
                    }
                    span.end();
                });
    }

    public String doWork() {
        // do some work
    }
}

Example is shown for the suspend asynchronous approach:

@Component
class MyService(private val tracer: io.opentelemetry.api.trace.Tracer) {

    suspend fun doTraceWork(): String {
        val ctx = ru.tinkoff.kora.common.Context.current()
        val otctx = OpentelemetryContext.get(ctx)
        val span = tracer.spanBuilder("myOperation")
            .setParent(otctx.getContext())
            .startSpan()

        OpentelemetryContext.set(ctx, otctx.add(span))
        return withContext(Context.Kotlin.asCoroutineContext(ctx)) {
            try {
                val result = doWork()
                span.setStatus(StatusCode.OK)
                result
            } catch (e: Exception) {
                span.recordException(e)
                span.setStatus(StatusCode.ERROR, e.message)
                throw e
            } finally {
                span.end()
                OpentelemetryContext.set(ctx, otctx)
            }
        }
    }

    fun doWork(): String = // do some work
}