Skip to content

Commit a4177f9

Browse files
authored
Add GlobalOpenTelemetry#getOrNoop, #isSet, Update #get to always returns obfuscated instance (#7819)
1 parent bb3ce80 commit a4177f9

File tree

5 files changed

+119
-28
lines changed

5 files changed

+119
-28
lines changed

api/all/src/main/java/io/opentelemetry/api/GlobalOpenTelemetry.java

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,48 @@
1818
import io.opentelemetry.context.propagation.ContextPropagators;
1919
import java.lang.reflect.InvocationTargetException;
2020
import java.lang.reflect.Method;
21+
import java.util.Objects;
2122
import java.util.function.Supplier;
2223
import java.util.logging.Level;
2324
import java.util.logging.Logger;
2425
import javax.annotation.Nullable;
2526
import javax.annotation.concurrent.ThreadSafe;
2627

2728
/**
28-
* A global singleton for the entrypoint to telemetry functionality for tracing, metrics and
29-
* baggage.
29+
* Provides access to a global singleton {@link OpenTelemetry} instance.
3030
*
31-
* <p>If using the OpenTelemetry SDK, you may want to instantiate the {@link OpenTelemetry} to
32-
* provide configuration, for example of {@code Resource} or {@code Sampler}. See {@code
33-
* OpenTelemetrySdk} and {@code OpenTelemetrySdk.builder} for information on how to construct the
34-
* SDK's {@link OpenTelemetry} implementation.
31+
* <p>WARNING: To avoid inherent complications around initialization ordering, it's best-practice to
32+
* pass around instances of {@link OpenTelemetry} rather than using {@link GlobalOpenTelemetry}.
33+
* However, the OpenTelemetry javagent makes the {@link OpenTelemetry} instance it installs
34+
* available via {@link GlobalOpenTelemetry}. As a result, {@link GlobalOpenTelemetry} plays an
35+
* important role in native instrumentation and application custom instrumentation.
3536
*
36-
* <p>WARNING: Due to the inherent complications around initialization order involving this class
37-
* and its single global instance, we strongly recommend *not* using GlobalOpenTelemetry unless you
38-
* have a use-case that absolutely requires it. Please favor using instances of OpenTelemetry
39-
* wherever possible.
37+
* <p>Native instrumentation should use {@link #getOrNoop()} as the default {@link OpenTelemetry}
38+
* instance, and expose APIs for setting a custom instance. This results in the following behavior:
4039
*
41-
* <p>If you are using the OpenTelemetry javaagent, it is generally best to only call
42-
* GlobalOpenTelemetry.get() once, and then pass the resulting reference where you need to use it.
40+
* <ul>
41+
* <li>If the OpenTelemetry javaagent is installed, the native instrumentation will default to the
42+
* {@link OpenTelemetry} it installs.
43+
* <li>If the OpenTelemetry javaagent is not installed, the native instrumentation will default to
44+
* a noop instance.
45+
* <li>If the user explicitly sets a custom instance, it will be used, regardless of whether or
46+
* not the OpenTelemetry javaagent is installed.
47+
* </ul>
4348
*
44-
* @see TracerProvider
45-
* @see ContextPropagators
49+
* <p>Applications with custom instrumentation should call {@link #isSet()} once during
50+
* initialization to access the javaagent instance or initialize (e.g. {@code isSet() ?
51+
* GlobalOpenTelemetry.get() : initializeSdk()}), and pass the resulting instance around manually
52+
* (or with dependency injection) to install custom instrumentation. This results in the following
53+
* behavior:
54+
*
55+
* <ul>
56+
* <li>If the OpenTelemetry javaagent is installed, custom instrumentation will use the {@link
57+
* OpenTelemetry} it installs.
58+
* <li>If the OpenTelemetry javaagent is not installed, custom instrumentation will use an {@link
59+
* OpenTelemetry} instance initialized by the application.
60+
* </ul>
4661
*/
47-
// We intentionally assign to be use for error reporting.
62+
// We intentionally assign for error reporting.
4863
@SuppressWarnings("StaticAssignmentOfThrowable")
4964
public final class GlobalOpenTelemetry {
5065

@@ -67,10 +82,56 @@ public final class GlobalOpenTelemetry {
6782
private GlobalOpenTelemetry() {}
6883

6984
/**
70-
* Returns the registered global {@link OpenTelemetry}.
85+
* Returns the registered global {@link OpenTelemetry} if set, or else {@link
86+
* OpenTelemetry#noop()}.
87+
*
88+
* <p>NOTE: if the global instance is set, the response is obfuscated to prevent callers from
89+
* casting to SDK implementation instances and inappropriately accessing non-instrumentation APIs.
90+
*
91+
* <p>NOTE: This does not result in the {@link #set(OpenTelemetry)} side effects of {@link
92+
* #get()}.
93+
*
94+
* <p>Native instrumentation should use this method to initialize their default {@link
95+
* OpenTelemetry} instance. See class javadoc for more details.
96+
*/
97+
public static OpenTelemetry getOrNoop() {
98+
synchronized (mutex) {
99+
return globalOpenTelemetry != null ? globalOpenTelemetry : OpenTelemetry.noop();
100+
}
101+
}
102+
103+
/**
104+
* Returns {@code true} if {@link GlobalOpenTelemetry} is set, otherwise {@code false}.
105+
*
106+
* <p>Application custom instrumentation should use this method during initialization. See class
107+
* javadoc for more details.
108+
*/
109+
public static boolean isSet() {
110+
synchronized (mutex) {
111+
return globalOpenTelemetry != null;
112+
}
113+
}
114+
115+
/**
116+
* Returns the registered global {@link OpenTelemetry} if set, else calls {@link
117+
* GlobalOpenTelemetry#set(OpenTelemetry)} with a no-op {@link OpenTelemetry} instance and returns
118+
* that.
119+
*
120+
* <p>NOTE: all returned instanced are obfuscated to prevent callers from casting to SDK
121+
* implementation instances and inappropriately accessing non-instrumentation APIs.
122+
*
123+
* <p>Native instrumentations should use {@link #getOrNoop()} instead. See class javadoc for more
124+
* details.
125+
*
126+
* <p>Application custom instrumentation should use {@link #isSet()} and only call this if the
127+
* response is {@code true}. See class javadoc for more details.
128+
*
129+
* <p>If the global instance has not been set, and {@code
130+
* io.opentelemetry:opentelemetry-sdk-extension-autoconfigure} is present, and {@value
131+
* GLOBAL_AUTOCONFIGURE_ENABLED_PROPERTY} is {@code true}, the global instance will be set to an
132+
* autoconfigured instance instead of {@link OpenTelemetry#noop()}.
71133
*
72-
* @throws IllegalStateException if a provider has been specified by system property using the
73-
* interface FQCN but the specified provider cannot be found.
134+
* @throws IllegalStateException if autoconfigure initialization is triggered and fails.
74135
*/
75136
public static OpenTelemetry get() {
76137
OpenTelemetry openTelemetry = globalOpenTelemetry;
@@ -85,7 +146,7 @@ public static OpenTelemetry get() {
85146
}
86147

87148
set(OpenTelemetry.noop());
88-
return OpenTelemetry.noop();
149+
openTelemetry = Objects.requireNonNull(globalOpenTelemetry);
89150
}
90151
}
91152
}

