Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,48 @@
import io.opentelemetry.context.propagation.ContextPropagators;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;

/**
* A global singleton for the entrypoint to telemetry functionality for tracing, metrics and
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This javadoc was outdated and needed a rewrite.

* baggage.
* Provides access to a global singleton {@link OpenTelemetry} instance.
*
* <p>If using the OpenTelemetry SDK, you may want to instantiate the {@link OpenTelemetry} to
* provide configuration, for example of {@code Resource} or {@code Sampler}. See {@code
* OpenTelemetrySdk} and {@code OpenTelemetrySdk.builder} for information on how to construct the
* SDK's {@link OpenTelemetry} implementation.
* <p>WARNING: To avoid inherent complications around initialization ordering, it's best-practice to
* pass around instances of {@link OpenTelemetry} rather than using {@link GlobalOpenTelemetry}.
* However, the OpenTelemetry javagent makes the {@link OpenTelemetry} instance it installs
* available via {@link GlobalOpenTelemetry}. As a result, {@link GlobalOpenTelemetry} plays an
* important role in native instrumentation and application custom instrumentation.
*
* <p>WARNING: Due to the inherent complications around initialization order involving this class
* and its single global instance, we strongly recommend *not* using GlobalOpenTelemetry unless you
* have a use-case that absolutely requires it. Please favor using instances of OpenTelemetry
* wherever possible.
* <p>Native instrumentation should use {@link #getOrNoop()} as the default {@link OpenTelemetry}
* instance, and expose APIs for setting a custom instance. This results in the following behavior:
*
* <p>If you are using the OpenTelemetry javaagent, it is generally best to only call
* GlobalOpenTelemetry.get() once, and then pass the resulting reference where you need to use it.
* <ul>
* <li>If the OpenTelemetry javaagent is installed, the native instrumentation will default to the
* {@link OpenTelemetry} it installs.
* <li>If the OpenTelemetry javaagent is not installed, the native instrumentation will default to
* a noop instance.
* <li>If the user explicitly sets a custom instance, it will be used, regardless of whether or
* not the OpenTelemetry javaagent is installed.
* </ul>
*
* @see TracerProvider
* @see ContextPropagators
* <p>Applications with custom instrumentation should call {@link #isSet()} once during
* initialization to access the javaagent instance or initialize (e.g. {@code isSet() ?
* GlobalOpenTelemetry.get() : initializeSdk()}), and pass the resulting instance around manually
* (or with dependency injection) to install custom instrumentation. This results in the following
* behavior:
*
* <ul>
* <li>If the OpenTelemetry javaagent is installed, custom instrumentation will use the {@link
* OpenTelemetry} it installs.
* <li>If the OpenTelemetry javaagent is not installed, custom instrumentation will use an {@link
* OpenTelemetry} instance initialized by the application.
* </ul>
*/
// We intentionally assign to be use for error reporting.
// We intentionally assign for error reporting.
@SuppressWarnings("StaticAssignmentOfThrowable")
public final class GlobalOpenTelemetry {

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

/**
* Returns the registered global {@link OpenTelemetry}.
* Returns the registered global {@link OpenTelemetry} if set, or else {@link
* OpenTelemetry#noop()}.
*
* <p>NOTE: if the global instance is set, the response is obfuscated to prevent callers from
* casting to SDK implementation instances and inappropriately accessing non-instrumentation APIs.
*
* <p>NOTE: This does not result in the {@link #set(OpenTelemetry)} side effects of {@link
* #get()}.
*
* <p>Native instrumentation should use this method to initialize their default {@link
* OpenTelemetry} instance. See class javadoc for more details.
*/
public static OpenTelemetry getOrNoop() {
synchronized (mutex) {
return globalOpenTelemetry != null ? globalOpenTelemetry : OpenTelemetry.noop();
}
}

/**
* Returns {@code true} if {@link GlobalOpenTelemetry} is set, otherwise {@code false}.
*
* <p>Application custom instrumentation should use this method during initialization. See class
* javadoc for more details.
*/
public static boolean isSet() {
synchronized (mutex) {
return globalOpenTelemetry != null;
}
}

/**
* Returns the registered global {@link OpenTelemetry} if set, else calls {@link
* GlobalOpenTelemetry#set(OpenTelemetry)} with a no-op {@link OpenTelemetry} instance and returns
* that.
*
* <p>NOTE: all returned instanced are obfuscated to prevent callers from casting to SDK
* implementation instances and inappropriately accessing non-instrumentation APIs.
*
* <p>Native instrumentations should use {@link #getOrNoop()} instead. See class javadoc for more
* details.
*
* <p>Application custom instrumentation should use {@link #isSet()} and only call this if the
* response is {@code true}. See class javadoc for more details.
*
* <p>If the global instance has not been set, and {@code
* io.opentelemetry:opentelemetry-sdk-extension-autoconfigure} is present, and {@value
* GLOBAL_AUTOCONFIGURE_ENABLED_PROPERTY} is {@code true}, the global instance will be set to an
* autoconfigured instance instead of {@link OpenTelemetry#noop()}.
*
* @throws IllegalStateException if a provider has been specified by system property using the
* interface FQCN but the specified provider cannot be found.
* @throws IllegalStateException if autoconfigure initialization is triggered and fails.
*/
public static OpenTelemetry get() {
OpenTelemetry openTelemetry = globalOpenTelemetry;
Expand All @@ -85,7 +146,7 @@ public static OpenTelemetry get() {
}

set(OpenTelemetry.noop());
return OpenTelemetry.noop();
openTelemetry = Objects.requireNonNull(globalOpenTelemetry);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ private void setOpenTelemetry() {
GlobalOpenTelemetry.set(getOpenTelemetry());
}

private static OpenTelemetry getGlobalOpenTelemetry() {
return GlobalOpenTelemetry.get();
}

@AfterEach
public void after() {
GlobalOpenTelemetry.resetForTest();
Expand Down Expand Up @@ -90,7 +86,9 @@ void independentNonGlobalPropagators() {

@Test
void setThenSet() {
assertThat(GlobalOpenTelemetry.isSet()).isFalse();
setOpenTelemetry();
assertThat(GlobalOpenTelemetry.isSet()).isTrue();
assertThatThrownBy(() -> GlobalOpenTelemetry.set(getOpenTelemetry()))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("GlobalOpenTelemetry.set has already been called")
Expand All @@ -99,14 +97,43 @@ void setThenSet() {

@Test
void getThenSet() {
assertThat(getGlobalOpenTelemetry().getClass().getName())
.isEqualTo("io.opentelemetry.api.DefaultOpenTelemetry");
assertThat(GlobalOpenTelemetry.isSet()).isFalse();
assertThat(GlobalOpenTelemetry.getOrNoop()).isSameAs(OpenTelemetry.noop());

// Calling GlobalOpenTelemetry.get() has the side affect of setting GlobalOpenTelemetry to an
// (obfuscated) noop.
// Call GlobalOpenTelemetry.get() using a utility method so we can later assert it was
// responsible for setting GlobalOpenTelemetry.
assertThat(getGlobalOpenTelemetry())
.satisfies(
instance ->
assertThat(instance.getClass().getName())
.isEqualTo("io.opentelemetry.api.GlobalOpenTelemetry$ObfuscatedOpenTelemetry"))
.extracting("delegate")
.isSameAs(OpenTelemetry.noop());

assertThat(GlobalOpenTelemetry.isSet()).isTrue();
assertThat(GlobalOpenTelemetry.getOrNoop()).isNotSameAs(OpenTelemetry.noop());

assertThatThrownBy(() -> GlobalOpenTelemetry.set(getOpenTelemetry()))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("GlobalOpenTelemetry.set has already been called")
.hasStackTraceContaining("getGlobalOpenTelemetry");
}

private static OpenTelemetry getGlobalOpenTelemetry() {
return GlobalOpenTelemetry.get();
}

@Test
void getOrNoop() {
// Should be able to call getOrNoop multiple times without side effect and later call set.
assertThat(GlobalOpenTelemetry.getOrNoop()).isSameAs(OpenTelemetry.noop());
assertThat(GlobalOpenTelemetry.getOrNoop()).isSameAs(OpenTelemetry.noop());
setOpenTelemetry();
assertThat(GlobalOpenTelemetry.getOrNoop()).isNotSameAs(OpenTelemetry.noop());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometimes obfuscated, sometime unobfuscated is a code smell https://github.com/open-telemetry/opentelemetry-java/pull/7819/files#r2529131045

}

@Test
void toString_noop_Valid() {
assertThat(getOpenTelemetry().toString())
Expand Down
5 changes: 4 additions & 1 deletion docs/apidiffs/current_vs_latest/opentelemetry-api.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
Comparing source compatibility of opentelemetry-api-1.57.0-SNAPSHOT.jar against opentelemetry-api-1.56.0.jar
No changes.
*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.api.GlobalOpenTelemetry (not serializable)
=== CLASS FILE FORMAT VERSION: 52.0 <- 52.0
+++ NEW METHOD: PUBLIC(+) STATIC(+) io.opentelemetry.api.OpenTelemetry getOrNoop()
+++ NEW METHOD: PUBLIC(+) STATIC(+) boolean isSet()
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ void initializeAndGet_noGlobal() {
@Test
void globalOpenTelemetry_AutoConfigureDisabled() {
// Autoconfigure is disabled by default and enabled via otel.java.global-autoconfigure.enabled
assertThat(GlobalOpenTelemetry.get()).isSameAs(OpenTelemetry.noop());
assertThat(GlobalOpenTelemetry.get()).extracting("delegate").isSameAs(OpenTelemetry.noop());

logs.assertContains(
"AutoConfiguredOpenTelemetrySdk found on classpath but automatic configuration is disabled."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ void afterAll() {
assertThat(extension.getSpans()).isNotEmpty();

extension.afterAll(null);
assertThat(GlobalOpenTelemetry.get()).isSameAs(OpenTelemetry.noop());
assertThat(GlobalOpenTelemetry.get()).extracting("delegate").isSameAs(OpenTelemetry.noop());

meter.counterBuilder("counter").build().add(10);
tracer.spanBuilder("span").startSpan().end();
Expand Down
Loading