使用 SDK 管理遥测数据

SDK 是 API 的内置参考实现,负责处理由插装 API 调用生成的遥测数据,并将其导出。本文档是 SDK 的概念概述,包括描述、相关 Javadoc 链接、工件坐标、示例程序化配置等。有关 SDK 配置的详细信息,请参阅 **配置 SDK**,包括 零代码 SDK 自动配置

SDK 由以下顶级组件构成:

  • SdkTracerProviderTracerProvider 的 SDK 实现,包括采样、处理和导出 span 的工具。
  • SdkMeterProviderMeterProvider 的 SDK 实现,包括配置指标流和读/导出指标的工具。
  • SdkLoggerProviderLoggerProvider 的 SDK 实现,包括处理和导出日志的工具。
  • TextMapPropagator:在进程边界上传播上下文。

这些组件组合成 OpenTelemetrySdk,一个便捷的载体对象,用于将完全配置好的 SDK 组件传递给插装。

SDK 提供了各种内置组件,足以满足许多用例的需求,并支持用于可扩展性的 插件接口

SDK 插件扩展接口

当内置组件不足以满足需求时,可以通过实现各种插件扩展接口来扩展 SDK。

SDK 组件

io.opentelemetry:opentelemetry-sdk:1.57.0 这个工件包含了 OpenTelemetry SDK。

以下各节将描述 SDK 的核心面向用户的组件。每个组件部分都包含:

  • 简要描述,包括指向 Javadoc 类型引用的链接。
  • 如果该组件是 插件扩展接口,则会列出可用的内置实现和 opentelemetry-java-contrib 实现的表格。
  • 有关 程序化配置的简单演示。
  • 如果该组件是 插件扩展接口,则会提供自定义实现的一个简单演示。

OpenTelemetrySdk

OpenTelemetrySdkOpenTelemetry 的 SDK 实现。它是一个顶级 SDK 组件的容器,使得将完全配置好的 SDK 组件传递给插装非常方便。

OpenTelemetrySdk 由应用程序所有者配置,并包含:

以下代码片段演示了 OpenTelemetrySdk 的程序化配置。

package otel;

import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.resources.Resource;

public class OpenTelemetrySdkConfig {
  public static OpenTelemetrySdk create() {
    Resource resource = ResourceConfig.create();
    return OpenTelemetrySdk.builder()
        .setTracerProvider(SdkTracerProviderConfig.create(resource))
        .setMeterProvider(SdkMeterProviderConfig.create(resource))
        .setLoggerProvider(SdkLoggerProviderConfig.create(resource))
        .setPropagators(ContextPropagatorsConfig.create())
        .build();
  }
}

资源

Resource 是一组定义遥测源的属性。应用程序应将相同的资源与 SdkTracerProviderSdkMeterProviderSdkLoggerProvider 相关联。

以下代码片段演示了 Resource 的程序化配置。

package otel;

import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.ServiceAttributes;

public class ResourceConfig {
  public static Resource create() {
    return Resource.getDefault().toBuilder()
        .put(ServiceAttributes.SERVICE_NAME, "my-service")
        .build();
  }
}

SdkTracerProvider

SdkTracerProviderTracerProvider 的 SDK 实现,负责处理 API 生成的跟踪遥测数据。

SdkTracerProvider 由应用程序所有者配置,并包含:

  • Resource:span 所关联的资源。
  • Sampler:配置记录和采样哪些 span。
  • SpanProcessors:在 span 开始和结束时处理 span。
  • SpanExporters:将 span 导出到进程外部(与关联的 SpanProcessor 一起)。
  • SpanLimits:控制与 span 相关联的数据的限制。

以下代码片段演示了 SdkTracerProvider 的程序化配置。

package otel;

import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;

public class SdkTracerProviderConfig {
  public static SdkTracerProvider create(Resource resource) {
    return SdkTracerProvider.builder()
        .setResource(resource)
        .addSpanProcessor(
            SpanProcessorConfig.batchSpanProcessor(
                SpanExporterConfig.otlpHttpSpanExporter("https://:4318/v1/spans")))
        .setSampler(SamplerConfig.parentBasedSampler(SamplerConfig.traceIdRatioBased(.25)))
        .setSpanLimits(SpanLimitsConfig::spanLimits)
        .build();
  }
}

Sampler

一个 Sampler 是一个 插件扩展接口,负责确定哪些 span 被记录和采样。

SDK 内置的采样器以及由社区在 opentelemetry-java-contrib 中维护的采样器

Artifact描述
ParentBasedio.opentelemetry:opentelemetry-sdk:1.57.0根据 span 的父级的采样状态对 span 进行采样。
AlwaysOnio.opentelemetry:opentelemetry-sdk:1.57.0采样所有 span。
AlwaysOffio.opentelemetry:opentelemetry-sdk:1.57.0丢弃所有 span。
TraceIdRatioBasedio.opentelemetry:opentelemetry-sdk:1.57.0根据可配置的比例对 span 进行采样。
JaegerRemoteSamplerio.opentelemetry:opentelemetry-sdk-extension-jaeger-remote-sampler:1.57.0根据远程服务器的配置对 span 进行采样。
LinksBasedSamplerio.opentelemetry.contrib:opentelemetry-samplers:1.52.0-alpha根据 span 的链接的采样状态对 span 进行采样。
RuleBasedRoutingSamplerio.opentelemetry.contrib:opentelemetry-samplers:1.52.0-alpha根据可配置的规则对 span 进行采样。
ConsistentSamplersio.opentelemetry.contrib:opentelemetry-consistent-sampling:1.52.0-alpha各种一致性采样器实现,如 概率采样中所定义的。

以下代码片段演示了 Sampler 的程序化配置。

package otel;

import io.opentelemetry.sdk.extension.trace.jaeger.sampler.JaegerRemoteSampler;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import java.time.Duration;

