Skip to content

Commit

Permalink
OpenTelemetry Logging
Browse files Browse the repository at this point in the history
  • Loading branch information
loicmathieu committed Aug 13, 2024
1 parent baf77db commit fcffb45
Show file tree
Hide file tree
Showing 27 changed files with 731 additions and 14 deletions.
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,30 @@ 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
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.ConfigurableLogRecordExporterProvider")));
}

// 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,20 @@
package io.quarkus.opentelemetry.deployment.logging;

import io.quarkus.agroal.spi.OpenTelemetryInitBuildItem;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Consume;
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)
@Consume(OpenTelemetryInitBuildItem.class)
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.quarkus.opentelemetry.runtime.exporter.otlp.logs;

import java.util.Collection;

import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;

public class NoopLogRecordExporter implements LogRecordExporter {
public static final NoopLogRecordExporter INSTANCE = new NoopLogRecordExporter();

private NoopLogRecordExporter() {
}

@Override
public CompletableResultCode export(Collection<LogRecordData> collection) {
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode flush() {
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode shutdown() {
return CompletableResultCode.ofSuccess();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.quarkus.opentelemetry.runtime.exporter.otlp.logs;

import java.util.Collection;

import io.opentelemetry.exporter.internal.grpc.GrpcExporter;
import io.opentelemetry.exporter.internal.otlp.logs.LogsRequestMarshaler;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.data.LogRecordData;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;

public class VertxGrpcLogRecordExporter implements LogRecordExporter {
private final GrpcExporter<LogsRequestMarshaler> delegate;

public VertxGrpcLogRecordExporter(GrpcExporter<LogsRequestMarshaler> delegate) {
this.delegate = delegate;
}

@Override
public CompletableResultCode export(Collection<LogRecordData> collection) {
return delegate.export(LogsRequestMarshaler.create(collection), collection.size());
}

@Override
public CompletableResultCode flush() {
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode shutdown() {
return delegate.shutdown();
}
}
Loading

0 comments on commit fcffb45

Please sign in to comment.