Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support using ReconfigurableOpenTelemetry instance through @Inject #14

Merged
merged 1 commit into from
Aug 27, 2024
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 @@ -9,20 +9,48 @@
import io.opentelemetry.sdk.resources.Resource;

import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Supplier;

/**
* Extension of {@link OpenTelemetry} that provides additional functionality:
* <ul>
* <li>Accessor to the {@link EventLoggerProvider} without requiring using a separate {@link io.opentelemetry.api.incubator.events.GlobalEventLoggerProvider}</li>
* <li>Read access top the {@link ConfigProperties} and {@link Resource}</li>
* <li>Ability to be reconfigured through {@link #configure(Map, Resource, boolean)}</li>
* <li>Ability to be used as a Jenkins {@link hudson.Extension}</li>
* </ul>
*/
public interface ExtendedOpenTelemetry extends ExtensionPoint, OpenTelemetry {
EventLoggerProvider getEventLoggerProvider();

EventLoggerBuilder eventLoggerBuilder(String instrumentationScopeName);

/**
* {@link ConfigProperties} used to instantiate this OpenTelemetry instance using the {@link io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk}.
*/
ConfigProperties getConfig();

/**
* {@link Resource} used by this OpenTelemetry instance for the resource attributes of the produced telemetry
*/
Resource getResource();

/**
*
* @deprecated use {@link #configure(Map, Resource, boolean)}
*/
@Deprecated
void configure(@NonNull Map<String, String> openTelemetryProperties, Resource openTelemetryResource);
default void configure(@NonNull Map<String, String> openTelemetryProperties, Resource openTelemetryResource, boolean disableShutdownHook){}

/**
* Reconfigures the {@link OpenTelemetry} instance. If no exporter is explicitly defined, this OpenTelemetry instance is NoOp.
*
* @param openTelemetryProperties properties used as {@link ConfigProperties} through {@link io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder#addPropertiesSupplier(Supplier)}
* @param openTelemetryResource resource attributes passed through {@link io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder#addResourceCustomizer(BiFunction)}
* @param disableShutdownHook enable / disable a shutdown hook
*/
default void configure(@NonNull Map<String, String> openTelemetryProperties, Resource openTelemetryResource, boolean disableShutdownHook) {
}

Check warning on line 53 in src/main/java/io/jenkins/plugins/opentelemetry/api/ExtendedOpenTelemetry.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 53 is not covered by tests

@Deprecated
OpenTelemetry getImplementation();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package io.jenkins.plugins.opentelemetry.api;

import com.google.inject.AbstractModule;
import hudson.Extension;
import io.opentelemetry.api.OpenTelemetry;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Support using @{@link javax.inject.Inject} to inject the OpenTelemetry instance in Jenkins @{@link Extension}.
*/
@Extension
public class OpenTelemetryApiGuiceModule extends AbstractModule {
static final Logger logger = Logger.getLogger(OpenTelemetryApiGuiceModule.class.getName());

public OpenTelemetryApiGuiceModule() {
logger.log(Level.FINE, "Creating OpenTelemetryApiGuiceModule");
}

@Override
public void configure() {
logger.log(Level.FINE, "Configuring OpenTelemetryApiGuiceModule");
ReconfigurableOpenTelemetry reconfigurableOpenTelemetry = ReconfigurableOpenTelemetry.get();
bind(OpenTelemetry.class).toInstance(reconfigurableOpenTelemetry);
bind(ExtendedOpenTelemetry.class).toInstance(reconfigurableOpenTelemetry);
bind(ReconfigurableOpenTelemetry.class).toInstance(reconfigurableOpenTelemetry);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.internal.SdkEventLoggerProvider;
Expand All @@ -41,24 +40,34 @@
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* <p>
* Reconfigurable {@link EventLoggerProvider} that allows to reconfigure the {@link Tracer}s,
* {@link io.opentelemetry.api.logs.Logger}s, and {@link EventLogger}s.
* Reconfigurable {@link OpenTelemetry}.
* </p>
* <p>
* We need reconfigurability because Jenkins supports changing the configuration of the OpenTelemetry params at runtime.
* All instantiated tracers, loggers, and eventLoggers are reconfigured when the configuration changes, when
* {@link ReconfigurableOpenTelemetry#configure(Map, Resource)} is invoked.
* </p>
* <p>
* Reconfigurable {@link EventLoggerProvider} that allows to reconfigure the {@link Tracer}s,
* {@link io.opentelemetry.api.logs.Logger}s, and {@link EventLogger}s.
* </p>
* <p>
* Jenkins components interested in being notified after the OpenTelemetry configuration changes can be marked as @{@link Extension}
* and implement {@link OpenTelemetryLifecycleListener}.
* </p>
*/
@Extension(ordinal = Integer.MAX_VALUE)
public class ReconfigurableOpenTelemetry implements ExtendedOpenTelemetry, OpenTelemetry, Closeable, ExtensionPoint {

protected final Logger logger = Logger.getLogger(getClass().getName());
private final static Logger logger = Logger.getLogger(ReconfigurableOpenTelemetry.class.getName());
private final static ReconfigurableOpenTelemetry INSTANCE = new ReconfigurableOpenTelemetry();
private final static AtomicInteger GET_INVOCATION_COUNT = new AtomicInteger(0);

Resource resource = Resource.empty();
ConfigProperties config = ConfigPropertiesUtils.emptyConfig();
OpenTelemetry openTelemetryImpl = OpenTelemetry.noop();
Expand All @@ -68,8 +77,39 @@
final ReconfigurableLoggerProvider loggerProviderImpl = new ReconfigurableLoggerProvider();
final ReconfigurableEventLoggerProvider eventLoggerProviderImpl = new ReconfigurableEventLoggerProvider();

/*
* Ensures this class is loaded and the static singleton `INSTANCE` is instantiated.
*/
@Initializer(after = InitMilestone.EXTENSIONS_AUGMENTED, before = InitMilestone.SYSTEM_CONFIG_LOADED)
public static void init() {
logger.log(Level.FINE, () -> "OpenTelemetry configured as NoOp: " + INSTANCE);
}

/**
* Initialize as NoOp
* Use a factory method for the @{@link Extension} to ensure single instantiation
* across Jenkins
* <p>
* The Jenkins component {@link ReconfigurableOpenTelemetry} is instantiated through the static factory method
* {@link #get()} rather than through the instance constructor to ensure that we have single
* instantiation across Jenkins' @{@link Extension} and Google Guice @{@link com.google.inject.Inject}.
* </p>
* <p>
* This {@link #get()} factory method works in conjunction with {@link OpenTelemetryApiGuiceModule}
* </p>
*/
@Extension(ordinal = Integer.MAX_VALUE)
public static ReconfigurableOpenTelemetry get() {
int getInvocationCount = GET_INVOCATION_COUNT.incrementAndGet();
logger.log(Level.FINE, () -> "get(invocationCount=" + getInvocationCount + "): " + INSTANCE);
return INSTANCE;
}

/**
* <p>
* Initialize as NoOp.
* </p>

* @see #get()
*/
public ReconfigurableOpenTelemetry() {
try {
Expand All @@ -88,13 +128,9 @@
"GlobalEventLoggerProvide with instance " + Optional.of(GlobalEventLoggerProvider.get()).map(elp -> elp + "@" + System.identityHashCode(elp)));
}

@Initializer(after = InitMilestone.EXTENSIONS_AUGMENTED, before = InitMilestone.SYSTEM_CONFIG_LOADED)
public void init() {
logger.log(Level.INFO, "OpenTelemetry configured as NoOp");
}

/**
* Configure the OpenTelemetry SDK with the given properties and resource disabling the OTel SDK shutdown hook
* @deprecated use {@link #configure(Map, Resource, boolean)} instead
*/
@Deprecated
@Override
Expand Down Expand Up @@ -136,30 +172,30 @@
.getOpenTelemetrySdk();
setOpenTelemetryImpl(openTelemetrySdk);

logger.log(Level.INFO, () -> "OpenTelemetry SDK configured: " + ConfigPropertiesUtils.prettyPrintOtelSdkConfig(this.config, this.resource));
logger.log(Level.INFO, () -> "OpenTelemetry configured: " + ConfigPropertiesUtils.prettyPrintOtelSdkConfig(this.config, this.resource));

if (disableShutdownHook) {
if (disableShutdownHook) {
if (shutdownHook == null) {
// nothing to do, no shutdownhook registered
} else {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
}
} else {
if (shutdownHook == null) {
shutdownHook = new Thread(this::close);
Runtime.getRuntime().addShutdownHook(shutdownHook);
} else {
// nothing to do, shutdown hook already registered
}
}

} else { // NO-OP

this.resource = Resource.empty();
this.config = ConfigPropertiesUtils.emptyConfig();
setOpenTelemetryImpl(OpenTelemetry.noop());

logger.log(Level.INFO, "OpenTelemetry configured as NoOp");
logger.log(Level.FINE, () -> "OpenTelemetry configured as NoOp");

Check warning on line 198 in src/main/java/io/jenkins/plugins/opentelemetry/api/ReconfigurableOpenTelemetry.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 175-198 are not covered by tests
}

postOpenTelemetrySdkConfiguration();
Expand Down Expand Up @@ -276,18 +312,4 @@
openTelemetryLifecycleListener.afterConfiguration(this.config);
});
}

static class ShutdownHook extends Thread {
final OpenTelemetrySdk openTelemetrySdk;

public ShutdownHook(OpenTelemetrySdk openTelemetrySdk) {
super("OpenTelemetry SDK Shutdown Hook");
this.openTelemetrySdk = openTelemetrySdk;
}

@Override
public void run() {
openTelemetrySdk.close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ public class ApacheHttpClientInstrumentationTest {

@Test
public void test_instantiate_instrumented_http_client() {
ReconfigurableOpenTelemetry openTelemetry = new ReconfigurableOpenTelemetry();
HttpClientBuilder httpClientBuilder = ApacheHttpClientTelemetry.create(openTelemetry).newHttpClientBuilder();
CloseableHttpClient httpClient = httpClientBuilder.build();
System.out.println(httpClient);
try (ReconfigurableOpenTelemetry openTelemetry = new ReconfigurableOpenTelemetry()) {
HttpClientBuilder httpClientBuilder = ApacheHttpClientTelemetry.create(openTelemetry).newHttpClientBuilder();
CloseableHttpClient httpClient = httpClientBuilder.build();
System.out.println(httpClient);
}
}
}