public class SamplerConfig {
  public static Sampler parentBasedSampler(Sampler root) {
    return Sampler.parentBasedBuilder(root)
        .setLocalParentNotSampled(Sampler.alwaysOff())
        .setLocalParentSampled(Sampler.alwaysOn())
        .setRemoteParentNotSampled(Sampler.alwaysOff())
        .setRemoteParentSampled(Sampler.alwaysOn())
        .build();
  }

  public static Sampler alwaysOn() {
    return Sampler.alwaysOn();
  }

  public static Sampler alwaysOff() {
    return Sampler.alwaysOff();
  }

  public static Sampler traceIdRatioBased(double ratio) {
    return Sampler.traceIdRatioBased(ratio);
  }

  public static Sampler jaegerRemoteSampler() {
    return JaegerRemoteSampler.builder()
        .setInitialSampler(Sampler.alwaysOn())
        .setEndpoint("http://endpoint")
        .setPollingInterval(Duration.ofSeconds(60))
        .setServiceName("my-service-name")
        .build();
  }
}

实现 Sampler 接口以提供您自己的自定义采样逻辑。例如:

package otel;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.trace.data.LinkData;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.opentelemetry.sdk.trace.samplers.SamplingResult;
import java.util.List;

public class CustomSampler implements Sampler {
  @Override
  public SamplingResult shouldSample(
      Context parentContext,
      String traceId,
      String name,
      SpanKind spanKind,
      Attributes attributes,
      List<LinkData> parentLinks) {
    // Callback invoked when span is started, before any SpanProcessor is called.
    // If the SamplingDecision is:
    // - DROP: the span is dropped. A valid span context is created and SpanProcessor#onStart is
    // still called, but no data is recorded and SpanProcessor#onEnd is not called.
    // - RECORD_ONLY: the span is recorded but not sampled. Data is recorded to the span,
    // SpanProcessor#onStart and SpanProcessor#onEnd are called, but the span's sampled status
    // indicates it should not be exported out of process.
    // - RECORD_AND_SAMPLE: the span is recorded and sampled. Data is recorded to the span,
    // SpanProcessor#onStart and SpanProcessor#onEnd are called, and the span's sampled status
    // indicates it should be exported out of process.
    return SpanKind.SERVER == spanKind ? SamplingResult.recordAndSample() : SamplingResult.drop();
  }

  @Override
  public String getDescription() {
    // Return a description of the sampler.
    return this.getClass().getSimpleName();
  }
}

SpanProcessor

一个 SpanProcessor 是一个 插件扩展接口,当 span 开始和结束时会调用其回调。它们通常与 SpanExporters 配对,将 span 导出到进程外部,但也有其他应用,例如数据丰富。

SDK 内置的 Span processors 以及由社区在 opentelemetry-java-contrib 中维护的 Span processors

Artifact描述
BatchSpanProcessorio.opentelemetry:opentelemetry-sdk:1.57.0批量处理已采样的 span,并通过可配置的 SpanExporter 导出它们。
SimpleSpanProcessorio.opentelemetry:opentelemetry-sdk:1.57.0通过可配置的 SpanExporter 导出每个已采样的 span。
BaggageSpanProcessorio.opentelemetry.contrib:opentelemetry-baggage-processor:1.52.0-alpha用 baggage 丰富 span。
JfrSpanProcessorio.opentelemetry.contrib:opentelemetry-jfr-events:1.52.0-alpha从 span 创建 JFR 事件。
StackTraceSpanProcessorio.opentelemetry.contrib:opentelemetry-span-stacktrace:1.52.0-alpha用堆栈跟踪数据丰富选定的 span。
InferredSpansProcessorio.opentelemetry.contrib:opentelemetry-inferred-spans:1.52.0-alpha从 async profiler 生成 span,而不是从插装生成。

以下代码片段演示了 SpanProcessor 的程序化配置。

package otel;

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 java.time.Duration;

public class SpanProcessorConfig {
  public static SpanProcessor batchSpanProcessor(SpanExporter spanExporter) {
    return BatchSpanProcessor.builder(spanExporter)
        .setMaxQueueSize(2048)
        .setExporterTimeout(Duration.ofSeconds(30))
        .setScheduleDelay(Duration.ofSeconds(5))
        .build();
  }

  public static SpanProcessor simpleSpanProcessor(SpanExporter spanExporter) {
    return SimpleSpanProcessor.builder(spanExporter).build();
  }
}

实现 SpanProcessor 接口以提供您自己的自定义 span 处理逻辑。例如:

package otel;

import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.trace.ReadWriteSpan;
import io.opentelemetry.sdk.trace.ReadableSpan;
import io.opentelemetry.sdk.trace.SpanProcessor;

public class CustomSpanProcessor implements SpanProcessor {

  @Override
  public void onStart(Context parentContext, ReadWriteSpan span) {
    // Callback invoked when span is started.
    // Enrich the record with a custom attribute.
    span.setAttribute("my.custom.attribute", "hello world");
  }

  @Override
  public boolean isStartRequired() {
    // Indicate if onStart should be called.
    return true;
  }

  @Override
  public void onEnd(ReadableSpan span) {
    // Callback invoked when span is ended.
  }

  @Override
  public boolean isEndRequired() {
    // Indicate if onEnd should be called.
    return false;
  }

  @Override
  public CompletableResultCode shutdown() {
    // Optionally shutdown the processor and cleanup any resources.
    return CompletableResultCode.ofSuccess();
  }

  @Override
  public CompletableResultCode forceFlush() {
    // Optionally process any records which have been queued up but not yet processed.
    return CompletableResultCode.ofSuccess();
  }
}

SpanExporter

一个 SpanExporter 是一个 插件扩展接口,负责将 span 导出到进程外部。它们不直接注册到 SdkTracerProvider,而是与 SpanProcessors(通常是 BatchSpanProcessor)配对。

