Add Prometheus metric exporter
MODIFY
gradle/dependencies.gradle
gradle/dependencies.gradle
@@ -34,6 +34,7 @@ ext {
nullaway : "0.8.0",
autoValue : "1.7.4",
systemLambda : "1.1.0",
+ prometheus : "0.9.0"
]
deps = [
@@ -46,6 +47,7 @@ ext {
opentelemetryJaeger : dependencies.create(group: 'io.opentelemetry', name: 'opentelemetry-exporters-jaeger', version: versions.opentelemetry),
opentelemetryOtlp : dependencies.create(group: 'io.opentelemetry', name: 'opentelemetry-exporters-otlp', version: versions.opentelemetryAnother),
opentelemetryZipkin : dependencies.create(group: 'io.opentelemetry', name: 'opentelemetry-exporters-zipkin', version: versions.opentelemetry),
+ opentelemetryPrometheus : dependencies.create(group: 'io.opentelemetry', name: 'opentelemetry-exporters-prometheus', version: versions.opentelemetry),
opentelemetryLogging : dependencies.create(group: 'io.opentelemetry', name: 'opentelemetry-exporters-logging', version: versions.opentelemetry),
opentelemetryProto : dependencies.create(group: 'io.opentelemetry', name: 'opentelemetry-proto', version: versions.opentelemetry),
@@ -63,6 +65,10 @@ ext {
autoValueAnnotations : "com.google.auto.value:auto-value-annotations:${versions.autoValue}",
// annotation processor
autoValue : "com.google.auto.value:auto-value:${versions.autoValue}",
+ prometheus : [
+ dependencies.create(group: 'io.prometheus', name: 'simpleclient', version: "${versions.prometheus}"),
+ dependencies.create(group: 'io.prometheus', name: 'simpleclient_httpserver', version: "${versions.prometheus}"),
+ ],
// Testing

MODIFY
javaagent-exporters/javaagent-exporters.gradle
javaagent-exporters/javaagent-exporters.gradle
@@ -12,6 +12,7 @@ dependencies {
testImplementation project(':javaagent-exporters:jaeger')
testImplementation project(':javaagent-exporters:logging')
testImplementation project(':javaagent-exporters:zipkin')
+ testImplementation project(':javaagent-exporters:prometheus')
}
tasks.withType(Test).configureEach() {
@@ -19,6 +20,7 @@ tasks.withType(Test).configureEach() {
dependsOn ':javaagent-exporters:jaeger:shadowJar'
dependsOn ':javaagent-exporters:logging:shadowJar'
dependsOn ':javaagent-exporters:zipkin:shadowJar'
+ dependsOn ':javaagent-exporters:prometheus:shadowJar'
doFirst {
systemProperty 'projectVersion', allprojects.version[0]
systemProperty 'adapterRoot', "$rootDir/javaagent-exporters"
@@ -26,6 +28,7 @@ tasks.withType(Test).configureEach() {
systemProperty 'jaegerExporterJar', project(':javaagent-exporters:jaeger').tasks.shadowJar.archivePath
systemProperty 'loggingExporterJar', project(':javaagent-exporters:logging').tasks.shadowJar.archivePath
systemProperty 'zipkinExporterJar', project(':javaagent-exporters:zipkin').tasks.shadowJar.archivePath
+ systemProperty 'prometheusExporterJar', project(':javaagent-exporters:prometheus').tasks.shadowJar.archivePath
}
}
@@ -38,6 +41,7 @@ dependencies {
shadowInclude project(path: ':javaagent-exporters:otlp', configuration: 'shadow')
shadowInclude project(path: ':javaagent-exporters:jaeger', configuration: 'shadow')
shadowInclude project(path: ':javaagent-exporters:zipkin', configuration: 'shadow')
+ shadowInclude project(path: ':javaagent-exporters:prometheus', configuration: 'shadow')
}
jar.enabled = false

ADD
javaagent-exporters/prometheus/prometheus.gradle
+plugins {
+ id "com.github.johnrengelman.shadow"
+}
+
+apply from: "$rootDir/gradle/java.gradle"
+apply from: "$rootDir/gradle/publish.gradle"
+
+archivesBaseName = 'javaagent-exporters-prometheus'
+
+dependencies {
+ compileOnly(project(':javaagent-spi'))
+ compileOnly(deps.opentelemetrySdk)
+
+ implementation deps.slf4j
+ implementation(deps.prometheus)
+
+ annotationProcessor deps.autoservice
+ compileOnly deps.autoservice
+
+ implementation(deps.opentelemetryPrometheus) {
+ exclude group: 'io.opentelemetry', module: 'opentelemetry-sdk'
+ }
+}
+
+jar.enabled = false
+shadowJar {
+ archiveClassifier = ''
+}

ADD
javaagent-exporters/prometheus/src/main/java/io/opentelemetry/javaagent/exporters/prometheus/PrometheusMetricServer.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.exporters.prometheus;
+
+import com.google.auto.service.AutoService;
+import io.opentelemetry.exporters.prometheus.PrometheusCollector;
+import io.opentelemetry.javaagent.spi.exporter.MetricServer;
+import io.opentelemetry.sdk.metrics.export.MetricProducer;
+import io.prometheus.client.exporter.HTTPServer;
+import java.io.IOException;
+import java.util.Properties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link PrometheusMetricServer} registers {@link MetricProducer} to the {@link
+ * PrometheusCollector}. The collector pulls metrics from the producer when the Prometheus server
+ * scraps metric endpoint.
+ */
+@AutoService(MetricServer.class)
+public class PrometheusMetricServer implements MetricServer {
+ private static final Logger log = LoggerFactory.getLogger(PrometheusMetricServer.class);
+
+ private static final String EXPORTER_NAME = "otel.exporter.prometheus";
+ private static final String PORT_CONF_PROP_NAME = EXPORTER_NAME + ".port";
+ private static final String HOST_CONF_PROP_NAME = EXPORTER_NAME + ".host";
+ private static final String DEFAULT_PORT = "9464";
+ // The empty address equals to any address
+ private static final String DEFAULT_HOST = "0.0.0.0";
+
+ @Override
+ public void start(MetricProducer producer, Properties config) {
+ PrometheusCollector.newBuilder().setMetricProducer(producer).buildAndRegister();
+ try {
+ String portStr = config.getProperty(PORT_CONF_PROP_NAME, DEFAULT_PORT);
+ String host = config.getProperty(HOST_CONF_PROP_NAME, DEFAULT_HOST);
+ log.info("Creating Prometheus exporter on host: '{}' and port: '{}'", host, portStr);
+ // Prometheus HTTP server uses global registry configured by PrometheusCollector.
+ new HTTPServer(host, Integer.parseInt(portStr), true);
+ } catch (IOException e) {
+ log.error("Failed to create Prometheus server", e);
+ }
+ }
+}

ADD
javaagent-spi/src/main/java/io/opentelemetry/javaagent/spi/exporter/MetricServer.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.spi.exporter;
+
+import io.opentelemetry.sdk.metrics.export.MetricProducer;
+import java.util.Properties;
+
+/**
+ * A {@link MetricServer} acts as the bootstrap for metric exporters that use {@link MetricProducer}
+ * to consume the metrics.
+ *
+ * <p>Implementation of {@link MetricServer} must be registered through the Java SPI framework.
+ */
+public interface MetricServer {
+
+ /**
+ * Start the metric server that pulls metric from the {@link MetricProducer}.
+ *
+ * @param producer The metric producer
+ * @param config The configuration
+ */
+ void start(MetricProducer producer, Properties config);
+}

MODIFY
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/TracerInstaller.java
javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/TracerInstaller.java
@@ -8,10 +8,12 @@ package io.opentelemetry.javaagent.tooling;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.javaagent.spi.TracerCustomizer;
import io.opentelemetry.javaagent.spi.exporter.MetricExporterFactory;
+import io.opentelemetry.javaagent.spi.exporter.MetricServer;
import io.opentelemetry.javaagent.spi.exporter.SpanExporterFactory;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.metrics.export.IntervalMetricReader;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
+import io.opentelemetry.sdk.metrics.export.MetricProducer;
import io.opentelemetry.sdk.trace.TracerSdkManagement;
import io.opentelemetry.sdk.trace.config.TraceConfig;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
@@ -73,6 +75,13 @@ public class TracerInstaller {
} else {
log.debug("No {} metric exporter found", exporterName);
}
+
+ MetricServer metricServer = findMetricServer(exporterName);
+ if (metricServer != null) {
+ installMetricServer(metricServer, config);
+ } else {
+ log.debug("No {} metric server found", exporterName);
+ }
}
}
@@ -93,6 +102,23 @@ public class TracerInstaller {
return null;
}
+ private static MetricServer findMetricServer(String exporterName) {
+ ServiceLoader<MetricServer> serviceLoader =
+ ServiceLoader.load(MetricServer.class, TracerInstaller.class.getClassLoader());
+
+ for (MetricServer metricServer : serviceLoader) {
+ if (metricServer
+ .getClass()
+ .getSimpleName()
+ .replace("_", "")
+ .toLowerCase()
+ .startsWith(exporterName.toLowerCase())) {
+ return metricServer;
+ }
+ }
+ return null;
+ }
+
private static SpanExporterFactory findSpanExporterFactory(String exporterName) {
ServiceLoader<SpanExporterFactory> serviceLoader =
ServiceLoader.load(SpanExporterFactory.class, TracerInstaller.class.getClassLoader());
@@ -159,6 +185,12 @@ public class TracerInstaller {
log.info("Installed span exporter: " + spanExporter.getClass().getName());
}
+ private static void installMetricServer(MetricServer metricServer, Properties config) {
+ MetricProducer metricProducer = OpenTelemetrySdk.getMeterProvider().getMetricProducer();
+ metricServer.start(metricProducer, config);
+ log.info("Installed metric server: " + metricServer.getClass().getName());
+ }
+
private static <F> F getExporterFactory(Class<F> service, ExporterClassLoader exporterLoader) {
ServiceLoader<F> serviceLoader = ServiceLoader.load(service, exporterLoader);
Iterator<F> i = serviceLoader.iterator();

MODIFY
settings.gradle
settings.gradle
@@ -195,6 +195,7 @@ include ":javaagent-exporters:jaeger"
include ":javaagent-exporters:logging"
include ":javaagent-exporters:otlp"
include ":javaagent-exporters:zipkin"
+include ":javaagent-exporters:prometheus"
// benchmark
include ':benchmark'

Add Prometheus metric exporter (#1262)