Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support OpenTelemetry Log signal #30712

Closed
brunobat opened this issue Jan 30, 2023 · 16 comments · Fixed by #38239
Closed

Support OpenTelemetry Log signal #30712

brunobat opened this issue Jan 30, 2023 · 16 comments · Fixed by #38239
Assignees
Milestone

Comments

@brunobat
Copy link
Contributor

brunobat commented Jan 30, 2023

Description

We need to support the OpenTelemetry logging signal available at: https://opentelemetry.io/docs/instrumentation/java/
The spec/impl is still under development (at the creation of this issue it's still experimental) and work should after it's marked as stable.

Depends on #29911

Implementation ideas

No response

@quarkus-bot
Copy link

quarkus-bot bot commented Jan 30, 2023

/cc @radcortez (opentelemetry)

@t1
Copy link

t1 commented Jun 16, 2023

According to the linked page, the spec is now stable 😁

@brunobat
Copy link
Contributor Author

Yes @t1. But supporting it on Quarkus will take some time to implement and it's not in the shot term roadmap.

@t1
Copy link

t1 commented Jun 16, 2023

I'm probably not deep enough into OTel, but isn't this about transporting log statements? If it is, I think this is crucial. I'm currently looking for a future-proof solution, and OTel looks very promising. But if Quarkus supports only traces and neither metrics nor logs (not to speak of profiling), I'll have to look for alternatives. Or do I get it wrong?

@brunobat
Copy link
Contributor Author

brunobat commented Jun 16, 2023

OTel OTLP protocol output will be standard.

There's native OTel Tracing support.

You can already get Micrometer metrics output using the OTel's OTLP protocol and receive that in the standard OTel collector. Micrometer just provides a more convenient and complete API to define metrics.

In relation to logs, there will be the need to forward current loggers output to OTel. That work will be done later.
OTel Metrics instrumentation support will be added after the Observation API is implemented.

@luciantimar
Copy link

Hi @brunobat,

To get logs exported to an OTEL collector is it possible to use some of the logs appenders provided in the examples OpenTelemetry Log Appenders ?

I know that Quarkus relies on the JBoss Logging library but is it possible to use the same approach?

@brunobat
Copy link
Contributor Author

That's the plan, however, logging is not yet supported by the quarkus-opentelemetry extension. It's the target of this issue.

@crumohr
Copy link
Contributor

crumohr commented Jan 16, 2024

@brunobat is there any update regarding the roadmap to implement this feature or any way to support?

@luciantimar
Copy link

Hi,

is not the best approach, but until the feature is ready we have managed to have the logs also exported by overwriting the default Open telemetry bean and providing our own addLoggerProviderCustomizer

import io.opentelemetry.api.OpenTelemetry;
import io.quarkus.arc.properties.IfBuildProperty;
import io.quarkus.runtime.StartupEvent;
import io.smallrye.config.Priorities;
import jakarta.annotation.Priority;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

@Singleton
@IfBuildProperty(name = "quarkus.otel.enabled", stringValue = "true")
public class OpenTelemetryStartup {
  @Inject OpenTelemetry openTelemetry;

  void onStart(@Priority(Priorities.APPLICATION - 1) @Observes StartupEvent event) {
    // need @Inject to override OpenTelemetry default bean
  }
}
import static java.lang.Boolean.TRUE;
import static java.util.Collections.emptyList;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.IdGenerator;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.samplers.Sampler;
import io.quarkus.arc.properties.IfBuildProperty;
import io.quarkus.opentelemetry.runtime.config.build.OTelBuildConfig;
import io.quarkus.opentelemetry.runtime.config.runtime.OTelRuntimeConfig;
import io.quarkus.opentelemetry.runtime.tracing.DelayedAttributes;
import io.quarkus.opentelemetry.runtime.tracing.DropTargetsSampler;
import io.quarkus.opentelemetry.runtime.tracing.TracerRecorder;
import io.quarkus.opentelemetry.runtime.tracing.TracerUtil;
import io.quarkus.runtime.ApplicationConfig;
import io.smallrye.config.ConfigValue;
import io.smallrye.config.NameIterator;
import io.smallrye.config.SmallRyeConfig;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.microprofile.config.ConfigProvider;

// TODO: copied from
// https://github.com/quarkusio/quarkus/blob/3.2.7.Final/extensions/opentelemetry/runtime/src/main/java/io/quarkus/opentelemetry/runtime/OpenTelemetryProducer.java
//  will be removed when quarkus adds support for logging, also remove from, also
// remove OpenTelemetryStartup
@Singleton
public class OpenTelemetryProducer {
  @Inject Instance<IdGenerator> idGenerator;
  @Inject @Any Instance<Resource> resources;
  @Inject @Any Instance<DelayedAttributes> delayedAttributes;
  @Inject @Any Instance<Sampler> sampler;
  @Inject @Any Instance<SpanProcessor> spanProcessors;
  @Inject OTelBuildConfig oTelBuildConfig;
  @Inject OTelRuntimeConfig oTelRuntimeConfig;
  @Inject ApplicationConfig appConfig;

  @Produces
  @IfBuildProperty(name = "quarkus.otel.enabled", stringValue = "true")
  @Singleton
  public OpenTelemetry getOpenTelemetry() {
    final Map<String, String> oTelConfigs = getOtelConfigs();

    if (oTelRuntimeConfig.sdkDisabled()) {
      return AutoConfiguredOpenTelemetrySdk.builder()
          .setResultAsGlobal(true)
          .registerShutdownHook(false)
          .addPropertiesSupplier(() -> oTelConfigs)
          .build()
          .getOpenTelemetrySdk();
    }

    final AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
        AutoConfiguredOpenTelemetrySdk.builder()
            .setResultAsGlobal(true)
            .registerShutdownHook(false)
            .addPropertiesSupplier(() -> oTelConfigs)
            .setServiceClassLoader(Thread.currentThread().getContextClassLoader())
            // no customization needed for spanExporter. Loads SPI from CDI
            .addResourceCustomizer(
                (existingResource, configProperties) -> {
                  if (oTelBuildConfig.traces().enabled().orElse(TRUE)) {
                    Resource consolidatedResource =
                        existingResource.merge(
                            Resource.create(delayedAttributes.get())); // from cdi

                    // if user explicitly set 'otel.service.name', make sure we don't override it
                    // with defaults
                    // inside resource customizer
                    String serviceName =
                        oTelRuntimeConfig
                            .serviceName()
                            .filter(sn -> !sn.equals(appConfig.name.orElse("unset")))
                            .orElse(null);

                    // Merge resource instances with env attributes
                    Resource resource =
                        resources.stream()
                            .reduce(Resource.empty(), Resource::merge)
                            .merge(
                                TracerUtil.mapResourceAttributes(
                                    oTelRuntimeConfig.resourceAttributes().orElse(emptyList()),
                                    serviceName)); // from properties
                    return consolidatedResource.merge(resource);
                  } else {
                    return Resource.builder().build();
                  }
                })
            .addSamplerCustomizer(
                (existingSampler, configProperties) -> {
                  if (oTelBuildConfig.traces().enabled().orElse(TRUE)) {
                    final Sampler effectiveSampler =
                        sampler.stream()
                            .findFirst()
                            .map(Sampler.class::cast) // use CDI if it exists
                            .orElse(existingSampler);

                    // collect default filtering targets (Needed for all samplers)
                    List<String> dropTargets = new ArrayList<>();
                    if (oTelRuntimeConfig
                        .traces()
                        .suppressNonApplicationUris()) { // default is true
                      dropTargets.addAll(TracerRecorder.dropNonApplicationUriTargets);
                    }
                    if (!oTelRuntimeConfig.traces().includeStaticResources()) { // default is false
                      dropTargets.addAll(TracerRecorder.dropStaticResourceTargets);
                    }

                    // make sure dropped targets are not sampled
                    if (!dropTargets.isEmpty()) {
                      return new DropTargetsSampler(effectiveSampler, dropTargets);
                    } else {
                      return effectiveSampler;
                    }
                  } else {
                    return Sampler.alwaysOff();
                  }
                })
            .addTracerProviderCustomizer(
                (builder, configProperties) -> {
                  if (oTelBuildConfig.traces().enabled().orElse(TRUE)) {
                    idGenerator.stream().findFirst().ifPresent(builder::setIdGenerator); // from cdi
                    spanProcessors.stream().forEach(builder::addSpanProcessor);
                  }
                  return builder;
                })
            .addLoggerProviderCustomizer(
                (existing, configProperties) ->
                    existing.addLogRecordProcessor(
                        BatchLogRecordProcessor.builder(OtlpGrpcLogRecordExporter.getDefault())
                            .build()))
            .build();
    return autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk();
  }

  private Map<String, String> getOtelConfigs() {
    Map<String, String> oTelConfigs = new HashMap<>();
    SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class);

    // instruct OTel that we are using the AutoConfiguredOpenTelemetrySdk
    oTelConfigs.put("otel.java.global-autoconfigure.enabled", "true");

    // load new properties
    for (String propertyName : config.getPropertyNames()) {
      if (propertyName.startsWith("quarkus.otel.")) {
        ConfigValue configValue = config.getConfigValue(propertyName);
        if (configValue.getValue() != null) {
          NameIterator name = new NameIterator(propertyName);
          name.next();
          oTelConfigs.put(name.getName().substring(name.getPosition() + 1), configValue.getValue());
        }
      }
    }
    return oTelConfigs;
  }
}

