diff --git a/sdk-extensions/autoconfigure/README.md b/sdk-extensions/autoconfigure/README.md index 4ade5ab7bdf..97e705be446 100644 --- a/sdk-extensions/autoconfigure/README.md +++ b/sdk-extensions/autoconfigure/README.md @@ -159,6 +159,19 @@ The logging exporter prints the name of the span along with its attributes to st | otel.metrics.exporter=logging | OTEL_METRICS_EXPORTER=logging | Select the logging exporter for metrics | | otel.logs.exporter=logging | OTEL_LOGS_EXPORTER=logging | Select the logging exporter for logs | +### Logging OTLP JSON exporter + +The logging-otlp exporter writes the telemetry data to the JUL logger in OLTP JSON form. It's a more verbose output mainly used for testing and debugging. + +| System property | Environment variable | Description | +|------------------------------------|------------------------------------|----------------------------------------------------| +| otel.traces.exporter=logging-otlp | OTEL_TRACES_EXPORTER=logging-otlp | Select the logging OTLP JSON exporter for tracing | +| otel.metrics.exporter=logging-otlp | OTEL_METRICS_EXPORTER=logging-otlp | Select the logging OTLP JSON exporter for metrics | +| otel.logs.exporter=logging-otlp | OTEL_LOGS_EXPORTER=logging-otlp | Select the logging OTLP JSON exporter for logs | + +**NOTE:** While the `OtlpJsonLogging{Signal}Exporters` are stable, specifying their use +via `otel.{signal}.exporter=logging-otlp` is experimental and subject to change or removal. + ## Propagator The propagators determine which distributed tracing header formats are used, and which baggage propagation header formats are used. diff --git a/sdk-extensions/autoconfigure/build.gradle.kts b/sdk-extensions/autoconfigure/build.gradle.kts index 6328e6785c4..7c1d6b827a3 100644 --- a/sdk-extensions/autoconfigure/build.gradle.kts +++ b/sdk-extensions/autoconfigure/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation(project(":exporters:common")) compileOnly(project(":exporters:jaeger")) + compileOnly(project(":exporters:logging-otlp")) compileOnly(project(":exporters:otlp:all")) compileOnly(project(":exporters:otlp:logs")) compileOnly(project(":exporters:otlp:common")) @@ -141,6 +142,12 @@ testing { } } } + val testLoggingOtlp by registering(JvmTestSuite::class) { + dependencies { + implementation(project(":exporters:logging-otlp")) + implementation("com.google.guava:guava") + } + } val testOtlp by registering(JvmTestSuite::class) { dependencies { implementation(project(":exporters:otlp:all")) diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/LogRecordExporterConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/LogRecordExporterConfiguration.java index 9dd3dc19647..693aa3303f8 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/LogRecordExporterConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/LogRecordExporterConfiguration.java @@ -11,6 +11,7 @@ import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.exporter.internal.retry.RetryUtil; +import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingLogRecordExporter; import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder; import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter; @@ -90,6 +91,12 @@ static LogRecordExporter configureExporter( switch (name) { case "otlp": return configureOtlpLogs(config, meterProvider); + case "logging-otlp": + ClasspathUtil.checkClassExists( + "io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingLogRecordExporter", + "OTLP JSON Logging Log Exporter", + "opentelemetry-exporter-logging-otlp"); + return OtlpJsonLoggingLogRecordExporter.create(); default: LogRecordExporter spiExporter = spiExportersManager.getByName(name); if (spiExporter == null) { diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfiguration.java index 75671f8d314..a82cc6c5cb1 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/MetricExporterConfiguration.java @@ -10,6 +10,7 @@ import static io.opentelemetry.sdk.autoconfigure.OtlpConfigUtil.PROTOCOL_HTTP_PROTOBUF; import io.opentelemetry.exporter.internal.retry.RetryUtil; +import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; @@ -53,6 +54,9 @@ static MetricReader configureExporter( case "otlp": metricExporter = configureOtlpMetrics(config); break; + case "logging-otlp": + metricExporter = configureLoggingOtlpExporter(); + break; default: MetricExporter spiExporter = configureSpiExporter(name, config, serviceClassLoader); if (spiExporter == null) { @@ -74,6 +78,14 @@ static MetricReader configureExporter( return configurePeriodicMetricReader(config, metricExporter); } + private static MetricExporter configureLoggingOtlpExporter() { + ClasspathUtil.checkClassExists( + "io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter", + "OTLP JSON Logging Metrics Exporter", + "opentelemetry-exporter-logging-otlp"); + return OtlpJsonLoggingMetricExporter.create(); + } + // Visible for testing. @Nullable static MetricExporter configureSpiExporter( diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfiguration.java index 89a42620b48..380cc3c5eaf 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/SpanExporterConfiguration.java @@ -14,6 +14,7 @@ import io.opentelemetry.exporter.internal.retry.RetryUtil; import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporter; import io.opentelemetry.exporter.jaeger.JaegerGrpcSpanExporterBuilder; +import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; @@ -98,6 +99,12 @@ static SpanExporter configureExporter( return configureJaeger(config, meterProvider); case "zipkin": return configureZipkin(config); + case "logging-otlp": + ClasspathUtil.checkClassExists( + "io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingSpanExporter", + "OTLP JSON Logging Trace Exporter", + "opentelemetry-exporter-logging-otlp"); + return OtlpJsonLoggingSpanExporter.create(); default: SpanExporter spiExporter = spiExportersManager.getByName(name); if (spiExporter == null) { diff --git a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/NotOnClasspathTest.java b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/NotOnClasspathTest.java index f3c36438fd0..06d971c8fc6 100644 --- a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/NotOnClasspathTest.java +++ b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/NotOnClasspathTest.java @@ -81,6 +81,18 @@ void loggingSpans() { + " Make sure to add it as a dependency."); } + @Test + void loggingSpansOtlp() { + assertThatThrownBy( + () -> + SpanExporterConfiguration.configureExporter( + "logging-otlp", EMPTY, NamedSpiManager.createEmpty(), MeterProvider.noop())) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining( + "OTLP JSON Logging Trace Exporter enabled but opentelemetry-exporter-logging-otlp not found on " + + "classpath"); + } + @Test void loggingMetrics() { assertThatThrownBy( @@ -96,6 +108,21 @@ void loggingMetrics() { + " Make sure to add it as a dependency."); } + @Test + void loggingMetricsOtlp() { + assertThatThrownBy( + () -> + MetricExporterConfiguration.configureExporter( + "logging-otlp", + EMPTY, + MetricExporterConfiguration.class.getClassLoader(), + (a, unused) -> a)) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining( + "OTLP JSON Logging Metrics Exporter enabled but opentelemetry-exporter-logging-otlp not found on " + + "classpath"); + } + @Test void loggingLogs() { assertThatThrownBy( @@ -108,6 +135,18 @@ void loggingLogs() { + " Make sure to add it as a dependency."); } + @Test + void loggingLogsOtlp() { + assertThatThrownBy( + () -> + LogRecordExporterConfiguration.configureExporter( + "logging-otlp", EMPTY, NamedSpiManager.createEmpty(), MeterProvider.noop())) + .isInstanceOf(ConfigurationException.class) + .hasMessageContaining( + "OTLP JSON Logging Log Exporter enabled but opentelemetry-exporter-logging-otlp not found on " + + "classpath"); + } + @Test void otlpGrpcMetrics() { assertThatCode( diff --git a/sdk-extensions/autoconfigure/src/testLoggingOtlp/java/io/opentelemetry/sdk/autoconfigure/LoggingOtlpTest.java b/sdk-extensions/autoconfigure/src/testLoggingOtlp/java/io/opentelemetry/sdk/autoconfigure/LoggingOtlpTest.java new file mode 100644 index 00000000000..aba93ae800e --- /dev/null +++ b/sdk-extensions/autoconfigure/src/testLoggingOtlp/java/io/opentelemetry/sdk/autoconfigure/LoggingOtlpTest.java @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import com.google.common.collect.ImmutableMap; +import io.github.netmikey.logunit.api.LogCapturer; +import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingLogRecordExporter; +import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingMetricExporter; +import io.opentelemetry.exporter.logging.otlp.OtlpJsonLoggingSpanExporter; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +class LoggingOtlpTest { + + @RegisterExtension + LogCapturer spansCapturer = + LogCapturer.create().captureForType(OtlpJsonLoggingSpanExporter.class); + + @RegisterExtension + LogCapturer metricsCapturer = + LogCapturer.create().captureForType(OtlpJsonLoggingMetricExporter.class); + + @RegisterExtension + LogCapturer logsCapturer = + LogCapturer.create().captureForType(OtlpJsonLoggingLogRecordExporter.class); + + @Test + void configures() { + OpenTelemetrySdk sdk = + AutoConfiguredOpenTelemetrySdk.builder() + .setConfig( + DefaultConfigProperties.createForTest( + ImmutableMap.of( + "otel.traces.exporter", "logging-otlp", + "otel.metrics.exporter", "logging-otlp", + "otel.logs.exporter", "logging-otlp"))) + .setResultAsGlobal(false) + .build() + .getOpenTelemetrySdk(); + + sdk.getTracerProvider().get("tracer").spanBuilder("test").startSpan().end(); + sdk.getMeterProvider().get("meter").counterBuilder("counter").build().add(10); + sdk.getSdkLoggerProvider().get("logger").logRecordBuilder().setBody("message").emit(); + + sdk.getSdkLoggerProvider().forceFlush().join(10, TimeUnit.SECONDS); + sdk.getSdkMeterProvider().forceFlush().join(10, TimeUnit.SECONDS); + sdk.getSdkLoggerProvider().forceFlush().join(10, TimeUnit.SECONDS); + + await() + .untilAsserted( + () -> assertThat(spansCapturer.getEvents().size()).isGreaterThanOrEqualTo(1)); + await() + .untilAsserted( + () -> assertThat(metricsCapturer.getEvents().size()).isGreaterThanOrEqualTo(1)); + await() + .untilAsserted(() -> assertThat(logsCapturer.getEvents().size()).isGreaterThanOrEqualTo(1)); + } +} diff --git a/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramValueGenerator.java b/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramValueGenerator.java index cbe48550dc6..aa603878640 100644 --- a/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramValueGenerator.java +++ b/sdk/metrics/src/jmh/java/io/opentelemetry/sdk/metrics/internal/aggregator/HistogramValueGenerator.java @@ -61,8 +61,6 @@ public double getAsDouble() { /** Constructs a pool using explicit bucket histogram boundaries. */ private static double[] explicitDefaultBucketPool() { List fixedBoundaries = new ArrayList(); - // Add minimal recording value. - fixedBoundaries.add(0.0); // Add the bucket LE bucket boundaries (starts at 5). fixedBoundaries.addAll(ExplicitBucketHistogramUtils.DEFAULT_HISTOGRAM_BUCKET_BOUNDARIES); // Add Double max value as our other extreme.