Skip to content

Commit

Permalink
OpenTelemetry Logging
Browse files Browse the repository at this point in the history
  • Loading branch information
loicmathieu committed Aug 9, 2024
1 parent d41ab4c commit abc8100
Show file tree
Hide file tree
Showing 29 changed files with 743 additions and 19 deletions.
4 changes: 2 additions & 2 deletions docs/src/main/asciidoc/opentelemetry-metrics.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,14 @@ Also provides and OTel collector to receive the data.

==== Logging exporter

You can output all metrics to the console by setting the exporter to `logging` in the `application.properties` file:
You can output all metrics to the console by setting the exporter to `logs` in the `application.properties` file:
[source, properties]
----
quarkus.otel.metrics.exporter=logging <1>
quarkus.otel.metric.export.interval=10000ms <2>
----

<1> Set the exporter to `logging`.
<1> Set the exporter to `logs`.
Normally you don't need to set this.

Check warning on line 172 in docs/src/main/asciidoc/opentelemetry-metrics.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/opentelemetry-metrics.adoc", "range": {"start": {"line": 172, "column": 9}}}, "severity": "INFO"}
The default is `cdi`.
<2> Set the interval to export the metrics.
Expand Down
6 changes: 3 additions & 3 deletions docs/src/main/asciidoc/opentelemetry.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -273,17 +273,17 @@ implementation("io.opentelemetry:opentelemetry-exporter-logging")
----


Then, setting the exporter to `logging` in the `application.properties` file:
Then, setting the exporter to `logs` in the `application.properties` file:
[source, properties]
----
quarkus.otel.metrics.exporter=logging <1>
quarkus.otel.metric.export.interval=10000ms <2>
quarkus.otel.traces.exporter=logging <3>
----

<1> Set the metrics exporter to `logging`. Normally you don't need to set this. The default is `cdi`.
<1> Set the metrics exporter to `logs`. Normally you don't need to set this. The default is `cdi`.

Check warning on line 284 in docs/src/main/asciidoc/opentelemetry.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/opentelemetry.adoc", "range": {"start": {"line": 284, "column": 38}}}, "severity": "INFO"}
<2> Set the interval to export the metrics. The default is `1m`, which is too long for debugging.
<3> Set the traces exporter to `logging`. Normally you don't need to set this. The default is `cdi`.
<3> Set the traces exporter to `logs`. Normally you don't need to set this. The default is `cdi`.

Check warning on line 286 in docs/src/main/asciidoc/opentelemetry.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/opentelemetry.adoc", "range": {"start": {"line": 286, "column": 37}}}, "severity": "INFO"}

== Visualizing the data

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@
import org.jboss.jandex.Type;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.exporter.otlp.internal.OtlpLogRecordExporterProvider;
import io.opentelemetry.exporter.otlp.internal.OtlpMetricExporterProvider;
import io.opentelemetry.exporter.otlp.internal.OtlpSpanExporterProvider;
import io.opentelemetry.instrumentation.annotations.AddingSpanAttributes;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider;
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
import io.opentelemetry.sdk.autoconfigure.spi.logs.ConfigurableLogRecordExporterProvider;
import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider;
import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSamplerProvider;
import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider;
Expand Down Expand Up @@ -86,7 +87,6 @@ public boolean test(AnnotationInstance annotationInstance) {
return annotationInstance.name().equals(ADD_SPAN_ATTRIBUTES);
}
};
private static final DotName SPAN_KIND = DotName.createSimple(SpanKind.class.getName());
private static final DotName WITH_SPAN_INTERCEPTOR = DotName.createSimple(WithSpanInterceptor.class.getName());
private static final DotName ADD_SPAN_ATTRIBUTES_INTERCEPTOR = DotName
.createSimple(AddingSpanAttributesInterceptor.class.getName());
Expand Down Expand Up @@ -163,10 +163,31 @@ void handleServices(OTelBuildConfig config,
Set.of("META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider")));
}

