Skip to content

Commit

Permalink
Merge branch 'main' into cleanup-otel-provider
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/main/java/io/jenkins/plugins/opentelemetry/JenkinsOpenTelemetryPluginConfiguration.java
#	src/main/java/io/jenkins/plugins/opentelemetry/OpenTelemetrySdkProvider.java
  • Loading branch information
cyrille-leclerc committed Jun 17, 2024
2 parents 9def781 + 03559ec commit 34102f2
Show file tree
Hide file tree
Showing 42 changed files with 450 additions and 242 deletions.
30 changes: 22 additions & 8 deletions docs/build-logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,21 @@ src="./images/jenkins-pipeline-logs-elastic-kibana-role.png" />
alt="Configuration - User creation in Kibana"
src="./images/jenkins-pipeline-logs-elastic-kibana-user.png" />

## Storing Jenkins Pipeline Logs in Loki

To store pipeline logs in Loki,

* Navigate to the OpenTelemetry section of Jenkins configuration screen,
* In the "Visualization" section, add the "Grafana" backend
* Set the Grafana URL
* In the section "Pipeline logs storage in Loki", select the desired strategy:
* "Don't store logs in Loki" to continue storing logs exclusively in Jenkins
* "Store pipeline logs in Loki and mirror them in Jenkins" to store logs in Loki and also store them in the Jenkins
home. Logs can be visualized from the Jenkins GUI retrieving them from the Jenkins home storage, and from Grafana retrieving them from Loki
* "Store pipeline logs In Loki and visualize logs exclusively in Grafana (logs no longer visible through Jenkins
screens)" to store logs exclusively in Loki. Pipeline logs are no longer visible through Jenkins screens,
an hyperlink to Grafana is displayed in the Jenkins pipeline build console.


## FAQ

Expand All @@ -113,24 +128,23 @@ receivers:
processors:
batch:
exporters:
otlp/elastic:
endpoint: "***.apm.***.gcp.cloud.es.io:443"
headers:
Authorization: "Bearer ****"
otlp/xyz:
endpoint: "otlp.example.com:4317"
#...
service:
pipelines:
metrics:
receivers: [otlp]
processors: [batch]
exporters: [otlp/elastic]
exporters: [otlp/xyz]
traces:
receivers: [otlp]
processors: [batch]
exporters: [otlp/elastic]
exporters: [otlp/xyz]
logs:
receivers: [otlp]
processors: [batch]
exporters: [otlp/elastic]
exporters: [otlp/xyz]
````

