Skip to content

Commit cacf0e0

Browse files
committed
getOrNoop, getOrSet
1 parent 2d675f1 commit cacf0e0

File tree

3 files changed

+141
-34
lines changed

3 files changed

+141
-34
lines changed

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

Lines changed: 88 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,36 @@
2626
import javax.annotation.concurrent.ThreadSafe;
2727

2828
/**
29-
* A global singleton for the entrypoint to telemetry functionality for tracing, metrics and
30-
* baggage.
29+
* Provides access to a global singleton {@link OpenTelemetry} instance.
3130
*
32-
* <p>If using the OpenTelemetry SDK, you may want to instantiate the {@link OpenTelemetry} to
33-
* provide configuration, for example of {@code Resource} or {@code Sampler}. See {@code
34-
* OpenTelemetrySdk} and {@code OpenTelemetrySdk.builder} for information on how to construct the
35-
* 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.
3636
*
37-
* <p>WARNING: Due to the inherent complications around initialization order involving this class
38-
* and its single global instance, we strongly recommend *not* using GlobalOpenTelemetry unless you
39-
* have a use-case that absolutely requires it. Please favor using instances of OpenTelemetry
40-
* 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:
4139
*
42-
* <p>If you are using the OpenTelemetry javaagent, it is generally best to only call
43-
* 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>
48+
*
49+
* <p>Applications with custom instrumentation should call {@link #getOrSet(Supplier)} once during
50+
* initialization, and pass the resulting instance around manually (or with dependency injection) to
51+
* install custom instrumentation. This results in the following behavior:
52+
*
53+
* <ul>
54+
* <li>If the OpenTelemetry javaagent is installed, custom instrumentation will use the {@link
55+
* OpenTelemetry} it installs.
56+
* <li>If the OpenTelemetry javaagent is not installed, custom instrumentation will use the {@link
57+
* OpenTelemetry} instance returned by {@link Supplier} passed to {@link #getOrSet(Supplier)}.
58+
* </ul>
4459
*
4560
* @see TracerProvider
4661
* @see ContextPropagators
@@ -68,10 +83,68 @@ public final class GlobalOpenTelemetry {
6883
private GlobalOpenTelemetry() {}
6984

7085
/**
71-
* Returns the registered global {@link OpenTelemetry}.
86+
* Returns the registered global {@link OpenTelemetry} if set, or else {@link
87+
* OpenTelemetry#noop()}.
88+
*
89+
* <p>NOTE: if the global instance is set, the response is obfuscated to prevent callers from
90+
* casting to SDK implementation instances and inappropriately accessing non-instrumentation APIs.
7291
*
73-
* @throws IllegalStateException if a provider has been specified by system property using the
74-
* interface FQCN but the specified provider cannot be found.
92+
* <p>NOTE: This does not result in the {@link #set(OpenTelemetry)} side effects of {@link
93+
* #get()}.
94+
*
95+
* <p>Native instrumentation should use this method to initialize their default {@link
96+
* OpenTelemetry} instance. See class javadoc for more details.
97+
*/
98+
public static OpenTelemetry getOrNoop() {
99+
synchronized (mutex) {
100+
return globalOpenTelemetry != null ? globalOpenTelemetry : OpenTelemetry.noop();
101+
}
102+
}
103+
104+
/**
105+
* Returns the registered global {@link OpenTelemetry} if set, or else calls {@link
106+
* #set(Supplier)} with the {@code supplier}.
107+
*
108+
* <p>NOTE: if the global instance is set, the response is obfuscated to prevent callers from
109+
* casting to SDK implementation instances and inappropriately accessing non-instrumentation APIs.
110+
*
111+
* <p>NOTE: This does not result in the {@link #set(OpenTelemetry)} side effects of {@link
112+
* #get()}.
113+
*
114+
* <p>Application custom instrumentation should use this method during initialization. See class
115+
* javadoc for more details.
116+
*/
117+
public static OpenTelemetry getOrSet(Supplier<OpenTelemetry> supplier) {
118+
synchronized (mutex) {
119+
if (globalOpenTelemetry == null) {
120+
OpenTelemetry openTelemetry = supplier.get();
121+
set(openTelemetry);
122+
return openTelemetry;
123+
}
124+
return globalOpenTelemetry;
125+
}
126+
}
127+
128+
/**
129+
* Returns the registered global {@link OpenTelemetry} if set, else calls {@link
130+
* GlobalOpenTelemetry#set(OpenTelemetry)} with a no-op {@link OpenTelemetry} instance and returns
131+
* that.
132+
*
133+
* <p>NOTE: all returned instanced are obfuscated to prevent callers from casting to SDK
134+
* implementation instances and inappropriately accessing non-instrumentation APIs.
135+
*
136+
* <p>Native instrumentations should use {@link #getOrNoop()} instad. See class javadoc for more
137+
* details.
138+
*
139+
* <p>Application custom instrumentation should use {@link #getOrSet(Supplier)} instead. See class
140+
* javadoc for more details.
141+
*
142+
* <p>If the global instance has not been set, and {@code
143+
* io.opentelemetry:opentelemetry-sdk-extension-autoconfigure} is present, and {@value
144+
* GLOBAL_AUTOCONFIGURE_ENABLED_PROPERTY} is {@code true}, the global instance will be set to an
145+
* autoconfigured instance instead of {@link OpenTelemetry#noop()}.
146+
*
147+
* @throws IllegalStateException if autoconfigure initialization is triggered and fails.
75148
*/
76149
public static OpenTelemetry get() {
77150
OpenTelemetry openTelemetry = globalOpenTelemetry;
@@ -134,20 +207,6 @@ public static void set(Supplier<OpenTelemetry> supplier) {
134207
}
135208
}
136209