final List<String> logRecordExporterProviders = ServiceUtil.classNamesNamedIn(
Thread.currentThread().getContextClassLoader(),
SPI_ROOT + ConfigurableLogRecordExporterProvider.class.getName())
.stream()
.filter(p -> !OtlpLogRecordExporterProvider.class.getName().equals(p))
.collect(toList()); // filter out OtlpLogRecordExporterProvider since it depends on OkHttp
System.out.println("logRecordExporterProviders: " + logRecordExporterProviders);
if (!logRecordExporterProviders.isEmpty()) {
services.produce(
new ServiceProviderBuildItem(ConfigurableLogRecordExporterProvider.class.getName(),
logRecordExporterProviders));
}
if (config.logs().exporter().stream().noneMatch(ExporterType.Constants.OTLP_VALUE::equals)) {
removedResources.produce(new RemovedResourceBuildItem(
ArtifactKey.fromString("io.opentelemetry:opentelemetry-exporter-otlp"),
Set.of("META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.logs.ConfigurableLogMeterExporterProvider")));
}

// TODO these classes don't exist!
runtimeReinitialized.produce(
new RuntimeReinitializedClassBuildItem("io.opentelemetry.sdk.autoconfigure.TracerProviderConfiguration"));
runtimeReinitialized.produce(
new RuntimeReinitializedClassBuildItem("io.opentelemetry.sdk.autoconfigure.MeterProviderConfiguration"));
runtimeReinitialized.produce(
new RuntimeReinitializedClassBuildItem("io.opentelemetry.sdk.autoconfigure.LogMeterProviderConfiguration"));

services.produce(ServiceProviderBuildItem.allProvidersFromClassPath(
ConfigurableSamplerProvider.class.getName()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.jboss.jandex.ParameterizedType;
import org.jboss.jandex.Type;

import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.export.SpanExporter;
Expand All @@ -34,6 +35,7 @@
public class OtlpExporterProcessor {

private static final DotName METRIC_EXPORTER = DotName.createSimple(MetricExporter.class.getName());
private static final DotName LOG_RECORD_EXPORTER = DotName.createSimple(LogRecordExporter.class.getName());

static class OtlpTracingExporterEnabled implements BooleanSupplier {
OtlpExporterBuildConfig exportBuildConfig;
Expand All @@ -59,6 +61,18 @@ public boolean getAsBoolean() {
}
}

static class OtlpLogRecordExporterEnabled implements BooleanSupplier {
OtlpExporterBuildConfig exportBuildConfig;
OTelBuildConfig otelBuildConfig;

public boolean getAsBoolean() {
return otelBuildConfig.enabled() &&
otelBuildConfig.logs().enabled().orElse(Boolean.TRUE) &&
otelBuildConfig.logs().exporter().contains(CDI_VALUE) &&
exportBuildConfig.enabled();
}
}

@SuppressWarnings("deprecation")
@BuildStep(onlyIf = OtlpExporterProcessor.OtlpTracingExporterEnabled.class)
@Record(ExecutionTime.RUNTIME_INIT)
Expand Down Expand Up @@ -123,4 +137,41 @@ void createMetricsExporterProcessor(
vertxBuildItem.getVertx()))
.done());
}

