From f883cde67bde69844964ce2013ef9eabca75cd32 Mon Sep 17 00:00:00 2001 From: Philippe Marschall Date: Sat, 4 Apr 2026 15:28:17 +0200 Subject: [PATCH] Add support for JFR contextual information Fixes #2057 --- jfr-events/README.md | 4 +-- .../contrib/jfrevent/Contextual.java | 26 +++++++++++++++++++ .../contrib/jfrevent/ScopeEvent.java | 2 +- .../contrib/jfrevent/SpanEvent.java | 2 +- .../jfrevent/JfrSpanProcessorTest.java | 21 +++++++++++++++ 5 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/Contextual.java diff --git a/jfr-events/README.md b/jfr-events/README.md index 9a610dd7af..65e9ef4fc1 100644 --- a/jfr-events/README.md +++ b/jfr-events/README.md @@ -5,7 +5,7 @@ Create JFR events that can be recorded and viewed in Java Mission Control (JMC). * Creates Open Telemetry Tracing/Span events for spans * The thread and stacktrace will be of the thead ending the span which might be different from the thread creating the span. * Has the fields - * Operation Name + * Operation Name (`@Contextual`) * Trace ID * Parent Span ID * Span ID @@ -13,7 +13,7 @@ Create JFR events that can be recorded and viewed in Java Mission Control (JMC). * Thread will match the thread the scope was active in and the stacktrace will be when scope was closed * Multiple scopes might be collected for a single span * Has the fields - * Trace ID + * Trace ID (`@Contextual`) * Span ID * Supports the Open Source version of JFR in Java 11. * Might support back port to OpenJDK 8, but not tested and classes are built with JDK 11 bytecode. diff --git a/jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/Contextual.java b/jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/Contextual.java new file mode 100644 index 0000000000..14d98d4b40 --- /dev/null +++ b/jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/Contextual.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.jfrevent; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import jdk.jfr.Label; +import jdk.jfr.MetadataDefinition; +import jdk.jfr.Name; + +/** + * Meta-annotation to support @Contextual without compiling against JDK 25. + * + * @see JDK-8356699 + */ +@MetadataDefinition +@Label("Context") +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +@Name("jdk.jfr.Contextual") +@interface Contextual {} diff --git a/jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/ScopeEvent.java b/jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/ScopeEvent.java index fb943320d8..288918b56d 100644 --- a/jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/ScopeEvent.java +++ b/jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/ScopeEvent.java @@ -20,7 +20,7 @@ + "in scope/active on this thread.") class ScopeEvent extends Event { - private final String traceId; + @Contextual private final String traceId; private final String spanId; ScopeEvent(SpanContext spanContext) { diff --git a/jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/SpanEvent.java b/jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/SpanEvent.java index f2d6cb8cdb..ea20487ba4 100644 --- a/jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/SpanEvent.java +++ b/jfr-events/src/main/java/io/opentelemetry/contrib/jfrevent/SpanEvent.java @@ -18,7 +18,7 @@ @Description("Open Telemetry trace event corresponding to a span.") class SpanEvent extends Event { - private final String operationName; + @Contextual private final String operationName; private final String traceId; private final String spanId; private final String parentId; diff --git a/jfr-events/src/test/java/io/opentelemetry/contrib/jfrevent/JfrSpanProcessorTest.java b/jfr-events/src/test/java/io/opentelemetry/contrib/jfrevent/JfrSpanProcessorTest.java index 7225e72fa7..8a42263fe7 100644 --- a/jfr-events/src/test/java/io/opentelemetry/contrib/jfrevent/JfrSpanProcessorTest.java +++ b/jfr-events/src/test/java/io/opentelemetry/contrib/jfrevent/JfrSpanProcessorTest.java @@ -16,6 +16,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; import jdk.jfr.Recording; import jdk.jfr.consumer.RecordedEvent; import jdk.jfr.consumer.RecordingFile; @@ -77,6 +79,15 @@ void basicSpan() throws IOException { .extracting(e -> e.getValue("spanId")) .isEqualTo(span.getSpanContext().getSpanId()); assertThat(events).extracting(e -> e.getValue("operationName")).isEqualTo(OPERATION_NAME); + assertThat(events) + .extracting( + e -> + e.getFields().stream() + .filter(f -> f.getName().equals("operationName")) + .map(d -> d.getAnnotation(Contextual.class)) + .filter(Objects::nonNull) + .collect(Collectors.toList())) + .isNotEmpty(); } finally { Files.delete(output); } @@ -119,6 +130,16 @@ void basicSpanWithScope() throws IOException, InterruptedException { .filteredOn(e -> "Span".equals(e.getEventType().getLabel())) .extracting(e -> e.getValue("operationName")) .isEqualTo(OPERATION_NAME); + assertThat(events) + .filteredOn(e -> "Scope".equals(e.getEventType().getLabel())) + .extracting( + e -> + e.getFields().stream() + .filter(f -> f.getName().equals("traceId")) + .map(d -> d.getAnnotation(Contextual.class)) + .filter(Objects::nonNull) + .collect(Collectors.toList())) + .isNotEmpty(); } finally { Files.delete(output);