SDK 内置的 Span exporters 以及由社区在 opentelemetry-java-contrib 中维护的 Span exporters

Artifact描述
OtlpHttpSpanExporter [1]io.opentelemetry:opentelemetry-exporter-otlp:1.57.0通过 OTLP http/protobuf 导出 span。
OtlpGrpcSpanExporter [1]io.opentelemetry:opentelemetry-exporter-otlp:1.57.0通过 OTLP grpc 导出 span。
LoggingSpanExporterio.opentelemetry:opentelemetry-exporter-logging:1.57.0以调试格式将 span 日志记录到 JUL。
OtlpJsonLoggingSpanExporterio.opentelemetry:opentelemetry-exporter-logging-otlp:1.57.0以 OTLP JSON 编码将 span 日志记录到 JUL。
OtlpStdoutSpanExporterio.opentelemetry:opentelemetry-exporter-logging-otlp:1.57.0将 span 以 OTLP JSON 文件编码(实验性)格式输出到 System.out
ZipkinSpanExporterio.opentelemetry:opentelemetry-exporter-zipkin:1.57.0导出 span 到 Zipkin。
InterceptableSpanExporterio.opentelemetry.contrib:opentelemetry-processors:1.52.0-alpha在导出前将 span 传递给灵活的拦截器。
KafkaSpanExporterio.opentelemetry.contrib:opentelemetry-kafka-exporter:1.52.0-alpha通过写入 Kafka 主题来导出 span。

[1]:有关实现细节,请参阅 OTLP 导出器

以下代码片段演示了 SpanExporter 的程序化配置。

package otel;

import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingSpanExporter;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.time.Duration;

public class SpanExporterConfig {
  public static SpanExporter otlpHttpSpanExporter(String endpoint) {
    return OtlpHttpSpanExporter.builder()
        .setEndpoint(endpoint)
        .addHeader("api-key", "value")
        .setTimeout(Duration.ofSeconds(10))
        .build();
  }

  public static SpanExporter otlpGrpcSpanExporter(String endpoint) {
    return OtlpGrpcSpanExporter.builder()
        .setEndpoint(endpoint)
        .addHeader("api-key", "value")
        .setTimeout(Duration.ofSeconds(10))
        .build();
  }

  public static SpanExporter logginSpanExporter() {
    return LoggingSpanExporter.create();
  }

  public static SpanExporter otlpJsonLoggingSpanExporter() {
    return OtlpJsonLoggingSpanExporter.create();
  }
}

实现 SpanExporter 接口以提供您自己的自定义 span 导出逻辑。例如:

package otel;

import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;

public class CustomSpanExporter implements SpanExporter {

  private static final Logger logger = Logger.getLogger(CustomSpanExporter.class.getName());

  @Override
  public CompletableResultCode export(Collection<SpanData> spans) {
    // Export the records. Typically, records are sent out of process via some network protocol, but
    // we simply log for illustrative purposes.
    logger.log(Level.INFO, "Exporting spans");
    spans.forEach(span -> logger.log(Level.INFO, "Span: " + span));
    return CompletableResultCode.ofSuccess();
  }

  @Override
  public CompletableResultCode flush() {
    // Export any records which have been queued up but not yet exported.
    logger.log(Level.INFO, "flushing");
    return CompletableResultCode.ofSuccess();
  }

  @Override
  public CompletableResultCode shutdown() {
    // Shutdown the exporter and cleanup any resources.
    logger.log(Level.INFO, "shutting down");
    return CompletableResultCode.ofSuccess();
  }
}

SpanLimits

SpanLimits 定义了 span 捕获的数据的约束,包括最大属性长度、最大属性数量等。

以下代码片段演示了 SpanLimits 的程序化配置。

package otel;

import io.opentelemetry.sdk.trace.SpanLimits;

public class SpanLimitsConfig {
  public static SpanLimits spanLimits() {
    return SpanLimits.builder()
        .setMaxNumberOfAttributes(128)
        .setMaxAttributeValueLength(1024)
        .setMaxNumberOfLinks(128)
        .setMaxNumberOfAttributesPerLink(128)
        .setMaxNumberOfEvents(128)
        .setMaxNumberOfAttributesPerEvent(128)
        .build();
  }
}

SdkMeterProvider

SdkMeterProviderMeterProvider 的 SDK 实现,负责处理 API 生成的指标遥测数据。

SdkMeterProvider 由应用程序所有者配置,并包含:

  • Resource:指标所关联的资源。
  • MetricReader:读取指标的聚合状态。
    • 可选地,可以使用 CardinalityLimitSelector 按仪器类型覆盖基数限制。如果未设置,每个仪器在每次收集周期内限制为 2000 种独特的属性组合。通过 视图也可以为单个仪器配置基数限制。有关基数限制的更多详细信息,请参阅 基数限制
  • MetricExporter:将指标导出到进程外部(与关联的 MetricReader 一起)。
  • Views:配置指标流,包括丢弃未使用的指标。

以下代码片段演示了 SdkMeterProvider 的程序化配置。

package otel;

import io.opentelemetry.sdk.metrics.SdkMeterProvider;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import io.opentelemetry.sdk.resources.Resource;
import java.util.List;
import java.util.Set;

public class SdkMeterProviderConfig {
  public static SdkMeterProvider create(Resource resource) {
    SdkMeterProviderBuilder builder =
        SdkMeterProvider.builder()
            .setResource(resource)
            .registerMetricReader(
                MetricReaderConfig.periodicMetricReader(
                    MetricExporterConfig.otlpHttpMetricExporter(
                        "https://:4318/v1/metrics")));
    // Uncomment to optionally register metric reader with cardinality limits
    // builder.registerMetricReader(
    //     MetricReaderConfig.periodicMetricReader(
    //         MetricExporterConfig.otlpHttpMetricExporter("https://:4318/v1/metrics")),
    //     instrumentType -> 100);

    ViewConfig.dropMetricView(builder, "some.custom.metric");
    ViewConfig.histogramBucketBoundariesView(
        builder, "http.server.request.duration", List.of(1.0, 5.0, 10.0));
    ViewConfig.attributeFilterView(
        builder, "http.client.request.duration", Set.of("http.request.method"));
    ViewConfig.cardinalityLimitsView(builder, "http.server.active_requests", 100);
    return builder.build();
  }
}