api/testing-internal/src/main/java/io/opentelemetry/api/testing/internal/AbstractOpenTelemetryTest.java

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,6 @@ private void setOpenTelemetry() {
3131
GlobalOpenTelemetry.set(getOpenTelemetry());
3232
}
3333

34-
private static OpenTelemetry getGlobalOpenTelemetry() {
35-
return GlobalOpenTelemetry.get();
36-
}
37-
3834
@AfterEach
3935
public void after() {
4036
GlobalOpenTelemetry.resetForTest();
@@ -90,7 +86,9 @@ void independentNonGlobalPropagators() {
9086

9187
@Test
9288
void setThenSet() {
89+
assertThat(GlobalOpenTelemetry.isSet()).isFalse();
9390
setOpenTelemetry();
91+
assertThat(GlobalOpenTelemetry.isSet()).isTrue();
9492
assertThatThrownBy(() -> GlobalOpenTelemetry.set(getOpenTelemetry()))
9593
.isInstanceOf(IllegalStateException.class)
9694
.hasMessageContaining("GlobalOpenTelemetry.set has already been called")
@@ -99,14 +97,43 @@ void setThenSet() {
9997

10098
@Test
10199
void getThenSet() {
102-
assertThat(getGlobalOpenTelemetry().getClass().getName())
103-
.isEqualTo("io.opentelemetry.api.DefaultOpenTelemetry");
100+
assertThat(GlobalOpenTelemetry.isSet()).isFalse();
101+
assertThat(GlobalOpenTelemetry.getOrNoop()).isSameAs(OpenTelemetry.noop());
102+
103+
// Calling GlobalOpenTelemetry.get() has the side affect of setting GlobalOpenTelemetry to an
104+
// (obfuscated) noop.
105+
// Call GlobalOpenTelemetry.get() using a utility method so we can later assert it was
106+
// responsible for setting GlobalOpenTelemetry.
107+
assertThat(getGlobalOpenTelemetry())
108+
.satisfies(
109+
instance ->
110+
assertThat(instance.getClass().getName())
111+
.isEqualTo("io.opentelemetry.api.GlobalOpenTelemetry$ObfuscatedOpenTelemetry"))
112+
.extracting("delegate")
113+
.isSameAs(OpenTelemetry.noop());
114+
115+
assertThat(GlobalOpenTelemetry.isSet()).isTrue();
116+
assertThat(GlobalOpenTelemetry.getOrNoop()).isNotSameAs(OpenTelemetry.noop());
117+
104118
assertThatThrownBy(() -> GlobalOpenTelemetry.set(getOpenTelemetry()))
105119
.isInstanceOf(IllegalStateException.class)
106120
.hasMessageContaining("GlobalOpenTelemetry.set has already been called")
107121
.hasStackTraceContaining("getGlobalOpenTelemetry");
108122
}
109123

124+
private static OpenTelemetry getGlobalOpenTelemetry() {
125+
return GlobalOpenTelemetry.get();
126+
}
127+
128+
@Test
129+
void getOrNoop() {
130+
// Should be able to call getOrNoop multiple times without side effect and later call set.
131+
assertThat(GlobalOpenTelemetry.getOrNoop()).isSameAs(OpenTelemetry.noop());
132+
assertThat(GlobalOpenTelemetry.getOrNoop()).isSameAs(OpenTelemetry.noop());
133+
setOpenTelemetry();
134+
assertThat(GlobalOpenTelemetry.getOrNoop()).isNotSameAs(OpenTelemetry.noop());
135+
}
136+
110137
@Test
111138
void toString_noop_Valid() {
112139
assertThat(getOpenTelemetry().toString())
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
Comparing source compatibility of opentelemetry-api-1.57.0-SNAPSHOT.jar against opentelemetry-api-1.56.0.jar
2-
No changes.
2+
*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.api.GlobalOpenTelemetry (not serializable)
3+
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
4+
+++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.OpenTelemetry getOrNoop()
5+
+++ NEW METHOD: PUBLIC(+) STATIC(+) boolean isSet()

sdk-extensions/autoconfigure/src/testFullConfig/java/io/opentelemetry/sdk/autoconfigure/AutoConfiguredOpenTelemetrySdkTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ void initializeAndGet_noGlobal() {
7373
@Test
7474
void globalOpenTelemetry_AutoConfigureDisabled() {
7575
// Autoconfigure is disabled by default and enabled via otel.java.global-autoconfigure.enabled
76-
assertThat(GlobalOpenTelemetry.get()).isSameAs(OpenTelemetry.noop());
76+
assertThat(GlobalOpenTelemetry.get()).extracting("delegate").isSameAs(OpenTelemetry.noop());
7777

7878
logs.assertContains(
7979
"AutoConfiguredOpenTelemetrySdk found on classpath but automatic configuration is disabled."

sdk/testing/src/test/java/io/opentelemetry/sdk/testing/junit5/OpenTelemetryExtensionTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ void afterAll() {
230230
assertThat(extension.getSpans()).isNotEmpty();
231231

232232
extension.afterAll(null);
233-
assertThat(GlobalOpenTelemetry.get()).isSameAs(OpenTelemetry.noop());
233+
assertThat(GlobalOpenTelemetry.get()).extracting("delegate").isSameAs(OpenTelemetry.noop());
234234

235235
meter.counterBuilder("counter").build().add(10);
236236
tracer.spanBuilder("span").startSpan().end();

0 commit comments

Comments
 (0)