diff --git a/pom.xml b/pom.xml index 1fcccc392..d855eca78 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,7 @@ jenkinsci/${project.artifactId}-plugin 1.39.0 2.5.0 + 1.25.0-alpha 1.36.0-alpha true 8.14.1 @@ -93,15 +94,32 @@ - io.jenkins.plugins + io.opentelemetry opentelemetry-api - 1.39.0-8.vfb_39d89a_2812 - io.opentelemetry opentelemetry-api-incubator + + io.opentelemetry + opentelemetry-context + + + io.opentelemetry.semconv + opentelemetry-semconv + ${opentelemetry-semconv.version} + + + io.opentelemetry.semconv + opentelemetry-semconv-incubating + ${opentelemetry-semconv.version} + + + io.opentelemetry.instrumentation + opentelemetry-instrumentation-api + + io.opentelemetry opentelemetry-sdk diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsControllerOpenTelemetry.java b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsControllerOpenTelemetry.java new file mode 100644 index 000000000..dcf93e86c --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsControllerOpenTelemetry.java @@ -0,0 +1,127 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.Extension; +import hudson.ExtensionList; +import io.jenkins.plugins.opentelemetry.opentelemetry.ReconfigurableOpenTelemetry; +import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.incubator.events.EventLogger; +import io.opentelemetry.api.incubator.events.GlobalEventLoggerProvider; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.instrumentation.resources.ProcessResourceProvider; +import io.opentelemetry.sdk.OpenTelemetrySdk; + +import javax.annotation.PreDestroy; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * {@link OpenTelemetry} instance intended to live on the Jenkins Controller. + */ +@Extension +public class JenkinsControllerOpenTelemetry extends ReconfigurableOpenTelemetry implements OpenTelemetry { + + private static final Logger LOGGER = Logger.getLogger(JenkinsControllerOpenTelemetry.class.getName()); + + /** + * See {@code OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS} + */ + public static final String DEFAULT_OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS = ProcessResourceProvider.class.getName(); + + public final static AtomicInteger INSTANCE_COUNTER = new AtomicInteger(0); + + @NonNull + private final transient Tracer defaultTracer; + protected transient Meter defaultMeter; + protected final transient EventLogger defaultEventLogger; + + public JenkinsControllerOpenTelemetry() { + super(); + if (INSTANCE_COUNTER.get() > 0) { + LOGGER.log(Level.WARNING, "More than one instance of JenkinsControllerOpenTelemetry created: " + INSTANCE_COUNTER.get()); + } + + String opentelemetryPluginVersion = OtelUtils.getOpentelemetryPluginVersion(); + + this.defaultTracer = + getTracerProvider() + .tracerBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME) + .setInstrumentationVersion(opentelemetryPluginVersion) + .build(); + + this.defaultEventLogger = getEventLoggerProvider() + .eventLoggerBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME) + .setInstrumentationVersion(opentelemetryPluginVersion) + .build(); + } + + @NonNull + public Tracer getDefaultTracer() { + return defaultTracer; + } + + public boolean isLogsEnabled() { + String otelLogsExporter = config.getString("otel.logs.exporter"); + return otelLogsExporter != null && !otelLogsExporter.equals("none"); + } + + public boolean isOtelLogsMirrorToDisk() { + String otelLogsExporter = config.getString("otel.logs.mirror_to_disk"); + return otelLogsExporter != null && otelLogsExporter.equals("true"); + } + + @VisibleForTesting + @NonNull + protected OpenTelemetrySdk getOpenTelemetrySdk() { + Preconditions.checkNotNull(getOpenTelemetryDelegate()); + if (getOpenTelemetryDelegate() instanceof OpenTelemetrySdk) { + return (OpenTelemetrySdk) getOpenTelemetryDelegate(); + } else { + throw new IllegalStateException("OpenTelemetry initialized as NoOp"); + } + } + + @PreDestroy + public void shutdown() { + super.close(); + } + + public void initialize(@NonNull OpenTelemetryConfiguration configuration) { + configure( + configuration.toOpenTelemetryProperties(), + configuration.toOpenTelemetryResource()); + } + + @Override + protected void postOpenTelemetrySdkConfiguration() { + String opentelemetryPluginVersion = OtelUtils.getOpentelemetryPluginVersion(); + + this.defaultMeter = getMeterProvider() + .meterBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME) + .setInstrumentationVersion(opentelemetryPluginVersion) + .build(); + + LOGGER.log(Level.FINER, () -> "Configure OpenTelemetryLifecycleListeners: " + ExtensionList.lookup(OpenTelemetryLifecycleListener.class).stream().sorted().map(e -> e.getClass().getName()).collect(Collectors.joining(", "))); + ExtensionList.lookup(OpenTelemetryLifecycleListener.class).stream() + .sorted() + .forEachOrdered(otelComponent -> { + otelComponent.afterSdkInitialized(defaultMeter, getOpenTelemetryDelegate().getLogsBridge(), defaultEventLogger, defaultTracer, config); + otelComponent.afterSdkInitialized(getOpenTelemetryDelegate(), config); + }); + } + + static public JenkinsControllerOpenTelemetry get() { + return ExtensionList.lookupSingleton(JenkinsControllerOpenTelemetry.class); + } +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java index e92ca5b73..5f4727120 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java @@ -126,9 +126,9 @@ public class JenkinsOpenTelemetryPluginConfiguration extends GlobalConfiguration private String ignoredSteps = "dir,echo,isUnix,pwd,properties"; - private String disabledResourceProviders = OpenTelemetrySdkProvider.DEFAULT_OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS; + private String disabledResourceProviders = JenkinsControllerOpenTelemetry.DEFAULT_OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS; - private transient OpenTelemetrySdkProvider openTelemetrySdkProvider; + private transient JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry; private transient LogStorageRetriever logStorageRetriever; @@ -182,12 +182,11 @@ public boolean configure(StaplerRequest req, JSONObject json) throws FormExcepti protected Object readResolve() { LOGGER.log(Level.FINE, "readResolve()"); if (this.disabledResourceProviders == null) { - this.disabledResourceProviders = OpenTelemetrySdkProvider.DEFAULT_OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS; + this.disabledResourceProviders = JenkinsControllerOpenTelemetry.DEFAULT_OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS; } return this; } - @NonNull public OpenTelemetryConfiguration toOpenTelemetryConfiguration() { Properties properties = new Properties(); @@ -197,7 +196,7 @@ public OpenTelemetryConfiguration toOpenTelemetryConfiguration() { LOGGER.log(Level.WARNING, "Exception parsing configuration properties", e); } - Map configurationProperties = new HashMap(); + Map configurationProperties = new HashMap<>(); getObservabilityBackends().forEach(backend -> configurationProperties.putAll(backend.getOtelConfigurationProperties())); configurationProperties.put(JenkinsOtelSemanticAttributes.JENKINS_VERSION.getKey(), OtelUtils.getJenkinsVersion()); configurationProperties.put(JenkinsOtelSemanticAttributes.JENKINS_URL.getKey(), this.jenkinsLocationConfiguration.getUrl()); @@ -209,7 +208,7 @@ public OpenTelemetryConfiguration toOpenTelemetryConfiguration() { return new OpenTelemetryConfiguration( Optional.ofNullable(this.getEndpoint()), Optional.ofNullable(this.getTrustedCertificatesPem()), - Optional.ofNullable(this.getAuthentication()), + Optional.of(this.getAuthentication()), Optional.ofNullable(this.getExporterTimeoutMillis()), Optional.ofNullable(this.getExporterIntervalMillis()), Optional.ofNullable(this.getServiceName()), @@ -218,6 +217,20 @@ public OpenTelemetryConfiguration toOpenTelemetryConfiguration() { configurationProperties); } + /** + * Register reconfigurable {@link io.opentelemetry.api.OpenTelemetry} + * on {@link io.opentelemetry.api.GlobalOpenTelemetry} + * and {@link io.jenkins.plugins.opentelemetry.opentelemetry.ReconfigurableEventLoggerProvider} + * on {@link io.opentelemetry.api.incubator.events.GlobalEventLoggerProvider} + * as early as possible in Jenkins lifecycle so any plugin invoking those Global setters will have the + * reconfigurable instance . + */ + @Initializer(after = InitMilestone.EXTENSIONS_AUGMENTED, before = InitMilestone.SYSTEM_CONFIG_LOADED) + public void initializeOpenTelemetryAfterExtensionsAugmented() { + LOGGER.log(Level.INFO, "Initialize Jenkins OpenTelemetry Plugin with a NoOp implementation..."); + jenkinsControllerOpenTelemetry.configure(Collections.emptyMap(), Resource.empty()); + } + /** * Initialize the Otel SDK, must happen after the plugin has been configured by the standard config and by JCasC * JCasC configuration happens during `SYSTEM_CONFIG_ADAPTED` (see `io.jenkins.plugins.casc.ConfigurationAsCode#init()`) @@ -225,12 +238,12 @@ public OpenTelemetryConfiguration toOpenTelemetryConfiguration() { @Initializer(after = InitMilestone.SYSTEM_CONFIG_ADAPTED, before = InitMilestone.JOB_LOADED) @SuppressWarnings("MustBeClosedChecker") public void initializeOpenTelemetry() { - LOGGER.log(Level.FINE, "Initialize Jenkins OpenTelemetry Plugin..."); + LOGGER.log(Level.INFO, "Initialize Jenkins OpenTelemetry Plugin..."); OpenTelemetryConfiguration newOpenTelemetryConfiguration = toOpenTelemetryConfiguration(); if (Objects.equals(this.currentOpenTelemetryConfiguration, newOpenTelemetryConfiguration)) { LOGGER.log(Level.FINE, "Configuration didn't change, skip reconfiguration"); } else { - openTelemetrySdkProvider.initialize(newOpenTelemetryConfiguration); + jenkinsControllerOpenTelemetry.initialize(newOpenTelemetryConfiguration); this.currentOpenTelemetryConfiguration = newOpenTelemetryConfiguration; } @@ -308,8 +321,8 @@ public List getObservabilityBackends() { } @Inject - public void setOpenTelemetrySdkProvider(OpenTelemetrySdkProvider openTelemetrySdkProvider) { - this.openTelemetrySdkProvider = openTelemetrySdkProvider; + public void setJenkinsControllerOpenTelemetry(JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry) { + this.jenkinsControllerOpenTelemetry = jenkinsControllerOpenTelemetry; } public Integer getExporterTimeoutMillis() { @@ -506,7 +519,7 @@ public void setServiceName(String serviceName) { } /** - * @see ServiceIncubatingAttributes#SERVICE_NAMESPACE + * @see io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes#SERVICE_NAMESPACE */ public String getServiceNamespace() { return (Strings.isNullOrEmpty(this.serviceNamespace)) ? JenkinsOtelSemanticAttributes.JENKINS : this.serviceNamespace; @@ -520,10 +533,10 @@ public void setServiceNamespace(String serviceNamespace) { @NonNull public Resource getResource() { - if (this.openTelemetrySdkProvider == null) { + if (this.jenkinsControllerOpenTelemetry == null) { return Resource.empty(); } else { - return this.openTelemetrySdkProvider.getResource(); + return this.jenkinsControllerOpenTelemetry.getResource(); } } @@ -540,10 +553,10 @@ public String getResourceAsText() { @NonNull public ConfigProperties getConfigProperties() { - if (this.openTelemetrySdkProvider == null) { + if (this.jenkinsControllerOpenTelemetry == null) { return ConfigPropertiesUtils.emptyConfig(); } else { - return this.openTelemetrySdkProvider.getConfig(); + return this.jenkinsControllerOpenTelemetry.getConfig(); } } @@ -568,11 +581,12 @@ public LogStorageRetriever getLogStorageRetriever() { @NonNull @MustBeClosed - @SuppressWarnings("MustBeClosedChecker") // false positive invoking backend.getLogStorageRetriever(templateBindingsProvider) + @SuppressWarnings("MustBeClosedChecker") + // false positive invoking backend.getLogStorageRetriever(templateBindingsProvider) private LogStorageRetriever resolveLogStorageRetriever() { LogStorageRetriever logStorageRetriever = null; - Resource otelSdkResource = openTelemetrySdkProvider.getResource(); + Resource otelSdkResource = jenkinsControllerOpenTelemetry.getResource(); String serviceName = Objects.requireNonNull(otelSdkResource.getAttribute(ServiceAttributes.SERVICE_NAME), "service.name can't be null"); String serviceNamespace = otelSdkResource.getAttribute(ServiceIncubatingAttributes.SERVICE_NAMESPACE); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/OtelComponent.java b/src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetryLifecycleListener.java similarity index 95% rename from src/main/java/io/jenkins/plugins/opentelemetry/OtelComponent.java rename to src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetryLifecycleListener.java index 7266143d7..4847c189b 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/OtelComponent.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetryLifecycleListener.java @@ -24,7 +24,7 @@ *

* Used by components that create counters... */ -public interface OtelComponent extends Comparable{ +public interface OpenTelemetryLifecycleListener extends Comparable{ /** * Invoked soon after the Otel SDK has been initialized. @@ -64,7 +64,7 @@ default int ordinal() { } @Override - default int compareTo(OtelComponent other) { + default int compareTo(OpenTelemetryLifecycleListener other) { if (this.ordinal() == other.ordinal()) { return this.getClass().getName().compareTo(other.getClass().getName()); } else { diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetryRootAction.java b/src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetryRootAction.java index e6a802931..0002f9bf4 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetryRootAction.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetryRootAction.java @@ -14,12 +14,15 @@ import java.util.logging.Level; import java.util.logging.Logger; +/** + * Decorates Jenkins navigation GUI with the OpenTelemetry dashboard link if defined + */ @Extension public class OpenTelemetryRootAction implements RootAction { private static final Logger logger = Logger.getLogger(OpenTelemetryRootAction.class.getName()); private JenkinsOpenTelemetryPluginConfiguration pluginConfiguration; - private OpenTelemetrySdkProvider openTelemetrySdkProvider; + private JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry; public Optional getFirstMetricsCapableObservabilityBackend() { final Optional observabilityBackend = pluginConfiguration.getObservabilityBackends() @@ -47,7 +50,7 @@ public String getDisplayName() { public String getUrlName() { // TODO we could keep in cache this URL return getFirstMetricsCapableObservabilityBackend() - .map(backend -> backend.getMetricsVisualizationUrl(this.openTelemetrySdkProvider.getResource())) + .map(backend -> backend.getMetricsVisualizationUrl(this.jenkinsControllerOpenTelemetry.getResource())) .orElse(null); } @@ -57,7 +60,7 @@ public void setJenkinsOpenTelemetryPluginConfiguration(JenkinsOpenTelemetryPlugi } @Inject - public void setOpenTelemetrySdkProvider(OpenTelemetrySdkProvider openTelemetrySdkProvider) { - this.openTelemetrySdkProvider = openTelemetrySdkProvider; + public void setJenkinsControllerOpenTelemetry(JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry) { + this.jenkinsControllerOpenTelemetry = jenkinsControllerOpenTelemetry; } } diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetrySdkProvider.java b/src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetrySdkProvider.java deleted file mode 100644 index cf72624aa..000000000 --- a/src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetrySdkProvider.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright The Original Author or Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.jenkins.plugins.opentelemetry; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Preconditions; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; -import hudson.Extension; -import hudson.ExtensionList; -import io.jenkins.plugins.opentelemetry.opentelemetry.ClosingOpenTelemetry; -import io.jenkins.plugins.opentelemetry.opentelemetry.autoconfigure.ConfigPropertiesUtils; -import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; -import io.opentelemetry.api.GlobalOpenTelemetry; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.incubator.events.EventLogger; -import io.opentelemetry.api.incubator.events.EventLoggerProvider; -import io.opentelemetry.api.incubator.events.GlobalEventLoggerProvider; -import io.opentelemetry.api.logs.LoggerProvider; -import io.opentelemetry.api.metrics.Meter; -import io.opentelemetry.api.trace.SpanBuilder; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.context.propagation.ContextPropagators; -import io.opentelemetry.instrumentation.resources.ProcessResourceProvider; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; -import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.resources.Resource; - -import javax.annotation.PreDestroy; -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.stream.Collectors; - - -@Extension -public class OpenTelemetrySdkProvider { - - private static final Logger LOGGER = Logger.getLogger(OpenTelemetrySdkProvider.class.getName()); - - /** - * See {@code OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS} - */ - public static final String DEFAULT_OTEL_JAVA_DISABLED_RESOURCE_PROVIDERS = ProcessResourceProvider.class.getName(); - - protected transient ClosingOpenTelemetry openTelemetry; - - protected transient Resource resource; - protected transient ConfigProperties config; - @Nullable - protected transient OpenTelemetrySdk openTelemetrySdk; - @NonNull - private final transient TracerDelegate tracer = new TracerDelegate(); - protected transient Meter meter; - protected transient EventLogger eventLogger; - - public OpenTelemetrySdkProvider() { - } - - - @NonNull - public Tracer getTracer() { - return Preconditions.checkNotNull(tracer.getDelegate()); - } - - @NonNull - public Meter getMeter() { - return Preconditions.checkNotNull(meter); - } - - @NonNull - public Resource getResource() { - return Preconditions.checkNotNull(resource); - } - - @NonNull - public ConfigProperties getConfig() { - return Preconditions.checkNotNull(config); - } - - public boolean isOtelLogsEnabled(){ - String otelLogsExporter = config.getString("otel.logs.exporter"); - return otelLogsExporter != null && !otelLogsExporter.equals("none"); - } - - public boolean isOtelLogsMirrorToDisk(){ - String otelLogsExporter = config.getString("otel.logs.mirror_to_disk"); - return otelLogsExporter != null && otelLogsExporter.equals("true"); - } - - @NonNull - public ContextPropagators getPropagators() { - return openTelemetry.getPropagators(); - } - - @NonNull - public LoggerProvider getLoggerProvider() { - return openTelemetry.getLogsBridge(); - } - - @VisibleForTesting - @NonNull - protected OpenTelemetrySdk getOpenTelemetrySdk() { - return Preconditions.checkNotNull(openTelemetrySdk); - } - - @PreDestroy - public void shutdown() { - if (this.openTelemetrySdk != null) { - LOGGER.log(Level.FINE, "Shutdown..."); - LOGGER.log(Level.FINE, () -> "Shutdown Otel SDK on components: " + ExtensionList.lookup(OtelComponent.class).stream().sorted().map(e -> e.getClass().getName()).collect(Collectors.joining(", "))); - - ExtensionList.lookup(OtelComponent.class).stream().sorted().forEachOrdered(OtelComponent::beforeSdkShutdown); - this.openTelemetry.close(); - CompletableResultCode shutdown = this.openTelemetrySdk.shutdown(); - if(!shutdown.join(1, TimeUnit.SECONDS).isSuccess()) { - LOGGER.log(Level.WARNING, "Failure to shutdown OTel SDK"); - } - } - GlobalOpenTelemetry.resetForTest(); - GlobalEventLoggerProvider.resetForTest(); - } - - public void initialize(@NonNull OpenTelemetryConfiguration configuration) { - shutdown(); // shutdown existing SDK - if (configuration.getEndpoint().isPresent()) { - initializeOtlp(configuration); - } else { - initializeNoOp(); - } - LOGGER.log(Level.FINE, () -> "Initialize Otel SDK on components: " + ExtensionList.lookup(OtelComponent.class).stream().sorted().map(e -> e.getClass().getName()).collect(Collectors.joining(", "))); - ExtensionList.lookup(OtelComponent.class).stream().sorted().forEachOrdered(otelComponent -> { - otelComponent.afterSdkInitialized(meter, openTelemetry.getLogsBridge(), eventLogger, tracer, config); - otelComponent.afterSdkInitialized(openTelemetry, config); - }); - } - - public void initializeOtlp(@NonNull OpenTelemetryConfiguration configuration) { - - AutoConfiguredOpenTelemetrySdkBuilder sdkBuilder = AutoConfiguredOpenTelemetrySdk.builder(); - // PROPERTIES - sdkBuilder.addPropertiesSupplier(configuration::toOpenTelemetryProperties); - sdkBuilder.addPropertiesCustomizer((Function>) configProperties -> { - // keep a reference to the computed config properties for future use in the plugin - OpenTelemetrySdkProvider.this.config = configProperties; - return Collections.emptyMap(); - }); - - // RESOURCE - sdkBuilder.addResourceCustomizer((resource, configProperties) -> { - // keep a reference to the computed Resource for future use in the plugin - this.resource = Resource.builder() - .putAll(resource) - .putAll(configuration.toOpenTelemetryResource()).build(); - return this.resource; - } - ); - - sdkBuilder - .disableShutdownHook() // SDK closed by io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider.preDestroy() - .setResultAsGlobal(); // ensure GlobalOpenTelemetry.set() is invoked - this.openTelemetrySdk = sdkBuilder.build().getOpenTelemetrySdk(); - this.openTelemetry = new ClosingOpenTelemetry(this.openTelemetrySdk); - String opentelemetryPluginVersion = OtelUtils.getOpentelemetryPluginVersion(); - this.tracer.setDelegate(openTelemetry.getTracerProvider() - .tracerBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME) - .setInstrumentationVersion(opentelemetryPluginVersion) - .build()); - this.meter = openTelemetry.getMeterProvider() - .meterBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME) - .setInstrumentationVersion(opentelemetryPluginVersion) - .build(); - this.eventLogger = GlobalEventLoggerProvider.get() - .eventLoggerBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME) - .setInstrumentationVersion(opentelemetryPluginVersion) - // .setEventDomain("jenkins") - .build(); - - LOGGER.log(Level.INFO, () -> "OpenTelemetry SDK initialized: " + OtelUtils.prettyPrintOtelSdkConfig(this.config, this.resource)); - } - - @VisibleForTesting - public void initializeNoOp() { - LOGGER.log(Level.FINE, "initializeNoOp"); - - this.openTelemetrySdk = null; - this.resource = Resource.getDefault(); - this.config = ConfigPropertiesUtils.emptyConfig(); - this.openTelemetry = ClosingOpenTelemetry.noop(); - GlobalOpenTelemetry.resetForTest(); // hack for testing in Intellij cause by DiskUsageMonitoringInitializer - GlobalEventLoggerProvider.resetForTest(); - GlobalOpenTelemetry.set(OpenTelemetry.noop()); - this.tracer.setDelegate(OpenTelemetry.noop().getTracer(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME)); - this.meter = OpenTelemetry.noop().getMeter(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME); - this.eventLogger = EventLoggerProvider.noop().get(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME); - LOGGER.log(Level.FINE, "OpenTelemetry initialized as NoOp"); - } - - static public OpenTelemetrySdkProvider get() { - return ExtensionList.lookupSingleton(OpenTelemetrySdkProvider.class); - } - - static class TracerDelegate implements Tracer { - private Tracer delegate; - - @Override - public synchronized SpanBuilder spanBuilder(@NonNull String spanName) { - return delegate.spanBuilder(spanName); - } - - public synchronized void setDelegate(Tracer delegate) { - this.delegate = delegate; - } - - public synchronized Tracer getDelegate() { - return delegate; - } - } -} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/backend/elastic/ElasticsearchLogStorageRetriever.java b/src/main/java/io/jenkins/plugins/opentelemetry/backend/elastic/ElasticsearchLogStorageRetriever.java index a56b891e7..6abd1576b 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/backend/elastic/ElasticsearchLogStorageRetriever.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/backend/elastic/ElasticsearchLogStorageRetriever.java @@ -21,7 +21,7 @@ import edu.umd.cs.findbugs.annotations.Nullable; import groovy.text.Template; import hudson.util.FormValidation; -import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; +import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry; import io.jenkins.plugins.opentelemetry.TemplateBindingsProvider; import io.jenkins.plugins.opentelemetry.backend.ElasticBackend; import io.jenkins.plugins.opentelemetry.backend.ObservabilityBackend; @@ -378,7 +378,7 @@ public String toString() { private Tracer getTracer() { if (_tracer == null) { - _tracer = OpenTelemetrySdkProvider.get().getTracer(); + _tracer = JenkinsControllerOpenTelemetry.get().getDefaultTracer(); } return _tracer; } diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java index b3819f937..0732bc420 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringCloudListener.java @@ -10,7 +10,7 @@ import hudson.model.Node; import hudson.slaves.CloudProvisioningListener; import hudson.slaves.NodeProvisioner; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics; import io.opentelemetry.api.incubator.events.EventLogger; import io.opentelemetry.api.logs.LoggerProvider; @@ -24,7 +24,7 @@ import java.util.logging.Logger; @Extension(dynamicLoadable = YesNoMaybe.YES, optional = true) -public class MonitoringCloudListener extends CloudProvisioningListener implements OtelComponent { +public class MonitoringCloudListener extends CloudProvisioningListener implements OpenTelemetryLifecycleListener { private final static Logger LOGGER = Logger.getLogger(MonitoringCloudListener.class.getName()); private LongCounter failureCloudCounter; diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringComputerListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringComputerListener.java index 2aed2d440..d4f3bb5f0 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringComputerListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/computer/MonitoringComputerListener.java @@ -12,7 +12,7 @@ import hudson.remoting.Channel; import hudson.slaves.ComputerListener; import io.jenkins.plugins.opentelemetry.OpenTelemetryAttributesAction; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics; import io.opentelemetry.api.common.AttributeKey; @@ -36,7 +36,7 @@ import java.util.logging.Logger; @Extension(dynamicLoadable = YesNoMaybe.YES, optional = true) -public class MonitoringComputerListener extends ComputerListener implements OtelComponent { +public class MonitoringComputerListener extends ComputerListener implements OpenTelemetryLifecycleListener { private final static Logger LOGGER = Logger.getLogger(MonitoringComputerListener.class.getName()); private LongCounter failureAgentCounter; diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/init/DiskUsageMonitoringInitializer.java b/src/main/java/io/jenkins/plugins/opentelemetry/init/DiskUsageMonitoringInitializer.java index 36f0740c3..2044f8752 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/init/DiskUsageMonitoringInitializer.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/init/DiskUsageMonitoringInitializer.java @@ -9,7 +9,7 @@ import com.cloudbees.simplediskusage.QuickDiskUsagePlugin; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics; import io.opentelemetry.api.incubator.events.EventLogger; import io.opentelemetry.api.logs.LoggerProvider; @@ -28,7 +28,7 @@ * Capture disk usage metrics relying on the {@link QuickDiskUsagePlugin} */ @Extension(dynamicLoadable = YesNoMaybe.YES, optional = true) -public class DiskUsageMonitoringInitializer implements OtelComponent { +public class DiskUsageMonitoringInitializer implements OpenTelemetryLifecycleListener { private final static Logger LOGGER = Logger.getLogger(DiskUsageMonitoringInitializer.class.getName()); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/init/GitHubClientMonitoring.java b/src/main/java/io/jenkins/plugins/opentelemetry/init/GitHubClientMonitoring.java index 46d45db2a..0af39047b 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/init/GitHubClientMonitoring.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/init/GitHubClientMonitoring.java @@ -7,7 +7,7 @@ import com.google.common.base.Preconditions; import hudson.Extension; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.incubator.events.EventLogger; @@ -39,7 +39,7 @@ * field of the {@link Connector} class because we have not found any public API to observe the state of this GitHub client. */ @Extension(dynamicLoadable = YesNoMaybe.YES, optional = true) -public class GitHubClientMonitoring implements OtelComponent { +public class GitHubClientMonitoring implements OpenTelemetryLifecycleListener { private final static Logger logger = Logger.getLogger(GitHubClientMonitoring.class.getName()); private final Field gitHub_clientField; diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/init/JenkinsExecutorMonitoringInitializer.java b/src/main/java/io/jenkins/plugins/opentelemetry/init/JenkinsExecutorMonitoringInitializer.java index 41701b4ba..5b24d2cf5 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/init/JenkinsExecutorMonitoringInitializer.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/init/JenkinsExecutorMonitoringInitializer.java @@ -7,7 +7,7 @@ import hudson.Extension; import hudson.model.LoadStatistics; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.incubator.events.EventLogger; @@ -22,7 +22,7 @@ import static io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics.*; @Extension(dynamicLoadable = YesNoMaybe.MAYBE, optional = true) -public class JenkinsExecutorMonitoringInitializer implements OtelComponent { +public class JenkinsExecutorMonitoringInitializer implements OpenTelemetryLifecycleListener { @Override public void afterSdkInitialized(Meter meter, LoggerProvider loggerProvider, EventLogger eventLogger, Tracer tracer, ConfigProperties configProperties) { diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/init/JvmMonitoringInitializer.java b/src/main/java/io/jenkins/plugins/opentelemetry/init/JvmMonitoringInitializer.java index 741a9c736..c56a2cef1 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/init/JvmMonitoringInitializer.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/init/JvmMonitoringInitializer.java @@ -6,7 +6,7 @@ package io.jenkins.plugins.opentelemetry.init; import hudson.Extension; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.instrumentation.runtimemetrics.java8.Classes; import io.opentelemetry.instrumentation.runtimemetrics.java8.Cpu; @@ -24,7 +24,7 @@ * Inspired by io.opentelemetry.instrumentation.javaagent.runtimemetrics.RuntimeMetricsInstaller */ @Extension(dynamicLoadable = YesNoMaybe.MAYBE, optional = true) -public class JvmMonitoringInitializer implements OtelComponent { +public class JvmMonitoringInitializer implements OpenTelemetryLifecycleListener { private final static Logger LOGGER = Logger.getLogger(JvmMonitoringInitializer.class.getName()); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/init/OtelJulHandler.java b/src/main/java/io/jenkins/plugins/opentelemetry/init/OtelJulHandler.java index d6e41cd63..d006e899d 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/init/OtelJulHandler.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/init/OtelJulHandler.java @@ -6,7 +6,7 @@ package io.jenkins.plugins.opentelemetry.init; import hudson.Extension; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.incubator.events.EventLogger; @@ -34,7 +34,7 @@ * Inspired by https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/v1.14.0/instrumentation/java-util-logging/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jul/JavaUtilLoggingHelper.java */ @Extension(dynamicLoadable = YesNoMaybe.YES, optional = true) -public class OtelJulHandler extends Handler implements OtelComponent { +public class OtelJulHandler extends Handler implements OpenTelemetryLifecycleListener { private final static Logger logger = Logger.getLogger(OtelJulHandler.class.getName()); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/init/SCMEventMonitoringInitializer.java b/src/main/java/io/jenkins/plugins/opentelemetry/init/SCMEventMonitoringInitializer.java index c14cbb35c..592d6a312 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/init/SCMEventMonitoringInitializer.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/init/SCMEventMonitoringInitializer.java @@ -6,7 +6,7 @@ package io.jenkins.plugins.opentelemetry.init; import hudson.Extension; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics; import io.opentelemetry.api.incubator.events.EventLogger; import io.opentelemetry.api.logs.LoggerProvider; @@ -23,7 +23,7 @@ * Capture SCM Events metrics */ @Extension(dynamicLoadable = YesNoMaybe.YES, optional = true) -public class SCMEventMonitoringInitializer implements OtelComponent { +public class SCMEventMonitoringInitializer implements OpenTelemetryLifecycleListener { private final static Logger logger = Logger.getLogger(SCMEventMonitoringInitializer.class.getName()); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/init/ServletFilterInitializer.java b/src/main/java/io/jenkins/plugins/opentelemetry/init/ServletFilterInitializer.java index 09b23ef38..099728aea 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/init/ServletFilterInitializer.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/init/ServletFilterInitializer.java @@ -7,7 +7,7 @@ import hudson.Extension; import hudson.util.PluginServletFilter; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; import io.jenkins.plugins.opentelemetry.servlet.StaplerInstrumentationServletFilter; import io.jenkins.plugins.opentelemetry.servlet.TraceContextServletFilter; @@ -30,7 +30,7 @@ * events can be associated to an HTTP trace. */ @Extension(dynamicLoadable = YesNoMaybe.MAYBE, optional = true) -public class ServletFilterInitializer implements OtelComponent { +public class ServletFilterInitializer implements OpenTelemetryLifecycleListener { private static final Logger logger = Logger.getLogger(ServletFilterInitializer.class.getName()); StaplerInstrumentationServletFilter staplerInstrumentationServletFilter; diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/job/MonitoringBuildStepListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/job/MonitoringBuildStepListener.java index a96986ea6..3498f3a1b 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/job/MonitoringBuildStepListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/job/MonitoringBuildStepListener.java @@ -14,7 +14,7 @@ import hudson.model.BuildStepListener; import hudson.tasks.BuildStep; import io.jenkins.plugins.opentelemetry.JenkinsOpenTelemetryPluginConfiguration; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.jenkins.plugins.opentelemetry.OtelUtils; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; import io.opentelemetry.api.incubator.events.EventLogger; @@ -38,7 +38,7 @@ import static io.jenkins.plugins.opentelemetry.OtelUtils.JENKINS_CORE; @Extension(dynamicLoadable = YesNoMaybe.YES) -public class MonitoringBuildStepListener extends BuildStepListener implements OtelComponent { +public class MonitoringBuildStepListener extends BuildStepListener implements OpenTelemetryLifecycleListener { protected static final Logger LOGGER = Logger.getLogger(MonitoringRunListener.class.getName()); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/job/MonitoringPipelineListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/job/MonitoringPipelineListener.java index 3a4a842e3..dee4c333d 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/job/MonitoringPipelineListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/job/MonitoringPipelineListener.java @@ -16,7 +16,7 @@ import hudson.model.Run; import io.jenkins.plugins.opentelemetry.JenkinsOpenTelemetryPluginConfiguration; import io.jenkins.plugins.opentelemetry.OpenTelemetryAttributesAction; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.jenkins.plugins.opentelemetry.OtelUtils; import io.jenkins.plugins.opentelemetry.job.jenkins.AbstractPipelineListener; import io.jenkins.plugins.opentelemetry.job.jenkins.PipelineListener; @@ -77,7 +77,7 @@ @Extension(dynamicLoadable = YesNoMaybe.YES, optional = true) -public class MonitoringPipelineListener extends AbstractPipelineListener implements PipelineListener, StepListener, OtelComponent { +public class MonitoringPipelineListener extends AbstractPipelineListener implements PipelineListener, StepListener, OpenTelemetryLifecycleListener { private final static Logger LOGGER = Logger.getLogger(MonitoringPipelineListener.class.getName()); private OtelTraceService otelTraceService; diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/job/action/OtelMonitoringAction.java b/src/main/java/io/jenkins/plugins/opentelemetry/job/action/OtelMonitoringAction.java index abb3cafd9..aed284966 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/job/action/OtelMonitoringAction.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/job/action/OtelMonitoringAction.java @@ -12,6 +12,9 @@ import java.util.Map; +/** + * Action to decorate {@link hudson.model.Job} steps to hold references to {@link Span}s + */ public interface OtelMonitoringAction extends Action { Map getW3cTraceContext(); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/job/action/RunPhaseMonitoringAction.java b/src/main/java/io/jenkins/plugins/opentelemetry/job/action/RunPhaseMonitoringAction.java index 552a8acac..8fc61b6b0 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/job/action/RunPhaseMonitoringAction.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/job/action/RunPhaseMonitoringAction.java @@ -5,10 +5,12 @@ package io.jenkins.plugins.opentelemetry.job.action; +import hudson.model.Action; import io.opentelemetry.api.trace.Span; /** - * Span reference associate with a phase of a {@link hudson.model.Run} + * Span reference associated with a phase of a {@link hudson.model.Run} as a {@link hudson.model.Run#getActions(Class)} + * @see hudson.model.Run#addAction(Action) * @see io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes#JENKINS_JOB_SPAN_PHASE_START_NAME * @see io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes#JENKINS_JOB_SPAN_PHASE_RUN_NAME * @see io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes#JENKINS_JOB_SPAN_PHASE_FINALIZE_NAME diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogSenderBuildListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogSenderBuildListener.java index 5d28b3ffa..d36291512 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogSenderBuildListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogSenderBuildListener.java @@ -9,7 +9,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.BuildListener; -import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; +import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry; import io.jenkins.plugins.opentelemetry.opentelemetry.GlobalOpenTelemetrySdk; import io.jenkins.plugins.opentelemetry.opentelemetry.common.OffsetClock; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; @@ -88,7 +88,7 @@ public synchronized final PrintStream getLogger() { /** * {@link OtelLogSenderBuildListener} implementation that runs on the Jenkins Controller and - * that retrieves the {@link io.opentelemetry.api.logs.Logger} from the {@link OpenTelemetrySdkProvider} + * that retrieves the {@link io.opentelemetry.api.logs.Logger} from the {@link JenkinsControllerOpenTelemetry} */ static final class OtelLogSenderBuildListenerOnController extends OtelLogSenderBuildListener { private static final long serialVersionUID = 1; @@ -104,7 +104,7 @@ public OtelLogSenderBuildListenerOnController(@NonNull RunTraceContext runTraceC @Override public io.opentelemetry.api.logs.Logger getOtelLogger() { JenkinsJVM.checkJenkinsJVM(); - return OpenTelemetrySdkProvider.get().getLoggerProvider().get(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME); + return JenkinsControllerOpenTelemetry.get().getLogsBridge().get(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME); } /** diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogStorage.java b/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogStorage.java index bc5a64aa4..552356565 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogStorage.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogStorage.java @@ -6,9 +6,9 @@ import hudson.model.BuildListener; import hudson.model.Run; import hudson.model.TaskListener; +import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry; import io.jenkins.plugins.opentelemetry.JenkinsOpenTelemetryPluginConfiguration; import io.jenkins.plugins.opentelemetry.OpenTelemetryConfiguration; -import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; import io.jenkins.plugins.opentelemetry.job.MonitoringAction; import io.jenkins.plugins.opentelemetry.job.OtelTraceService; import io.jenkins.plugins.opentelemetry.job.log.util.TeeBuildListener; @@ -80,7 +80,7 @@ public BuildListener overallListener() throws IOException { OtelLogSenderBuildListener otelLogSenderBuildListener = new OtelLogSenderBuildListener.OtelLogSenderBuildListenerOnController(runTraceContext, otelConfigurationProperties, otelResourceAttributes); BuildListener result; - if (OpenTelemetrySdkProvider.get().isOtelLogsMirrorToDisk()) { + if (JenkinsControllerOpenTelemetry.get().isOtelLogsMirrorToDisk()) { try { File logFile = new File(runFolderPath, "log"); BuildListener fileStorageBuildListener = FileLogStorage.forFile(logFile).overallListener(); @@ -120,7 +120,7 @@ public BuildListener nodeListener(@NonNull FlowNode flowNode) throws IOException OtelLogSenderBuildListener otelLogSenderBuildListener = new OtelLogSenderBuildListener.OtelLogSenderBuildListenerOnController(flowNodeTraceContext, otelConfigurationProperties, otelResourceAttributes); BuildListener result; - if (OpenTelemetrySdkProvider.get().isOtelLogsMirrorToDisk()) { + if (JenkinsControllerOpenTelemetry.get().isOtelLogsMirrorToDisk()) { try { File logFile = new File(runFolderPath, "log"); BuildListener fileStorageBuildListener = BuildListenerAdapter.wrap(FileLogStorage.forFile(logFile).nodeListener(flowNode)); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogStorageFactory.java b/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogStorageFactory.java index 923ee2642..720e7dca2 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogStorageFactory.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/job/log/OtelLogStorageFactory.java @@ -11,8 +11,8 @@ import hudson.ExtensionList; import hudson.model.Queue; import hudson.model.Run; -import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.jenkins.plugins.opentelemetry.job.OtelTraceService; import io.opentelemetry.api.incubator.events.EventLogger; import io.opentelemetry.api.logs.LoggerProvider; @@ -34,7 +34,7 @@ * See https://github.com/jenkinsci/pipeline-cloudwatch-logs-plugin/blob/pipeline-cloudwatch-logs-0.2/src/main/java/io/jenkins/plugins/pipeline_cloudwatch_logs/PipelineBridge.java */ @Extension -public final class OtelLogStorageFactory implements LogStorageFactory, OtelComponent { +public final class OtelLogStorageFactory implements LogStorageFactory, OpenTelemetryLifecycleListener { private final static Logger logger = Logger.getLogger(OtelLogStorageFactory.class.getName()); @@ -43,8 +43,7 @@ public final class OtelLogStorageFactory implements LogStorageFactory, OtelCompo System.setProperty("org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep.USE_WATCHING", "true"); } - @Nullable - private OpenTelemetrySdkProvider openTelemetrySdkProvider; + JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry; @Nullable private OtelTraceService otelTraceService; @@ -58,7 +57,7 @@ static OtelLogStorageFactory get() { @Nullable @Override public LogStorage forBuild(@NonNull final FlowExecutionOwner owner) { - if (!getOpenTelemetrySdkProvider().isOtelLogsEnabled()) { + if (!getJenkinsControllerOpenTelemetry().isLogsEnabled()) { logger.log(Level.FINE, () -> "OTel Logs disabled"); return null; } @@ -83,11 +82,11 @@ public LogStorage forBuild(@NonNull final FlowExecutionOwner owner) { * Workaround dependency injection problem. @Inject doesn't work here */ @NonNull - private OpenTelemetrySdkProvider getOpenTelemetrySdkProvider() { - if (openTelemetrySdkProvider == null) { - openTelemetrySdkProvider = OpenTelemetrySdkProvider.get(); + private JenkinsControllerOpenTelemetry getJenkinsControllerOpenTelemetry() { + if (jenkinsControllerOpenTelemetry == null) { + jenkinsControllerOpenTelemetry = JenkinsControllerOpenTelemetry.get(); } - return openTelemetrySdkProvider; + return jenkinsControllerOpenTelemetry; } /** diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/job/opentelemetry/OtelContextAwareAbstractRunListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/job/opentelemetry/OtelContextAwareAbstractRunListener.java index 68f51cbb3..27ee4ddd3 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/job/opentelemetry/OtelContextAwareAbstractRunListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/job/opentelemetry/OtelContextAwareAbstractRunListener.java @@ -13,7 +13,7 @@ import hudson.model.Run; import hudson.model.TaskListener; import hudson.model.listeners.RunListener; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.jenkins.plugins.opentelemetry.job.OtelTraceService; import io.opentelemetry.api.incubator.events.EventLogger; import io.opentelemetry.api.logs.LoggerProvider; @@ -31,7 +31,7 @@ * {@link RunListener} that setups the OpenTelemetry {@link io.opentelemetry.context.Context} * with the current {@link Span}. */ -public abstract class OtelContextAwareAbstractRunListener extends RunListener implements OtelComponent { +public abstract class OtelContextAwareAbstractRunListener extends RunListener implements OpenTelemetryLifecycleListener { private final static Logger LOGGER = Logger.getLogger(OtelContextAwareAbstractRunListener.class.getName()); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ClosingOpenTelemetry.java b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/CloseableMeterProvider.java similarity index 68% rename from src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ClosingOpenTelemetry.java rename to src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/CloseableMeterProvider.java index bf27f4acf..0f19b1064 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ClosingOpenTelemetry.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/CloseableMeterProvider.java @@ -6,13 +6,30 @@ package io.jenkins.plugins.opentelemetry.opentelemetry; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.logs.LoggerProvider; -import io.opentelemetry.api.metrics.*; -import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.api.trace.TracerBuilder; -import io.opentelemetry.api.trace.TracerProvider; -import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.api.metrics.BatchCallback; +import io.opentelemetry.api.metrics.DoubleCounter; +import io.opentelemetry.api.metrics.DoubleCounterBuilder; +import io.opentelemetry.api.metrics.DoubleGaugeBuilder; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; +import io.opentelemetry.api.metrics.DoubleUpDownCounter; +import io.opentelemetry.api.metrics.DoubleUpDownCounterBuilder; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongCounterBuilder; +import io.opentelemetry.api.metrics.LongGaugeBuilder; +import io.opentelemetry.api.metrics.LongUpDownCounter; +import io.opentelemetry.api.metrics.LongUpDownCounterBuilder; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterBuilder; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.metrics.ObservableDoubleCounter; +import io.opentelemetry.api.metrics.ObservableDoubleGauge; +import io.opentelemetry.api.metrics.ObservableDoubleMeasurement; +import io.opentelemetry.api.metrics.ObservableDoubleUpDownCounter; +import io.opentelemetry.api.metrics.ObservableLongCounter; +import io.opentelemetry.api.metrics.ObservableLongGauge; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; +import io.opentelemetry.api.metrics.ObservableLongUpDownCounter; +import io.opentelemetry.api.metrics.ObservableMeasurement; import java.io.Closeable; import java.util.ArrayList; @@ -21,28 +38,38 @@ import java.util.logging.Level; import java.util.logging.Logger; -/** - * Holds a reference on all the instantiated {@link AutoCloseable} instrument in order to properly close them before - * reconfigurations (eg {@link ObservableLongUpDownCounter}, {@link ObservableLongCounter}...). - */ -public class ClosingOpenTelemetry implements OpenTelemetry, Closeable { +public class CloseableMeterProvider implements MeterProvider, Closeable { + private final static Logger LOGGER = Logger.getLogger(CloseableMeterProvider.class.getName()); + + /** + * Reference on all the instantiated {@link AutoCloseable} instrument in order to properly close them before + * reconfigurations (eg {@link ObservableLongUpDownCounter}, {@link ObservableLongCounter}...). + */ + final List closeables = new ArrayList<>(); - private final static Logger LOGGER = Logger.getLogger(ClosingOpenTelemetry.class.getName()); + final MeterProvider delegate; - public static ClosingOpenTelemetry noop() { - return new ClosingOpenTelemetry(OpenTelemetry.noop()); + public CloseableMeterProvider(MeterProvider delegate) { + this.delegate = delegate; } - final OpenTelemetry delegate; + @Override + public Meter get(String instrumentationScopeName) { + return new CloseableMeter(delegate.get(instrumentationScopeName)); + } - final List closeables = new ArrayList<>(); + @Override + public MeterBuilder meterBuilder(String instrumentationScopeName) { + return new CloseableMeterBuilder(delegate.meterBuilder(instrumentationScopeName)); + } @Override public void close() { + LOGGER.log(Level.FINE, () -> "Close " + closeables.size() + " instruments"); + LOGGER.log(Level.FINEST, () -> "Close " + closeables); List instruments = new ArrayList<>(this.closeables); this.closeables.clear(); // reset the list of instruments for reuse - LOGGER.log(Level.FINE, () -> "Close " + instruments.size() + " instruments"); - LOGGER.log(Level.FINEST, () -> "Close " + instruments); + for (AutoCloseable instrument : instruments) { try { instrument.close(); @@ -53,77 +80,10 @@ public void close() { } } - public ClosingOpenTelemetry(OpenTelemetry delegate) { - this.delegate = delegate; - } - - @Override - public TracerProvider getTracerProvider() { - return delegate.getTracerProvider(); - } - - @Override - public Tracer getTracer(String instrumentationScopeName) { - return delegate.getTracer(instrumentationScopeName); - } - - @Override - public Tracer getTracer(String instrumentationScopeName, String instrumentationScopeVersion) { - return delegate.getTracer(instrumentationScopeName, instrumentationScopeVersion); - } - - @Override - public TracerBuilder tracerBuilder(String instrumentationScopeName) { - return delegate.tracerBuilder(instrumentationScopeName); - } - - @Override - public MeterProvider getMeterProvider() { - return new ClosingMeterProvider(delegate.getMeterProvider()); - } - - @Override - public Meter getMeter(String instrumentationScopeName) { - return new ClosingMeter(delegate.getMeter(instrumentationScopeName)); - } - - @Override - public LoggerProvider getLogsBridge() { - return delegate.getLogsBridge(); - } - - @Override - public MeterBuilder meterBuilder(String instrumentationScopeName) { - return new ClosingMeterBuilder(delegate.meterBuilder(instrumentationScopeName)); - } - - @Override - public ContextPropagators getPropagators() { - return delegate.getPropagators(); - } - - class ClosingMeterProvider implements MeterProvider { - final MeterProvider delegate; - - public ClosingMeterProvider(MeterProvider delegate) { - this.delegate = delegate; - } - - @Override - public Meter get(String instrumentationScopeName) { - return new ClosingMeter(delegate.get(instrumentationScopeName)); - } - - @Override - public MeterBuilder meterBuilder(String instrumentationScopeName) { - return new ClosingMeterBuilder(delegate.meterBuilder(instrumentationScopeName)); - } - } - - class ClosingMeterBuilder implements MeterBuilder { + class CloseableMeterBuilder implements MeterBuilder { final MeterBuilder delegate; - ClosingMeterBuilder(MeterBuilder delegate) { + CloseableMeterBuilder(MeterBuilder delegate) { this.delegate = delegate; } @@ -143,25 +103,25 @@ public MeterBuilder setInstrumentationVersion(String instrumentationScopeVersion @Override public Meter build() { - return new ClosingMeter(delegate.build()); + return new CloseableMeter(delegate.build()); } } - class ClosingMeter implements Meter { + class CloseableMeter implements Meter { private final Meter delegate; - ClosingMeter(Meter delegate) { + CloseableMeter(Meter delegate) { this.delegate = delegate; } @Override public LongCounterBuilder counterBuilder(String name) { - return new ClosingLongCounterBuilder(delegate.counterBuilder(name)); + return new CloseableLongCounterBuilder(delegate.counterBuilder(name)); } @Override public LongUpDownCounterBuilder upDownCounterBuilder(String name) { - return new ClosingLongUpDownCounterBuilder(delegate.upDownCounterBuilder(name)); + return new CloseableLongUpDownCounterBuilder(delegate.upDownCounterBuilder(name)); } @Override @@ -171,7 +131,7 @@ public DoubleHistogramBuilder histogramBuilder(String name) { @Override public DoubleGaugeBuilder gaugeBuilder(String name) { - return new ClosingDoubleGaugeBuilder(delegate.gaugeBuilder(name)); + return new CloseableDoubleGaugeBuilder(delegate.gaugeBuilder(name)); } @Override @@ -182,10 +142,10 @@ public BatchCallback batchCallback(Runnable callback, ObservableMeasurement obse } } - class ClosingLongCounterBuilder implements LongCounterBuilder { + class CloseableLongCounterBuilder implements LongCounterBuilder { final LongCounterBuilder delegate; - ClosingLongCounterBuilder(LongCounterBuilder delegate) { + CloseableLongCounterBuilder(LongCounterBuilder delegate) { this.delegate = delegate; } @@ -205,7 +165,7 @@ public LongCounterBuilder setUnit(String unit) { @Override public DoubleCounterBuilder ofDoubles() { - return new ClosingDoubleCounterBuilder(delegate.ofDoubles()); + return new CloseableDoubleCounterBuilder(delegate.ofDoubles()); } @Override @@ -226,7 +186,7 @@ public ObservableLongMeasurement buildObserver() { } } - class ClosingLongUpDownCounterBuilder implements LongUpDownCounterBuilder { + class CloseableLongUpDownCounterBuilder implements LongUpDownCounterBuilder { @Override @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT") public LongUpDownCounterBuilder setDescription(String description) { @@ -243,7 +203,7 @@ public LongUpDownCounterBuilder setUnit(String unit) { @Override public DoubleUpDownCounterBuilder ofDoubles() { - return new ClosingDoubleUpDownCounterBuilder(delegate.ofDoubles()); + return new CloseableDoubleUpDownCounterBuilder(delegate.ofDoubles()); } @Override @@ -265,16 +225,16 @@ public ObservableLongMeasurement buildObserver() { final LongUpDownCounterBuilder delegate; - ClosingLongUpDownCounterBuilder(LongUpDownCounterBuilder delegate) { + CloseableLongUpDownCounterBuilder(LongUpDownCounterBuilder delegate) { this.delegate = delegate; } } - class ClosingDoubleUpDownCounterBuilder implements DoubleUpDownCounterBuilder { + class CloseableDoubleUpDownCounterBuilder implements DoubleUpDownCounterBuilder { final DoubleUpDownCounterBuilder delegate; - ClosingDoubleUpDownCounterBuilder(DoubleUpDownCounterBuilder delegate) { + CloseableDoubleUpDownCounterBuilder(DoubleUpDownCounterBuilder delegate) { this.delegate = delegate; } @@ -310,10 +270,10 @@ public ObservableDoubleMeasurement buildObserver() { } } - class ClosingDoubleGaugeBuilder implements DoubleGaugeBuilder { + class CloseableDoubleGaugeBuilder implements DoubleGaugeBuilder { final DoubleGaugeBuilder delegate; - ClosingDoubleGaugeBuilder(DoubleGaugeBuilder delegate) { + CloseableDoubleGaugeBuilder(DoubleGaugeBuilder delegate) { this.delegate = delegate; } @@ -333,7 +293,7 @@ public DoubleGaugeBuilder setUnit(String unit) { @Override public LongGaugeBuilder ofLongs() { - return new ClosingLongGaugeBuilder(delegate.ofLongs()); + return new CloseableLongGaugeBuilder(delegate.ofLongs()); } @Override @@ -349,10 +309,10 @@ public ObservableDoubleMeasurement buildObserver() { } } - class ClosingLongGaugeBuilder implements LongGaugeBuilder { + class CloseableLongGaugeBuilder implements LongGaugeBuilder { final LongGaugeBuilder delegate; - ClosingLongGaugeBuilder(LongGaugeBuilder delegate) { + CloseableLongGaugeBuilder(LongGaugeBuilder delegate) { this.delegate = delegate; } @@ -383,10 +343,10 @@ public ObservableLongMeasurement buildObserver() { } } - class ClosingDoubleCounterBuilder implements DoubleCounterBuilder { + class CloseableDoubleCounterBuilder implements DoubleCounterBuilder { final DoubleCounterBuilder delegate; - ClosingDoubleCounterBuilder(DoubleCounterBuilder delegate) { + CloseableDoubleCounterBuilder(DoubleCounterBuilder delegate) { this.delegate = delegate; } @@ -421,4 +381,4 @@ public ObservableDoubleMeasurement buildObserver() { return delegate.buildObserver(); } } -} +} \ No newline at end of file diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/InstrumentationScope.java b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/InstrumentationScope.java new file mode 100644 index 000000000..16d0e6d80 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/InstrumentationScope.java @@ -0,0 +1,53 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.opentelemetry; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.Objects; + +public class InstrumentationScope { + @Nonnull + final String instrumentationScopeName; + @Nullable + final String schemaUrl; + @Nullable + final String instrumentationScopeVersion; + + public InstrumentationScope(String instrumentationScopeName, @Nullable String schemaUrl, @Nullable String instrumentationScopeVersion) { + this.instrumentationScopeName = Objects.requireNonNull(instrumentationScopeName); + this.schemaUrl = schemaUrl; + this.instrumentationScopeVersion = instrumentationScopeVersion; + } + + public InstrumentationScope(@Nonnull String instrumentationScopeName) { + this.instrumentationScopeName = instrumentationScopeName; + this.schemaUrl = null; + this.instrumentationScopeVersion = null; + } + + @Override + public String toString() { + return "InstrumentationScope{" + + "instrumentationScopeName='" + instrumentationScopeName + '\'' + + ", schemaUrl='" + schemaUrl + '\'' + + ", instrumentationScopeVersion='" + instrumentationScopeVersion + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InstrumentationScope that = (InstrumentationScope) o; + return Objects.equals(instrumentationScopeName, that.instrumentationScopeName) && Objects.equals(schemaUrl, that.schemaUrl) && Objects.equals(instrumentationScopeVersion, that.instrumentationScopeVersion); + } + + @Override + public int hashCode() { + return Objects.hash(instrumentationScopeName, schemaUrl, instrumentationScopeVersion); + } +} \ No newline at end of file diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableEventLoggerProvider.java b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableEventLoggerProvider.java new file mode 100644 index 000000000..81ba97de6 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableEventLoggerProvider.java @@ -0,0 +1,96 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.opentelemetry; + +import com.google.common.annotations.VisibleForTesting; +import io.opentelemetry.api.incubator.events.EventBuilder; +import io.opentelemetry.api.incubator.events.EventLogger; +import io.opentelemetry.api.incubator.events.EventLoggerBuilder; +import io.opentelemetry.api.incubator.events.EventLoggerProvider; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class ReconfigurableEventLoggerProvider implements EventLoggerProvider { + + private final ConcurrentMap eventLoggers = new ConcurrentHashMap<>(); + private EventLoggerProvider delegate = EventLoggerProvider.noop(); + + @Override + public EventLoggerBuilder eventLoggerBuilder(String instrumentationScopeName) { + return new ReconfigurableEventLoggerBuilder(delegate.eventLoggerBuilder(instrumentationScopeName), instrumentationScopeName); + } + + @Override + public EventLogger get(String instrumentationScopeName) { + return eventLoggers.computeIfAbsent(new InstrumentationScope(instrumentationScopeName), key -> new ReconfigurableEventLogger(delegate.get(key.instrumentationScopeName))); + } + + public void setDelegate(EventLoggerProvider delegateEventLoggerBuilder) { + this.delegate = delegateEventLoggerBuilder; + eventLoggers.forEach((key, reconfigurableEventLogger) -> { + EventLoggerBuilder eventLoggerBuilder = delegateEventLoggerBuilder.eventLoggerBuilder(key.instrumentationScopeName); + Optional.ofNullable(key.schemaUrl).ifPresent(eventLoggerBuilder::setSchemaUrl); + Optional.ofNullable(key.instrumentationScopeVersion).ifPresent(eventLoggerBuilder::setInstrumentationVersion); + reconfigurableEventLogger.delegateEventLogger = eventLoggerBuilder.build(); + }); + } + + @VisibleForTesting + protected Map getEventLoggers() { + return eventLoggers; + } + + @VisibleForTesting + protected static class ReconfigurableEventLogger implements EventLogger { + EventLogger delegateEventLogger; + + public ReconfigurableEventLogger(EventLogger delegateEventLogger) { + this.delegateEventLogger = Objects.requireNonNull(delegateEventLogger); + } + + @Override + public EventBuilder builder(String eventName) { + return delegateEventLogger.builder(eventName); + } + } + + @VisibleForTesting + protected class ReconfigurableEventLoggerBuilder implements EventLoggerBuilder { + EventLoggerBuilder delegate; + String instrumentationScopeName; + String schemaUrl; + String instrumentationScopeVersion; + + public ReconfigurableEventLoggerBuilder(EventLoggerBuilder delegate, String instrumentationScopeName) { + this.delegate = Objects.requireNonNull(delegate); + this.instrumentationScopeName = Objects.requireNonNull(instrumentationScopeName); + } + + @Override + public EventLoggerBuilder setSchemaUrl(String schemaUrl) { + delegate.setSchemaUrl(schemaUrl); + this.schemaUrl = schemaUrl; + return this; + } + + @Override + public EventLoggerBuilder setInstrumentationVersion(String instrumentationScopeVersion) { + delegate.setInstrumentationVersion(instrumentationScopeVersion); + this.instrumentationScopeVersion = instrumentationScopeVersion; + return this; + } + + @Override + public EventLogger build() { + InstrumentationScope key = new InstrumentationScope(instrumentationScopeName, schemaUrl, instrumentationScopeVersion); + return eventLoggers.computeIfAbsent(key, k -> new ReconfigurableEventLogger(delegate.build())); + } + } +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableLoggerProvider.java b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableLoggerProvider.java new file mode 100644 index 000000000..e9f69901e --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableLoggerProvider.java @@ -0,0 +1,104 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.opentelemetry; + +import com.google.common.annotations.VisibleForTesting; +import io.opentelemetry.api.logs.LogRecordBuilder; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.api.logs.LoggerBuilder; +import io.opentelemetry.api.logs.LoggerProvider; +import io.opentelemetry.api.trace.TracerBuilder; + +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class ReconfigurableLoggerProvider implements LoggerProvider { + private LoggerProvider delegate; + + private final ConcurrentMap loggers = new ConcurrentHashMap<>(); + + public ReconfigurableLoggerProvider() { + this(LoggerProvider.noop()); + } + + public ReconfigurableLoggerProvider(LoggerProvider delegate) { + this.delegate = delegate; + } + + @Override + public LoggerBuilder loggerBuilder(String instrumentationScopeName) { + return new ReconfigurableLoggerBuilder(delegate.loggerBuilder(instrumentationScopeName), instrumentationScopeName); + } + + @Override + public Logger get(String instrumentationScopeName) { + InstrumentationScope instrumentationScope = new InstrumentationScope(instrumentationScopeName); + return loggers.computeIfAbsent(instrumentationScope, scope -> new ReconfigurableLogger(delegate.get(instrumentationScopeName))); + } + + public void setDelegate(LoggerProvider delegate) { + this.delegate = delegate; + loggers.forEach((instrumentationScope, reconfigurableTracer) -> { + LoggerBuilder loggerBuilder = delegate.loggerBuilder(instrumentationScope.instrumentationScopeName); + Optional.ofNullable(instrumentationScope.instrumentationScopeVersion).ifPresent(loggerBuilder::setInstrumentationVersion); + Optional.ofNullable(instrumentationScope.schemaUrl).ifPresent(loggerBuilder::setSchemaUrl); + reconfigurableTracer.setDelegate(loggerBuilder.build()); + }); + } + + @VisibleForTesting + protected class ReconfigurableLoggerBuilder implements LoggerBuilder { + LoggerBuilder delegate; + String instrumentationScopeName; + String schemaUrl; + String instrumentationScopeVersion; + + public ReconfigurableLoggerBuilder(LoggerBuilder delegate, String instrumentationScopeName) { + this.delegate = Objects.requireNonNull(delegate); + this.instrumentationScopeName = Objects.requireNonNull(instrumentationScopeName); + } + + @Override + public LoggerBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + delegate.setSchemaUrl(schemaUrl); + return this; + } + + @Override + public LoggerBuilder setInstrumentationVersion(String instrumentationScopeVersion) { + this.instrumentationScopeVersion = instrumentationScopeVersion; + delegate.setInstrumentationVersion(instrumentationScopeVersion); + return this; + } + + @Override + public Logger build() { + InstrumentationScope instrumentationScope = new InstrumentationScope(instrumentationScopeName, schemaUrl, instrumentationScopeVersion); + return loggers.computeIfAbsent(instrumentationScope, scope -> new ReconfigurableLogger(delegate.build())); + } + } + + @VisibleForTesting + protected static class ReconfigurableLogger implements Logger { + Logger delegate; + + public ReconfigurableLogger(Logger delegate) { + this.delegate = delegate; + } + + @Override + public synchronized LogRecordBuilder logRecordBuilder() { + return delegate.logRecordBuilder(); + } + + public synchronized void setDelegate(Logger delegate) { + this.delegate = delegate; + } + } +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableOpenTelemetry.java b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableOpenTelemetry.java new file mode 100644 index 000000000..a894ded55 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableOpenTelemetry.java @@ -0,0 +1,231 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.opentelemetry; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.ExtensionList; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; +import io.jenkins.plugins.opentelemetry.OtelUtils; +import io.jenkins.plugins.opentelemetry.opentelemetry.autoconfigure.ConfigPropertiesUtils; +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.incubator.events.EventLoggerProvider; +import io.opentelemetry.api.incubator.events.GlobalEventLoggerProvider; +import io.opentelemetry.api.logs.LoggerProvider; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.MeterBuilder; +import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerBuilder; +import io.opentelemetry.api.trace.TracerProvider; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.internal.SdkEventLoggerProvider; +import io.opentelemetry.sdk.resources.Resource; + +import java.io.Closeable; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Reconfigurable {@link OpenTelemetry} + */ +public class ReconfigurableOpenTelemetry implements OpenTelemetry, Closeable { + + private final Logger LOGGER = Logger.getLogger(ReconfigurableOpenTelemetry.class.getName()); + protected transient Resource resource; + protected transient ConfigProperties config; + protected transient OpenTelemetry openTelemetryImpl = OpenTelemetry.noop(); + protected transient CloseableMeterProvider meterProviderImpl = new CloseableMeterProvider(MeterProvider.noop()); + protected final transient ReconfigurableTracerProvider traceProviderImpl = new ReconfigurableTracerProvider(); + protected final transient ReconfigurableEventLoggerProvider eventLoggerProviderImpl = new ReconfigurableEventLoggerProvider(); + protected final transient ReconfigurableLoggerProvider loggerProviderImpl = new ReconfigurableLoggerProvider(); + + /** + * Initialize as NoOp + */ + public ReconfigurableOpenTelemetry() { + try { + GlobalOpenTelemetry.set(this); + } catch (IllegalStateException e) { + LOGGER.log(Level.WARNING, "GlobalOpenTelemetry already set", e); + } + try { + GlobalEventLoggerProvider.set(eventLoggerProviderImpl); + } catch (IllegalStateException e) { + LOGGER.log(Level.WARNING, "GlobalEventLoggerProvider already set", e); + } + + LOGGER.log(Level.FINE, () -> "Initialize " + + "GlobalOpenTelemetry with instance " + Optional.of(GlobalOpenTelemetry.get()).map(ot -> ot + "@" + System.identityHashCode(ot)) + "and " + + "GlobalEventLoggerProvide with instance " + Optional.of(GlobalEventLoggerProvider.get()).map(elp -> elp + "@" + System.identityHashCode(elp))); + } + + public void configure(@NonNull Map openTelemetryProperties, Resource openTelemetryResource) { + close(); // shutdown existing SDK + if (openTelemetryProperties.containsKey("otel.exporter.otlp.endpoint") || + openTelemetryProperties.containsKey("otel.traces.exporter") || + openTelemetryProperties.containsKey("otel.metrics.exporter") || + openTelemetryProperties.containsKey("otel.logs.exporter")) { + + LOGGER.log(Level.FINE, "initializeOtlp"); + + // OPENTELEMETRY SDK + OpenTelemetrySdk openTelemetrySdk = AutoConfiguredOpenTelemetrySdk + .builder() + // properties + .addPropertiesSupplier(() -> openTelemetryProperties) + .addPropertiesCustomizer((Function>) configProperties -> { + // keep a reference to the computed config properties for future use in the plugin + this.config = configProperties; + return Collections.emptyMap(); + }) + // resource + .addResourceCustomizer((resource1, configProperties) -> { + // keep a reference to the computed Resource for future use in the plugin + this.resource = Resource.builder() + .putAll(resource1) + .putAll(openTelemetryResource).build(); + return this.resource; + } + ) + // disable shutdown hook, SDK closed by #close() + .disableShutdownHook() + .build() + .getOpenTelemetrySdk(); + + // OTEL IMPL + this.openTelemetryImpl = openTelemetrySdk; + // TRACER PROVIDER + traceProviderImpl.setDelegate(openTelemetryImpl.getTracerProvider()); + // METER PROVIDER + meterProviderImpl = new CloseableMeterProvider(openTelemetryImpl.getMeterProvider()); + // LOGGER PROVIDER + loggerProviderImpl.setDelegate(openTelemetryImpl.getLogsBridge()); + // EVENT LOGGER PROVIDER + eventLoggerProviderImpl.setDelegate(SdkEventLoggerProvider.create(openTelemetrySdk.getSdkLoggerProvider())); + + LOGGER.log(Level.INFO, () -> "OpenTelemetry initialized: " + OtelUtils.prettyPrintOtelSdkConfig(this.config, this.resource)); + + } else { // NO-OP + + this.resource = Resource.getDefault(); + this.config = ConfigPropertiesUtils.emptyConfig(); + this.openTelemetryImpl = OpenTelemetry.noop(); + this.traceProviderImpl.setDelegate(TracerProvider.noop()); + this.meterProviderImpl = new CloseableMeterProvider(MeterProvider.noop()); + this.loggerProviderImpl.setDelegate(LoggerProvider.noop()); + this.eventLoggerProviderImpl.setDelegate(EventLoggerProvider.noop()); + + LOGGER.log(Level.INFO, "OpenTelemetry initialized as NoOp"); + } + + postOpenTelemetrySdkConfiguration(); + } + + @Override + public void close() { + LOGGER.log(Level.FINE, "Shutdown..."); + + // METER PROVIDER + meterProviderImpl.close(); + + // OTEL LIFECYCLE LISTENERS + LOGGER.log(Level.FINE, () -> "Shutdown Otel SDK on components: " + ExtensionList.lookup(OpenTelemetryLifecycleListener.class).stream().sorted().map(e -> e.getClass().getName()).collect(Collectors.joining(", "))); + ExtensionList.lookup(OpenTelemetryLifecycleListener.class).stream().sorted().forEachOrdered(OpenTelemetryLifecycleListener::beforeSdkShutdown); + + // OTEL SDK + if (this.openTelemetryImpl instanceof OpenTelemetrySdk) { + LOGGER.log(Level.FINE, () -> "Shutdown OTel SDK..."); + CompletableResultCode shutdown = ((OpenTelemetrySdk) this.openTelemetryImpl).shutdown(); + if (!shutdown.join(1, TimeUnit.SECONDS).isSuccess()) { + LOGGER.log(Level.WARNING, "Failure to shutdown OTel SDK"); + } + } + GlobalOpenTelemetry.resetForTest(); + GlobalEventLoggerProvider.resetForTest(); + } + + @Override + public TracerProvider getTracerProvider() { + return traceProviderImpl; + } + + @Override + public Tracer getTracer(String instrumentationScopeName) { + return traceProviderImpl.get(instrumentationScopeName); + } + + @Override + public Tracer getTracer(String instrumentationScopeName, String instrumentationScopeVersion) { + return traceProviderImpl.get(instrumentationScopeName, instrumentationScopeVersion); + } + + @Override + public TracerBuilder tracerBuilder(String instrumentationScopeName) { + return traceProviderImpl.tracerBuilder(instrumentationScopeName); + } + + @Override + public MeterProvider getMeterProvider() { + return meterProviderImpl; + } + + public EventLoggerProvider getEventLoggerProvider() { + return eventLoggerProviderImpl; + } + + @Override + public Meter getMeter(String instrumentationScopeName) { + return meterProviderImpl.get(instrumentationScopeName); + } + + @Override + public MeterBuilder meterBuilder(String instrumentationScopeName) { + return meterProviderImpl.meterBuilder(instrumentationScopeName); + } + + protected OpenTelemetry getOpenTelemetryDelegate() { + return openTelemetryImpl; + } + + @NonNull + public Resource getResource() { + return Preconditions.checkNotNull(resource); + } + + @NonNull + public ConfigProperties getConfig() { + return Preconditions.checkNotNull(config); + } + + @Override + public LoggerProvider getLogsBridge() { + return loggerProviderImpl; + } + + @Override + public ContextPropagators getPropagators() { + return openTelemetryImpl.getPropagators(); + } + + /** + * For extension purpose + */ + protected void postOpenTelemetrySdkConfiguration() { + } + +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableTracerProvider.java b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableTracerProvider.java new file mode 100644 index 000000000..e45274e44 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableTracerProvider.java @@ -0,0 +1,123 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.opentelemetry; + +import com.google.common.annotations.VisibleForTesting; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerBuilder; +import io.opentelemetry.api.trace.TracerProvider; + +import javax.annotation.Nonnull; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +public class ReconfigurableTracerProvider implements TracerProvider { + + private TracerProvider delegate; + + private final ConcurrentMap tracers = new ConcurrentHashMap<>(); + + public ReconfigurableTracerProvider() { + this(TracerProvider.noop()); + } + + public ReconfigurableTracerProvider(TracerProvider delegate) { + this.delegate = delegate; + } + + @Override + public synchronized Tracer get(String instrumentationScopeName) { + return tracers.computeIfAbsent( + new InstrumentationScope(instrumentationScopeName), + instrumentationScope -> new ReconfigurableTracer(delegate.get(instrumentationScope.instrumentationScopeName))); + } + + public synchronized void setDelegate(TracerProvider delegate) { + this.delegate = delegate; + tracers.forEach((instrumentationScope, reconfigurableTracer) -> { + TracerBuilder tracerBuilder = delegate.tracerBuilder(instrumentationScope.instrumentationScopeName); + Optional.ofNullable(instrumentationScope.instrumentationScopeVersion).ifPresent(tracerBuilder::setInstrumentationVersion); + Optional.ofNullable(instrumentationScope.schemaUrl).ifPresent(tracerBuilder::setSchemaUrl); + reconfigurableTracer.setDelegate(tracerBuilder.build()); + }); + } + + @Override + public Tracer get(String instrumentationScopeName, String instrumentationScopeVersion) { + return tracers.computeIfAbsent( + new InstrumentationScope(instrumentationScopeName, null, instrumentationScopeVersion), + instrumentationScope -> new ReconfigurableTracer(delegate.get(instrumentationScopeName, instrumentationScopeVersion))); + } + + @Override + public TracerBuilder tracerBuilder(String instrumentationScopeName) { + return new ReconfigurableTracerBuilder(delegate.tracerBuilder(instrumentationScopeName), instrumentationScopeName); + } + + public synchronized TracerProvider getDelegate() { + return delegate; + } + + + @VisibleForTesting + protected class ReconfigurableTracerBuilder implements TracerBuilder { + TracerBuilder delegate; + String instrumentationScopeName; + String schemaUrl; + String instrumentationScopeVersion; + + public ReconfigurableTracerBuilder(TracerBuilder delegate, String instrumentationScopeName) { + this.delegate = Objects.requireNonNull(delegate); + this.instrumentationScopeName = Objects.requireNonNull(instrumentationScopeName); + } + + @Override + public TracerBuilder setSchemaUrl(String schemaUrl) { + delegate.setSchemaUrl(schemaUrl); + this.schemaUrl = schemaUrl; + return this; + } + + @Override + public TracerBuilder setInstrumentationVersion(String instrumentationScopeVersion) { + delegate.setInstrumentationVersion(instrumentationScopeVersion); + this.instrumentationScopeVersion = instrumentationScopeVersion; + return this; + } + + @Override + public Tracer build() { + InstrumentationScope instrumentationScope = new InstrumentationScope(instrumentationScopeName, schemaUrl, instrumentationScopeVersion); + return tracers.computeIfAbsent(instrumentationScope, k -> new ReconfigurableTracer(delegate.build())); + } + } + + @VisibleForTesting + protected static class ReconfigurableTracer implements Tracer { + Tracer delegate; + + public ReconfigurableTracer(Tracer delegate) { + this.delegate = delegate; + } + + @Override + public synchronized SpanBuilder spanBuilder(@Nonnull String spanName) { + return delegate.spanBuilder(spanName); + } + + public synchronized void setDelegate(Tracer delegate) { + this.delegate = delegate; + } + + public synchronized Tracer getDelegate() { + return delegate; + } + } + +} diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/queue/MonitoringQueueListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/queue/MonitoringQueueListener.java index 0bda6fe52..9bfc3ddc1 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/queue/MonitoringQueueListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/queue/MonitoringQueueListener.java @@ -8,8 +8,8 @@ import hudson.Extension; import hudson.model.Queue; import hudson.model.queue.QueueListener; -import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics; import io.opentelemetry.api.incubator.events.EventLogger; @@ -33,7 +33,7 @@ * Monitor the Jenkins Build queue */ @Extension(dynamicLoadable = YesNoMaybe.YES, optional = true) -public class MonitoringQueueListener extends QueueListener implements OtelComponent { +public class MonitoringQueueListener extends QueueListener implements OpenTelemetryLifecycleListener { private final static Logger LOGGER = Logger.getLogger(MonitoringQueueListener.class.getName()); @@ -110,6 +110,6 @@ public void onEnterWaiting(Queue.WaitingItem wi) { } private boolean isRemoteSpanEnabled() { - return OpenTelemetrySdkProvider.get().getConfig().getBoolean(JenkinsOtelSemanticAttributes.OTEL_INSTRUMENTATION_JENKINS_REMOTE_SPAN_ENABLED,false); + return JenkinsControllerOpenTelemetry.get().getConfig().getBoolean(JenkinsOtelSemanticAttributes.OTEL_INSTRUMENTATION_JENKINS_REMOTE_SPAN_ENABLED,false); } } diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/security/AuditingSecurityListener.java b/src/main/java/io/jenkins/plugins/opentelemetry/security/AuditingSecurityListener.java index 8a9b4e820..601aa4e07 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/security/AuditingSecurityListener.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/security/AuditingSecurityListener.java @@ -8,7 +8,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.model.User; -import io.jenkins.plugins.opentelemetry.OtelComponent; +import io.jenkins.plugins.opentelemetry.OpenTelemetryLifecycleListener; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics; import io.opentelemetry.api.common.Attributes; @@ -40,7 +40,7 @@ * within a trace. */ @Extension(dynamicLoadable = YesNoMaybe.YES, optional = true) -public class AuditingSecurityListener extends SecurityListener implements OtelComponent { +public class AuditingSecurityListener extends SecurityListener implements OpenTelemetryLifecycleListener { private final static Logger LOGGER = Logger.getLogger(AuditingSecurityListener.class.getName()); diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/servlet/TraceContextServletFilter.java b/src/main/java/io/jenkins/plugins/opentelemetry/servlet/TraceContextServletFilter.java index 59fe3951e..7b3731ae6 100644 --- a/src/main/java/io/jenkins/plugins/opentelemetry/servlet/TraceContextServletFilter.java +++ b/src/main/java/io/jenkins/plugins/opentelemetry/servlet/TraceContextServletFilter.java @@ -1,7 +1,7 @@ package io.jenkins.plugins.opentelemetry.servlet; import edu.umd.cs.findbugs.annotations.NonNull; -import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; +import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry; import io.jenkins.plugins.opentelemetry.OtelUtils; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; import io.opentelemetry.context.Context; @@ -24,7 +24,7 @@ public class TraceContextServletFilter implements Filter { protected static final Pattern JENKINS_TRIGGER_BUILD_URL_PATTERN = Pattern.compile("^(/[^/]+)?/job/([\\w/-]+)/build(WithParameters)?$"); - private OpenTelemetrySdkProvider openTelemetrySdkProvider; + private JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry; @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { @@ -37,7 +37,7 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo public void _doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { if (isW3cTraceContextPropagationEnabled() && isJenkinsRemoteBuildTriggerRequest(request)) { - Context context = getOpenTelemetrySdkProvider() + Context context = getJenkinsControllerOpenTelemetry() .getPropagators() .getTextMapPropagator() .extract(Context.current(), request, new OtelUtils.HttpServletRequestTextMapGetter()); @@ -57,7 +57,7 @@ private boolean isJenkinsRemoteBuildTriggerRequest(@NonNull HttpServletRequest r } private boolean isW3cTraceContextPropagationEnabled() { - return getOpenTelemetrySdkProvider().getConfig() + return getJenkinsControllerOpenTelemetry().getConfig() .getBoolean(JenkinsOtelSemanticAttributes.OTEL_INSTRUMENTATION_JENKINS_REMOTE_SPAN_ENABLED, false); } @@ -72,10 +72,10 @@ public int hashCode() { return TraceContextServletFilter.class.hashCode(); } - protected OpenTelemetrySdkProvider getOpenTelemetrySdkProvider() { - if (this.openTelemetrySdkProvider == null) { - this.openTelemetrySdkProvider = OpenTelemetrySdkProvider.get(); + protected JenkinsControllerOpenTelemetry getJenkinsControllerOpenTelemetry() { + if (this.jenkinsControllerOpenTelemetry == null) { + this.jenkinsControllerOpenTelemetry = JenkinsControllerOpenTelemetry.get(); } - return this.openTelemetrySdkProvider; + return this.jenkinsControllerOpenTelemetry; } } diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/BaseIntegrationTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/BaseIntegrationTest.java index 5e7d9c9d9..8e45dc6ab 100644 --- a/src/test/java/io/jenkins/plugins/opentelemetry/BaseIntegrationTest.java +++ b/src/test/java/io/jenkins/plugins/opentelemetry/BaseIntegrationTest.java @@ -81,7 +81,7 @@ public class BaseIntegrationTest { @Rule public ExtendedGitSampleRepoRule sampleRepo = new ExtendedGitSampleRepoRule(); - static OpenTelemetrySdkProvider openTelemetrySdkProvider; + static JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry; @Before public void before() throws Exception { @@ -101,20 +101,32 @@ public static void beforeClass() throws Exception { jenkinsRule.waitUntilNoActivity(); LOGGER.log(Level.INFO, "Jenkins started"); - ExtensionList openTelemetrySdkProviders = jenkinsRule.getInstance().getExtensionList(OpenTelemetrySdkProvider.class); - verify(openTelemetrySdkProviders.size() == 1, "Number of openTelemetrySdkProviders: %s", openTelemetrySdkProviders.size()); - openTelemetrySdkProvider = openTelemetrySdkProviders.get(0); + ExtensionList jenkinsOpenTelemetries = jenkinsRule.getInstance().getExtensionList(JenkinsControllerOpenTelemetry.class); + verify(jenkinsOpenTelemetries.size() == 1, "Number of jenkinsControllerOpenTelemetrys: %s", jenkinsOpenTelemetries.size()); + jenkinsControllerOpenTelemetry = jenkinsOpenTelemetries.get(0); - // verify(openTelemetrySdkProvider.openTelemetry == null, "OpenTelemetrySdkProvider has already been configured"); + // verify(jenkinsControllerOpenTelemetry.openTelemetry == null, "JenkinsControllerOpenTelemetry has already been configured"); OpenTelemetryConfiguration.TESTING_INMEMORY_MODE = true; - openTelemetrySdkProvider.initialize(new OpenTelemetryConfiguration( - of("http://localhost:4317"), Optional.empty(), - Optional.empty(), - Optional.empty(), Optional.empty(), - Optional.empty(), Optional.empty(), Optional.empty(), - Collections.emptyMap())); - - // openTelemetrySdkProvider.tracer.setDelegate(openTelemetrySdkProvider.openTelemetry.getTracer("jenkins")); + try { + OpenTelemetryConfiguration configuration = new OpenTelemetryConfiguration( + of("http://localhost:4317"), empty(), + empty(), + empty(), empty(), + empty(), empty(), empty(), + Collections.emptyMap()); + + LOGGER.log(Level.INFO, "Initialize OTel with configuration " + configuration.toOpenTelemetryProperties()); + jenkinsControllerOpenTelemetry.initialize(configuration); + } catch (RuntimeException e) { + LOGGER.log(Level.INFO, "Exception initializing OTel plugin", e); + throw e; + } catch (Error e) { + LOGGER.log(Level.INFO, "Error initializing OTel plugin", e); + throw e; + } + LOGGER.log(Level.INFO, "OTel plugin initialized"); + + // jenkinsControllerOpenTelemetry.tracer.setDelegate(jenkinsControllerOpenTelemetry.openTelemetry.getTracer("jenkins")); } protected void checkChainOfSpans(Tree spanTree, String... expectedSpanNames) { @@ -168,7 +180,7 @@ protected Tree getGeneratedSpans() { } protected Tree getGeneratedSpans(int index) { - CompletableResultCode completableResultCode = openTelemetrySdkProvider.getOpenTelemetrySdk().getSdkTracerProvider().forceFlush(); + CompletableResultCode completableResultCode = jenkinsControllerOpenTelemetry.getOpenTelemetrySdk().getSdkTracerProvider().forceFlush(); completableResultCode.join(1, TimeUnit.SECONDS); List spans = InMemorySpanExporterProvider.LAST_CREATED_INSTANCE.getFinishedSpanItems(); diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/OpenTelemetrySdkProviderTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/JenkinsControllerOpenTelemetryTest.java similarity index 91% rename from src/test/java/io/jenkins/plugins/opentelemetry/OpenTelemetrySdkProviderTest.java rename to src/test/java/io/jenkins/plugins/opentelemetry/JenkinsControllerOpenTelemetryTest.java index ce5517bc7..1d61a3f3d 100644 --- a/src/test/java/io/jenkins/plugins/opentelemetry/OpenTelemetrySdkProviderTest.java +++ b/src/test/java/io/jenkins/plugins/opentelemetry/JenkinsControllerOpenTelemetryTest.java @@ -17,7 +17,7 @@ import java.util.Map; import java.util.Optional; -public class OpenTelemetrySdkProviderTest { +public class JenkinsControllerOpenTelemetryTest { @Test public void testOverwriteDefaultConfig() { @@ -56,10 +56,10 @@ private void testDefaultConfigurationOverwrite(String serviceNameDefinedInConfig Optional.empty(), configurationProperties); - OpenTelemetrySdkProvider openTelemetrySdkProvider = new OpenTelemetrySdkProvider(); - openTelemetrySdkProvider.initialize(openTelemetryConfiguration); + JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry = new JenkinsControllerOpenTelemetry(); + jenkinsControllerOpenTelemetry.initialize(openTelemetryConfiguration); - Resource resource = openTelemetrySdkProvider.getResource(); + Resource resource = jenkinsControllerOpenTelemetry.getResource(); // resource.getAttributes().forEach((key, value)-> System.out.println(key + ": " + value)); MatcherAssert.assertThat( @@ -85,6 +85,6 @@ private void testDefaultConfigurationOverwrite(String serviceNameDefinedInConfig CoreMatchers.is("1.2.3")); - openTelemetrySdkProvider.shutdown(); + jenkinsControllerOpenTelemetry.shutdown(); } } diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/JenkinsOtelPluginIntegrationTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/JenkinsOtelPluginIntegrationTest.java index cb132f967..e4d65748d 100644 --- a/src/test/java/io/jenkins/plugins/opentelemetry/JenkinsOtelPluginIntegrationTest.java +++ b/src/test/java/io/jenkins/plugins/opentelemetry/JenkinsOtelPluginIntegrationTest.java @@ -83,7 +83,7 @@ public void testSimplePipeline() throws Exception { // FIXME REPAIR METRICS TESTS /* // WORKAROUND because we don't know how to force the IntervalMetricReader to collect metrics - openTelemetrySdkProvider.getOpenTelemetrySdk().getSdkMeterProvider().forceFlush(); + jenkinsControllerOpenTelemetry.getOpenTelemetrySdk().getSdkMeterProvider().forceFlush(); Map exportedMetrics = InMemoryMetricExporterUtils.getLastExportedMetricByMetricName(InMemoryMetricExporterProvider.LAST_CREATED_INSTANCE.getFinishedMetricItems()); dumpMetrics(exportedMetrics); MetricData runStartedCounterData = exportedMetrics.get(JenkinsSemanticMetrics.CI_PIPELINE_RUN_STARTED); @@ -105,7 +105,7 @@ public void testMetricsWithDiskUsagePlugin() throws Exception { Thread.sleep(100); // FIXME LOGGER.log(Level.INFO, "slept"); - openTelemetrySdkProvider.getOpenTelemetrySdk().getSdkMeterProvider().forceFlush(); + jenkinsControllerOpenTelemetry.getOpenTelemetrySdk().getSdkMeterProvider().forceFlush(); LOGGER.log(Level.INFO, "InMemoryMetricExporterProvider.LAST_CREATED_INSTANCE: " + InMemoryMetricExporterProvider.LAST_CREATED_INSTANCE); Map exportedMetrics = InMemoryMetricExporterUtils.getLastExportedMetricByMetricName(InMemoryMetricExporterProvider.LAST_CREATED_INSTANCE.getFinishedMetricItems()); diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/job/log/OtelLocaLogMirroringTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/job/log/OtelLocaLogMirroringTest.java index f66ed1d60..b07abc93e 100644 --- a/src/test/java/io/jenkins/plugins/opentelemetry/job/log/OtelLocaLogMirroringTest.java +++ b/src/test/java/io/jenkins/plugins/opentelemetry/job/log/OtelLocaLogMirroringTest.java @@ -3,7 +3,7 @@ import hudson.ExtensionList; import hudson.model.Result; import io.jenkins.plugins.opentelemetry.OpenTelemetryConfiguration; -import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; +import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry; import io.jenkins.plugins.opentelemetry.job.OtelTraceService; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporterProvider; @@ -36,7 +36,7 @@ public class OtelLocaLogMirroringTest { @ClassRule public static JenkinsRule jenkinsRule = new JenkinsRule(); - static OpenTelemetrySdkProvider openTelemetrySdkProvider; + static JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry; static WorkflowJob pipeline; @@ -52,15 +52,15 @@ public static void beforeClass() throws Exception { OpenTelemetryConfiguration.TESTING_INMEMORY_MODE = true; OtelTraceService.STRICT_MODE = true; - ExtensionList openTelemetrySdkProviders = jenkinsRule.getInstance().getExtensionList(OpenTelemetrySdkProvider.class); - verify(openTelemetrySdkProviders.size() == 1, "Number of openTelemetrySdkProviders: %s", openTelemetrySdkProviders.size()); + ExtensionList jenkinsOpenTelemetries = jenkinsRule.getInstance().getExtensionList(JenkinsControllerOpenTelemetry.class); + verify(jenkinsOpenTelemetries.size() == 1, "Number of jenkinsControllerOpenTelemetrys: %s", jenkinsOpenTelemetries.size()); - openTelemetrySdkProvider = openTelemetrySdkProviders.get(0); - openTelemetrySdkProvider.initialize(new OpenTelemetryConfiguration(of("http://localhost:4317"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Collections.emptyMap())); + jenkinsControllerOpenTelemetry = jenkinsOpenTelemetries.get(0); + jenkinsControllerOpenTelemetry.initialize(new OpenTelemetryConfiguration(of("http://localhost:4317"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Collections.emptyMap())); } @AfterClass public static void afterClass() { - openTelemetrySdkProvider.shutdown(); + jenkinsControllerOpenTelemetry.shutdown(); GlobalOpenTelemetry.resetForTest(); } @@ -96,7 +96,7 @@ private WorkflowRun runBuild() throws Exception { } private void reInitProvider(Map configuration) { - openTelemetrySdkProvider.initialize(new OpenTelemetryConfiguration(of("http://localhost:4317"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), configuration)); + jenkinsControllerOpenTelemetry.initialize(new OpenTelemetryConfiguration(of("http://localhost:4317"), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), configuration)); } diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/CloseableMeterProviderTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/CloseableMeterProviderTest.java new file mode 100644 index 000000000..acd3abac3 --- /dev/null +++ b/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/CloseableMeterProviderTest.java @@ -0,0 +1,67 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.opentelemetry; + +import io.opentelemetry.api.metrics.*; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +public class CloseableMeterProviderTest { + + + @Test + public void test() { + CloseableMeterProvider meterProvider = new CloseableMeterProvider(MeterProvider.noop()); + testMeterMetricsWithCallback(meterProvider.get("test-meter-1"), meterProvider); + + testMeterMetricsWithCallback(meterProvider.meterBuilder("test-meter").setSchemaUrl("https://example.com").setInstrumentationVersion("123").build(), meterProvider); + + System.out.println("number of closeable meters: " + meterProvider.closeables.size()); + + meterProvider.close(); + + } + + private static void testMeterMetricsWithCallback(Meter meter, CloseableMeterProvider closeableMeterProvider) { + assertThat(meter, instanceOf(CloseableMeterProvider.CloseableMeter.class)); + + int before = closeableMeterProvider.closeables.size(); + ObservableLongCounter observableLongCounter = meter.counterBuilder("test-counter").setDescription("desc").setUnit("s").buildWithCallback(om -> om.record(1L)); + int after = closeableMeterProvider.closeables.size(); + assertThat(after, is(before + 1)); + + before = closeableMeterProvider.closeables.size(); + meter.gaugeBuilder("test-double-gauge").setDescription("desc").setUnit("ms").buildWithCallback(om -> om.record(1.0)); + after = closeableMeterProvider.closeables.size(); + assertThat(after, is(before + 1)); + + before = closeableMeterProvider.closeables.size(); + meter.gaugeBuilder("test-long-gauge").ofLongs().setDescription("desc").setUnit("ms").buildWithCallback(om -> om.record(1L)); + after = closeableMeterProvider.closeables.size(); + assertThat(after, is(before + 1)); + + before = closeableMeterProvider.closeables.size(); + meter.upDownCounterBuilder("test-up-down-counter").setDescription("desc").setUnit("ms").buildWithCallback(om -> om.record(1L)); + after = closeableMeterProvider.closeables.size(); + assertThat(after, is(before + 1)); + + before = closeableMeterProvider.closeables.size(); + meter.upDownCounterBuilder("test-double-up-down-counter").ofDoubles().setDescription("desc").setUnit("ms").buildWithCallback(om -> om.record(1.0)); + after = closeableMeterProvider.closeables.size(); + assertThat(after, is(before + 1)); + + before = closeableMeterProvider.closeables.size(); + final ObservableDoubleMeasurement observableDoubleMeasurement = meter.gaugeBuilder("another-gauge").setUnit("1").setDescription("desc").buildObserver(); + after = closeableMeterProvider.closeables.size(); + assertThat(after, is(before)); + meter.batchCallback(() -> observableDoubleMeasurement.record(1), observableDoubleMeasurement); + after = closeableMeterProvider.closeables.size(); + assertThat(after, is(before + 1)); + } + +} \ No newline at end of file diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ClosingOpenTelemetryTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ClosingOpenTelemetryTest.java deleted file mode 100644 index f755ce498..000000000 --- a/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ClosingOpenTelemetryTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright The Original Author or Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.jenkins.plugins.opentelemetry.opentelemetry; - -import io.opentelemetry.api.OpenTelemetry; -import io.opentelemetry.api.metrics.*; -import org.junit.Test; - -import static org.hamcrest.MatcherAssert.*; -import static org.hamcrest.Matchers.*; - -public class ClosingOpenTelemetryTest { - - - @Test - public void test() { - ClosingOpenTelemetry closingOpenTelemetry = new ClosingOpenTelemetry(OpenTelemetry.noop()); - - testMeterProvider(closingOpenTelemetry.getMeterProvider(), closingOpenTelemetry); - testMeter(closingOpenTelemetry.getMeter("test"), closingOpenTelemetry); - - Meter meter = closingOpenTelemetry.meterBuilder("test-meter").setSchemaUrl("https://example.com").setInstrumentationVersion("123").build(); - testMeter(meter, closingOpenTelemetry); - - System.out.println("number of tests: " + closingOpenTelemetry.closeables.size()); - - } - - private static void testMeterProvider(MeterProvider meterProvider, ClosingOpenTelemetry closingOpenTelemetry) { - assertThat(meterProvider, instanceOf(ClosingOpenTelemetry.ClosingMeterProvider.class)); - - - MeterBuilder meterBuilder = meterProvider.meterBuilder("test"); - - assertThat(meterBuilder, instanceOf(ClosingOpenTelemetry.ClosingMeterBuilder.class)); - - testMeter(meterBuilder.setInstrumentationVersion("123").setSchemaUrl("https://example.com").build(), closingOpenTelemetry); - - testMeter(meterProvider.get("test"), closingOpenTelemetry); - } - - private static void testMeter(Meter meter, ClosingOpenTelemetry closingOpenTelemetry) { - assertThat(meter, instanceOf(ClosingOpenTelemetry.ClosingMeter.class)); - - int before = closingOpenTelemetry.closeables.size(); - meter.counterBuilder("test-counter").setDescription("desc").setUnit("s").buildWithCallback(om -> om.record(1L)); - int after = closingOpenTelemetry.closeables.size(); - assertThat(after, is(before + 1)); - - before = closingOpenTelemetry.closeables.size(); - meter.gaugeBuilder("test-double-gauge").setDescription("desc").setUnit("ms").buildWithCallback(om -> om.record(1.0)); - after = closingOpenTelemetry.closeables.size(); - assertThat(after, is(before + 1)); - - before = closingOpenTelemetry.closeables.size(); - meter.gaugeBuilder("test-long-gauge").ofLongs().setDescription("desc").setUnit("ms").buildWithCallback(om -> om.record(1L)); - after = closingOpenTelemetry.closeables.size(); - assertThat(after, is(before + 1)); - - before = closingOpenTelemetry.closeables.size(); - meter.upDownCounterBuilder("test-up-down-counter").setDescription("desc").setUnit("ms").buildWithCallback(om -> om.record(1L)); - after = closingOpenTelemetry.closeables.size(); - assertThat(after, is(before + 1)); - - before = closingOpenTelemetry.closeables.size(); - meter.upDownCounterBuilder("test-double-up-down-counter").ofDoubles().setDescription("desc").setUnit("ms").buildWithCallback(om -> om.record(1.0)); - after = closingOpenTelemetry.closeables.size(); - assertThat(after, is(before + 1)); - - before = closingOpenTelemetry.closeables.size(); - final ObservableDoubleMeasurement observableDoubleMeasurement = meter.gaugeBuilder("another-gauge").setUnit("1").setDescription("desc").buildObserver(); - after = closingOpenTelemetry.closeables.size(); - assertThat(after, is(before)); - meter.batchCallback(() -> observableDoubleMeasurement.record(1), observableDoubleMeasurement); - after = closingOpenTelemetry.closeables.size(); - assertThat(after, is(before+1)); - } - -} \ No newline at end of file diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableEventLoggerProviderTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableEventLoggerProviderTest.java new file mode 100644 index 000000000..e04720e03 --- /dev/null +++ b/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableEventLoggerProviderTest.java @@ -0,0 +1,156 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.opentelemetry; + +import io.opentelemetry.api.incubator.events.EventBuilder; +import io.opentelemetry.api.incubator.events.EventLogger; +import io.opentelemetry.api.incubator.events.EventLoggerBuilder; +import io.opentelemetry.api.incubator.events.EventLoggerProvider; + +import javax.annotation.Nullable; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + +public class ReconfigurableEventLoggerProviderTest { + + @org.junit.Test + public void testEventLoggerBuilder() { + ReconfigurableEventLoggerProvider eventLoggerProvider = new ReconfigurableEventLoggerProvider(); + + EventLoggerProviderMock eventLoggerProviderImpl_1 = new EventLoggerProviderMock(); + eventLoggerProvider.setDelegate(eventLoggerProviderImpl_1); + + ReconfigurableEventLoggerProvider.ReconfigurableEventLogger authenticationEventLogger = (ReconfigurableEventLoggerProvider.ReconfigurableEventLogger) eventLoggerProvider + .eventLoggerBuilder("io.jenkins.authentication") + .setInstrumentationVersion("1.0.0") + .build(); + + EventLoggerMock authenticationEventLoggerImpl = (EventLoggerMock) authenticationEventLogger.delegateEventLogger; + assertEquals("io.jenkins.authentication", authenticationEventLoggerImpl.instrumentationScopeName); + assertNull(authenticationEventLoggerImpl.schemaUrl); + assertEquals("1.0.0", authenticationEventLoggerImpl.instrumentationVersion); + assertEquals(eventLoggerProviderImpl_1.id, authenticationEventLoggerImpl.eventLoggerProviderId); + + + ReconfigurableEventLoggerProvider.ReconfigurableEventLogger buildEventLogger = (ReconfigurableEventLoggerProvider.ReconfigurableEventLogger) eventLoggerProvider + .eventLoggerBuilder("io.jenkins.build") + .setSchemaUrl("https://jenkins.io/build") + .build(); + EventLoggerMock buildEventLoggerImpl = (EventLoggerMock) buildEventLogger.delegateEventLogger; + assertEquals("io.jenkins.build", buildEventLoggerImpl.instrumentationScopeName); + assertEquals("https://jenkins.io/build", buildEventLoggerImpl.schemaUrl); + assertNull(buildEventLoggerImpl.instrumentationVersion); + assertEquals(eventLoggerProviderImpl_1.id, buildEventLoggerImpl.eventLoggerProviderId); + + ReconfigurableEventLoggerProvider.ReconfigurableEventLogger buildEventLoggerShouldBeTheSameInstance = (ReconfigurableEventLoggerProvider.ReconfigurableEventLogger) eventLoggerProvider + .eventLoggerBuilder("io.jenkins.build") + .setSchemaUrl("https://jenkins.io/build") + .build(); + + assertEquals(buildEventLogger, buildEventLoggerShouldBeTheSameInstance); + + EventLoggerProviderMock eventLoggerProviderImpl_2 = new EventLoggerProviderMock(); + assertNotEquals(eventLoggerProviderImpl_1.id, eventLoggerProviderImpl_2.id); + + // CHANGE THE IMPLEMENTATION OF THE EVENT LOGGER PROVIDER + eventLoggerProvider.setDelegate(eventLoggerProviderImpl_2); + + // VERIFY THE DELEGATE IMPL HAS CHANGED WHILE THE PARAMS REMAINS UNCHANGED + EventLoggerMock authenticationEventLoggerImpl_2 = (EventLoggerMock) authenticationEventLogger.delegateEventLogger; + assertEquals("io.jenkins.authentication", authenticationEventLoggerImpl_2.instrumentationScopeName); + assertNull(authenticationEventLoggerImpl_2.schemaUrl); + assertEquals("1.0.0", authenticationEventLoggerImpl_2.instrumentationVersion); + assertEquals(eventLoggerProviderImpl_2.id, authenticationEventLoggerImpl_2.eventLoggerProviderId); + + EventLoggerMock buildEventLoggerImpl_2 = (EventLoggerMock) buildEventLogger.delegateEventLogger; + + assertEquals("io.jenkins.build", buildEventLoggerImpl_2.instrumentationScopeName); + assertEquals("https://jenkins.io/build", buildEventLoggerImpl_2.schemaUrl); + assertNull(buildEventLoggerImpl_2.instrumentationVersion); + assertEquals(eventLoggerProviderImpl_2.id, buildEventLoggerImpl_2.eventLoggerProviderId); + } + + + static class EventLoggerProviderMock implements EventLoggerProvider { + static AtomicInteger ID_SOURCE = new AtomicInteger(0); + final String id; + + public EventLoggerProviderMock() { + this.id = "EventLoggerProviderMock-" + ID_SOURCE.incrementAndGet(); + } + + @Override + public EventLoggerBuilder eventLoggerBuilder(String instrumentationScopeName) { + return new EventLoggerBuilderMock(instrumentationScopeName, id); + } + + @Override + public EventLogger get(String instrumentationScopeName) { + return new EventLoggerMock(instrumentationScopeName, id); + } + } + + static class EventLoggerMock implements EventLogger { + static AtomicInteger ID_SOURCE = new AtomicInteger(0); + + final String instrumentationScopeName; + final String eventLoggerProviderId; + final String id; + + final String schemaUrl; + final String instrumentationVersion; + + public EventLoggerMock(String instrumentationScopeName, String eventLoggerProviderId) { + this(instrumentationScopeName, eventLoggerProviderId, null, null); + } + public EventLoggerMock(String instrumentationScopeName, String eventLoggerProviderId, @Nullable String schemaUrl, @Nullable String instrumentationVersion) { + this.id = "EventLoggerMock-" + ID_SOURCE.incrementAndGet(); + this.instrumentationScopeName = instrumentationScopeName; + this.eventLoggerProviderId = eventLoggerProviderId; + this.schemaUrl = schemaUrl; + this.instrumentationVersion = instrumentationVersion; + } + + @Override + public EventBuilder builder(String eventName) { + throw new UnsupportedOperationException(); + } + } + + static class EventLoggerBuilderMock implements EventLoggerBuilder { + static AtomicInteger ID_SOURCE = new AtomicInteger(0); + final String id; + final String instrumentationScopeName; + final String eventLoggerProviderId; + String schemaUrl; + String instrumentationVersion; + + + public EventLoggerBuilderMock(String instrumentationScopeName, String eventLoggerProviderId) { + this.id = "EventLoggerBuilderMock-" + ID_SOURCE.incrementAndGet(); + this.instrumentationScopeName = instrumentationScopeName; + this.eventLoggerProviderId = eventLoggerProviderId; + } + + @Override + public EventLoggerBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; + } + + @Override + public EventLoggerBuilder setInstrumentationVersion(String instrumentationVersion) { + this.instrumentationVersion = instrumentationVersion; + return this; + } + + @Override + public EventLogger build() { + return new EventLoggerMock(instrumentationScopeName, eventLoggerProviderId, schemaUrl, instrumentationVersion); + } + } +} \ No newline at end of file diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableLoggerProviderTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableLoggerProviderTest.java new file mode 100644 index 000000000..7920ff5cf --- /dev/null +++ b/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableLoggerProviderTest.java @@ -0,0 +1,157 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.opentelemetry; + + +import io.opentelemetry.api.logs.LogRecordBuilder; +import io.opentelemetry.api.logs.Logger; +import io.opentelemetry.api.logs.LoggerBuilder; +import io.opentelemetry.api.logs.LoggerProvider; + +import javax.annotation.Nullable; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + +public class ReconfigurableLoggerProviderTest { + + @org.junit.Test + public void testLoggerBuilder() { + ReconfigurableLoggerProvider loggerProvider = new ReconfigurableLoggerProvider(); + + ReconfigurableLoggerProviderTest.LoggerProviderMock loggerProviderImpl_1 = new ReconfigurableLoggerProviderTest.LoggerProviderMock(); + loggerProvider.setDelegate(loggerProviderImpl_1); + + ReconfigurableLoggerProvider.ReconfigurableLogger authenticationLogger = (ReconfigurableLoggerProvider.ReconfigurableLogger) loggerProvider + .loggerBuilder("io.jenkins.authentication") + .setInstrumentationVersion("1.0.0") + .build(); + + ReconfigurableLoggerProviderTest.LoggerMock authenticationLoggerImpl = (ReconfigurableLoggerProviderTest.LoggerMock) authenticationLogger.delegate; + assertEquals("io.jenkins.authentication", authenticationLoggerImpl.instrumentationScopeName); + assertNull(authenticationLoggerImpl.schemaUrl); + assertEquals("1.0.0", authenticationLoggerImpl.instrumentationVersion); + assertEquals(loggerProviderImpl_1.id, authenticationLoggerImpl.loggerProviderId); + + + ReconfigurableLoggerProvider.ReconfigurableLogger buildLogger = (ReconfigurableLoggerProvider.ReconfigurableLogger) loggerProvider + .loggerBuilder("io.jenkins.build") + .setSchemaUrl("https://jenkins.io/build") + .build(); + ReconfigurableLoggerProviderTest.LoggerMock buildLoggerImpl = (ReconfigurableLoggerProviderTest.LoggerMock) buildLogger.delegate; + assertEquals("io.jenkins.build", buildLoggerImpl.instrumentationScopeName); + assertEquals("https://jenkins.io/build", buildLoggerImpl.schemaUrl); + assertNull(buildLoggerImpl.instrumentationVersion); + assertEquals(loggerProviderImpl_1.id, buildLoggerImpl.loggerProviderId); + + ReconfigurableLoggerProvider.ReconfigurableLogger buildLoggerShouldBeTheSameInstance = (ReconfigurableLoggerProvider.ReconfigurableLogger) loggerProvider + .loggerBuilder("io.jenkins.build") + .setSchemaUrl("https://jenkins.io/build") + .build(); + + assertEquals(buildLogger, buildLoggerShouldBeTheSameInstance); + + ReconfigurableLoggerProviderTest.LoggerProviderMock loggerProviderImpl_2 = new ReconfigurableLoggerProviderTest.LoggerProviderMock(); + assertNotEquals(loggerProviderImpl_1.id, loggerProviderImpl_2.id); + + // CHANGE THE IMPLEMENTATION OF THE EVENT LOGGER PROVIDER + loggerProvider.setDelegate(loggerProviderImpl_2); + + // VERIFY THE DELEGATE IMPL HAS CHANGED WHILE THE PARAMS REMAINS UNCHANGED + ReconfigurableLoggerProviderTest.LoggerMock authenticationLoggerImpl_2 = (ReconfigurableLoggerProviderTest.LoggerMock) authenticationLogger.delegate; + assertEquals("io.jenkins.authentication", authenticationLoggerImpl_2.instrumentationScopeName); + assertNull(authenticationLoggerImpl_2.schemaUrl); + assertEquals("1.0.0", authenticationLoggerImpl_2.instrumentationVersion); + assertEquals(loggerProviderImpl_2.id, authenticationLoggerImpl_2.loggerProviderId); + + ReconfigurableLoggerProviderTest.LoggerMock buildLoggerImpl_2 = (ReconfigurableLoggerProviderTest.LoggerMock) buildLogger.delegate; + + assertEquals("io.jenkins.build", buildLoggerImpl_2.instrumentationScopeName); + assertEquals("https://jenkins.io/build", buildLoggerImpl_2.schemaUrl); + assertNull(buildLoggerImpl_2.instrumentationVersion); + assertEquals(loggerProviderImpl_2.id, buildLoggerImpl_2.loggerProviderId); + } + + + static class LoggerProviderMock implements LoggerProvider { + static AtomicInteger ID_SOURCE = new AtomicInteger(0); + final String id; + + public LoggerProviderMock() { + this.id = "LoggerProviderMock-" + ID_SOURCE.incrementAndGet(); + } + + @Override + public LoggerBuilder loggerBuilder(String instrumentationScopeName) { + return new ReconfigurableLoggerProviderTest.LoggerBuilderMock(instrumentationScopeName, id); + } + + @Override + public Logger get(String instrumentationScopeName) { + return new ReconfigurableLoggerProviderTest.LoggerMock(instrumentationScopeName, id); + } + } + + static class LoggerMock implements Logger { + static AtomicInteger ID_SOURCE = new AtomicInteger(0); + + final String instrumentationScopeName; + final String loggerProviderId; + final String id; + + final String schemaUrl; + final String instrumentationVersion; + + public LoggerMock(String instrumentationScopeName, String loggerProviderId) { + this(instrumentationScopeName, loggerProviderId, null, null); + } + public LoggerMock(String instrumentationScopeName, String loggerProviderId, @Nullable String schemaUrl, @Nullable String instrumentationVersion) { + this.id = "LoggerMock-" + ID_SOURCE.incrementAndGet(); + this.instrumentationScopeName = instrumentationScopeName; + this.loggerProviderId = loggerProviderId; + this.schemaUrl = schemaUrl; + this.instrumentationVersion = instrumentationVersion; + } + + @Override + public LogRecordBuilder logRecordBuilder() { + throw new UnsupportedOperationException(); + } + } + + static class LoggerBuilderMock implements LoggerBuilder { + static AtomicInteger ID_SOURCE = new AtomicInteger(0); + final String id; + final String instrumentationScopeName; + final String loggerProviderId; + String schemaUrl; + String instrumentationVersion; + + + public LoggerBuilderMock(String instrumentationScopeName, String loggerProviderId) { + this.id = "LoggerBuilderMock-" + ID_SOURCE.incrementAndGet(); + this.instrumentationScopeName = instrumentationScopeName; + this.loggerProviderId = loggerProviderId; + } + + @Override + public LoggerBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; + } + + @Override + public LoggerBuilder setInstrumentationVersion(String instrumentationVersion) { + this.instrumentationVersion = instrumentationVersion; + return this; + } + + @Override + public Logger build() { + return new ReconfigurableLoggerProviderTest.LoggerMock(instrumentationScopeName, loggerProviderId, schemaUrl, instrumentationVersion); + } + } +} \ No newline at end of file diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableTracerProviderTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableTracerProviderTest.java new file mode 100644 index 000000000..b8c257464 --- /dev/null +++ b/src/test/java/io/jenkins/plugins/opentelemetry/opentelemetry/ReconfigurableTracerProviderTest.java @@ -0,0 +1,164 @@ +/* + * Copyright The Original Author or Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.jenkins.plugins.opentelemetry.opentelemetry; + +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.api.trace.TracerBuilder; +import io.opentelemetry.api.trace.TracerProvider; + +import javax.annotation.Nullable; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.*; + +public class ReconfigurableTracerProviderTest { + @org.junit.Test + public void test() { + ReconfigurableTracerProvider tracerProvider = new ReconfigurableTracerProvider(); + + ReconfigurableTracerProviderTest.TracerProviderMock tracerProviderImpl_1 = new ReconfigurableTracerProviderTest.TracerProviderMock(); + tracerProvider.setDelegate(tracerProviderImpl_1); + + ReconfigurableTracerProvider.ReconfigurableTracer authenticationTracer = (ReconfigurableTracerProvider.ReconfigurableTracer) tracerProvider + .tracerBuilder("io.jenkins.authentication") + .setInstrumentationVersion("1.0.0") + .build(); + + ReconfigurableTracerProviderTest.TracerMock authenticationTracerImpl = (ReconfigurableTracerProviderTest.TracerMock) authenticationTracer.delegate; + assertEquals("io.jenkins.authentication", authenticationTracerImpl.instrumentationScopeName); + assertNull(authenticationTracerImpl.schemaUrl); + assertEquals("1.0.0", authenticationTracerImpl.instrumentationVersion); + assertEquals(tracerProviderImpl_1.id, authenticationTracerImpl.tracerProviderId); + + + ReconfigurableTracerProvider.ReconfigurableTracer buildTracer = (ReconfigurableTracerProvider.ReconfigurableTracer) tracerProvider + .tracerBuilder("io.jenkins.build") + .setSchemaUrl("https://jenkins.io/build") + .build(); + ReconfigurableTracerProviderTest.TracerMock buildTracerImpl = (ReconfigurableTracerProviderTest.TracerMock) buildTracer.delegate; + assertEquals("io.jenkins.build", buildTracerImpl.instrumentationScopeName); + assertEquals("https://jenkins.io/build", buildTracerImpl.schemaUrl); + assertNull(buildTracerImpl.instrumentationVersion); + assertEquals(tracerProviderImpl_1.id, buildTracerImpl.tracerProviderId); + + ReconfigurableTracerProvider.ReconfigurableTracer buildTracerShouldBeTheSameInstance = (ReconfigurableTracerProvider.ReconfigurableTracer) tracerProvider + .tracerBuilder("io.jenkins.build") + .setSchemaUrl("https://jenkins.io/build") + .build(); + + assertEquals(buildTracer, buildTracerShouldBeTheSameInstance); + + ReconfigurableTracerProviderTest.TracerProviderMock tracerProviderImpl_2 = new ReconfigurableTracerProviderTest.TracerProviderMock(); + assertNotEquals(tracerProviderImpl_1.id, tracerProviderImpl_2.id); + + // CHANGE THE IMPLEMENTATION OF THE EVENT TRACER PROVIDER + tracerProvider.setDelegate(tracerProviderImpl_2); + + // VERIFY THE DELEGATE IMPL HAS CHANGED WHILE THE PARAMS REMAINS UNCHANGED + ReconfigurableTracerProviderTest.TracerMock authenticationTracerImpl_2 = (ReconfigurableTracerProviderTest.TracerMock) authenticationTracer.delegate; + assertEquals("io.jenkins.authentication", authenticationTracerImpl_2.instrumentationScopeName); + assertNull(authenticationTracerImpl_2.schemaUrl); + assertEquals("1.0.0", authenticationTracerImpl_2.instrumentationVersion); + assertEquals(tracerProviderImpl_2.id, authenticationTracerImpl_2.tracerProviderId); + + ReconfigurableTracerProviderTest.TracerMock buildTracerImpl_2 = (ReconfigurableTracerProviderTest.TracerMock) buildTracer.delegate; + + assertEquals("io.jenkins.build", buildTracerImpl_2.instrumentationScopeName); + assertEquals("https://jenkins.io/build", buildTracerImpl_2.schemaUrl); + assertNull(buildTracerImpl_2.instrumentationVersion); + assertEquals(tracerProviderImpl_2.id, buildTracerImpl_2.tracerProviderId); + } + + + static class TracerProviderMock implements TracerProvider { + static AtomicInteger ID_SOURCE = new AtomicInteger(0); + final String id; + + public TracerProviderMock() { + this.id = "TracerProviderMock-" + ID_SOURCE.incrementAndGet(); + } + + @Override + public TracerBuilder tracerBuilder(String instrumentationScopeName) { + return new ReconfigurableTracerProviderTest.TracerBuilderMock(instrumentationScopeName, id); + } + + @Override + public Tracer get(String instrumentationScopeName) { + return new ReconfigurableTracerProviderTest.TracerMock(instrumentationScopeName, id); + } + + @Override + public Tracer get(String instrumentationScopeName, String instrumentationScopeVersion) { + return new ReconfigurableTracerProviderTest.TracerMock(instrumentationScopeName, instrumentationScopeVersion, id); + } + } + + static class TracerMock implements Tracer { + static AtomicInteger ID_SOURCE = new AtomicInteger(0); + + final String instrumentationScopeName; + final String tracerProviderId; + final String id; + + final String schemaUrl; + final String instrumentationVersion; + + public TracerMock(String instrumentationScopeName, String tracerProviderId) { + this(instrumentationScopeName, tracerProviderId, null, null); + } + public TracerMock(String instrumentationScopeName, String instrumentationVersion, String tracerProviderId) { + this(instrumentationScopeName, tracerProviderId, null, instrumentationVersion); + } + + public TracerMock(String instrumentationScopeName, String tracerProviderId, @Nullable String schemaUrl, @Nullable String instrumentationVersion) { + this.id = "TracerMock-" + ID_SOURCE.incrementAndGet(); + this.instrumentationScopeName = instrumentationScopeName; + this.tracerProviderId = tracerProviderId; + this.schemaUrl = schemaUrl; + this.instrumentationVersion = instrumentationVersion; + } + + @Override + public SpanBuilder spanBuilder(String spanName) { + throw new UnsupportedOperationException(); + } + } + + static class TracerBuilderMock implements TracerBuilder { + static AtomicInteger ID_SOURCE = new AtomicInteger(0); + final String id; + final String instrumentationScopeName; + final String tracerProviderId; + String schemaUrl; + String instrumentationVersion; + + + public TracerBuilderMock(String instrumentationScopeName, String tracerProviderId) { + this.id = "TracerBuilderMock-" + ID_SOURCE.incrementAndGet(); + this.instrumentationScopeName = instrumentationScopeName; + this.tracerProviderId = tracerProviderId; + } + + @Override + public TracerBuilder setSchemaUrl(String schemaUrl) { + this.schemaUrl = schemaUrl; + return this; + } + + @Override + public TracerBuilder setInstrumentationVersion(String instrumentationVersion) { + this.instrumentationVersion = instrumentationVersion; + return this; + } + + @Override + public Tracer build() { + return new ReconfigurableTracerProviderTest.TracerMock(instrumentationScopeName, tracerProviderId, schemaUrl, instrumentationVersion); + } + } +} \ No newline at end of file diff --git a/src/test/java/io/jenkins/plugins/opentelemetry/remotespan/RemoteSpanTest.java b/src/test/java/io/jenkins/plugins/opentelemetry/remotespan/RemoteSpanTest.java index 03dde6811..484eb4679 100644 --- a/src/test/java/io/jenkins/plugins/opentelemetry/remotespan/RemoteSpanTest.java +++ b/src/test/java/io/jenkins/plugins/opentelemetry/remotespan/RemoteSpanTest.java @@ -13,7 +13,7 @@ import io.jenkins.plugins.opentelemetry.BaseIntegrationTest; import io.jenkins.plugins.opentelemetry.JenkinsOpenTelemetryPluginConfiguration; import io.jenkins.plugins.opentelemetry.OpenTelemetryConfiguration; -import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; +import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry; import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes; import jenkins.model.GlobalConfiguration; import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; @@ -37,11 +37,11 @@ public class RemoteSpanTest extends BaseIntegrationTest { @Before public void enableRemoteSpan() { - ExtensionList openTelemetrySdkProviders = jenkinsRule.getInstance().getExtensionList(OpenTelemetrySdkProvider.class); - verify(openTelemetrySdkProviders.size() == 1, "Number of openTelemetrySdkProviders: %s", openTelemetrySdkProviders.size()); - OpenTelemetrySdkProvider openTelemetrySdkProvider = openTelemetrySdkProviders.get(0); + ExtensionList jenkinsOpenTelemetries = jenkinsRule.getInstance().getExtensionList(JenkinsControllerOpenTelemetry.class); + verify(jenkinsOpenTelemetries.size() == 1, "Number of jenkinsControllerOpenTelemetrys: %s", jenkinsOpenTelemetries.size()); + JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry = jenkinsOpenTelemetries.get(0); - // verify(openTelemetrySdkProvider.openTelemetry == null, "OpenTelemetrySdkProvider has already been configured"); + // verify(jenkinsControllerOpenTelemetry.openTelemetry == null, "JenkinsControllerOpenTelemetry has already been configured"); OpenTelemetryConfiguration.TESTING_INMEMORY_MODE = true; @@ -50,7 +50,7 @@ public void enableRemoteSpan() { configuration.setConfigurationProperties(JenkinsOtelSemanticAttributes.OTEL_INSTRUMENTATION_JENKINS_REMOTE_SPAN_ENABLED + "=true"); OpenTelemetryConfiguration config = configuration.toOpenTelemetryConfiguration(); - openTelemetrySdkProvider.initialize(config); + jenkinsControllerOpenTelemetry.initialize(config); } @Test public void testRemoteTriggerParentChildTrace() throws Exception {