MetricReader

一个 MetricReader 是一个 插件扩展接口,负责读取聚合后的指标。它们通常与 MetricExporters 配对,将指标导出到进程外部,但也可以用于在拉式协议中将指标提供给外部抓取器。

SDK 内置的 Metric readers 以及由社区在 opentelemetry-java-contrib 中维护的 Metric readers

Artifact描述
PeriodicMetricReaderio.opentelemetry:opentelemetry-sdk:1.57.0定期读取指标并通过可配置的 MetricExporter 导出它们。
PrometheusHttpServerio.opentelemetry:opentelemetry-exporter-prometheus:1.57.0-alpha以各种 Prometheus 格式在 HTTP 服务器上提供指标。

以下代码片段演示了 MetricReader 的程序化配置。

package otel;

import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
import java.time.Duration;

public class MetricReaderConfig {
  public static MetricReader periodicMetricReader(MetricExporter metricExporter) {
    return PeriodicMetricReader.builder(metricExporter).setInterval(Duration.ofSeconds(60)).build();
  }

  public static MetricReader prometheusMetricReader() {
    return PrometheusHttpServer.builder().setHost("localhost").setPort(9464).build();
  }
}

实现 MetricReader 接口以提供您自己的自定义指标读取器逻辑。例如:

package otel;

import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.export.MemoryMode;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
import io.opentelemetry.sdk.metrics.export.CollectionRegistration;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;

public class CustomMetricReader implements MetricReader {

  private static final Logger logger = Logger.getLogger(CustomMetricExporter.class.getName());

  private final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
  private final AtomicReference<CollectionRegistration> collectionRef =
      new AtomicReference<>(CollectionRegistration.noop());

  @Override
  public void register(CollectionRegistration collectionRegistration) {
    // Callback invoked when SdkMeterProvider is initialized, providing a handle to collect metrics.
    collectionRef.set(collectionRegistration);
    executorService.scheduleWithFixedDelay(this::collectMetrics, 0, 60, TimeUnit.SECONDS);
  }

  private void collectMetrics() {
    // Collect metrics. Typically, records are sent out of process via some network protocol, but we
    // simply log for illustrative purposes.
    logger.log(Level.INFO, "Collecting metrics");
    collectionRef
        .get()
        .collectAllMetrics()
        .forEach(metric -> logger.log(Level.INFO, "Metric: " + metric));
  }

  @Override
  public CompletableResultCode forceFlush() {
    // Export any records which have been queued up but not yet exported.
    logger.log(Level.INFO, "flushing");
    return CompletableResultCode.ofSuccess();
  }

  @Override
  public CompletableResultCode shutdown() {
    // Shutdown the exporter and cleanup any resources.
    logger.log(Level.INFO, "shutting down");
    return CompletableResultCode.ofSuccess();
  }

  @Override
  public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
    // Specify the required aggregation temporality as a function of instrument type
    return AggregationTemporalitySelector.deltaPreferred()
        .getAggregationTemporality(instrumentType);
  }

  @Override
  public MemoryMode getMemoryMode() {
    // Optionally specify the memory mode, indicating whether metric records can be reused or must
    // be immutable
    return MemoryMode.REUSABLE_DATA;
  }

  @Override
  public Aggregation getDefaultAggregation(InstrumentType instrumentType) {
    // Optionally specify the default aggregation as a function of instrument kind
    return Aggregation.defaultAggregation();
  }
}

MetricExporter

一个 MetricExporter 是一个 插件扩展接口,负责将指标导出到进程外部。它们不直接注册到 SdkMeterProvider,而是与 PeriodicMetricReader 配对。

SDK 内置的 Metric exporters 以及由社区在 opentelemetry-java-contrib 中维护的 Metric exporters

Artifact描述
OtlpHttpMetricExporter [1]io.opentelemetry:opentelemetry-exporter-otlp:1.57.0通过 OTLP http/protobuf 导出指标。
OtlpGrpcMetricExporter [1]io.opentelemetry:opentelemetry-exporter-otlp:1.57.0通过 OTLP grpc 导出指标。
LoggingMetricExporterio.opentelemetry:opentelemetry-exporter-logging:1.57.0以调试格式将指标日志记录到 JUL。
OtlpJsonLoggingMetricExporterio.opentelemetry:opentelemetry-exporter-logging-otlp:1.57.0以 OTLP JSON 编码将指标日志记录到 JUL。
OtlpStdoutMetricExporterio.opentelemetry:opentelemetry-exporter-logging-otlp:1.57.0将指标以 OTLP JSON 文件编码(实验性)格式输出到 System.out
InterceptableMetricExporterio.opentelemetry.contrib:opentelemetry-processors:1.52.0-alpha在导出前将指标传递给灵活的拦截器。

[1]:有关实现细节,请参阅 OTLP 导出器

以下代码片段演示了 MetricExporter 的程序化配置。

package otel;

import io.opentelemetry.exporter.logging.LoggingMetricExporter;
import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter;
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import java.time.Duration;

public class MetricExporterConfig {
  public static MetricExporter otlpHttpMetricExporter(String endpoint) {
    return OtlpHttpMetricExporter.builder()
        .setEndpoint(endpoint)
        .addHeader("api-key", "value")
        .setTimeout(Duration.ofSeconds(10))
        .build();
  }

