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