All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.tracing.providers.jaeger.JaegerTracerBuilder Maven / Gradle / Ivy

/*
 * Copyright (c) 2019, 2024 Oracle and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.helidon.tracing.providers.jaeger;

import java.lang.System.Logger.Level;
import java.time.Duration;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import io.helidon.common.config.Config;
import io.helidon.config.metadata.Configured;
import io.helidon.config.metadata.ConfiguredOption;
import io.helidon.tracing.Tracer;
import io.helidon.tracing.TracerBuilder;
import io.helidon.tracing.providers.opentelemetry.HelidonOpenTelemetry;
import io.helidon.tracing.providers.opentelemetry.OpenTelemetryTracerProvider;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter;
import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporterBuilder;
import io.opentelemetry.extension.trace.propagation.B3Propagator;
import io.opentelemetry.extension.trace.propagation.JaegerPropagator;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;

/**
 * The JaegerTracerBuilder is a convenience builder for {@link io.helidon.tracing.Tracer} to use with Jaeger.
 * 

* Unless You want to explicitly depend on Jaeger in Your code, please * use {@link io.helidon.tracing.TracerBuilder#create(String)} or * {@link io.helidon.tracing.TracerBuilder#create(io.helidon.common.config.Config)} that is abstracted. *

* The Jaeger tracer uses environment variables and system properties to override the defaults. * Except for {@code protocol} and {@code service} these are honored, unless overridden in configuration * or through the builder methods. * See Jaeger documentation * for details. *

* The following table lists jaeger specific defaults and configuration options. *

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Tracer Configuration Options
optiondefaultdescription
{@code service} Service name
{@code protocol}{@code http}The protocol to use. By default http is used.
{@code host}{@code 127.0.0.1}Host to use
{@code port}{@value #DEFAULT_HTTP_PORT}Port to be used
{@code path} Path to be used.
{@code exporter-timeout}10 secondsTimeout of exporter
{@code private-key-pem} Client private key in PEM format
{@code client-cert-pem} Client certificate in PEM format
{@code trusted-cert-pem} Trusted certificates in PEM format
{@code sampler-type}{@code const} with param set to {@code 1}Sampler type {@code const} (0 to disable, 1 to always enabled), * {@code ratio} (sample param contains the ratio as a double)
{@code sampler-param}sampler type defaultNumeric parameter specifying details for the sampler type.
{@code tags} see {@link io.helidon.tracing.TracerBuilder}
{@code boolean-tags} see {@link io.helidon.tracing.TracerBuilder}
{@code int-tags} see {@link io.helidon.tracing.TracerBuilder}
*/ @Configured(prefix = "tracing", root = true, description = "Jaeger tracer configuration.") public class JaegerTracerBuilder implements TracerBuilder { static final System.Logger LOGGER = System.getLogger(JaegerTracerBuilder.class.getName()); static final boolean DEFAULT_ENABLED = true; static final String DEFAULT_HTTP_HOST = "localhost"; static final int DEFAULT_SCHEDULE_DELAY = 5_000; static final int DEFAULT_HTTP_PORT = 14250; static final int DEFAULT_MAX_QUEUE_SIZE = 2048; static final int DEFAULT_MAX_EXPORT_BATCH_SIZE = 512; private final Map tags = new HashMap<>(); // this is a backward incompatible change, but the correct choice is Jaeger, not B3 private final Set propagationFormats = EnumSet.of(PropagationFormat.JAEGER); private boolean isPropagationFormatsDefaulted = true; private String serviceName; private String protocol = "http"; private String host = DEFAULT_HTTP_HOST; private int port = DEFAULT_HTTP_PORT; private SamplerType samplerType = SamplerType.CONSTANT; private Number samplerParam = 1; private boolean enabled = DEFAULT_ENABLED; private boolean global = true; private byte[] privateKey; private byte[] certificate; private byte[] trustedCertificates; private String path; private Duration exporterTimeout = Duration.ofSeconds(10); private Duration scheduleDelay = Duration.ofMillis(DEFAULT_SCHEDULE_DELAY); private int maxQueueSize = DEFAULT_MAX_QUEUE_SIZE; private int maxExportBatchSize = DEFAULT_MAX_EXPORT_BATCH_SIZE; private SpanProcessorType spanProcessorType = SpanProcessorType.BATCH; private final List adHocExporters = new ArrayList<>(); // primarily for testing /** * Default constructor, does not modify any state. */ protected JaegerTracerBuilder() { } /** * Get a Jaeger {@link io.helidon.tracing.Tracer } builder for processing tracing data of a service with a given name. * * @param serviceName name of the service that will be using the tracer. * @return {@code Tracer} builder for Jaeger. */ public static JaegerTracerBuilder forService(String serviceName) { return create() .serviceName(serviceName); } /** * Create a new builder based on values in configuration. * This requires at least a key "service" in the provided config. * * @param config configuration to load this builder from * @return a new builder instance. * @see io.helidon.tracing.providers.jaeger.JaegerTracerBuilder#config(io.helidon.common.config.Config) */ public static JaegerTracerBuilder create(Config config) { return create().config(config); } static JaegerTracerBuilder create() { return new JaegerTracerBuilder(); } @Override public JaegerTracerBuilder serviceName(String name) { this.serviceName = name; return this; } @Override public JaegerTracerBuilder collectorProtocol(String protocol) { this.protocol = protocol; return this; } @Override public JaegerTracerBuilder collectorPort(int port) { this.port = port; return this; } @Override public JaegerTracerBuilder collectorPath(String path) { this.path = path; return this; } @Override public JaegerTracerBuilder collectorHost(String host) { this.host = host; return this; } @Override public JaegerTracerBuilder addTracerTag(String key, String value) { this.tags.put(key, value); return this; } @Override public JaegerTracerBuilder addTracerTag(String key, Number value) { this.tags.put(key, String.valueOf(value)); return this; } @Override public JaegerTracerBuilder addTracerTag(String key, boolean value) { this.tags.put(key, String.valueOf(value)); return this; } @Override public JaegerTracerBuilder config(Config config) { config.get("enabled").asBoolean().ifPresent(this::enabled); config.get("service").asString().ifPresent(this::serviceName); config.get("protocol").asString().ifPresent(this::collectorProtocol); config.get("host").asString().ifPresent(this::collectorHost); config.get("port").asInt().ifPresent(this::collectorPort); config.get("path").asString().ifPresent(this::collectorPath); config.get("sampler-type").asString().as(SamplerType::create).ifPresent(this::samplerType); config.get("sampler-param").asDouble().ifPresent(this::samplerParam); config.get("private-key-pem").map(io.helidon.common.configurable.Resource::create).ifPresent(this::privateKey); config.get("client-cert-pem").map(io.helidon.common.configurable.Resource::create).ifPresent(this::clientCertificate); config.get("trusted-cert-pem").map(io.helidon.common.configurable.Resource::create).ifPresent(this::trustedCertificates); config.get("propagation").asList(String.class) .ifPresent(propagationStrings -> { propagationStrings.stream() .map(String::toUpperCase) .map(PropagationFormat::valueOf) .forEach(this::addPropagation); }); config.get("tags").detach() .asMap() .orElseGet(Map::of) .forEach(this::addTracerTag); config.get("boolean-tags") .asNodeList() .ifPresent(nodes -> { nodes.forEach(node -> { this.addTracerTag(node.key().name(), node.asBoolean().get()); }); }); config.get("int-tags") .asNodeList() .ifPresent(nodes -> { nodes.forEach(node -> { this.addTracerTag(node.key().name(), node.asInt().get()); }); }); config.get("global").asBoolean().ifPresent(this::registerGlobal); config.get("span-processor-type").asString() .ifPresent(it -> spanProcessorType(SpanProcessorType.valueOf(it.toUpperCase()))); config.get("exporter-timeout").as(Duration.class).ifPresent(this::exporterTimeout); config.get("schedule-delay").as(Duration.class).ifPresent(this::scheduleDelay); config.get("max-queue-size").asInt().ifPresent(this::maxQueueSize); config.get("max-export-batch-size").asInt().ifPresent(this::maxExportBatchSize); return this; } /** * Private key in PEM format. * * @param resource key resource * @return updated builder */ @ConfiguredOption(key = "private-key-pem") public JaegerTracerBuilder privateKey(io.helidon.common.configurable.Resource resource) { this.privateKey = resource.bytes(); return this; } /** * Certificate of client in PEM format. * * @param resource certificate resource * @return updated builder */ @ConfiguredOption(key = "client-cert-pem") public JaegerTracerBuilder clientCertificate(io.helidon.common.configurable.Resource resource) { this.certificate = resource.bytes(); return this; } /** * Trusted certificates in PEM format. * * @param resource trusted certificates resource * @return updated builder */ @ConfiguredOption(key = "trusted-cert-pem") public JaegerTracerBuilder trustedCertificates(io.helidon.common.configurable.Resource resource) { this.trustedCertificates = resource.bytes(); return this; } /** * Span Processor type used. * * @param spanProcessorType to use * @return updated builder */ @ConfiguredOption("batch") public JaegerTracerBuilder spanProcessorType(SpanProcessorType spanProcessorType) { this.spanProcessorType = spanProcessorType; return this; } /** * Schedule Delay of exporter requests. * * @param scheduleDelay timeout to use * @return updated builder */ @ConfiguredOption("PT5S") public JaegerTracerBuilder scheduleDelay(Duration scheduleDelay) { this.scheduleDelay = scheduleDelay; return this; } /** * Maximum Queue Size of exporter requests. * * @param maxQueueSize to use * @return updated builder */ @ConfiguredOption("2048") public JaegerTracerBuilder maxQueueSize(int maxQueueSize) { this.maxQueueSize = maxQueueSize; return this; } /** * Maximum Export Batch Size of exporter requests. * * @param maxExportBatchSize to use * @return updated builder */ @ConfiguredOption("512") public JaegerTracerBuilder maxExportBatchSize(int maxExportBatchSize) { this.maxExportBatchSize = maxExportBatchSize; return this; } /** * Timeout of exporter requests. * * @param exporterTimeout timeout to use * @return updated builder */ @ConfiguredOption("PT10S") public JaegerTracerBuilder exporterTimeout(Duration exporterTimeout) { this.exporterTimeout = exporterTimeout; return this; } @Override public JaegerTracerBuilder enabled(boolean enabled) { this.enabled = enabled; return this; } @Override public JaegerTracerBuilder registerGlobal(boolean global) { this.global = global; return this; } @Override public B unwrap(Class builderClass) { if (builderClass.isAssignableFrom(getClass())) { return builderClass.cast(this); } throw new IllegalArgumentException("This builder is a Jaeger tracer builder, cannot be unwrapped to " + builderClass.getName()); } /** * The sampler parameter (number). * * @param samplerParam parameter of the sampler * @return updated builder instance */ @ConfiguredOption("1") public JaegerTracerBuilder samplerParam(Number samplerParam) { this.samplerParam = samplerParam; return this; } /** * Sampler type. *

* See Sampler types. * * @param samplerType type of the sampler * @return updated builder instance */ @ConfiguredOption("CONSTANT") public JaegerTracerBuilder samplerType(SamplerType samplerType) { this.samplerType = samplerType; return this; } /** * Add propagation format to use. * * @param propagationFormat propagation value * @return updated builder instance */ @ConfiguredOption(key = "propagation", kind = ConfiguredOption.Kind.LIST, type = PropagationFormat.class, value = "JAEGER") public JaegerTracerBuilder addPropagation(PropagationFormat propagationFormat) { Objects.requireNonNull(propagationFormat); if (isPropagationFormatsDefaulted) { isPropagationFormatsDefaulted = false; this.propagationFormats.clear(); } this.propagationFormats.add(propagationFormat); return this; } /** * Builds the {@link io.helidon.tracing.Tracer} for Jaeger based on the configured parameters. * * @return the tracer */ @Override public Tracer build() { Tracer result; if (enabled) { if (serviceName == null) { throw new IllegalArgumentException( "Configuration must at least contain the 'service' key ('tracing.service` in MP) with service name"); } JaegerGrpcSpanExporterBuilder spanExporterBuilder = JaegerGrpcSpanExporter.builder() .setEndpoint(protocol + "://" + host + ":" + port + (path == null ? "" : path)) .setTimeout(exporterTimeout); if (privateKey != null && certificate != null) { spanExporterBuilder.setClientTls(privateKey, certificate); } if (trustedCertificates != null) { spanExporterBuilder.setTrustedCertificates(trustedCertificates); } SpanExporter exporter = spanExporterBuilder.build(); Sampler sampler = switch (samplerType) { case RATIO -> Sampler.traceIdRatioBased(samplerParam.doubleValue()); case CONSTANT -> samplerParam.intValue() == 1 ? Sampler.alwaysOn() : Sampler.alwaysOff(); }; AttributesBuilder attributesBuilder = Attributes.builder() .put(ResourceAttributes.SERVICE_NAME, serviceName); tags.forEach(attributesBuilder::put); Resource otelResource = Resource.create(attributesBuilder.build()); SdkTracerProviderBuilder sdkTracerProviderBuilder = SdkTracerProvider.builder() .setSampler(sampler) .setResource(otelResource) .addSpanProcessor(spanProcessor(exporter)); adHocExporters.stream() .map(this::spanProcessor) .forEach(sdkTracerProviderBuilder::addSpanProcessor); OpenTelemetry ot = OpenTelemetrySdk.builder() .setTracerProvider(sdkTracerProviderBuilder.build()) .setPropagators(ContextPropagators.create(TextMapPropagator.composite(createPropagators()))) .build(); result = HelidonOpenTelemetry.create(ot, ot.getTracer(this.serviceName), Map.of()); if (global) { GlobalOpenTelemetry.set(ot); } LOGGER.log(Level.INFO, () -> "Creating Jaeger tracer for '" + this.serviceName + "' configured with " + protocol + "://" + host + ":" + port); } else { LOGGER.log(Level.INFO, "Jaeger Tracer is explicitly disabled."); result = Tracer.noOp(); } if (global) { OpenTelemetryTracerProvider.globalTracer(result); } return result; } String path() { return path; } Map tags() { return tags; } String serviceName() { return serviceName; } String protocol() { return protocol; } String host() { return host; } Integer port() { return port; } SamplerType samplerType() { return samplerType; } SpanProcessorType spanProcessorType() { return spanProcessorType; } Duration exporterTimeout() { return exporterTimeout; } Duration scheduleDelay() { return scheduleDelay; } int maxQueueSize() { return maxQueueSize; } int maxExportBatchSize() { return maxExportBatchSize; } Number samplerParam() { return samplerParam; } boolean isEnabled() { return enabled; } List createPropagators() { return propagationFormats.stream() .map(JaegerTracerBuilder::mapFormatToPropagator) .toList(); } // Primarily for testing JaegerTracerBuilder exporter(SpanExporter spanExporter) { adHocExporters.add(spanExporter); return this; } private SpanProcessor spanProcessor(SpanExporter exporter) { return switch (spanProcessorType) { case BATCH -> BatchSpanProcessor.builder(exporter) .setScheduleDelay(scheduleDelay) .setMaxQueueSize(maxQueueSize) .setMaxExportBatchSize(maxExportBatchSize) .setExporterTimeout(exporterTimeout) .build(); case SIMPLE -> SimpleSpanProcessor.create(exporter); }; } private static TextMapPropagator mapFormatToPropagator(PropagationFormat propagationFormat) { return switch (propagationFormat) { case B3 -> B3Propagator.injectingMultiHeaders(); case B3_SINGLE -> B3Propagator.injectingSingleHeader(); case W3C -> W3CBaggagePropagator.getInstance(); // jaeger and unknown are jaeger default -> JaegerPropagator.getInstance(); }; } /** * Sampler type definition. * Available options are "const", "probabilistic", "ratelimiting" and "remote". */ public enum SamplerType { /** * Constant sampler always makes the same decision for all traces. * It either samples all traces {@code 1} or none of them {@code 0}. */ CONSTANT("const"), /** * Ratio of the requests to sample, double value. */ RATIO("ratio"); private final String config; SamplerType(String config) { this.config = config; } static SamplerType create(String value) { for (SamplerType sampler : SamplerType.values()) { if (sampler.config().equals(value)) { return sampler; } } throw new IllegalStateException("SamplerType " + value + " is not supported"); } String config() { return config; } } /** * Supported Jaeger trace context propagation formats. */ public enum PropagationFormat { /** * The Zipkin B3 trace context propagation format using multiple headers. */ B3, /** * B3 trace context propagation using a single header. */ B3_SINGLE, /** * The Jaeger trace context propagation format. */ JAEGER, /** * The W3C trace context propagation format. */ W3C } /** * Span Processor type. Batch is default for production. */ public enum SpanProcessorType { /** * Simple Span Processor. */ SIMPLE, /** * Batch Span Processor. */ BATCH } }