  public static MetricExporter otlpGrpcMetricExporter(String endpoint) {
    return OtlpGrpcMetricExporter.builder()
        .setEndpoint(endpoint)
        .addHeader("api-key", "value")
        .setTimeout(Duration.ofSeconds(10))
        .build();
  }

  public static MetricExporter logginMetricExporter() {
    return LoggingMetricExporter.create();
  }

  public static MetricExporter otlpJsonLoggingMetricExporter() {
    return OtlpJsonLoggingMetricExporter.create();
  }
}

实现 MetricExporter 接口以提供您自己的自定义指标导出逻辑。例如:

package otel;

import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.common.export.MemoryMode;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;

public class CustomMetricExporter implements MetricExporter {

  private static final Logger logger = Logger.getLogger(CustomMetricExporter.class.getName());

  @Override
  public CompletableResultCode export(Collection<MetricData> metrics) {
    // Export the records. Typically, records are sent out of process via some network protocol, but
    // we simply log for illustrative purposes.
    logger.log(Level.INFO, "Exporting metrics");
    metrics.forEach(metric -> logger.log(Level.INFO, "Metric: " + metric));
    return CompletableResultCode.ofSuccess();
  }

  @Override
  public CompletableResultCode flush() {
    // Export any records which have been queued up but not yet exported.
    logger.log(Level.INFO, "flushing");
    return CompletableResultCode.ofSuccess();
  }

  @Override
  public CompletableResultCode shutdown() {
    // Shutdown the exporter and cleanup any resources.
    logger.log(Level.INFO, "shutting down");
    return CompletableResultCode.ofSuccess();
  }

  @Override
  public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
    // Specify the required aggregation temporality as a function of instrument type
    return AggregationTemporalitySelector.deltaPreferred()
        .getAggregationTemporality(instrumentType);
  }

  @Override
  public MemoryMode getMemoryMode() {
    // Optionally specify the memory mode, indicating whether metric records can be reused or must
    // be immutable
    return MemoryMode.REUSABLE_DATA;
  }

  @Override
  public Aggregation getDefaultAggregation(InstrumentType instrumentType) {
    // Optionally specify the default aggregation as a function of instrument kind
    return Aggregation.defaultAggregation();
  }
}

Views (视图)

Views 允许自定义指标流,包括更改指标名称、指标描述、指标聚合(即直方图存储桶边界)、要保留的属性键集、基数限制等。

以下代码片段演示了 View 的程序化配置。

package otel;

import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentSelector;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import io.opentelemetry.sdk.metrics.View;
import java.util.List;
import java.util.Set;

public class ViewConfig {
  public static SdkMeterProviderBuilder dropMetricView(
      SdkMeterProviderBuilder builder, String metricName) {
    return builder.registerView(
        InstrumentSelector.builder().setName(metricName).build(),
        View.builder().setAggregation(Aggregation.drop()).build());
  }

  public static SdkMeterProviderBuilder histogramBucketBoundariesView(
      SdkMeterProviderBuilder builder, String metricName, List<Double> bucketBoundaries) {
    return builder.registerView(
        InstrumentSelector.builder().setName(metricName).build(),
        View.builder()
            .setAggregation(Aggregation.explicitBucketHistogram(bucketBoundaries))
            .build());
  }

  public static SdkMeterProviderBuilder attributeFilterView(
      SdkMeterProviderBuilder builder, String metricName, Set<String> keysToRetain) {
    return builder.registerView(
        InstrumentSelector.builder().setName(metricName).build(),
        View.builder().setAttributeFilter(keysToRetain).build());
  }

  public static SdkMeterProviderBuilder cardinalityLimitsView(
      SdkMeterProviderBuilder builder, String metricName, int cardinalityLimit) {
    return builder.registerView(
        InstrumentSelector.builder().setName(metricName).build(),
        View.builder().setCardinalityLimit(cardinalityLimit).build());
  }
}

SdkLoggerProvider

SdkLoggerProviderLoggerProvider 的 SDK 实现,负责处理日志桥 API 生成的日志遥测数据。

SdkLoggerProvider 由应用程序所有者配置,并包含:

  • Resource:日志所关联的资源。
  • LogRecordProcessor:在日志记录发出时处理日志记录。
  • LogRecordExporter:将日志记录导出到进程外部(与关联的 LogRecordProcessor 一起)。
  • LogLimits:控制与日志记录相关联的数据的限制。

以下代码片段演示了 SdkLoggerProvider 的程序化配置。

package otel;

import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.resources.Resource;

public class SdkLoggerProviderConfig {
  public static SdkLoggerProvider create(Resource resource) {
    return SdkLoggerProvider.builder()
        .setResource(resource)
        .addLogRecordProcessor(
            LogRecordProcessorConfig.batchLogRecordProcessor(
                LogRecordExporterConfig.otlpHttpLogRecordExporter("https://:4318/v1/logs")))
        .setLogLimits(LogLimitsConfig::logLimits)
        .build();
  }
}

LogRecordProcessor

一个 LogRecordProcessor 是一个 插件扩展接口,当日志记录发出时会调用其回调。它们通常与 LogRecordExporters 配对,将日志记录导出到进程外部,但也有其他应用,例如数据丰富。

SDK 内置的 Log record processors 以及由社区在 opentelemetry-java-contrib 中维护的 Log record processors

Artifact描述
BatchLogRecordProcessorio.opentelemetry:opentelemetry-sdk:1.57.0批量处理日志记录,并通过可配置的 LogRecordExporter 导出它们。
SimpleLogRecordProcessorio.opentelemetry:opentelemetry-sdk:1.57.0通过可配置的 LogRecordExporter 导出每个日志记录。
EventToSpanEventBridgeio.opentelemetry.contrib:opentelemetry-processors:1.52.0-alpha将事件日志记录作为 span 事件记录在当前 span 上。

以下代码片段演示了 LogRecordProcessor 的程序化配置。

package otel;

