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