Description
Hi!
I would like to propose an improvement in the way the OpenTelemetry metrics and tracing are auto-configured.
This is related to the issue: #30156 and my comment at the end.
As a starting point, the tracing OpenTelemetryAutoConfiguration
sets up an instance of the OpenTelemetry and fetches a list of tracing providers. It is a great way to specify a non-supported exporter (e.g. OtlpGrpcSpanExporter
instead of brave/zipkin/wavefront).
But the OtlpMetricsExportAutoConfiguration
does not use the OpenTelemetry instance at all and registers the OtlpMeterRegistry
based on a configuration. Currently I have no way of specifying a OtlpGrpcMetricExporter
that it want to use.
My proposition is to have separate basic OpenTelemetry auto-configuration, a OpenTelemetry tracing auto-configuration and finally a OpenTelemetry metrics auto-configuration.
The main OpenTeleletry autoconfiguration would have the following:
@Bean
@ConditionalOnMissingBean
// puts the sevice name based on the spring.application.name, additionally reads in additional attributes like OtlpMetricsExportAutoConfiguration does
fun otelResource(environment: Environment): Resource {
val applicationName = environment.getProperty("spring.application.name", "application")
return Resource.create(Attributes.of(ResourceAttributes.SERVICE_NAME, applicationName))
}
@Bean
@ConditionalOnMissingBean
fun openTelemetry(
sdkTracerProvider: ObjectProvider<SdkTracerProvider>,
sdkMeterProvider: ObjectProvider<SdkMeterProvider>, // This is the new bit - custom SdkMeterProvider that is set up like SdkTracerProvider allowing me to create a OtlpGrpcMetricExporter bean
contextPropagators: ObjectProvider<ContextPropagators>
): OpenTelemetry {
val builder = OpenTelemetrySdk.builder()
contextPropagators.ifUnique {
builder.setPropagators(it)
}
sdkMeterProvider.ifUnique {
builder.setMeterProvider(it)
}
sdkTracerProvider.ifUnique {
// set by org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration.otelSdkTracerProvider
builder.setTracerProvider(it)
}
return builder.build()
}
The otelResource
is a consistent way to specify the service name and other parameters for both metrics and tracing. The OpenTelemetry accepts the additional sdkMeterProvider
and allows a setup of a custom MetricExporter
.
The OtlpMetricsExportAutoConfiguration
would create the SdkMeterProvider
, Exporter and Registry:
@Bean
fun sdkMeterProvider(
metricExportersProvider: ObjectProvider<List<MetricExporter>>,
otelResource: Resource
): SdkMeterProvider {
// from https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/spring/spring-boot-autoconfigure/src/main/java/io/opentelemetry/instrumentation/spring/autoconfigure/OpenTelemetryAutoConfiguration.java
val meterProviderBuilder = SdkMeterProvider.builder()
val interval = properties.metrics.interval
metricExportersProvider.getIfAvailable { emptyList() }
.map { metricExporter ->
val metricReaderBuilder = PeriodicMetricReader.builder(metricExporter)
if (interval != null) {
metricReaderBuilder.setInterval(interval)
}
metricReaderBuilder.build()
}
.forEach { reader ->
meterProviderBuilder.registerMetricReader(reader)
}
return meterProviderBuilder.setResource(otelResource).build()
}
@Bean
fun otelMeterRegistry(openTelemetry: OpenTelemetry): MeterRegistry {
return OpenTelemetryMeterRegistry.create(openTelemetry)
}
@Bean
@ConditionalOnMissingBean(MetricExporter::class)
fun metricExporter(...) : OtlpHttpMetricExporter {
// ...
}
So now both tracing and metrics use the same OpenTelemetry instance that is set up with a single OpenTelemetry Resource instance. The default metric exporter is the OtlpHttpMetricExporter
and can be overridden with the GRPC version.