import io.opentelemetry.sdk.logs.LogRecordProcessor;
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import io.opentelemetry.sdk.logs.export.SimpleLogRecordProcessor;
import java.time.Duration;

public class LogRecordProcessorConfig {
  public static LogRecordProcessor batchLogRecordProcessor(LogRecordExporter logRecordExporter) {
    return BatchLogRecordProcessor.builder(logRecordExporter)
        .setMaxQueueSize(2048)
        .setExporterTimeout(Duration.ofSeconds(30))
        .setScheduleDelay(Duration.ofSeconds(1))
        .build();
  }

  public static LogRecordProcessor simpleLogRecordProcessor(LogRecordExporter logRecordExporter) {
    return SimpleLogRecordProcessor.create(logRecordExporter);
  }
}

实现 LogRecordProcessor 接口以提供您自己的自定义日志处理逻辑。例如:

package otel;

import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.context.Context;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.LogRecordProcessor;
import io.opentelemetry.sdk.logs.ReadWriteLogRecord;

public class CustomLogRecordProcessor implements LogRecordProcessor {

  @Override
  public void onEmit(Context context, ReadWriteLogRecord logRecord) {
    // Callback invoked when log record is emitted.
    // Enrich the record with a custom attribute.
    logRecord.setAttribute(AttributeKey.stringKey("my.custom.attribute"), "hello world");
  }

  @Override
  public CompletableResultCode shutdown() {
    // Optionally shutdown the processor and cleanup any resources.
    return CompletableResultCode.ofSuccess();
  }

  @Override
  public CompletableResultCode forceFlush() {
    // Optionally process any records which have been queued up but not yet processed.
    return CompletableResultCode.ofSuccess();
  }
}

LogRecordExporter

一个 LogRecordExporter 是一个 插件扩展接口,负责将日志记录导出到进程外部。它们不直接注册到 SdkLoggerProvider,而是与 LogRecordProcessors(通常是 BatchLogRecordProcessor)配对。

SDK 内置的 Log record exporters 以及由社区在 opentelemetry-java-contrib 中维护的 Log record exporters

Artifact描述
OtlpHttpLogRecordExporter [1]io.opentelemetry:opentelemetry-exporter-otlp:1.57.0通过 OTLP http/protobuf 导出日志记录。
OtlpGrpcLogRecordExporter [1]io.opentelemetry:opentelemetry-exporter-otlp:1.57.0通过 OTLP grpc 导出日志记录。
SystemOutLogRecordExporterio.opentelemetry:opentelemetry-exporter-logging:1.57.0以调试格式将日志记录输出到标准输出。
OtlpJsonLoggingLogRecordExporter [2]io.opentelemetry:opentelemetry-exporter-logging-otlp:1.57.0以 OTLP JSON 编码将日志记录输出到 JUL。
OtlpStdoutLogRecordExporterio.opentelemetry:opentelemetry-exporter-logging-otlp:1.57.0将日志记录以 OTLP JSON 文件编码(实验性)格式输出到 System.out
InterceptableLogRecordExporterio.opentelemetry.contrib:opentelemetry-processors:1.52.0-alpha在导出前将日志记录传递给灵活的拦截器。

[1]:有关实现细节,请参阅 OTLP 导出器

[2]OtlpJsonLoggingLogRecordExporter 会将日志记录输出到 JUL,如果配置不当,可能会导致无限循环(例如,JUL -> SLF4J -> Logback -> OpenTelemetry Appender -> OpenTelemetry Log SDK -> JUL)。

以下代码片段演示了 LogRecordExporter 的程序化配置。

package otel;

import io.opentelemetry.exporter.logging.SystemOutLogRecordExporter;
import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingLogRecordExporter;
import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import java.time.Duration;

public class LogRecordExporterConfig {
  public static LogRecordExporter otlpHttpLogRecordExporter(String endpoint) {
    return OtlpHttpLogRecordExporter.builder()
        .setEndpoint(endpoint)
        .addHeader("api-key", "value")
        .setTimeout(Duration.ofSeconds(10))
        .build();
  }

  public static LogRecordExporter otlpGrpcLogRecordExporter(String endpoint) {
    return OtlpGrpcLogRecordExporter.builder()
        .setEndpoint(endpoint)
        .addHeader("api-key", "value")
        .setTimeout(Duration.ofSeconds(10))
        .build();
  }

  public static LogRecordExporter systemOutLogRecordExporter() {
    return SystemOutLogRecordExporter.create();
  }

  public static LogRecordExporter otlpJsonLoggingLogRecordExporter() {
    return OtlpJsonLoggingLogRecordExporter.create();
  }
}

实现 LogRecordExporter 接口以提供您自己的自定义日志记录导出逻辑。例如:

package otel;

import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;

public class CustomLogRecordExporter implements LogRecordExporter {

  private static final Logger logger = Logger.getLogger(CustomLogRecordExporter.class.getName());

  @Override
  public CompletableResultCode export(Collection<LogRecordData> logs) {
    // Export the records. Typically, records are sent out of process via some network protocol, but
    // we simply log for illustrative purposes.
    System.out.println("Exporting logs");
    logs.forEach(log -> System.out.println("log record: " + log));
    return CompletableResultCode.ofSuccess();
  }

  @Override
  public CompletableResultCode flush() {
    // Export any records which have been queued up but not yet exported.
    logger.log(Level.INFO, "flushing");
    return CompletableResultCode.ofSuccess();
  }

  @Override
  public CompletableResultCode shutdown() {
    // Shutdown the exporter and cleanup any resources.
    logger.log(Level.INFO, "shutting down");
    return CompletableResultCode.ofSuccess();
  }
}

LogLimits

LogLimits 定义了日志记录捕获数据的约束,包括最大属性长度和最大属性数量。

以下代码片段演示了 LogLimits 的程序化配置。

package otel;

import io.opentelemetry.sdk.logs.LogLimits;

