diff --git a/custom/src/main/java/co/elastic/otel/config/DynamicInstrumentation.java b/custom/src/main/java/co/elastic/otel/config/DynamicInstrumentation.java index 799b8fc0..14b72335 100644 --- a/custom/src/main/java/co/elastic/otel/config/DynamicInstrumentation.java +++ b/custom/src/main/java/co/elastic/otel/config/DynamicInstrumentation.java @@ -51,11 +51,14 @@ */ public class DynamicInstrumentation { - private static final Logger logger = Logger.getLogger(DynamicInstrumentation.class.getName()); public static final String INSTRUMENTATION_NAME_PREPEND = "io.opentelemetry."; + public static final String ALL_INSTRUMENTATION = "_ALL_"; // note the option can't be an env because no OSes support changing envs while the program runs public static final String INSTRUMENTATION_DISABLE_OPTION = "elastic.otel.java.disable_instrumentations"; + private static final String ALL_INSTRUMENTATION_FULL_NAME = + INSTRUMENTATION_NAME_PREPEND + ALL_INSTRUMENTATION; + private static final Logger logger = Logger.getLogger(DynamicInstrumentation.class.getName()); private static Object getField(String fieldname, Object target) { try { @@ -186,6 +189,14 @@ public static void disableTracesFor(String instrumentationName) { updateTracerConfigurations(GlobalOpenTelemetry.getTracerProvider()); } + public static void disableAllTraces() { + disableTracesFor(ALL_INSTRUMENTATION); + } + + public static void stopDisablingAllTraces() { + reenableTracesFor(ALL_INSTRUMENTATION); + } + public static class UpdatableConfigurator implements ScopeConfigurator { public static final UpdatableConfigurator INSTANCE = new UpdatableConfigurator(); private final ConcurrentMap map = new ConcurrentHashMap<>(); @@ -194,6 +205,11 @@ private UpdatableConfigurator() {} @Override public TracerConfig apply(InstrumentationScopeInfo scopeInfo) { + // If key "_ALL_" is set to disabled, then always return disabled + // otherwise fallback to the individual instrumentation + if (!map.getOrDefault(ALL_INSTRUMENTATION_FULL_NAME, TracerConfig.enabled()).isEnabled()) { + return TracerConfig.disabled(); + } return map.getOrDefault(scopeInfo.getName(), TracerConfig.defaultConfig()); } diff --git a/smoke-tests/src/test/java/com/example/javaagent/smoketest/DynamicInstrumentationSmokeTest.java b/smoke-tests/src/test/java/com/example/javaagent/smoketest/DynamicInstrumentationSmokeTest.java index 3aa3de44..5ad83c34 100644 --- a/smoke-tests/src/test/java/com/example/javaagent/smoketest/DynamicInstrumentationSmokeTest.java +++ b/smoke-tests/src/test/java/com/example/javaagent/smoketest/DynamicInstrumentationSmokeTest.java @@ -25,6 +25,7 @@ import io.opentelemetry.proto.trace.v1.Span; import java.util.List; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -36,7 +37,7 @@ public static void start() { (container) -> { container.addEnv( "OTEL_INSTRUMENTATION_METHODS_INCLUDE", - "co.elastic.otel.test.DynamicInstrumentationController[flipMethods]"); + "co.elastic.otel.test.DynamicInstrumentationController[flipMethods,flipAll]"); container.addEnv("ELASTIC_OTEL_JAVA_DISABLE_INSTRUMENTATIONS_CHECKER", "true"); container.addEnv("OTEL_JAVAAGENT_DEBUG", "true"); }); @@ -47,28 +48,53 @@ public static void end() { stopApp(); } + @AfterEach + public void endTest() throws InterruptedException { + doRequest(getUrl("/dynamic/reset"), okResponseBody("reset")); + Thread.sleep(2000L); // give the reset time to be applied + } + @Test public void flipMethodInstrumentation() throws InterruptedException { - doRequest(getUrl("/dynamic"), okResponseBody("enabled")); + dynamicFlipInstrumentation("Methods", 1); + } + + @Test + public void flipAllInstrumentation() throws InterruptedException { + dynamicFlipInstrumentation("All", 0); + } + + private void dynamicFlipInstrumentation(String extensionName, int SpanCountWhenDisabled) + throws InterruptedException { + doRequest(getUrl("/dynamic/flip" + extensionName), okResponseBody("enabled")); List traces = waitForTraces(); List spans = getSpans(traces).toList(); assertThat(spans) .hasSize(2) .extracting("name") - .containsOnly("GET /dynamic", "DynamicInstrumentationController.flipMethods"); + .containsOnly( + "GET /dynamic/flip" + extensionName, + "DynamicInstrumentationController.flip" + extensionName); ByteString firstTraceID = spans.get(0).getTraceId(); Thread.sleep(2000L); // give the flip time to be applied - doRequest(getUrl("/dynamic"), okResponseBody("disabled")); + doRequest(getUrl("/dynamic/flip" + extensionName), okResponseBody("disabled")); traces = waitForTraces(); spans = getSpans(traces).dropWhile(span -> span.getTraceId().equals(firstTraceID)).toList(); - assertThat(spans).hasSize(1).extracting("name").containsOnly("GET /dynamic"); - ByteString secondTraceID = spans.get(0).getTraceId(); + if (SpanCountWhenDisabled > 0) { + assertThat(spans) + .hasSize(SpanCountWhenDisabled) + .extracting("name") + .containsOnly("GET /dynamic/flip" + extensionName); + } else { + assertThat(spans).hasSize(0); + } + ByteString secondTraceID = SpanCountWhenDisabled > 0 ? spans.get(0).getTraceId() : firstTraceID; Thread.sleep(2000L); // give the flip time to be applied - doRequest(getUrl("/dynamic"), okResponseBody("enabled")); + doRequest(getUrl("/dynamic/flip" + extensionName), okResponseBody("enabled")); traces = waitForTraces(); spans = getSpans(traces) @@ -80,6 +106,8 @@ public void flipMethodInstrumentation() throws InterruptedException { assertThat(spans) .hasSize(2) .extracting("name") - .containsOnly("GET /dynamic", "DynamicInstrumentationController.flipMethods"); + .containsOnly( + "GET /dynamic/flip" + extensionName, + "DynamicInstrumentationController.flip" + extensionName); } } diff --git a/smoke-tests/test-app/src/main/java/co/elastic/otel/test/DynamicInstrumentationController.java b/smoke-tests/test-app/src/main/java/co/elastic/otel/test/DynamicInstrumentationController.java index bf054ab7..f4ed055d 100644 --- a/smoke-tests/test-app/src/main/java/co/elastic/otel/test/DynamicInstrumentationController.java +++ b/smoke-tests/test-app/src/main/java/co/elastic/otel/test/DynamicInstrumentationController.java @@ -29,14 +29,33 @@ public class DynamicInstrumentationController { "elastic.otel.java.disable_instrumentations"; // note synchronized to make enable/disable faster with DynamicInstrumentation - @GetMapping + @GetMapping("/flipMethods") public synchronized String flipMethods() { String old = System.getProperty(INSTRUMENTATION_DISABLE_OPTION, ""); if (old.isEmpty()) { System.setProperty(INSTRUMENTATION_DISABLE_OPTION, "methods"); + return "enabled"; } else { System.setProperty(INSTRUMENTATION_DISABLE_OPTION, ""); + return "disabled"; } - return old.isEmpty() ? "enabled" : "disabled"; + } + + @RequestMapping("/flipAll") + public synchronized String flipAll() { + String old = System.getProperty(INSTRUMENTATION_DISABLE_OPTION, ""); + if (old.isEmpty()) { + System.setProperty(INSTRUMENTATION_DISABLE_OPTION, "_ALL_"); + return "enabled"; + } else { + System.setProperty(INSTRUMENTATION_DISABLE_OPTION, ""); + return "disabled"; + } + } + + @RequestMapping("/reset") + public synchronized String reset() { + System.setProperty(INSTRUMENTATION_DISABLE_OPTION, ""); + return "reset"; } }