137-
/**
138-
* Evaluate if the global instance has been set without the side effects of {@link #get()}.
139-
*
140-
* <p>This is useful for evaluating if any code, like the OpenTelemetry javaagent, has previously
141-
* set the global instance while preserving the ability to set it.
142-
*
143-
* @return true if the global instance has been set, false otherwise.
144-
*/
145-
public static boolean isSet() {
146-
synchronized (mutex) {
147-
return globalOpenTelemetry != null;
148-
}
149-
}
150-
151210
/** Returns the globally registered {@link TracerProvider}. */
152211
public static TracerProvider getTracerProvider() {
153212
return get().getTracerProvider();

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

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import io.opentelemetry.api.metrics.MeterProvider;
1515
import io.opentelemetry.api.trace.TracerProvider;
1616
import io.opentelemetry.context.propagation.ContextPropagators;
17+
import java.util.concurrent.atomic.AtomicInteger;
18+
import java.util.function.Supplier;
1719
import org.junit.jupiter.api.AfterEach;
1820
import org.junit.jupiter.api.BeforeAll;
1921
import org.junit.jupiter.api.Test;
@@ -86,9 +88,9 @@ void independentNonGlobalPropagators() {
8688

8789
@Test
8890
void setThenSet() {
89-
assertThat(GlobalOpenTelemetry.isSet()).isFalse();
91+
assertThat(GlobalOpenTelemetry.getOrNoop()).isSameAs(OpenTelemetry.noop());
9092
setOpenTelemetry();
91-
assertThat(GlobalOpenTelemetry.isSet()).isTrue();
93+
assertThat(GlobalOpenTelemetry.getOrNoop()).isNotSameAs(OpenTelemetry.noop());
9294
assertThatThrownBy(() -> GlobalOpenTelemetry.set(getOpenTelemetry()))
9395
.isInstanceOf(IllegalStateException.class)
9496
.hasMessageContaining("GlobalOpenTelemetry.set has already been called")
@@ -97,7 +99,7 @@ void setThenSet() {
9799

98100
@Test
99101
void getThenSet() {
100-
assertThat(GlobalOpenTelemetry.isSet()).isFalse();
102+
assertThat(GlobalOpenTelemetry.getOrNoop()).isSameAs(OpenTelemetry.noop());
101103
// Calling GlobalOpenTelemetry.get() has the side affect of setting GlobalOpenTelemetry to an
102104
// (obfuscated) noop.
103105
// Call GlobalOpenTelemetry.get() using a utility method so we can later assert it was
@@ -109,7 +111,7 @@ void getThenSet() {
109111
.isEqualTo("io.opentelemetry.api.GlobalOpenTelemetry$ObfuscatedOpenTelemetry"))
110112
.extracting("delegate")
111113
.isSameAs(OpenTelemetry.noop());
112-
assertThat(GlobalOpenTelemetry.isSet()).isTrue();
114+
assertThat(GlobalOpenTelemetry.getOrNoop()).isNotSameAs(OpenTelemetry.noop());
113115
assertThatThrownBy(() -> GlobalOpenTelemetry.set(getOpenTelemetry()))
114116
.isInstanceOf(IllegalStateException.class)
115117
.hasMessageContaining("GlobalOpenTelemetry.set has already been called")
@@ -120,6 +122,51 @@ private static OpenTelemetry getGlobalOpenTelemetry() {
120122
return GlobalOpenTelemetry.get();
121123
}
122124

125+
@Test
126+
void getOrNoop() {
127+
// Should be able to call getOrNoop multiple times without side effect and later call set.
128+
assertThat(GlobalOpenTelemetry.getOrNoop()).isSameAs(OpenTelemetry.noop());
129+
assertThat(GlobalOpenTelemetry.getOrNoop()).isSameAs(OpenTelemetry.noop());
130+
setOpenTelemetry();
131+
assertThat(GlobalOpenTelemetry.getOrNoop()).isNotSameAs(OpenTelemetry.noop());
132+
}
133+
134+
@Test
135+
void getOrSet_NotPreviouslySet() {
136+
AtomicInteger supplierCallCount = new AtomicInteger();
137+
Supplier<OpenTelemetry> supplier =
138+
() -> {
139+
supplierCallCount.incrementAndGet();
140+
return OpenTelemetry.noop();
141+
};
142+
143+
assertThat(GlobalOpenTelemetry.getOrSet(supplier)).isSameAs(OpenTelemetry.noop());
144+
assertThat(supplierCallCount.get()).isEqualTo(1);
145+
146+
// The second time getOrSet is called, we get an obfuscated instance
147+
assertThat(GlobalOpenTelemetry.getOrSet(supplier).getClass().getName())
148+
.isEqualTo("io.opentelemetry.api.GlobalOpenTelemetry$ObfuscatedOpenTelemetry");
149+
assertThat(supplierCallCount.get()).isEqualTo(1);
150+
}
151+
152+
@Test
153+
void getOrSet_PreviouslySet() {
154+
setOpenTelemetry();
155+
156+
AtomicInteger supplierCallCount = new AtomicInteger();
157+
Supplier<OpenTelemetry> supplier =
158+
() -> {
159+
supplierCallCount.incrementAndGet();
160+
return OpenTelemetry.noop();
161+
};
162+
163+
assertThat(GlobalOpenTelemetry.getOrSet(supplier).getClass().getName())
164+
.isEqualTo("io.opentelemetry.api.GlobalOpenTelemetry$ObfuscatedOpenTelemetry");
165+
assertThat(GlobalOpenTelemetry.getOrSet(supplier).getClass().getName())
166+
.isEqualTo("io.opentelemetry.api.GlobalOpenTelemetry$ObfuscatedOpenTelemetry");
167+
assertThat(supplierCallCount.get()).isEqualTo(0);
168+
}
169+
123170
@Test
124171
void toString_noop_Valid() {
125172
assertThat(getOpenTelemetry().toString())
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Comparing source compatibility of opentelemetry-api-1.57.0-SNAPSHOT.jar against opentelemetry-api-1.56.0.jar
22
*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.api.GlobalOpenTelemetry (not serializable)
33
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
4-
+++ NEW METHOD: PUBLIC(+) STATIC(+) boolean isSet()
4+
+++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.OpenTelemetry getOrNoop()
5+
+++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.OpenTelemetry getOrSet(java.util.function.Supplier<io.opentelemetry.api.OpenTelemetry>)

0 commit comments

Comments
 (0)