public class LogLimitsConfig {
  public static LogLimits logLimits() {
    return LogLimits.builder()
        .setMaxNumberOfAttributes(128)
        .setMaxAttributeValueLength(1024)
        .build();
  }
}

TextMapPropagator

TextMapPropagator 是一个 插件扩展接口,负责以文本格式在进程边界上传播上下文。

SDK 内置的 TextMapPropagators 以及由社区在 opentelemetry-java-contrib 中维护的 TextMapPropagators

Artifact描述
W3CTraceContextPropagatorio.opentelemetry:opentelemetry-api:1.57.0使用 W3C trace context 传播协议传播跟踪上下文。
W3CBaggagePropagatorio.opentelemetry:opentelemetry-api:1.57.0使用 W3C baggage 传播协议传播 baggage。
MultiTextMapPropagatorio.opentelemetry:opentelemetry-context:1.57.0组合多个传播器。
JaegerPropagatorio.opentelemetry:opentelemetry-extension-trace-propagators:1.57.0使用 Jaeger 传播协议传播跟踪上下文。
B3Propagatorio.opentelemetry:opentelemetry-extension-trace-propagators:1.57.0使用 B3 传播协议传播跟踪上下文。
OtTracePropagatorio.opentelemetry:opentelemetry-extension-trace-propagators:1.57.0使用 OpenTracing 传播协议传播跟踪上下文。
PassThroughPropagatorio.opentelemetry:opentelemetry-api-incubator:1.57.0-alpha传播可配置的一组字段,而不参与遥测。
AwsXrayPropagatorio.opentelemetry.contrib:opentelemetry-aws-xray-propagator:1.52.0-alpha使用 AWS X-Ray 传播协议传播跟踪上下文。
AwsXrayLambdaPropagatorio.opentelemetry.contrib:opentelemetry-aws-xray-propagator:1.52.0-alpha使用环境变量和 AWS X-Ray 传播协议传播跟踪上下文。

以下代码片段演示了 TextMapPropagator 的程序化配置。

package otel;

import io.opentelemetry.api.baggage.propagation.W3CBaggagePropagator;
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;

public class ContextPropagatorsConfig {
  public static ContextPropagators create() {
    return ContextPropagators.create(
        TextMapPropagator.composite(
            W3CTraceContextPropagator.getInstance(), W3CBaggagePropagator.getInstance()));
  }
}

实现 TextMapPropagator 接口以提供您自己的自定义传播器逻辑。例如:

package otel;

import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapGetter;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.context.propagation.TextMapSetter;
import java.util.Collection;
import java.util.Collections;

public class CustomTextMapPropagator implements TextMapPropagator {

  @Override
  public Collection<String> fields() {
    // Return fields used for propagation. See W3CTraceContextPropagator for reference
    // implementation.
    return Collections.emptyList();
  }

  @Override
  public <C> void inject(Context context, C carrier, TextMapSetter<C> setter) {
    // Inject context. See W3CTraceContextPropagator for reference implementation.
  }

  @Override
  public <C> Context extract(Context context, C carrier, TextMapGetter<C> getter) {
    // Extract context. See W3CTraceContextPropagator for reference implementation.
    return context;
  }
}

附录

内部日志记录

SDK 组件将各种信息记录到 java.util.logging,使用不同的日志级别,并使用基于相关组件的完全限定类名的日志记录器名称。

默认情况下,日志消息由应用程序的根处理器处理。如果尚未为应用程序安装自定义根处理器,则默认情况下,级别为 INFO 或更高级别的日志将发送到控制台。

您可能希望更改 OpenTelemetry 日志记录器的行为。例如,您可以降低日志记录级别以输出额外的调试信息,提高特定类的级别以忽略该类产生的错误,或者安装自定义处理器或过滤器,以便在 OpenTelemetry 记录特定消息时运行自定义代码。没有维护详细的日志记录器名称和日志信息列表。但是,所有 OpenTelemetry API、SDK、contrib 和插装组件都共享相同的 io.opentelemetry.* 包前缀。启用更精细粒度的日志记录所有 io.opentelemetry.*,检查输出,然后缩小到感兴趣的包或 FQCN 可能很有用。

例如

## Turn off all OpenTelemetry logging
io.opentelemetry.level = OFF
## Turn off logging for just the BatchSpanProcessor
io.opentelemetry.sdk.trace.export.BatchSpanProcessor.level = OFF
## Log "FINE" messages for help in debugging
io.opentelemetry.level = FINE

## Sets the default ConsoleHandler's logger's level
## Note this impacts the logging outside of OpenTelemetry as well
java.util.logging.ConsoleHandler.level = FINE

为了进行更精细粒度的控制和特殊情况处理,可以使用代码指定自定义处理器和过滤器。

// Custom filter which does not log errors which come from the export
public class IgnoreExportErrorsFilter implements java.util.logging.Filter {

 public boolean isLoggable(LogRecord record) {
    return !record.getMessage().contains("Exception thrown by the export");
 }
}
## Registering the custom filter on the BatchSpanProcessor
io.opentelemetry.sdk.trace.export.BatchSpanProcessor = io.opentelemetry.extension.logging.IgnoreExportErrorsFilter

OTLP 导出器

span 导出器metric 导出器log 导出器部分描述了以下形式的 OTLP 导出器:

  • OtlpHttp{Signal}Exporter,通过 OTLP http/protobuf 导出数据。
  • OtlpGrpc{Signal}Exporter,通过 OTLP grpc 导出数据。

所有信号的导出器都可以通过 io.opentelemetry:opentelemetry-exporter-otlp:1.57.0 获得,并且在 OTLP 协议的 grpchttp/protobuf 版本之间,以及在信号之间有很大的重叠。以下部分将详细介绍这些关键概念:

  • Senders:用于不同 HTTP / gRPC 客户端库的抽象。
  • OTLP 导出器的 身份验证选项。

Sender