@BuildStep(onlyIf = OtlpLogRecordExporterEnabled.class)
@Record(ExecutionTime.RUNTIME_INIT)
@Consume(TlsRegistryBuildItem.class)
void createLogRecordExporterProcessor(
BeanDiscoveryFinishedBuildItem beanDiscovery,
OTelExporterRecorder recorder,
List<ExternalOtelExporterBuildItem> externalOtelExporterBuildItem,
OTelRuntimeConfig otelRuntimeConfig,
OtlpExporterRuntimeConfig exporterRuntimeConfig,
CoreVertxBuildItem vertxBuildItem,
BuildProducer<SyntheticBeanBuildItem> syntheticBeanBuildItemBuildProducer) {

if (!externalOtelExporterBuildItem.isEmpty()) {
// if there is an external exporter, we don't want to create the default one.
// External exporter also use synthetic beans. However, synthetic beans don't show in the BeanDiscoveryFinishedBuildItem
return;
}

if (!beanDiscovery.beanStream().withBeanType(LOG_RECORD_EXPORTER).isEmpty()) {
// if there is a MetricExporter bean impl around, we don't want to create the default one
return;
}

syntheticBeanBuildItemBuildProducer.produce(SyntheticBeanBuildItem
.configure(LogRecordExporter.class)
.types(LogRecordExporter.class)
.setRuntimeInit()
.scope(Singleton.class)
.unremovable()
.addInjectionPoint(ParameterizedType.create(DotName.createSimple(Instance.class),
new Type[] { ClassType.create(DotName.createSimple(LogRecordExporter.class.getName())) }, null))
.addInjectionPoint(ClassType.create(DotName.createSimple(TlsConfigurationRegistry.class)))
.createWith(recorder.createLogRecordExporter(otelRuntimeConfig, exporterRuntimeConfig,
vertxBuildItem.getVertx()))
.done());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.opentelemetry.deployment.logging;

import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.LogHandlerBuildItem;
import io.quarkus.opentelemetry.runtime.logs.OpenTelemetryLogConfig;
import io.quarkus.opentelemetry.runtime.logs.OpenTelemetryLogRecorder;

class LogHandlerProcessor {

@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
LogHandlerBuildItem build(OpenTelemetryLogRecorder recorder, OpenTelemetryLogConfig config) {
return new LogHandlerBuildItem(recorder.initializeHandler(config));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.opentelemetry.runtime.config.build;

import static io.quarkus.opentelemetry.runtime.config.build.ExporterType.Constants.CDI_VALUE;

import java.util.List;
import java.util.Optional;

import io.smallrye.config.WithDefault;

public interface LogsBuildConfig {
/**
* Enable logs with OpenTelemetry.
* <p>
* This property is not available in the Open Telemetry SDK. It's Quarkus specific.
* <p>
* Support for logs will be enabled if OpenTelemetry support is enabled
* and either this value is true, or this value is unset.
*/
@WithDefault("false")
Optional<Boolean> enabled();

/**
* The Logs exporter to use.
*/
@WithDefault(CDI_VALUE)
List<String> exporter();
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public interface OTelBuildConfig {
*/
MetricsBuildConfig metrics();

/**
* Logs exporter configurations.
*/
LogsBuildConfig logs();

/**
* No Log exporter for now.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.quarkus.opentelemetry.runtime.config.runtime.exporter;

import io.quarkus.runtime.annotations.ConfigGroup;

@ConfigGroup
public interface OtlpExporterLogsConfig extends OtlpExporterConfig {
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ public interface OtlpExporterRuntimeConfig extends OtlpExporterConfig {
* OTLP metrics exporter configuration.
*/
OtlpExporterMetricsConfig metrics();
// TODO logs();

/**
* OTLP logs exporter configuration.
*/
OtlpExporterLogsConfig logs();
// TODO additional global exporter configuration

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
import io.opentelemetry.exporter.internal.ExporterBuilderUtil;
import io.opentelemetry.exporter.internal.grpc.GrpcExporter;
import io.opentelemetry.exporter.internal.http.HttpExporter;
import io.opentelemetry.exporter.internal.otlp.logs.LogsRequestMarshaler;
import io.opentelemetry.exporter.internal.otlp.metrics.MetricsRequestMarshaler;
import io.opentelemetry.exporter.internal.otlp.traces.TraceRequestMarshaler;
import io.opentelemetry.exporter.otlp.internal.OtlpUserAgent;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import io.opentelemetry.sdk.metrics.Aggregation;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector;
Expand All @@ -37,11 +39,10 @@
import io.opentelemetry.sdk.trace.export.SpanExporter;
import io.quarkus.arc.SyntheticCreationalContext;
import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig;
import io.quarkus.opentelemetry.runtime.config.runtime.exporter.CompressionType;
import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterConfig;
import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterMetricsConfig;
import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterRuntimeConfig;
import io.quarkus.opentelemetry.runtime.config.runtime.exporter.OtlpExporterTracesConfig;
import io.quarkus.opentelemetry.runtime.config.runtime.exporter.*;
import io.quarkus.opentelemetry.runtime.exporter.otlp.logs.NoopLogRecordExporter;
import io.quarkus.opentelemetry.runtime.exporter.otlp.logs.VertxGrpcLogRecordExporter;
import io.quarkus.opentelemetry.runtime.exporter.otlp.logs.VertxHttpLogRecordExporter;
import io.quarkus.opentelemetry.runtime.exporter.otlp.metrics.NoopMetricExporter;
import io.quarkus.opentelemetry.runtime.exporter.otlp.metrics.VertxGrpcMetricExporter;
import io.quarkus.opentelemetry.runtime.exporter.otlp.metrics.VertxHttpMetricsExporter;
Expand Down Expand Up @@ -196,7 +197,7 @@ public MetricExporter apply(SyntheticCreationalContext<MetricExporter> context)
OtlpExporterMetricsConfig metricsConfig = exporterRuntimeConfig.metrics();
if (metricsConfig.protocol().isEmpty()) {
throw new IllegalStateException("No OTLP protocol specified. " +
"Please check `quarkus.otel.exporter.otlp.traces.protocol` property");
"Please check `quarkus.otel.exporter.otlp.metrics.protocol` property");
}

String protocol = metricsConfig.protocol().get();
Expand Down Expand Up @@ -237,7 +238,7 @@ public MetricExporter apply(SyntheticCreationalContext<MetricExporter> context)
aggregationResolver(metricsConfig));
} else {
throw new IllegalArgumentException(String.format("Unsupported OTLP protocol %s specified. " +
"Please check `quarkus.otel.exporter.otlp.traces.protocol` property", protocol));
"Please check `quarkus.otel.exporter.otlp.metrics.protocol` property", protocol));
}

} catch (IllegalArgumentException iae) {
Expand All @@ -248,6 +249,74 @@ public MetricExporter apply(SyntheticCreationalContext<MetricExporter> context)
};
}