Best regards
Lucian

@brunobat
Copy link
Contributor Author

@loicmathieu can you submit your draft PR for this issue?

@loicmathieu
Copy link
Contributor

Hi,
I'll try to submit my PR tomorrow so we can move forward on this topic.

@loicmathieu
Copy link
Contributor

Hi, draft PR is here: #38239

@blazmrak
Copy link
Contributor

Quarkus >3.3.0 changed how OpenTelemetry is produced. How can I export the logs to OpenTelemetry collector? Should I use the agent that just exports the logs and disable trace/metrics instrumentation?

@alexalmeida52
Copy link

I need to display logs in Jaeger, but I'm not able to export them with opentelemetry. Do you already have a solution for this?

@loicmathieu
Copy link
Contributor

@alexalmeida52 you can follow #38239 that shoud provide this functionality.

For the moment, there is some work to be done at the Quarkus OpenTelemetry implementation to be able to send logs via the logs bridge so the PR is blocked until this work is done.

@brunobat
Copy link
Contributor Author

@alexalmeida52, as far as I know, Jaeger is not able to display logs, just traces. But you should check that in https://github.com/jaegertracing/jaeger

@brunobat brunobat moved this from Todo to In Progress in Quarkus Roadmap/Planning Jul 15, 2024
@brunobat brunobat self-assigned this Jul 15, 2024
@github-project-automation github-project-automation bot moved this from In Progress to Done in Quarkus Roadmap/Planning Oct 1, 2024
@quarkus-bot quarkus-bot bot added this to the 3.16 - main milestone Oct 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

7 participants