OTLP 导出器依赖于各种客户端库来执行 HTTP 和 gRPC 请求。Java 生态系统中没有一个单一的 HTTP / gRPC 客户端库能够满足所有用例。

  • Java 11+ 带来了内置的 java.net.http.HttpClient,但 opentelemetry-java 需要支持 Java 8+ 用户,并且由于不支持尾部头信息,因此无法用于通过 gRPC 导出。
  • OkHttp 提供了一个强大的 HTTP 客户端,支持尾部头信息,但依赖于 kotlin 标准库。
  • grpc-java 提供了自己的 ManagedChannel 抽象,具有各种 传输实现,但不适合 http/protobuf

为了适应各种用例,opentelemetry-exporter-otlp 使用内部的“发送器”抽象,并提供多种实现来适应不同的应用约束。要选择其他实现,请排除默认依赖项 io.opentelemetry:opentelemetry-exporter-sender-okhttp,然后添加对替代项的依赖。

Artifact描述OTLP 协议默认值
io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.57.0基于 OkHttp 的实现。grpc, http/protobuf
io.opentelemetry:opentelemetry-exporter-sender-jdk:1.57.0基于 Java 11+ java.net.http.HttpClient 的实现。http/protobuf
io.opentelemetry:opentelemetry-exporter-sender-grpc-managed-channel:1.57.0 [1]基于 grpc-java ManagedChannel 的实现。grpc

[1]: 要使用 opentelemetry-exporter-sender-grpc-managed-channel,您还必须添加对 gRPC 传输实现的依赖。

身份验证

OTLP 导出器提供了用于静态和动态基于标头的身份验证以及 mTLS 的机制。

如果使用 零代码 SDK 自动配置,并通过环境变量和系统属性进行配置,请参阅 相关系统属性

  • otel.exporter.otlp.headers 用于静态基于标头的身份验证。
  • otel.exporter.otlp.client.key, otel.exporter.otlp.client.certificate 用于 mTLS 身份验证。

以下代码片段演示了静态和动态基于标头的身份验证的程序化配置

package otel;

import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import java.time.Duration;
import java.time.Instant;
import java.util.Collections;
import java.util.Map;
import java.util.function.Supplier;

public class OtlpAuthenticationConfig {
  public static void staticAuthenticationHeader(String endpoint) {
    // If the OTLP destination accepts a static, long-lived authentication header like an API key,
    // set it as a header.
    // This reads the API key from the OTLP_API_KEY env var to avoid hard coding the secret in
    // source code.
    String apiKeyHeaderName = "api-key";
    String apiKeyHeaderValue = System.getenv("OTLP_API_KEY");

    // Initialize OTLP Span, Metric, and LogRecord exporters using a similar pattern
    OtlpHttpSpanExporter spanExporter =
        OtlpHttpSpanExporter.builder()
            .setEndpoint(endpoint)
            .addHeader(apiKeyHeaderName, apiKeyHeaderValue)
            .build();
    OtlpHttpMetricExporter metricExporter =
        OtlpHttpMetricExporter.builder()
            .setEndpoint(endpoint)
            .addHeader(apiKeyHeaderName, apiKeyHeaderValue)
            .build();
    OtlpHttpLogRecordExporter logRecordExporter =
        OtlpHttpLogRecordExporter.builder()
            .setEndpoint(endpoint)
            .addHeader(apiKeyHeaderName, apiKeyHeaderValue)
            .build();
  }

  public static void dynamicAuthenticationHeader(String endpoint) {
    // If the OTLP destination requires a dynamic authentication header, such as a JWT which needs
    // to be periodically refreshed, use a header supplier.
    // Here we implement a simple supplier which adds a header of the form "Authorization: Bearer
    // <token>", where <token> is fetched from refreshBearerToken every 10 minutes.
    String username = System.getenv("OTLP_USERNAME");
    String password = System.getenv("OTLP_PASSWORD");
    Supplier<Map<String, String>> supplier =
        new AuthHeaderSupplier(() -> refreshToken(username, password), Duration.ofMinutes(10));

    // Initialize OTLP Span, Metric, and LogRecord exporters using a similar pattern
    OtlpHttpSpanExporter spanExporter =
        OtlpHttpSpanExporter.builder().setEndpoint(endpoint).setHeaders(supplier).build();
    OtlpHttpMetricExporter metricExporter =
        OtlpHttpMetricExporter.builder().setEndpoint(endpoint).setHeaders(supplier).build();
    OtlpHttpLogRecordExporter logRecordExporter =
        OtlpHttpLogRecordExporter.builder().setEndpoint(endpoint).setHeaders(supplier).build();
  }

  private static class AuthHeaderSupplier implements Supplier<Map<String, String>> {
    private final Supplier<String> tokenRefresher;
    private final Duration tokenRefreshInterval;
    private Instant refreshedAt = Instant.ofEpochMilli(0);
    private String currentTokenValue;

    private AuthHeaderSupplier(Supplier<String> tokenRefresher, Duration tokenRefreshInterval) {
      this.tokenRefresher = tokenRefresher;
      this.tokenRefreshInterval = tokenRefreshInterval;
    }

    @Override
    public Map<String, String> get() {
      return Collections.singletonMap("Authorization", "Bearer " + getToken());
    }

    private synchronized String getToken() {
      Instant now = Instant.now();
      if (currentTokenValue == null || now.isAfter(refreshedAt.plus(tokenRefreshInterval))) {
        currentTokenValue = tokenRefresher.get();
        refreshedAt = now;
      }
      return currentTokenValue;
    }
  }

  private static String refreshToken(String username, String password) {
    // For a production scenario, this would be replaced with an out-of-band request to exchange
    // username / password for bearer token.
    return "abc123";
  }
}

测试

待办事项:记录用于测试 SDK 的工具


最后修改于 2025 年 10 月 20 日:更新 sdk.md (#8176) (ff6284d6)