For more details, se the OpenTelemetry Collector [configuration guide](https://opentelemetry.io/docs/collector/configuration/).
Expand All @@ -148,7 +162,7 @@ This means that the timestamp of log messages emitted on the Jenkins Agents does
This clock adjustment is required to display in the right ascending order the log messages.
Note that distributed traces don't require such a clock adjustment because all spans are emitted from the Jenkins Controller.

### Can pipeline logs be stored in other backends than Elastic?
### Can pipeline logs be stored in other backends than Elastic or Grafana?

Yes any observability backend that support OpenTelemetry logs can be used.

Expand Down
34 changes: 24 additions & 10 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,13 @@
<changelist>999999-SNAPSHOT</changelist>
<jenkins.version>2.401.3</jenkins.version>
<gitHubRepo>jenkinsci/${project.artifactId}-plugin</gitHubRepo>
<opentelemetry.version>1.36.0</opentelemetry.version>
<!--
Don't forget to bump opentelemetry-resources
-->
<opentelemetry-alpha.version>1.36.0-alpha</opentelemetry-alpha.version>
<opentelemetry-instrumentation-alpha.version>1.33.0-alpha</opentelemetry-instrumentation-alpha.version>
<opentelemetry-contrib.version>1.33.0-alpha</opentelemetry-contrib.version>
<opentelemetry-semconv>1.23.1-alpha</opentelemetry-semconv>
<opentelemetry.version>1.39.0</opentelemetry.version>
<opentelemetry-alpha.version>1.39.0-alpha</opentelemetry-alpha.version>
<opentelemetry-instrumentation-alpha.version>2.4.0-alpha</opentelemetry-instrumentation-alpha.version>
<opentelemetry-contrib.version>1.36.0-alpha</opentelemetry-contrib.version>
<opentelemetry-semconv>1.25.0-alpha</opentelemetry-semconv>
<useBeta>true</useBeta>
<elasticstack.version>8.13.4</elasticstack.version>
<elasticstack.version>8.14.0</elasticstack.version>
<error-prone.version>2.28.0</error-prone.version>
</properties>
<name>OpenTelemetry Plugin</name>
Expand Down Expand Up @@ -89,7 +86,7 @@
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api-events</artifactId>
<artifactId>opentelemetry-api-incubator</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
Expand All @@ -100,6 +97,11 @@
<artifactId>opentelemetry-semconv</artifactId>
<version>${opentelemetry-semconv}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry.semconv</groupId>
<artifactId>opentelemetry-semconv-incubating</artifactId>
<version>${opentelemetry-semconv}</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-trace</artifactId>
Expand Down Expand Up @@ -155,6 +157,18 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.opentelemetry.contrib</groupId>
<artifactId>opentelemetry-gcp-resources</artifactId>
<version>${opentelemetry-contrib.version}</version>
<exclusions>
<exclusion>
<!-- we get okhttp from org.jenkins-ci.plugins:jackson2-api -->
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
import io.jenkins.plugins.opentelemetry.opentelemetry.AbstractReconfigurableOpenTelemetryWrapper;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.events.EventEmitter;
import io.opentelemetry.api.events.GlobalEventEmitterProvider;
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;
Expand All @@ -38,12 +39,18 @@ public class JenkinsControllerOpenTelemetry extends AbstractReconfigurableOpenTe
*/
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 ReconfigurableTracer defaultTracer = new ReconfigurableTracer();
protected transient Meter defaultMeter;
protected transient EventEmitter defaultEventEmitter;
protected transient EventLogger defaultEventLogger;

public JenkinsControllerOpenTelemetry() {
super();
if (INSTANCE_COUNTER.get() > 0) {

Check warning on line 51 in src/main/java/io/jenkins/plugins/opentelemetry/JenkinsControllerOpenTelemetry.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 51 is only partially covered, one branch is missing
LOGGER.log(Level.WARNING, "More than one instance of JenkinsControllerOpenTelemetry created: " + INSTANCE_COUNTER.get());

Check warning on line 52 in src/main/java/io/jenkins/plugins/opentelemetry/JenkinsControllerOpenTelemetry.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 52 is not covered by tests
}
}

@NonNull
Expand Down Expand Up @@ -96,17 +103,16 @@ protected void postOpenTelemetrySdkConfiguration() {
.meterBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME)
.setInstrumentationVersion(opentelemetryPluginVersion)
.build();
this.defaultEventEmitter = GlobalEventEmitterProvider.get()
.eventEmitterBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME)
this.defaultEventLogger = GlobalEventLoggerProvider.get()
.eventLoggerBuilder(JenkinsOtelSemanticAttributes.INSTRUMENTATION_NAME)
.setInstrumentationVersion(opentelemetryPluginVersion)
.setEventDomain("jenkins")
.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(), defaultEventEmitter, defaultTracer, config);
otelComponent.afterSdkInitialized(defaultMeter, getOpenTelemetryDelegate().getLogsBridge(), defaultEventLogger, defaultTracer, config);
otelComponent.afterSdkInitialized(getOpenTelemetryDelegate(), config);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
import io.jenkins.plugins.opentelemetry.semconv.OTelEnvironmentVariablesConventions;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.ResourceAttributes;
import io.opentelemetry.semconv.ServiceAttributes;
import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes;
import jenkins.model.CauseOfInterruption;
import jenkins.model.GlobalConfiguration;
import jenkins.model.Jenkins;
Expand Down Expand Up @@ -193,7 +194,7 @@ public OpenTelemetryConfiguration toOpenTelemetryConfiguration() {
configurationProperties.put(JenkinsOtelSemanticAttributes.JENKINS_URL.getKey(), this.jenkinsLocationConfiguration.getUrl());
// use same Jenkins instance identifier as the Jenkins Support Core plugin. No need to add the complexity of the instance-identity-plugin
// https://github.com/jenkinsci/support-core-plugin/blob/support-core-2.81/src/main/java/com/cloudbees/jenkins/support/impl/AboutJenkins.java#L401
configurationProperties.put(ResourceAttributes.SERVICE_INSTANCE_ID.getKey(), Jenkins.get().getLegacyInstanceId());
configurationProperties.put(ServiceIncubatingAttributes.SERVICE_INSTANCE_ID.getKey(), Jenkins.get().getLegacyInstanceId());
properties.forEach((k, v) -> configurationProperties.put(Objects.toString(k, "#null#"), Objects.toString(v, "#null#")));

return new OpenTelemetryConfiguration(
Expand Down Expand Up @@ -483,7 +484,7 @@ public String findSymbolOrDefault(@NonNull String buildStepName, @Nullable Descr
}

/**
* @see ResourceAttributes#SERVICE_NAME
* @see io.opentelemetry.semconv.ServiceAttributes#SERVICE_NAME
*/
public String getServiceName() {
return (Strings.isNullOrEmpty(this.serviceName)) ? JenkinsOtelSemanticAttributes.JENKINS : this.serviceName;
Expand All @@ -496,7 +497,7 @@ public void setServiceName(String serviceName) {
}

/**
* @see ResourceAttributes#SERVICE_NAMESPACE
* @see io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes#SERVICE_NAMESPACE
*/
public String getServiceNamespace() {
return (Strings.isNullOrEmpty(this.serviceNamespace)) ? JenkinsOtelSemanticAttributes.JENKINS : this.serviceNamespace;
Expand Down Expand Up @@ -563,8 +564,8 @@ private LogStorageRetriever resolveLogStorageRetriever() {
LogStorageRetriever logStorageRetriever = null;

Resource otelSdkResource = jenkinsControllerOpenTelemetry.getResource();
String serviceName = Objects.requireNonNull(otelSdkResource.getAttribute(ResourceAttributes.SERVICE_NAME), "service.name can't be null");
String serviceNamespace = otelSdkResource.getAttribute(ResourceAttributes.SERVICE_NAMESPACE);
String serviceName = Objects.requireNonNull(otelSdkResource.getAttribute(ServiceAttributes.SERVICE_NAME), "service.name can't be null");
String serviceNamespace = otelSdkResource.getAttribute(ServiceIncubatingAttributes.SERVICE_NAMESPACE);

Map<String, Object> bindings;
if (serviceNamespace == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.jenkins.plugins.opentelemetry.authentication.OtlpAuthentication;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.resources.ResourceBuilder;
import io.opentelemetry.semconv.ResourceAttributes;
import io.opentelemetry.semconv.ServiceAttributes;
import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes;
import org.apache.commons.lang.StringUtils;

import edu.umd.cs.findbugs.annotations.NonNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -157,10 +158,10 @@ public Map<String, String> toOpenTelemetryProperties() {
public Resource toOpenTelemetryResource() {
ResourceBuilder resourceBuilder = Resource.builder();
this.getServiceName().ifPresent(serviceName ->
resourceBuilder.put(ResourceAttributes.SERVICE_NAME, serviceName));
resourceBuilder.put(ServiceAttributes.SERVICE_NAME, serviceName));

this.getServiceNamespace().ifPresent(serviceNamespace ->
resourceBuilder.put(ResourceAttributes.SERVICE_NAMESPACE, serviceNamespace));
resourceBuilder.put(ServiceIncubatingAttributes.SERVICE_NAMESPACE, serviceNamespace));

resourceBuilder.put(JenkinsOtelSemanticAttributes.JENKINS_OPEN_TELEMETRY_PLUGIN_VERSION, OtelUtils.getOpentelemetryPluginVersion());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
package io.jenkins.plugins.opentelemetry;

import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.events.EventEmitter;
import io.opentelemetry.api.incubator.events.EventLogger;
import io.opentelemetry.api.logs.LoggerProvider;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableLongCounter;
Expand All @@ -33,11 +33,11 @@ public interface OpenTelemetryLifecycleListener extends Comparable<OpenTelemetry
*
* @param meter {@link Meter} of the newly initialized Otel SDK
* @param loggerProvider {@link io.opentelemetry.api.logs.Logger} of the newly initialized Otel SDK
* @param eventEmitter
* @param eventLogger
* @param tracer {@link Tracer} of the newly initialized Otel SDK
* @param configProperties {@link ConfigProperties} of the newly initialized Otel SDK
*/
default void afterSdkInitialized(Meter meter, LoggerProvider loggerProvider, EventEmitter eventEmitter, Tracer tracer, ConfigProperties configProperties) {}
default void afterSdkInitialized(Meter meter, LoggerProvider loggerProvider, EventLogger eventLogger, Tracer tracer, ConfigProperties configProperties) {}

/**
* Invoked soon after the Otel SDK has been initialized.
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/io/jenkins/plugins/opentelemetry/OtelUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,19 @@
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.ReadableSpan;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.semconv.ResourceAttributes;
import io.opentelemetry.semconv.ServiceAttributes;
import io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes;
import jenkins.model.Jenkins;
import jenkins.scm.api.SCMHead;
import jenkins.scm.api.mixin.ChangeRequestSCMHead;
import jenkins.scm.api.mixin.TagSCMHead;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject;
import org.apache.commons.codec.net.URLCodec;

import javax.servlet.http.HttpServletRequest;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -257,7 +257,7 @@ public static String prettyPrintOtelSdkConfig(ConfigProperties configProperties,
JenkinsOtelSemanticAttributes.OTEL_INSTRUMENTATION_JENKINS_WEB_ENABLED);

private final static List<AttributeKey> noteworthyResourceAttributeKeys = Arrays.asList(
ResourceAttributes.SERVICE_NAME, ResourceAttributes.SERVICE_NAMESPACE, ResourceAttributes.SERVICE_VERSION
ServiceAttributes.SERVICE_NAME, ServiceIncubatingAttributes.SERVICE_NAMESPACE, ServiceAttributes.SERVICE_VERSION
) ;

public static String prettyPrintConfiguration(ConfigProperties config) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.Extension;
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import io.jenkins.plugins.opentelemetry.TemplateBindingsProvider;
import io.jenkins.plugins.opentelemetry.backend.grafana.GrafanaLogsBackend;
import io.jenkins.plugins.opentelemetry.job.log.LogStorageRetriever;
Expand Down Expand Up @@ -273,6 +274,13 @@ public FormValidation doCheckGrafanaBaseUrl(@QueryParameter("grafanaBaseUrl") St
}
return FormValidation.ok();
}

public ListBoxModel doFillTempoQueryTypeItems() {
ListBoxModel items = new ListBoxModel();
items.add("Query Tempo using TraceQL", "traceql");
items.add("Query Tempo by TraceID (older Tempo versions)", "traceid");
return items;
}
}

/**
Expand Down
Loading

0 comments on commit 34102f2

Please sign in to comment.