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        PrometheusMetricProducer.builder(
044                registry, instrumentationScopeInfo, getResourceField(sdk), preserveNames)
045            .exporterFilterProperties(config.getExporterFilterProperties())
046            .build());
047    return reader;
048  }
049
050  static AutoConfiguredOpenTelemetrySdk createAutoConfiguredOpenTelemetrySdk(
051      OpenTelemetryExporter.Builder builder,
052      AtomicReference<MetricReader> readerRef,
053      ExporterOpenTelemetryProperties properties,
054      InstrumentationScopeInfo instrumentationScopeInfo) {
055    PropertyMapper propertyMapper = PropertyMapper.create(properties, builder);
056
057    return AutoConfiguredOpenTelemetrySdk.builder()
058        .addPropertiesSupplier(() -> propertyMapper.configLowPriority)
059        .addPropertiesCustomizer(
060            c -> PropertyMapper.customizeProperties(propertyMapper.configHighPriority, c))
061        .addMetricReaderCustomizer(
062            (reader, unused) -> {
063              readerRef.set(reader);
064              return reader;
065            })
066        .addResourceCustomizer(
067            (resource, c) ->
068                getResource(builder, resource, instrumentationScopeInfo, c, properties))
069        .build();
070  }
071
072  private static Resource getResource(
073      OpenTelemetryExporter.Builder builder,
074      Resource resource,
075      InstrumentationScopeInfo instrumentationScopeInfo,
076      ConfigProperties configProperties,
077      ExporterOpenTelemetryProperties properties) {
078    return resource
079        .merge(
080            PropertiesResourceProvider.mergeResource(
081                builder.resourceAttributes,
082                builder.serviceName,
083                builder.serviceNamespace,
084                builder.serviceInstanceId,
085                builder.serviceVersion))
086        .merge(ResourceConfiguration.createEnvironmentResource(configProperties))
087        .merge(
088            PropertiesResourceProvider.mergeResource(
089                properties.getResourceAttributes(),
090                properties.getServiceName(),
091                properties.getServiceNamespace(),
092                properties.getServiceInstanceId(),
093                properties.getServiceVersion()))
094        .merge(Resource.create(otelResourceAttributes(instrumentationScopeInfo)));
095  }
096
097  /**
098   * Only copy the service instance id from the Otel agent resource attributes.
099   *
100   * <p>All other attributes are calculated from the configuration using OTel SDK AutoConfig.
101   */
102  private static Attributes otelResourceAttributes(
103      InstrumentationScopeInfo instrumentationScopeInfo) {
104    AttributesBuilder builder = Attributes.builder();
105    Map<String, String> attributes =
106        ResourceAttributesFromOtelAgent.getResourceAttributes(instrumentationScopeInfo.getName());
107    String id = attributes.get(SERVICE_INSTANCE_ID);
108    if (id != null) {
109      builder.put(SERVICE_INSTANCE_ID, id);
110    }
111    return builder.build();
112  }
113
114  static boolean resolvePreserveNames(
115      OpenTelemetryExporter.Builder builder, PrometheusProperties config) {
116    if (builder.preserveNames != null) {
117      return builder.preserveNames;
118    }
119    Boolean fromConfig = config.getExporterOpenTelemetryProperties().getPreserveNames();
120    return fromConfig != null && fromConfig;
121  }
122
123  static Resource getResourceField(AutoConfiguredOpenTelemetrySdk sdk) {
124    try {
125      Method method = AutoConfiguredOpenTelemetrySdk.class.getDeclaredMethod("getResource");
126      method.setAccessible(true);
127      return (Resource) method.invoke(sdk);
128    } catch (ReflectiveOperationException e) {
129      throw new RuntimeException(e);
130    }
131  }
132}