diff --git a/docs/monitoring-metrics.md b/docs/monitoring-metrics.md
index 9a7117f31..9b497350f 100644
--- a/docs/monitoring-metrics.md
+++ b/docs/monitoring-metrics.md
@@ -117,6 +117,44 @@ Inventory of health metrics collected by the Jenkins OpenTelemetry integration:
|
Job failed |
+
+ jenkins.executor |
+ ${executors} |
+
+ label ,
+ status
+ |
+
+ Jenkins build agent label code> like linux
+ busy , idle , connecting
+ |
+
+ Jenkins executors broken down by label and status . Executors annotated with
+ multiple label are reported multiple times
+ |
+
+
+ jenkins.executor.total |
+ ${executors} |
+
+ status
+ |
+
+ busy , idle
+ |
+ Jenkins executors broken down by status |
+
+
+ jenkins.node |
+ ${nodes} |
+
+ status
+ |
+
+ online , offline
+ |
+ Jenkins build nodes |
+
jenkins.executor.available |
${executors} |
@@ -166,6 +204,15 @@ Inventory of health metrics collected by the Jenkins OpenTelemetry integration:
|
|
+
+ jenkins.queue |
+ ${tasks} |
+ status |
+
+ blocked , buildable , stuck , waiting , unknown
+ |
+ Number of tasks in the queue. See status code> description [here](https://javadoc.jenkins.io/hudson/model/Queue.html) |
+
jenkins.queue.waiting |
${items} |
@@ -208,6 +255,35 @@ Inventory of health metrics collected by the Jenkins OpenTelemetry integration:
|
Disk Usage size |
+
+ http.server.request.duration |
+ s |
+
+ http.request.method ,
+ url.scheme ,
+ error.type ,
+ http.response.status_code ,
+ http.route ,
+ server.address ,
+ server.port
+ |
+ |
+ HTTP server duration metric as defined by the OpenTelemetry specification ([here](https://opentelemetry.io/docs/specs/semconv/http/http-metrics/#metric-httpserverrequestduration)) |
+
+
+ jenkins.plugins |
+ ${plugins} |
+ status |
+ active , inactive , failed |
+ Jenkins plugins broken down by activation status |
+
+
+ jenkins.plugins.updates |
+ ${plugins} |
+ status |
+ hasUpdate , isUpToDate |
+ Jenkins plugins broken down by updatability status |
+
## Jenkins agents metrics
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 89734b063..a8aa3cc48 100644
--- a/src/main/java/io/jenkins/plugins/opentelemetry/init/JenkinsExecutorMonitoringInitializer.java
+++ b/src/main/java/io/jenkins/plugins/opentelemetry/init/JenkinsExecutorMonitoringInitializer.java
@@ -5,11 +5,15 @@
package io.jenkins.plugins.opentelemetry.init;
+import static io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes.STATUS;
+
import hudson.Extension;
+import hudson.model.Computer;
import hudson.model.LoadStatistics;
+import hudson.model.Node;
import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry;
import io.jenkins.plugins.opentelemetry.api.OpenTelemetryLifecycleListener;
-import io.opentelemetry.api.common.AttributeKey;
+import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.metrics.ObservableLongMeasurement;
@@ -19,6 +23,8 @@
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -42,30 +48,87 @@ public void postConstruct() {
logger.log(Level.FINE, () -> "Start monitoring Jenkins controller executor pool...");
Meter meter = Objects.requireNonNull(jenkinsControllerOpenTelemetry).getDefaultMeter();
+
+ final ObservableLongMeasurement queueLength = meter.gaugeBuilder(JENKINS_EXECUTOR_QUEUE).setUnit("${items}").setDescription("Executors queue items").ofLongs().buildObserver();
+ final ObservableLongMeasurement totalExecutors = meter.gaugeBuilder(JENKINS_EXECUTOR_TOTAL).setUnit("${executors}").setDescription("Total executors").ofLongs().buildObserver();
+ final ObservableLongMeasurement nodes = meter.gaugeBuilder(JENKINS_NODE).setUnit("${nodes}").setDescription("Nodes").ofLongs().buildObserver();
+ final ObservableLongMeasurement executors = meter.gaugeBuilder(JENKINS_EXECUTOR).setUnit("${executors}").setDescription("Per label executors").ofLongs().buildObserver();
+
+ // TODO the metrics below should be deprecated in favor of
+ // * `jenkins.executor` metric with the `status` and `label`attributes
+ // * `jenkins.node` metric with the `status` attribute
+ // * `jenkins.executor.total` metric with the `status` attribute
final ObservableLongMeasurement availableExecutors = meter.gaugeBuilder(JENKINS_EXECUTOR_AVAILABLE).setUnit("${executors}").setDescription("Available executors").ofLongs().buildObserver();
final ObservableLongMeasurement busyExecutors = meter.gaugeBuilder(JENKINS_EXECUTOR_BUSY).setUnit("${executors}").setDescription("Busy executors").ofLongs().buildObserver();
final ObservableLongMeasurement idleExecutors = meter.gaugeBuilder(JENKINS_EXECUTOR_IDLE).setUnit("${executors}").setDescription("Idle executors").ofLongs().buildObserver();
final ObservableLongMeasurement onlineExecutors = meter.gaugeBuilder(JENKINS_EXECUTOR_ONLINE).setUnit("${executors}").setDescription("Online executors").ofLongs().buildObserver();
final ObservableLongMeasurement connectingExecutors = meter.gaugeBuilder(JENKINS_EXECUTOR_CONNECTING).setUnit("${executors}").setDescription("Connecting executors").ofLongs().buildObserver();
final ObservableLongMeasurement definedExecutors = meter.gaugeBuilder(JENKINS_EXECUTOR_DEFINED).setUnit("${executors}").setDescription("Defined executors").ofLongs().buildObserver();
- final ObservableLongMeasurement queueLength = meter.gaugeBuilder(JENKINS_EXECUTOR_QUEUE).setUnit("${items}").setDescription("Executors queue items").ofLongs().buildObserver();
+
logger.log(Level.FINER, () -> "Metrics: " + availableExecutors + ", " + busyExecutors + ", " + idleExecutors + ", " + onlineExecutors + ", " + connectingExecutors + ", " + definedExecutors + ", " + queueLength);
meter.batchCallback(() -> {
logger.log(Level.FINE, () -> "Recording Jenkins controller executor pool metrics...");
- logger.log(Level.FINER, () -> "Metrics: " + availableExecutors + ", " + busyExecutors + ", " + idleExecutors + ", " + onlineExecutors + ", " + connectingExecutors + ", " + definedExecutors + ", " + queueLength);
Jenkins jenkins = Jenkins.get();
+
+ // TOTAL EXECUTORS
+ AtomicInteger totalExecutorsIdle = new AtomicInteger();
+ AtomicInteger totalExecutorsBusy = new AtomicInteger();
+ AtomicInteger nodeOnline = new AtomicInteger();
+ AtomicInteger nodeOffline = new AtomicInteger();
+
+ if (jenkins.getNumExecutors() > 0) {
+ nodeOnline.incrementAndGet();
+ Optional.ofNullable(jenkins.toComputer())
+ .map(Computer::getExecutors)
+ .ifPresent(e -> e.forEach(executor -> {
+ if (executor.isIdle()) {
+ totalExecutorsIdle.incrementAndGet();
+ } else {
+ totalExecutorsBusy.incrementAndGet();
+ }
+ }));
+ }
+ jenkins.getNodes().stream().map(Node::toComputer).filter(Objects::nonNull).forEach(node -> {
+ if (node.isOnline()) {
+ nodeOnline.incrementAndGet();
+ node.getExecutors()
+ .forEach(executor -> {
+ if (executor.isIdle()) {
+ totalExecutorsIdle.incrementAndGet();
+ } else {
+ totalExecutorsBusy.incrementAndGet();
+ }
+ });
+ } else {
+ nodeOffline.incrementAndGet();
+ }
+ });
+
+ totalExecutors.record(totalExecutorsBusy.get(), Attributes.of(STATUS, "busy"));
+ totalExecutors.record(totalExecutorsIdle.get(), Attributes.of(STATUS, "idle"));
+ nodes.record(nodeOnline.get(), Attributes.of(STATUS, "online"));
+ nodes.record(nodeOffline.get(), Attributes.of(STATUS, "offline"));
+
+ // PER LABEL
jenkins.getLabels().forEach(label -> {
LoadStatistics.LoadStatisticsSnapshot loadStatisticsSnapshot = label.loadStatistics.computeSnapshot();
- Attributes attributes = Attributes.of(AttributeKey.stringKey("label"), label.getDisplayName());
+ Attributes attributes = Attributes.of(JenkinsOtelSemanticAttributes.LABEL, label.getDisplayName());
+
+ executors.record(loadStatisticsSnapshot.getBusyExecutors(), attributes.toBuilder().put(STATUS, "busy").build());
+ executors.record(loadStatisticsSnapshot.getIdleExecutors(), attributes.toBuilder().put(STATUS, "idle").build());
+ executors.record(loadStatisticsSnapshot.getConnectingExecutors(), attributes.toBuilder().put(STATUS, "connecting").build());
+ queueLength.record(loadStatisticsSnapshot.getQueueLength(), attributes);
+
+ // TODO the metrics below should be deprecated in favor of `jenkins.executor` metric with the `status`
+ // and `label`attributes
availableExecutors.record(loadStatisticsSnapshot.getAvailableExecutors(), attributes);
busyExecutors.record(loadStatisticsSnapshot.getBusyExecutors(), attributes);
idleExecutors.record(loadStatisticsSnapshot.getIdleExecutors(), attributes);
onlineExecutors.record(loadStatisticsSnapshot.getOnlineExecutors(), attributes);
definedExecutors.record(loadStatisticsSnapshot.getDefinedExecutors(), attributes);
connectingExecutors.record(loadStatisticsSnapshot.getConnectingExecutors(), attributes);
- queueLength.record(loadStatisticsSnapshot.getQueueLength(), attributes);
});
- }, availableExecutors, busyExecutors, idleExecutors, onlineExecutors, connectingExecutors, definedExecutors, queueLength);
+ }, availableExecutors, busyExecutors, idleExecutors, onlineExecutors, connectingExecutors, definedExecutors, totalExecutors, executors, nodes, queueLength);
}
}
diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/init/PluginMonitoringInitializer.java b/src/main/java/io/jenkins/plugins/opentelemetry/init/PluginMonitoringInitializer.java
new file mode 100644
index 000000000..4394492c6
--- /dev/null
+++ b/src/main/java/io/jenkins/plugins/opentelemetry/init/PluginMonitoringInitializer.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright The Original Author or Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.jenkins.plugins.opentelemetry.init;
+
+import hudson.Extension;
+import hudson.PluginManager;
+import io.jenkins.plugins.opentelemetry.JenkinsControllerOpenTelemetry;
+import io.jenkins.plugins.opentelemetry.api.OpenTelemetryLifecycleListener;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.api.metrics.ObservableLongMeasurement;
+import jenkins.YesNoMaybe;
+import jenkins.model.Jenkins;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes.STATUS;
+import static io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics.JENKINS_PLUGINS;
+import static io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics.JENKINS_PLUGINS_UPDATES;
+
+/**
+ *
+ * Monitor the Jenkins plugins
+ *
+ *
+ * TODO report on `hasUpdate` plugin count.
+ *
+ */
+@Extension(dynamicLoadable = YesNoMaybe.MAYBE, optional = true)
+public class PluginMonitoringInitializer implements OpenTelemetryLifecycleListener {
+
+ private static final Logger logger = Logger.getLogger(PluginMonitoringInitializer.class.getName());
+
+ @Inject
+ JenkinsControllerOpenTelemetry jenkinsControllerOpenTelemetry;
+
+ @PostConstruct
+ public void postConstruct() {
+
+ logger.log(Level.FINE, () -> "Start monitoring Jenkins plugins...");
+
+ Meter meter = Objects.requireNonNull(jenkinsControllerOpenTelemetry).getDefaultMeter();
+
+ final ObservableLongMeasurement plugins = meter
+ .gaugeBuilder(JENKINS_PLUGINS)
+ .setUnit("${plugins}")
+ .setDescription("Jenkins plugins")
+ .ofLongs()
+ .buildObserver();
+ final ObservableLongMeasurement pluginUpdates = meter
+ .gaugeBuilder(JENKINS_PLUGINS_UPDATES)
+ .setUnit("${plugins}")
+ .setDescription("Jenkins plugin updates")
+ .ofLongs()
+ .buildObserver();
+ meter.batchCallback(() -> {
+ logger.log(Level.FINE, () -> "Recording Jenkins controller executor pool metrics...");
+
+ AtomicInteger active = new AtomicInteger();
+ AtomicInteger inactive = new AtomicInteger();
+ AtomicInteger hasUpdate = new AtomicInteger();
+ AtomicInteger isUpToDate = new AtomicInteger();
+
+ PluginManager pluginManager = Jenkins.get().getPluginManager();
+ pluginManager.getPlugins().forEach(plugin -> {
+ if (plugin.isActive()) {
+ active.incrementAndGet();
+ } else {
+ inactive.incrementAndGet();
+ }
+ if (plugin.hasUpdate()) {
+ hasUpdate.incrementAndGet();
+ } else {
+ isUpToDate.incrementAndGet();
+ }
+ });
+ int failed = pluginManager.getFailedPlugins().size();
+ plugins.record(active.get(), Attributes.of(STATUS, "active"));
+ plugins.record(inactive.get(), Attributes.of(STATUS, "inactive"));
+ plugins.record(failed, Attributes.of(STATUS, "failed"));
+ pluginUpdates.record(hasUpdate.get(), Attributes.of(STATUS, "hasUpdate"));
+ pluginUpdates.record(isUpToDate.get(), Attributes.of(STATUS, "isUpToDate"));
+ }, plugins, pluginUpdates);
+ }
+}
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 be9415dee..8f875498d 100644
--- a/src/main/java/io/jenkins/plugins/opentelemetry/queue/MonitoringQueueListener.java
+++ b/src/main/java/io/jenkins/plugins/opentelemetry/queue/MonitoringQueueListener.java
@@ -5,6 +5,9 @@
package io.jenkins.plugins.opentelemetry.queue;
+import static io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics.*;
+import static io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes.STATUS;
+
import hudson.Extension;
import hudson.model.Queue;
import hudson.model.queue.QueueListener;
@@ -12,8 +15,10 @@
import io.jenkins.plugins.opentelemetry.api.OpenTelemetryLifecycleListener;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics;
+import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.LongCounter;
import io.opentelemetry.api.metrics.Meter;
+import io.opentelemetry.api.metrics.ObservableLongMeasurement;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.context.Context;
@@ -23,12 +28,15 @@
import javax.annotation.PostConstruct;
import javax.inject.Inject;
+import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
+import static io.jenkins.plugins.opentelemetry.semconv.JenkinsSemanticMetrics.JENKINS_QUEUE;
+
/**
* Monitor the Jenkins Build queue
*/
@@ -37,7 +45,6 @@ public class MonitoringQueueListener extends QueueListener implements OpenTeleme
private final static Logger LOGGER = Logger.getLogger(MonitoringQueueListener.class.getName());
- private final AtomicInteger blockedItemGauge = new AtomicInteger();
private LongCounter leftItemCounter;
private LongCounter timeInQueueInMillisCounter;
@Inject
@@ -48,53 +55,89 @@ public class MonitoringQueueListener extends QueueListener implements OpenTeleme
public void afterConfiguration(ConfigProperties configProperties) {
traceContextPropagationEnabled.set(configProperties.getBoolean(JenkinsOtelSemanticAttributes.OTEL_INSTRUMENTATION_JENKINS_REMOTE_SPAN_ENABLED, false));
}
+
@PostConstruct
public void postConstruct() {
LOGGER.log(Level.FINE, () -> "Start monitoring Jenkins queue...");
Meter meter = jenkinsControllerOpenTelemetry.getDefaultMeter();
- meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_QUEUE_WAITING)
+ final ObservableLongMeasurement queueItems = meter.gaugeBuilder(JENKINS_QUEUE)
+ .ofLongs()
+ .setDescription("Number of tasks in the queue")
+ .setUnit("${tasks}")
+ .buildObserver();
+ // should be deprecated in favor of "jenkins.queue" metric with status attribute
+ final ObservableLongMeasurement queueWaitingItems = meter.gaugeBuilder(JENKINS_QUEUE_WAITING)
.ofLongs()
.setDescription("Number of tasks in the queue with the status 'waiting', 'buildable' or 'pending'")
.setUnit("{tasks}")
- .buildWithCallback(valueObserver -> valueObserver.record((long)
- Optional.ofNullable(Jenkins.getInstanceOrNull()).map(j -> j.getQueue()).
- map(q -> q.getUnblockedItems().size()).orElse(0)));
-
- meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_QUEUE_BLOCKED)
+ .buildObserver();
+ // should be deprecated in favor of "jenkins.queue" metric with status attribute
+ final ObservableLongMeasurement queueBlockedItems = meter.gaugeBuilder(JENKINS_QUEUE_BLOCKED)
.ofLongs()
.setDescription("Number of blocked tasks in the queue. Note that waiting for an executor to be available is not a reason to be counted as blocked")
.setUnit("{tasks}")
- .buildWithCallback(valueObserver -> valueObserver.record(this.blockedItemGauge.longValue()));
-
- meter.gaugeBuilder(JenkinsSemanticMetrics.JENKINS_QUEUE_BUILDABLE)
+ .buildObserver();
+ // should be deprecated in favor of "jenkins.queue" metric with status attribute
+ final ObservableLongMeasurement queueBuildableItems = meter.gaugeBuilder(JENKINS_QUEUE_BUILDABLE)
.ofLongs()
.setDescription("Number of tasks in the queue with the status 'buildable' or 'pending'")
.setUnit("{tasks}")
- .buildWithCallback(valueObserver -> valueObserver.record((long)
- Optional.ofNullable(Jenkins.getInstanceOrNull()).map(j -> j.getQueue()).
- map(q -> q.countBuildableItems()).orElse(0)));
-
- leftItemCounter = meter.counterBuilder(JenkinsSemanticMetrics.JENKINS_QUEUE_LEFT)
+ .buildObserver();
+
+ meter.batchCallback(() -> {
+ LOGGER.log(Level.FINE, () -> "Recording Jenkins queue metrics...");
+
+ Optional queue = Optional.ofNullable(Jenkins.getInstanceOrNull()).map(Jenkins::getQueue);
+ queue.map(Queue::getItems)
+ .ifPresent(items -> {
+ AtomicInteger blocked = new AtomicInteger();
+ AtomicInteger buildable = new AtomicInteger();
+ AtomicInteger left = new AtomicInteger();
+ AtomicInteger stuck = new AtomicInteger();
+ AtomicInteger unknown = new AtomicInteger();
+ AtomicInteger waiting = new AtomicInteger();
+ Arrays.stream(items).forEach(item -> {
+ if (item instanceof Queue.BlockedItem) {
+ blocked.incrementAndGet();
+ } else if (item instanceof Queue.BuildableItem) {
+ if (item.isStuck()) {
+ // buildable but here for too long
+ stuck.incrementAndGet();
+ } else {
+ buildable.incrementAndGet();
+ }
+ } else if (item instanceof Queue.WaitingItem) {
+ waiting.incrementAndGet();
+ } else if (item instanceof Queue.LeftItem) {
+ left.incrementAndGet();
+ } else {
+ LOGGER.log(Level.INFO, () -> "Unknown item: " + item + " - class=" + item.getClass());
+ unknown.incrementAndGet();
+ }
+ });
+ queueItems.record(blocked.get(), Attributes.of(STATUS, "blocked"));
+ queueBlockedItems.record(blocked.get());
+ queueItems.record(buildable.get(), Attributes.of(STATUS, "buildable"));
+ queueBuildableItems.record(buildable.get());
+ queueItems.record(stuck.get(), Attributes.of(STATUS, "stuck"));
+ if (unknown.get() > 0) {
+ queueItems.record(unknown.get(), Attributes.of(STATUS, "unknown"));
+ }
+ queueItems.record(waiting.get(), Attributes.of(STATUS, "waiting"));
+ queueWaitingItems.record(waiting.get());
+ });
+ }, queueItems, queueWaitingItems, queueBlockedItems, queueBuildableItems);
+
+ leftItemCounter = meter.counterBuilder(JENKINS_QUEUE_LEFT)
.setDescription("Total count of tasks that have been processed")
.setUnit("{tasks}")
.build();
- timeInQueueInMillisCounter = meter.counterBuilder(JenkinsSemanticMetrics.JENKINS_QUEUE_TIME_SPENT_MILLIS)
+ timeInQueueInMillisCounter = meter.counterBuilder(JENKINS_QUEUE_TIME_SPENT_MILLIS)
.setDescription("Total time spent in queue by the tasks that have been processed")
.setUnit("ms")
.build();
-
- }
-
- @Override
- public void onEnterBlocked(Queue.BlockedItem bi) {
- this.blockedItemGauge.incrementAndGet();
- }
-
- @Override
- public void onLeaveBlocked(Queue.BlockedItem bi) {
- this.blockedItemGauge.decrementAndGet();
}
@Override
@@ -108,13 +151,11 @@ public void onLeft(Queue.LeftItem li) {
public void onEnterWaiting(Queue.WaitingItem wi) {
if (traceContextPropagationEnabled.get()) {
Span span = Span.fromContextOrNull(Context.current());
- if (span != null && wi.getActions(RemoteSpanAction.class) != null) {
+ if (span != null) {
SpanContext spanContext = span.getSpanContext();
wi.addAction(new RemoteSpanAction(spanContext.getTraceId(), spanContext.getSpanId(), spanContext.getTraceFlags().asByte(), spanContext.getTraceState().asMap()));
LOGGER.log(Level.FINE, () -> "attach RemoteSpanAction to " + wi);
}
}
}
-
-
}
diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java
index 71f2ca239..71ac95dc2 100644
--- a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java
+++ b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsOtelSemanticAttributes.java
@@ -173,6 +173,10 @@ public static final class EventCategoryValues {
public static final String AUTHENTICATION = "authentication";
}
+ public static final AttributeKey STATUS = AttributeKey.stringKey("status");
+ public static final AttributeKey LABEL = AttributeKey.stringKey("label");
+
+
/**
* Values in {@link EventOutcomeValues}
*/
diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsSemanticMetrics.java b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsSemanticMetrics.java
index fb0bfc437..e1f24e78d 100644
--- a/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsSemanticMetrics.java
+++ b/src/main/java/io/jenkins/plugins/opentelemetry/semconv/JenkinsSemanticMetrics.java
@@ -21,6 +21,10 @@ public class JenkinsSemanticMetrics {
public static final String JENKINS_EXECUTOR_CONNECTING = "jenkins.executor.connecting";
public static final String JENKINS_EXECUTOR_DEFINED = "jenkins.executor.defined";
public static final String JENKINS_EXECUTOR_QUEUE = "jenkins.executor.queue";
+ public static final String JENKINS_EXECUTOR_TOTAL = "jenkins.executor.total";
+ public static final String JENKINS_EXECUTOR = "jenkins.executor";
+ public static final String JENKINS_NODE = "jenkins.node";
+ public static final String JENKINS_QUEUE = "jenkins.queue";
public static final String JENKINS_QUEUE_WAITING = "jenkins.queue.waiting";
public static final String JENKINS_QUEUE_BLOCKED = "jenkins.queue.blocked";
public static final String JENKINS_QUEUE_BUILDABLE = "jenkins.queue.buildable";
@@ -34,6 +38,9 @@ public class JenkinsSemanticMetrics {
public static final String JENKINS_CLOUD_AGENTS_COMPLETED = "jenkins.cloud.agents.completed";
public static final String JENKINS_DISK_USAGE_BYTES = "jenkins.disk.usage.bytes";
+ public static final String JENKINS_PLUGINS = "jenkins.plugins";
+ public static final String JENKINS_PLUGINS_UPDATES = "jenkins.plugins.updates";
+
public static final String JENKINS_SCM_EVENT_POOL_SIZE = "jenkins.scm.event.pool_size";
public static final String JENKINS_SCM_EVENT_ACTIVE_THREADS = "jenkins.scm.event.active_threads";
public static final String JENKINS_SCM_EVENT_QUEUED_TASKS = "jenkins.scm.event.queued_tasks";
diff --git a/src/main/java/io/jenkins/plugins/opentelemetry/servlet/StaplerInstrumentationServletFilter.java b/src/main/java/io/jenkins/plugins/opentelemetry/servlet/StaplerInstrumentationServletFilter.java
index 027a3469b..cec01fd9e 100644
--- a/src/main/java/io/jenkins/plugins/opentelemetry/servlet/StaplerInstrumentationServletFilter.java
+++ b/src/main/java/io/jenkins/plugins/opentelemetry/servlet/StaplerInstrumentationServletFilter.java
@@ -5,37 +5,42 @@
package io.jenkins.plugins.opentelemetry.servlet;
+import edu.umd.cs.findbugs.annotations.Nullable;
import hudson.Extension;
import hudson.model.User;
import io.jenkins.plugins.opentelemetry.api.OpenTelemetryLifecycleListener;
import io.jenkins.plugins.opentelemetry.api.ReconfigurableOpenTelemetry;
import io.jenkins.plugins.opentelemetry.semconv.JenkinsOtelSemanticAttributes;
+import io.opentelemetry.api.common.Attributes;
+import io.opentelemetry.api.common.AttributesBuilder;
+import io.opentelemetry.api.metrics.Meter;
import io.opentelemetry.api.trace.Span;
-import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
+import io.opentelemetry.instrumentation.api.instrumenter.OperationListener;
+import io.opentelemetry.instrumentation.api.semconv.http.HttpServerMetrics;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.semconv.ClientAttributes;
+import io.opentelemetry.semconv.ErrorAttributes;
import io.opentelemetry.semconv.HttpAttributes;
-import io.opentelemetry.semconv.NetworkAttributes;
import io.opentelemetry.semconv.ServerAttributes;
import io.opentelemetry.semconv.UrlAttributes;
-import io.opentelemetry.semconv.incubating.EnduserIncubatingAttributes;
+import io.opentelemetry.semconv.UserAgentAttributes;
import io.opentelemetry.semconv.incubating.ThreadIncubatingAttributes;
-import org.apache.commons.lang.StringUtils;
-
-import edu.umd.cs.findbugs.annotations.Nullable;
-
-import javax.inject.Inject;
+import io.opentelemetry.semconv.incubating.UserIncubatingAttributes;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
-import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.lang.StringUtils;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -49,8 +54,6 @@
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
-import java.util.Set;
-import java.util.HashSet;
/**
* Instrumentation of the Stapler MVC framework.
@@ -60,11 +63,17 @@
*/
@Extension
public class StaplerInstrumentationServletFilter implements Filter, OpenTelemetryLifecycleListener {
- private static final Set SKIP_PATHS = new HashSet<>(Arrays.asList("static", "adjuncts", "scripts", "plugin", "images", "sse-gateway"));
private final static Logger logger = Logger.getLogger(StaplerInstrumentationServletFilter.class.getName());
final AtomicBoolean enabled = new AtomicBoolean(false);
List capturedRequestParameters;
Tracer tracer;
+ Meter meter;
+ OperationListener httpServerMetrics;
+
+ @PostConstruct
+ public void postConstruct() {
+ httpServerMetrics = HttpServerMetrics.get().create(meter);
+ }
@Override
public void afterConfiguration(ConfigProperties configProperties) {
@@ -87,174 +96,175 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
}
public void _doFilter(HttpServletRequest servletRequest, HttpServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
- String pathInfo = servletRequest.getPathInfo();
- List pathInfoTokens = Collections.list(new StringTokenizer(pathInfo, "/")).stream()
- .map(token -> (String) token)
- .filter(t -> !t.isEmpty())
- .collect(Collectors.toList());
-
- if (pathInfoTokens.isEmpty()) {
- pathInfoTokens = Collections.singletonList("");
- }
- String rootPath = pathInfoTokens.get(0);
- // The matched route (path template).
- String httpRoute;
- if (SKIP_PATHS.contains(rootPath)) {
- // skip
- filterChain.doFilter(servletRequest, servletResponse);
- return;
- }
- if (rootPath.equals("$stapler")) {
- // TODO handle URL pattern /$stapler/bound/ec328aeb-26be-43da-94a3-59f2d683131c/news
- filterChain.doFilter(servletRequest, servletResponse);
- return;
- }
- final SpanBuilder spanBuilder;
+ // Attributes common to Http Server span and metric
+ AttributesBuilder httpServerMetricOnStartAttributesBuilder = Attributes.builder()
+ .put(HttpAttributes.HTTP_REQUEST_METHOD, servletRequest.getMethod())
+ .put(UrlAttributes.URL_SCHEME, servletRequest.getScheme())
+ .put(ServerAttributes.SERVER_ADDRESS, servletRequest.getServerName())
+ .put(ServerAttributes.SERVER_PORT, (long) servletRequest.getServerPort());
+
+ Thread currentThread = Thread.currentThread();
+ AttributesBuilder httpServerSpanAttributesBuilder = Attributes.builder()
+ .putAll(httpServerMetricOnStartAttributesBuilder.build())
+ .put(ThreadIncubatingAttributes.THREAD_NAME, currentThread.getName())
+ .put(ThreadIncubatingAttributes.THREAD_ID, currentThread.getId())
+ .put(ClientAttributes.CLIENT_ADDRESS, servletRequest.getRemoteAddr())
+ .put(ClientAttributes.CLIENT_PORT, (long) servletRequest.getRemotePort())
+ // See https://opentelemetry.io/docs/specs/semconv/attributes-registry/url/#url-full
+ // Security notes:
+ // * `HttpServletRequest.getRequestURL()` is safe not including URL credentials
+ // * Omit the URL query string to ensure we don't surface secrets.
+ // The OTel `url.full` spec requires to redact sensitive info including query parameters like
+ // `AWSAccessKeyId`, `Signature`, `X-Goog-Credential`, `X-Goog-Signature`, or `sig`. It's safer to omit
+ // the query string.
+ // Interesting query parameters should be captured explicitly and users can explicitly capture more
+ // using the config param `otel.instrumentation.servlet.experimental.capture-request-parameters`
+ // Note: OTel specs may stop making `url.full` mandatory in the future:
+ // https://github.com/open-telemetry/semantic-conventions/issues/128
+ .put(UrlAttributes.URL_FULL, servletRequest.getRequestURL().toString())
+ .put(UserAgentAttributes.USER_AGENT_ORIGINAL, servletRequest.getHeader("User-Agent"));
+ Optional.ofNullable(User.current()).ifPresent(user -> httpServerSpanAttributesBuilder.put(UserIncubatingAttributes.USER_ID, user.getId()));
+
+ Context httpServerDurationMetricContext = httpServerMetrics.onStart(Context.current(), httpServerMetricOnStartAttributesBuilder.build(), System.nanoTime());
+
+ AttributesBuilder httpServerMetricOnEndAttributesBuilder = Attributes.builder();
try {
- if (rootPath.equals("job")) {
- // e.g /job/my-war/job/master/lastBuild/console
- // e.g /job/my-war/job/master/2/console
- ParsedJobUrl parsedJobUrl = parseJobUrl(pathInfoTokens);
- httpRoute = parsedJobUrl.urlPattern;
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + parsedJobUrl.urlPattern);
- if (parsedJobUrl.jobName != null) {
- spanBuilder.setAttribute(JenkinsOtelSemanticAttributes.CI_PIPELINE_ID, parsedJobUrl.jobName);
- }
- if (parsedJobUrl.runNumber != null) {
- spanBuilder.setAttribute(JenkinsOtelSemanticAttributes.CI_PIPELINE_RUN_NUMBER, parsedJobUrl.runNumber);
- }
- } else if (rootPath.equals("blue")) {
- if (pathInfoTokens.size() == 1) {
- httpRoute = "/blue/";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + httpRoute);
- } else if ("rest".equals(pathInfoTokens.get(1))) {
- if (pathInfoTokens.size() == 2) {
- httpRoute = "/blue/rest/";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + httpRoute);
- } else if ("organizations".equals(pathInfoTokens.get(2)) && pathInfoTokens.size() > 7) {
- // eg /blue/rest/organizations/jenkins/pipelines/ecommerce-antifraud/branches/main/runs/110/blueTestSummary/
-
- ParsedJobUrl parsedBlueOceanPipelineUrl = parseBlueOceanRestPipelineUrl(pathInfoTokens);
- httpRoute = parsedBlueOceanPipelineUrl.urlPattern;
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + parsedBlueOceanPipelineUrl.urlPattern);
- if (parsedBlueOceanPipelineUrl.jobName != null) {
- spanBuilder.setAttribute(JenkinsOtelSemanticAttributes.CI_PIPELINE_ID, parsedBlueOceanPipelineUrl.jobName);
+
+ List pathInfoTokens = Collections.list(new StringTokenizer(servletRequest.getPathInfo(), "/")).stream()
+ .map(token -> (String) token)
+ .filter(t -> !t.isEmpty())
+ .collect(Collectors.toList());
+
+ if (pathInfoTokens.isEmpty()) {
+ pathInfoTokens = Collections.singletonList("");
+ }
+
+ String rootPath = pathInfoTokens.get(0);
+ String httpRoute;
+
+ boolean skipSpan = false;
+ try {
+ switch (rootPath) {
+ case "job" -> {
+ // e.g /job/my-war/job/master/lastBuild/console
+ // e.g /job/my-war/job/master/2/console
+ ParsedJobUrl parsedJobUrl = parseJobUrl(pathInfoTokens);
+ httpRoute = parsedJobUrl.urlPattern;
+ Optional.ofNullable(parsedJobUrl.jobName).ifPresent(jobName -> httpServerSpanAttributesBuilder.put(JenkinsOtelSemanticAttributes.CI_PIPELINE_ID, jobName));
+ Optional.ofNullable(parsedJobUrl.runNumber).ifPresent(runNumber -> httpServerSpanAttributesBuilder.put(JenkinsOtelSemanticAttributes.CI_PIPELINE_RUN_NUMBER, runNumber));
+ }
+ case "blue" -> {
+ if (pathInfoTokens.size() == 1) {
+ httpRoute = "/blue/";
+ } else if ("rest".equals(pathInfoTokens.get(1))) {
+ if (pathInfoTokens.size() == 2) {
+ httpRoute = "/blue/rest/";
+ } else if ("organizations".equals(pathInfoTokens.get(2)) && pathInfoTokens.size() > 7) {
+ // eg /blue/rest/organizations/jenkins/pipelines/ecommerce-antifraud/branches/main/runs/110/blueTestSummary/
+ ParsedJobUrl parsedBlueOceanPipelineUrl = parseBlueOceanRestPipelineUrl(pathInfoTokens);
+ httpRoute = parsedBlueOceanPipelineUrl.urlPattern;
+ Optional.ofNullable(parsedBlueOceanPipelineUrl.jobName).ifPresent(jobName -> httpServerSpanAttributesBuilder.put(JenkinsOtelSemanticAttributes.CI_PIPELINE_ID, jobName));
+ Optional.ofNullable(parsedBlueOceanPipelineUrl.runNumber).ifPresent(runNumber -> httpServerSpanAttributesBuilder.put(JenkinsOtelSemanticAttributes.CI_PIPELINE_RUN_NUMBER, runNumber));
+ } else if ("classes".equals(pathInfoTokens.get(2)) && pathInfoTokens.size() > 3) {
+ // eg /blue/rest/classes/io.jenkins.blueocean.rest.impl.pipeline.PipelineRunImpl/
+ String blueOceanClass = pathInfoTokens.get(3);
+ httpRoute = "/blue/rest/classes/:blueOceanClass";
+ httpServerSpanAttributesBuilder.put("blueOceanClass", blueOceanClass);
+ } else {
+ // eg /blue/rest/i18n/blueocean-personalization/1.25.2/jenkins.plugins.blueocean.personalization.Messages/en-US
+ httpRoute = "/blue/rest/" + pathInfoTokens.get(2) + "/*";
+ }
+ } else {
+ httpRoute = "/blue/" + pathInfoTokens.get(1) + "/*";
}
- if (parsedBlueOceanPipelineUrl.runNumber != null) {
- spanBuilder.setAttribute(JenkinsOtelSemanticAttributes.CI_PIPELINE_RUN_NUMBER, parsedBlueOceanPipelineUrl.runNumber);
+ }
+ case "administrativeMonitor" -> {
+ // eg GET /administrativeMonitor/hudson.diagnosis.ReverseProxySetupMonitor/testForReverseProxySetup/http://localhost:8080/jenkins/manage/
+ httpRoute = "/administrativeMonitor/:administrativeMonitor/*";
+ if (pathInfoTokens.size() > 1) {
+ httpServerSpanAttributesBuilder.put("administrativeMonitor", pathInfoTokens.get(1));
}
- } else if ("classes".equals(pathInfoTokens.get(2)) && pathInfoTokens.size() > 3) {
- // eg /blue/rest/classes/io.jenkins.blueocean.rest.impl.pipeline.PipelineRunImpl/
- String blueOceanClass = pathInfoTokens.get(3);
- httpRoute = "/blue/rest/classes/:blueOceanClass";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + httpRoute)
- .setAttribute("blueOceanClass", blueOceanClass);
- } else {
- // eg /blue/rest/i18n/blueocean-personalization/1.25.2/jenkins.plugins.blueocean.personalization.Messages/en-US
- httpRoute = "/blue/rest/" + pathInfoTokens.get(2) + "/*";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + httpRoute);
}
- } else {
- httpRoute = "/blue/" + pathInfoTokens.get(1) + "/*";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + httpRoute);
+ case "asynchPeople" -> {
+ httpRoute = "/asynchPeople";
+ }
+ case "computer" -> {
+ // /computer/(master)/
+ httpRoute = "/computer/:computer/*";
+ // TODO add details
+ }
+ case "credentials" -> {
+ // eg /credentials/store/system/domain/_/
+ httpRoute = "/credentials/store/:store/domain/:domain/*";
+ // TODO add details
+ }
+ case "descriptorByName" -> {
+ httpRoute = "/descriptorByName/:descriptor/*";
+ if (pathInfoTokens.size() > 1) {
+ httpServerSpanAttributesBuilder.put("descriptor", pathInfoTokens.get(1));
+ }
+ }
+ case "extensionList" -> {
+ // eg /extensionList/hudson.diagnosis.MemoryUsageMonitor/0/heap/graph
+ httpRoute = "/extensionList/:extension/*";
+ if (pathInfoTokens.size() > 1) {
+ httpServerSpanAttributesBuilder.put("extension", pathInfoTokens.get(1));
+ }
+ }
+ case "fingerprint" -> {
+ httpRoute = "/fingerprint/:fingerprint";
+ httpServerSpanAttributesBuilder.put("fingerprint", servletRequest.getPathInfo().substring("/fingerprint/".length()));
+ if (pathInfoTokens.size() > 1) {
+ httpServerSpanAttributesBuilder.put("fingerprint", pathInfoTokens.get(1));
+ }
+ }
+ case "user" -> {
+ //eg /user/cyrille.leclerc/ /user/cyrille.leclerc/configure /user/cyrille.leclerc/my-views/view/all/
+ httpRoute = "/user/:user/*";
+ if (pathInfoTokens.size() > 1) {
+ httpServerSpanAttributesBuilder.put("user", pathInfoTokens.get(1));
+ }
+ }
+ default -> {
+ // "static", "adjuncts", "scripts", "plugin", "images", "sse-gateway"
+ // e.g /$stapler/bound/ec328aeb-26be-43da-94a3-59f2d683131c/news
+ httpRoute = "/*";
+ skipSpan = true;
+ }
}
+ } catch (RuntimeException e) {
+ logger.log(Level.INFO, () -> "Exception processing URL " + servletRequest.getPathInfo() + ", skip instrumentation with tracing: " + e);
+ httpRoute = "/##error-processing-url-to-extract-http-route##";
+ }
- } else if (rootPath.equals("administrativeMonitor")) {
- // eg GET /administrativeMonitor/hudson.diagnosis.ReverseProxySetupMonitor/testForReverseProxySetup/http://localhost:8080/jenkins/manage/
- httpRoute = "/administrativeMonitor/:administrativeMonitor/*";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + "/administrativeMonitor/");
- if (pathInfoTokens.size() > 1) {
- spanBuilder.setAttribute("administrativeMonitor", pathInfoTokens.get(1));
- }
- } else if (rootPath.equals("asynchPeople")) {
- httpRoute = "/asynchPeople";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + "/asynchPeople");
- } else if (rootPath.equals("computer")) {
- // /computer/(master)/
- httpRoute = "/computer/:computer/*";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + "/computer/*");
- // TODO more details
- } else if (rootPath.equals("credentials")) {
- // eg /credentials/store/system/domain/_/
- httpRoute = "/credentials/store/:store/domain/:domain/*";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + "/credentials/*");
- // TODO more details
- } else if (rootPath.equals("descriptorByName")) {
- httpRoute = "/descriptorByName/:descriptor/*";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + "/descriptorByName/");
- if (pathInfoTokens.size() > 1) {
- spanBuilder.setAttribute("descriptor", pathInfoTokens.get(1));
- }
- } else if (rootPath.equals("extensionList")) {
- // eg /extensionList/hudson.diagnosis.MemoryUsageMonitor/0/heap/graph
- httpRoute = "/extensionList/:extension/*";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + "/extensionList/");
- if (pathInfoTokens.size() > 1) {
- spanBuilder.setAttribute("extension", pathInfoTokens.get(1));
- }
- } else if (rootPath.equals("fingerprint")) {
- httpRoute = "/fingerprint/:fingerprint";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + "/fingerprint/");
- spanBuilder.setAttribute("fingerprint", pathInfo.substring("/fingerprint/".length()));
- if (pathInfoTokens.size() > 1) {
- spanBuilder.setAttribute("fingerprint", pathInfoTokens.get(1));
+ httpServerSpanAttributesBuilder.put(HttpAttributes.HTTP_ROUTE, httpRoute);
+ httpServerMetricOnEndAttributesBuilder.put(HttpAttributes.HTTP_ROUTE, httpRoute);
+ capturedRequestParameters.forEach(
+ parameterName ->
+ Optional.ofNullable(servletRequest.getParameter(parameterName))
+ .ifPresent(value -> httpServerSpanAttributesBuilder.put("http.request.parameter." + parameterName, value)));
+
+ Span span = skipSpan ? Span.getInvalid() : tracer.spanBuilder(servletRequest.getMethod() + " " + httpRoute)
+ .setAllAttributes(httpServerSpanAttributesBuilder.build())
+ .setSpanKind(SpanKind.SERVER).startSpan();
+ try (Scope scope = span.makeCurrent()) {
+ filterChain.doFilter(servletRequest, servletResponse);
+ } catch (IOException | ServletException | RuntimeException e) {
+ if (servletResponse.getStatus() < 500) {
+ servletResponse.setStatus(500);
}
- } else if (rootPath.equals("user")) {
- //eg /user/cyrille.leclerc/ /user/cyrille.leclerc/configure /user/cyrille.leclerc/my-views/view/all/
- httpRoute = "/user/:user/*";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + "/user/*");
- if (pathInfoTokens.size() > 1) {
- spanBuilder.setAttribute("user", pathInfoTokens.get(1));
- }
- } else {
- httpRoute = "/" + rootPath + "/*";
- spanBuilder = tracer.spanBuilder(servletRequest.getMethod() + " " + pathInfo);
+ span.recordException(e);
+ httpServerMetricOnEndAttributesBuilder.put(ErrorAttributes.ERROR_TYPE, e.getClass().getName());
+ throw e;
+ } finally {
+ span.setAttribute(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, servletResponse.getStatus());
+ httpServerMetricOnEndAttributesBuilder.put(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, servletResponse.getStatus());
+ span.end();
}
- } catch (RuntimeException e) {
- logger.log(Level.INFO, () -> "Exception processing URL " + pathInfo + ", skip instrumentation with tracing: " + e);
- filterChain.doFilter(servletRequest, servletResponse);
- return;
- }
-
-
- String httpTarget = servletRequest.getRequestURI();
- String queryString = servletRequest.getQueryString();
- if (queryString != null && !queryString.isEmpty()) {
- httpTarget += "?" + queryString;
- }
-
- Thread currentThread = Thread.currentThread();
- spanBuilder
- .setAttribute(ClientAttributes.CLIENT_ADDRESS, servletRequest.getRemoteAddr())
- .setAttribute(UrlAttributes.URL_SCHEME, servletRequest.getScheme())
- .setAttribute(ServerAttributes.SERVER_ADDRESS, servletRequest.getServerName())
- .setAttribute(ServerAttributes.SERVER_PORT, (long) servletRequest.getServerPort())
- .setAttribute(HttpAttributes.HTTP_REQUEST_METHOD, servletRequest.getMethod())
- .setAttribute(UrlAttributes.URL_PATH, httpTarget)
- .setAttribute(HttpAttributes.HTTP_ROUTE, httpRoute)
- .setAttribute(NetworkAttributes.NETWORK_TRANSPORT, NetworkAttributes.NetworkTransportValues.TCP)
- .setAttribute(ClientAttributes.CLIENT_ADDRESS, servletRequest.getRemoteAddr())
- .setAttribute(ClientAttributes.CLIENT_PORT, (long) servletRequest.getRemotePort())
- .setAttribute(ThreadIncubatingAttributes.THREAD_NAME, currentThread.getName())
- .setAttribute(ThreadIncubatingAttributes.THREAD_ID, currentThread.getId())
- .setSpanKind(SpanKind.SERVER);
-
- Optional.ofNullable(User.current()).ifPresent(user -> spanBuilder.setAttribute(EnduserIncubatingAttributes.ENDUSER_ID, user.getId()));
-
- capturedRequestParameters.forEach(
- parameterName ->
- Optional.ofNullable(servletRequest.getParameter(parameterName))
- .ifPresent(value -> spanBuilder.setAttribute("http.request.parameter." + parameterName, value)));
-
- Span span = spanBuilder.startSpan();
- try (Scope scope = span.makeCurrent()) {
- filterChain.doFilter(servletRequest, servletResponse);
- span.setAttribute(HttpAttributes.HTTP_RESPONSE_STATUS_CODE, servletResponse.getStatus());
} finally {
- span.end();
+ httpServerMetrics.onEnd(httpServerDurationMetricContext, httpServerMetricOnEndAttributesBuilder.build(), System.nanoTime());
}
-
}
/**
@@ -628,16 +638,6 @@ public String toString() {
}
}
- @Override
- public void destroy() {
-
- }
-
- @Override
- public void init(FilterConfig filterConfig) throws ServletException {
-
- }
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -652,5 +652,6 @@ public int hashCode() {
@Inject
public void setTracer(ReconfigurableOpenTelemetry openTelemetry) {
this.tracer = openTelemetry.getTracer("io.jenkins.stapler");
+ this.meter = openTelemetry.getMeter("io.jenkins.stapler");
}
}