001package io.prometheus.metrics.exporter.opentelemetry;
002
003import static java.util.Objects.requireNonNull;
004
005import io.opentelemetry.api.common.Attributes;
006import io.opentelemetry.api.common.AttributesBuilder;
007import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
008import io.opentelemetry.sdk.autoconfigure.ResourceConfiguration;
009import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
010import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
011import io.opentelemetry.sdk.metrics.export.MetricReader;
012import io.opentelemetry.sdk.resources.Resource;
013import io.prometheus.metrics.config.ExporterOpenTelemetryProperties;
014import io.prometheus.metrics.config.PrometheusProperties;
015import io.prometheus.metrics.model.registry.PrometheusRegistry;
016import io.prometheus.otelagent.ResourceAttributesFromOtelAgent;
017import java.lang.reflect.Method;
018import java.util.Map;
019import java.util.concurrent.atomic.AtomicReference;
020
021public class OtelAutoConfig {
022
023  private static final String SERVICE_INSTANCE_ID = "service.instance.id";
024
025  static MetricReader createReader(
026      OpenTelemetryExporter.Builder builder,
027      PrometheusProperties config,
028      PrometheusRegistry registry) {
029    AtomicReference<MetricReader> readerRef = new AtomicReference<>();
030    InstrumentationScopeInfo instrumentationScopeInfo =
031        PrometheusInstrumentationScope.loadInstrumentationScopeInfo();
032
033    AutoConfiguredOpenTelemetrySdk sdk =
034        createAutoConfiguredOpenTelemetrySdk(
035            builder,
036            readerRef,
037            config.getExporterOpenTelemetryProperties(),
038            instrumentationScopeInfo);
039
040    MetricReader reader = requireNonNull(readerRef.get());
041    boolean preserveNames = resolvePreserveNames(builder, config);
042    reader.register(
043        new PrometheusMetricProducer(
044            registry, instrumentationScopeInfo, getResourceField(sdk), preserveNames));
045    return reader;
046  }
047
048  static AutoConfiguredOpenTelemetrySdk createAutoConfiguredOpenTelemetrySdk(
049      OpenTelemetryExporter.Builder builder,
050      AtomicReference<MetricReader> readerRef,
051      ExporterOpenTelemetryProperties properties,
052      InstrumentationScopeInfo instrumentationScopeInfo) {
053    PropertyMapper propertyMapper = PropertyMapper.create(properties, builder);
054
055    return AutoConfiguredOpenTelemetrySdk.builder()
056        .addPropertiesSupplier(() -> propertyMapper.configLowPriority)
057        .addPropertiesCustomizer(
058            c -> PropertyMapper.customizeProperties(propertyMapper.configHighPriority, c))
059        .addMetricReaderCustomizer(
060            (reader, unused) -> {
061              readerRef.set(reader);
062              return reader;
063            })
064        .addResourceCustomizer(
065            (resource, c) ->
066                getResource(builder, resource, instrumentationScopeInfo, c, properties))
067        .build();
068  }
069
070  private static Resource getResource(
071      OpenTelemetryExporter.Builder builder,
072      Resource resource,
073      InstrumentationScopeInfo instrumentationScopeInfo,
074      ConfigProperties configProperties,
075      ExporterOpenTelemetryProperties properties) {
076    return resource
077        .merge(
078            PropertiesResourceProvider.mergeResource(
079                builder.resourceAttributes,
080                builder.serviceName,
081                builder.serviceNamespace,
082                builder.serviceInstanceId,
083                builder.serviceVersion))
084        .merge(ResourceConfiguration.createEnvironmentResource(configProperties))
085        .merge(
086            PropertiesResourceProvider.mergeResource(
087                properties.getResourceAttributes(),
088                properties.getServiceName(),
089                properties.getServiceNamespace(),
090                properties.getServiceInstanceId(),
091                properties.getServiceVersion()))
092        .merge(Resource.create(otelResourceAttributes(instrumentationScopeInfo)));
093  }
094
095  /**
096   * Only copy the service instance id from the Otel agent resource attributes.
097   *
098   * <p>All other attributes are calculated from the configuration using OTel SDK AutoConfig.
099   */
100  private static Attributes otelResourceAttributes(
101      InstrumentationScopeInfo instrumentationScopeInfo) {
102    AttributesBuilder builder = Attributes.builder();
103    Map<String, String> attributes =
104        ResourceAttributesFromOtelAgent.getResourceAttributes(instrumentationScopeInfo.getName());
105    String id = attributes.get(SERVICE_INSTANCE_ID);
106    if (id != null) {
107      builder.put(SERVICE_INSTANCE_ID, id);
108    }
109    return builder.build();
110  }
111
112  static boolean resolvePreserveNames(
113      OpenTelemetryExporter.Builder builder, PrometheusProperties config) {
114    if (builder.preserveNames != null) {
115      return builder.preserveNames;
116    }
117    Boolean fromConfig = config.getExporterOpenTelemetryProperties().getPreserveNames();
118    return fromConfig != null && fromConfig;
119  }
120
121  static Resource getResourceField(AutoConfiguredOpenTelemetrySdk sdk) {
122    try {
123      Method method = AutoConfiguredOpenTelemetrySdk.class.getDeclaredMethod("getResource");
124      method.setAccessible(true);
125      return (Resource) method.invoke(sdk);
126    } catch (ReflectiveOperationException e) {
127      throw new RuntimeException(e);
128    }
129  }
130}