public Function<SyntheticCreationalContext<LogRecordExporter>, LogRecordExporter> createLogRecordExporter(
OTelRuntimeConfig otelRuntimeConfig, OtlpExporterRuntimeConfig exporterRuntimeConfig, Supplier<Vertx> vertx) {
final URI baseUri = getMetricsUri(exporterRuntimeConfig);

return new Function<>() {
@Override
public LogRecordExporter apply(SyntheticCreationalContext<LogRecordExporter> context) {

if (otelRuntimeConfig.sdkDisabled() || baseUri == null) {
return NoopLogRecordExporter.INSTANCE;
}

LogRecordExporter logRecordExporter;

try {
TlsConfigurationRegistry tlsConfigurationRegistry = context
.getInjectedReference(TlsConfigurationRegistry.class);
OtlpExporterLogsConfig logsConfig = exporterRuntimeConfig.logs();
if (logsConfig.protocol().isEmpty()) {
throw new IllegalStateException("No OTLP protocol specified. " +
"Please check `quarkus.otel.exporter.otlp.logs.protocol` property");
}

String protocol = logsConfig.protocol().get();
if (GRPC.equals(protocol)) {
logRecordExporter = new VertxGrpcLogRecordExporter(
new GrpcExporter<LogsRequestMarshaler>(
OTLP_VALUE, // use the same as OTel does
"log", // use the same as OTel does
new VertxGrpcSender(
baseUri,
VertxGrpcSender.GRPC_LOG_SERVICE_NAME,
determineCompression(logsConfig),
logsConfig.timeout(),
populateTracingExportHttpHeaders(logsConfig),
new HttpClientOptionsConsumer(logsConfig, baseUri, tlsConfigurationRegistry),
vertx.get()),
MeterProvider::noop));
} else if (HTTP_PROTOBUF.equals(protocol)) {
boolean exportAsJson = false; //TODO: this will be enhanced in the future
logRecordExporter = new VertxHttpLogRecordExporter(
new HttpExporter<LogsRequestMarshaler>(
OTLP_VALUE, // use the same as OTel does
"log", // use the same as OTel does
new VertxHttpSender(
baseUri,
VertxHttpSender.LOGS_PATH,
determineCompression(logsConfig),
logsConfig.timeout(),
populateTracingExportHttpHeaders(logsConfig),
exportAsJson ? "application/json" : "application/x-protobuf",
new HttpClientOptionsConsumer(logsConfig, baseUri, tlsConfigurationRegistry),
vertx.get()),
MeterProvider::noop,
exportAsJson));
} else {
throw new IllegalArgumentException(String.format("Unsupported OTLP protocol %s specified. " +
"Please check `quarkus.otel.exporter.otlp.logs.protocol` property", protocol));
}

} catch (IllegalArgumentException iae) {
throw new IllegalStateException("Unable to install OTLP Exporter", iae);
}
return logRecordExporter;
}
};
}

private static DefaultAggregationSelector aggregationResolver(OtlpExporterMetricsConfig metricsConfig) {
String defaultHistogramAggregation = metricsConfig.defaultHistogramAggregation()
.map(s -> s.toLowerCase(Locale.ROOT))
Expand Down
Loading

0 comments on commit abc8100

Please sign in to comment.