From faaff77ba9837a521f2d10c5e1d9a2fd203139da Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Fri, 17 Apr 2026 07:10:29 +0200 Subject: [PATCH 1/4] Refactor telemetry client --- doc/changes/changelog.md | 1 + doc/changes/changes_0.2.0.md | 10 + pk_generated_parent.pom | 2 +- pom.xml | 8 +- .../telemetry/AsyncTelemetryClient.java | 168 +++++++++++++++ .../exasol/telemetry/NoOpTelemetryClient.java | 17 ++ .../com/exasol/telemetry/TelemetryClient.java | 194 ++---------------- .../com/exasol/telemetry/ShutdownFlushIT.java | 4 +- .../com/exasol/telemetry/StatusLoggingIT.java | 3 +- .../exasol/telemetry/TelemetryClientTest.java | 5 +- .../com/exasol/telemetry/TrackingApiIT.java | 22 +- 11 files changed, 222 insertions(+), 212 deletions(-) create mode 100644 doc/changes/changes_0.2.0.md create mode 100644 src/main/java/com/exasol/telemetry/AsyncTelemetryClient.java create mode 100644 src/main/java/com/exasol/telemetry/NoOpTelemetryClient.java diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md index 9e62d07..39f13d8 100644 --- a/doc/changes/changelog.md +++ b/doc/changes/changelog.md @@ -1,3 +1,4 @@ # Changes +* [0.2.0](changes_0.2.0.md) * [0.1.0](changes_0.1.0.md) diff --git a/doc/changes/changes_0.2.0.md b/doc/changes/changes_0.2.0.md new file mode 100644 index 0000000..56af214 --- /dev/null +++ b/doc/changes/changes_0.2.0.md @@ -0,0 +1,10 @@ +# Telemetry Java 0.2.0, released 2026-??-?? + +Code name: + +## Summary + +## Features + +* ISSUE_NUMBER: description + diff --git a/pk_generated_parent.pom b/pk_generated_parent.pom index 228b85e..e433067 100644 --- a/pk_generated_parent.pom +++ b/pk_generated_parent.pom @@ -3,7 +3,7 @@ 4.0.0 com.exasol telemetry-java-generated-parent - 0.1.0 + 0.2.0 pom UTF-8 diff --git a/pom.xml b/pom.xml index 7ca6712..17dc7d7 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,9 @@ - + 4.0.0 com.exasol telemetry-java - 0.1.0 + 0.2.0 telemetry-java Minimal, zero-dependency telemetry library for Java applications. https://github.com/exasol/telemetry-java/ @@ -114,7 +112,7 @@ telemetry-java-generated-parent com.exasol - 0.1.0 + 0.2.0 pk_generated_parent.pom diff --git a/src/main/java/com/exasol/telemetry/AsyncTelemetryClient.java b/src/main/java/com/exasol/telemetry/AsyncTelemetryClient.java new file mode 100644 index 0000000..6438291 --- /dev/null +++ b/src/main/java/com/exasol/telemetry/AsyncTelemetryClient.java @@ -0,0 +1,168 @@ +package com.exasol.telemetry; + +import static java.util.Objects.requireNonNull; + +import java.time.*; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +final class AsyncTelemetryClient implements TelemetryClient { + private final TelemetryConfig config; + private final BlockingQueue queue; + private final HttpTransport transport; + private final Thread senderThread; + private final Clock clock; + private final CountDownLatch terminated = new CountDownLatch(1); + private volatile boolean closed; + + AsyncTelemetryClient(final TelemetryConfig config) { + this(config, Clock.systemUTC()); + } + + AsyncTelemetryClient(final TelemetryConfig config, final Clock clock) { + this.config = requireNonNull(config, "config"); + this.clock = requireNonNull(clock, "clock"); + this.queue = new ArrayBlockingQueue<>(config.getQueueCapacity()); + this.transport = new HttpTransport(config); + this.senderThread = new Thread(this::runSender, "telemetry-java-sender"); + this.senderThread.setDaemon(true); + this.senderThread.start(); + } + + @Override + public void track(final String feature) { + if (closed || feature == null) { + return; + } + final TelemetryEvent event = new TelemetryEvent(feature, clock.instant()); + enqueue(event); + } + + @SuppressWarnings("java:S899") // Intentionally ignore return value of offer() to avoid blocking the caller. + private void enqueue(final TelemetryEvent event) { + queue.offer(event); + } + + private void runSender() { + try { + while (!closed || !queue.isEmpty()) { + final TelemetryEvent event = queue.poll(100, TimeUnit.MILLISECONDS); + if (event != null) { + sendWithRetry(drainBatch(event)); + } + } + } catch (final InterruptedException ignored) { + Thread.currentThread().interrupt(); + } finally { + terminated.countDown(); + } + } + + private List drainBatch(final TelemetryEvent firstEvent) { + final List batch = new ArrayList<>(); + batch.add(firstEvent); + queue.drainTo(batch); + return batch; + } + + // [impl~telemetry-client-send-with-retry~1->req~async-delivery~1] + private void sendWithRetry(final List events) { + final Instant start = clock.instant(); + final Message message = Message.fromEvents(config.getProjectTag(), config.getProductVersion(), start, events); + final Instant deadline = start.plus(config.getRetryTimeout()); + Duration delay = config.getInitialRetryDelay(); + + while (true) { + if (Thread.currentThread().isInterrupted()) { + return; + } + try { + transport.send(message); + LOGGER.fine(() -> "Telemetry sent to the server with " + events.size() + " event(s)."); + return; + } catch (final Exception exception) { + LOGGER.fine(() -> "Telemetry sending failed for " + events.size() + " event(s): " + + rootCauseMessage(exception)); + if (Thread.currentThread().isInterrupted()) { + return; + } + final Instant now = clock.instant(); + if (!now.isBefore(deadline)) { + return; + } + final Duration remaining = Duration.between(now, deadline); + sleep(min(delay, remaining)); + delay = min(delay.multipliedBy(2), config.getMaxRetryDelay()); + } + } + } + + private static Duration min(final Duration left, final Duration right) { + return left.compareTo(right) <= 0 ? left : right; + } + + private static String rootCauseMessage(final Throwable throwable) { + Throwable cause = throwable; + while (cause != null) { + if (cause instanceof HttpException) { + final HttpException httpException = (HttpException) cause; + return "server status " + httpException.getStatusCode() + " (" + httpException.getServerStatus() + ")"; + } + if (cause.getCause() == null) { + final String message = cause.getMessage(); + if (message == null || message.isBlank()) { + return cause.getClass().getSimpleName(); + } + } + cause = cause.getCause(); + } + return ""; + } + + private void sleep(final Duration duration) { + try { + Thread.sleep(Math.max(1, duration.toMillis())); + } catch (final InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + } + + // Visible for testing + boolean awaitStopped(final Duration timeout) throws InterruptedException { + return terminated.await(timeout.toMillis(), TimeUnit.MILLISECONDS); + } + + // Visible for testing + boolean isRunning() { + return senderThread.isAlive(); + } + + @Override + public void close() { + if (closed) { + return; + } + closed = true; + awaitSenderStop(); + LOGGER.fine("Telemetry is stopped."); + } + + private void awaitSenderStop() { + final long timeoutNanos = config.getRetryTimeout().toNanos(); + final long deadlineNanos = System.nanoTime() + timeoutNanos; + try { + while (senderThread.isAlive()) { + final long remainingNanos = deadlineNanos - System.nanoTime(); + if (remainingNanos <= 0) { + senderThread.interrupt(); + senderThread.join(); + return; + } + TimeUnit.NANOSECONDS.timedJoin(senderThread, remainingNanos); + } + } catch (final InterruptedException ignored) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/src/main/java/com/exasol/telemetry/NoOpTelemetryClient.java b/src/main/java/com/exasol/telemetry/NoOpTelemetryClient.java new file mode 100644 index 0000000..363d4e8 --- /dev/null +++ b/src/main/java/com/exasol/telemetry/NoOpTelemetryClient.java @@ -0,0 +1,17 @@ +package com.exasol.telemetry; + +final class NoOpTelemetryClient implements TelemetryClient { + NoOpTelemetryClient() { + // Intentionally empty. + } + + @Override + public void track(final String feature) { + // Intentionally does nothing. + } + + @Override + public void close() { + // Intentionally does nothing. + } +} diff --git a/src/main/java/com/exasol/telemetry/TelemetryClient.java b/src/main/java/com/exasol/telemetry/TelemetryClient.java index 692e0f6..36353e2 100644 --- a/src/main/java/com/exasol/telemetry/TelemetryClient.java +++ b/src/main/java/com/exasol/telemetry/TelemetryClient.java @@ -1,11 +1,5 @@ package com.exasol.telemetry; -import static java.util.Objects.requireNonNull; - -import java.time.*; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.*; import java.util.logging.Logger; /** @@ -13,38 +7,8 @@ * Create a client by building a {@link TelemetryConfig} with {@link TelemetryConfig#builder(String, String)} and passing it * to {@link #create(TelemetryConfig)}. */ -public final class TelemetryClient implements AutoCloseable { - private static final Logger LOGGER = Logger.getLogger(TelemetryClient.class.getName()); - - private final TelemetryConfig config; - private final BlockingQueue queue; - private final HttpTransport transport; - private final Thread senderThread; - private final Clock clock; - private final boolean trackingEnabled; - private final CountDownLatch terminated = new CountDownLatch(1); - private volatile boolean closed; - - private TelemetryClient(final TelemetryConfig config) { - this(config, Clock.systemUTC()); - } - - TelemetryClient(final TelemetryConfig config, final Clock clock) { - this.config = requireNonNull(config, "config"); - this.clock = requireNonNull(clock, "clock"); - this.queue = new ArrayBlockingQueue<>(config.getQueueCapacity()); - this.transport = new HttpTransport(config); - this.trackingEnabled = !config.isTrackingDisabled(); - this.senderThread = new Thread(this::runSender, "telemetry-java-sender"); - this.senderThread.setDaemon(true); - if (trackingEnabled) { - this.senderThread.start(); - logEnabled(); - } else { - terminated.countDown(); - logDisabled(); - } - } +public interface TelemetryClient extends AutoCloseable { + Logger LOGGER = Logger.getLogger(TelemetryClient.class.getName()); /** * Create a telemetry client for the provided configuration. @@ -53,8 +17,13 @@ private TelemetryClient(final TelemetryConfig config) { * @return telemetry client */ // [impl~telemetry-client-create~1->req~tracking-api~1] - public static TelemetryClient create(final TelemetryConfig config) { - return new TelemetryClient(config); + static TelemetryClient create(final TelemetryConfig config) { + if (config.isTrackingDisabled()) { + logDisabled(config); + return new NoOpTelemetryClient(); + } + logEnabled(config); + return new AsyncTelemetryClient(config); } /** @@ -63,157 +32,22 @@ public static TelemetryClient create(final TelemetryConfig config) { * @param feature feature name provided by the caller */ // [impl~telemetry-client-track~1->req~tracking-api~1] - public void track(final String feature) { - if (!trackingEnabled || closed) { - return; - } - - if (feature == null) { - return; - } - - final TelemetryEvent event = new TelemetryEvent(feature, clock.instant()); - enqueue(event); - } - - @SuppressWarnings("java:S899") // Return value of offer ignored, this is fire-and-forget - private void enqueue(final TelemetryEvent event) { - queue.offer(event); - } - - private void runSender() { - try { - while (!closed || !queue.isEmpty()) { - final TelemetryEvent event = queue.poll(100, TimeUnit.MILLISECONDS); - if (event != null) { - sendWithRetry(drainBatch(event)); - } - } - } catch (final InterruptedException ignored) { - Thread.currentThread().interrupt(); - } finally { - terminated.countDown(); - } - } - - private List drainBatch(final TelemetryEvent firstEvent) { - final List batch = new ArrayList<>(); - batch.add(firstEvent); - queue.drainTo(batch); - return batch; - } - - // [impl~telemetry-client-send-with-retry~1->req~async-delivery~1] - private void sendWithRetry(final List events) { - final Instant start = clock.instant(); - final Message message = Message.fromEvents(config.getProjectTag(), config.getProductVersion(), start, events); - final Instant deadline = start.plus(config.getRetryTimeout()); - Duration delay = config.getInitialRetryDelay(); - - while (true) { - if (Thread.currentThread().isInterrupted()) { - return; - } - try { - transport.send(message); - LOGGER.fine(() -> "Telemetry sent to the server with " + events.size() + " event(s)."); - return; - } catch (final Exception exception) { - LOGGER.fine(() -> "Telemetry sending failed for " + events.size() + " event(s): " - + rootCauseMessage(exception)); - if (Thread.currentThread().isInterrupted()) { - return; - } - final Instant now = clock.instant(); - if (!now.isBefore(deadline)) { - return; - } - final Duration remaining = Duration.between(now, deadline); - sleep(min(delay, remaining)); - delay = min(delay.multipliedBy(2), config.getMaxRetryDelay()); - } - } - } - - private static Duration min(final Duration left, final Duration right) { - return left.compareTo(right) <= 0 ? left : right; - } - - private static String rootCauseMessage(final Throwable throwable) { - Throwable cause = throwable; - while (cause != null) { - if (cause instanceof HttpException) { - final HttpException httpException = (HttpException) cause; - return "server status " + httpException.getStatusCode() + " (" + httpException.getServerStatus() + ")"; - } - if (cause.getCause() == null) { - final String message = cause.getMessage(); - if (message == null || message.isBlank()) { - return cause.getClass().getSimpleName(); - } - } - cause = cause.getCause(); - } - return ""; - } - - private void sleep(final Duration duration) { - try { - Thread.sleep(Math.max(1, duration.toMillis())); - } catch (final InterruptedException ignored) { - Thread.currentThread().interrupt(); - } - } - - boolean awaitStopped(final Duration timeout) throws InterruptedException { - return terminated.await(timeout.toMillis(), TimeUnit.MILLISECONDS); - } - - boolean isRunning() { - return senderThread.isAlive(); - } + void track(String feature); /** - * Stop the sender thread and wait for any queued events to be flushed before returning. + * Stop the client, send all remaining queued telemetry messages and release any resources that it owns. */ @Override // [impl~telemetry-client-close~1->req~shutdown-flush~1] - public void close() { - if (closed) { - return; - } - closed = true; - if (trackingEnabled) { - awaitSenderStop(); - } - LOGGER.fine("Telemetry is stopped."); - } - - private void awaitSenderStop() { - final long timeoutNanos = config.getRetryTimeout().toNanos(); - final long deadlineNanos = System.nanoTime() + timeoutNanos; - try { - while (senderThread.isAlive()) { - final long remainingNanos = deadlineNanos - System.nanoTime(); - if (remainingNanos <= 0) { - senderThread.interrupt(); - senderThread.join(); - return; - } - TimeUnit.NANOSECONDS.timedJoin(senderThread, remainingNanos); - } - } catch (final InterruptedException ignored) { - Thread.currentThread().interrupt(); - } - } + void close(); - private void logEnabled() { + private static void logEnabled(final TelemetryConfig config) { LOGGER.info(() -> "Telemetry is enabled. Set " + TelemetryConfig.DISABLED_ENV + " to any non-empty value to disable telemetry. " + TelemetryConfig.DISABLED_ENV + "=" + formatEnvValue(config.getDisabledEnvValue()) + ", " + TelemetryConfig.CI_ENV + "=" + formatEnvValue(config.getCiEnvValue()) + "."); } - private void logDisabled() { + private static void logDisabled(final TelemetryConfig config) { LOGGER.info(() -> "Telemetry is disabled via " + config.getDisableMechanism() + "=" + formatEnvValue(config.getDisableMechanismValue()) + "."); } diff --git a/src/test/java/com/exasol/telemetry/ShutdownFlushIT.java b/src/test/java/com/exasol/telemetry/ShutdownFlushIT.java index f5b14d7..2ab671c 100644 --- a/src/test/java/com/exasol/telemetry/ShutdownFlushIT.java +++ b/src/test/java/com/exasol/telemetry/ShutdownFlushIT.java @@ -36,9 +36,9 @@ void flushesPendingEventsOnClose() throws Exception { // [itest~shutdown-flush-stops-background-thread~1->req~shutdown-flush~1] @Test void stopsBackgroundThreadsAfterClose() throws Exception { - final TelemetryClient client; + final AsyncTelemetryClient client; try (RecordingHttpServer server = RecordingHttpServer.createSuccessServer()) { - client = TelemetryClient.create(server.configBuilder("shop-ui", PRODUCT_VERSION).build()); + client = new AsyncTelemetryClient(server.configBuilder("shop-ui", PRODUCT_VERSION).build()); client.track("checkout-started"); client.close(); } diff --git a/src/test/java/com/exasol/telemetry/StatusLoggingIT.java b/src/test/java/com/exasol/telemetry/StatusLoggingIT.java index 4d65b36..a596689 100644 --- a/src/test/java/com/exasol/telemetry/StatusLoggingIT.java +++ b/src/test/java/com/exasol/telemetry/StatusLoggingIT.java @@ -28,7 +28,8 @@ void logsWhenTelemetryIsEnabled() throws Exception { final LogRecord enabledRecord = capture.await(logRecord -> logRecord.getLevel() == Level.INFO && logRecord.getMessage().contains("Telemetry is enabled"), Duration.ofSeconds(1)); - assertThat(client.isRunning(), is(true)); + assertThat(client, instanceOf(AsyncTelemetryClient.class)); + assertThat(((AsyncTelemetryClient) client).isRunning(), is(true)); assertThat(enabledRecord.getMessage(), containsString("Set EXASOL_TELEMETRY_DISABLE to any non-empty value to disable telemetry.")); assertThat(enabledRecord.getMessage(), containsString("EXASOL_TELEMETRY_DISABLE=")); assertThat(enabledRecord.getMessage(), containsString("CI=")); diff --git a/src/test/java/com/exasol/telemetry/TelemetryClientTest.java b/src/test/java/com/exasol/telemetry/TelemetryClientTest.java index 358d21f..41b5fb7 100644 --- a/src/test/java/com/exasol/telemetry/TelemetryClientTest.java +++ b/src/test/java/com/exasol/telemetry/TelemetryClientTest.java @@ -1,11 +1,11 @@ package com.exasol.telemetry; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import java.net.URI; -import java.time.Duration; import java.util.Map; import org.junit.jupiter.api.Test; @@ -21,8 +21,7 @@ void doesNotRunSenderWhenTrackingIsDisabled() throws Exception { final TelemetryClient client = TelemetryClient.create(config); try { client.track("feature"); - assertThat(client.awaitStopped(Duration.ofMillis(10)), is(true)); - assertThat(client.isRunning(), is(false)); + assertThat(client, instanceOf(NoOpTelemetryClient.class)); } finally { client.close(); } diff --git a/src/test/java/com/exasol/telemetry/TrackingApiIT.java b/src/test/java/com/exasol/telemetry/TrackingApiIT.java index f8bca83..b903254 100644 --- a/src/test/java/com/exasol/telemetry/TrackingApiIT.java +++ b/src/test/java/com/exasol/telemetry/TrackingApiIT.java @@ -74,11 +74,10 @@ void makesDisabledTrackingNoOpWithoutTelemetryOverhead() throws Exception { .environment(new MapEnvironment(Map.of(TelemetryConfig.DISABLED_ENV, "disabled"))) .build(); - try (TelemetryClient client = new TelemetryClient(config, new FailingClock())) { + try (TelemetryClient client = TelemetryClient.create(config)) { client.track(FEATURE); - assertThat(client.awaitStopped(Duration.ofMillis(10)), is(true)); - assertThat(client.isRunning(), is(false)); + assertThat(client, instanceOf(NoOpTelemetryClient.class)); assertThat(server.awaitRequests(1, Duration.ofMillis(150)), empty()); } } @@ -110,21 +109,4 @@ void ignoresNullFeatureNames() throws Exception { assertThat(server.awaitRequests(1, Duration.ofMillis(150)), empty()); } } - - private static final class FailingClock extends Clock { - @Override - public ZoneId getZone() { - return ZoneOffset.UTC; - } - - @Override - public Clock withZone(final ZoneId zone) { - return this; - } - - @Override - public Instant instant() { - throw new AssertionError("disabled tracking should not capture a timestamp"); - } - } } From 46f39fa7ccd7ebdbf6d31e731418180d33cb4e63 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Fri, 17 Apr 2026 07:19:53 +0200 Subject: [PATCH 2/4] Move logger to private static method --- .../com/exasol/telemetry/AsyncTelemetryClient.java | 3 +++ .../java/com/exasol/telemetry/TelemetryClient.java | 11 +++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/exasol/telemetry/AsyncTelemetryClient.java b/src/main/java/com/exasol/telemetry/AsyncTelemetryClient.java index 6438291..38fdcc7 100644 --- a/src/main/java/com/exasol/telemetry/AsyncTelemetryClient.java +++ b/src/main/java/com/exasol/telemetry/AsyncTelemetryClient.java @@ -6,8 +6,11 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; +import java.util.logging.Logger; final class AsyncTelemetryClient implements TelemetryClient { + private static final Logger LOGGER = Logger.getLogger(AsyncTelemetryClient.class.getName()); + private final TelemetryConfig config; private final BlockingQueue queue; private final HttpTransport transport; diff --git a/src/main/java/com/exasol/telemetry/TelemetryClient.java b/src/main/java/com/exasol/telemetry/TelemetryClient.java index 36353e2..ee23365 100644 --- a/src/main/java/com/exasol/telemetry/TelemetryClient.java +++ b/src/main/java/com/exasol/telemetry/TelemetryClient.java @@ -8,8 +8,6 @@ * to {@link #create(TelemetryConfig)}. */ public interface TelemetryClient extends AutoCloseable { - Logger LOGGER = Logger.getLogger(TelemetryClient.class.getName()); - /** * Create a telemetry client for the provided configuration. * @@ -42,16 +40,21 @@ static TelemetryClient create(final TelemetryConfig config) { void close(); private static void logEnabled(final TelemetryConfig config) { - LOGGER.info(() -> "Telemetry is enabled. Set " + TelemetryConfig.DISABLED_ENV + " to any non-empty value to disable telemetry. " + logger().info(() -> "Telemetry is enabled. Set " + TelemetryConfig.DISABLED_ENV + " to any non-empty value to disable telemetry. " + TelemetryConfig.DISABLED_ENV + "=" + formatEnvValue(config.getDisabledEnvValue()) + ", " + TelemetryConfig.CI_ENV + "=" + formatEnvValue(config.getCiEnvValue()) + "."); } private static void logDisabled(final TelemetryConfig config) { - LOGGER.info(() -> "Telemetry is disabled via " + config.getDisableMechanism() + "=" + logger().info(() -> "Telemetry is disabled via " + config.getDisableMechanism() + "=" + formatEnvValue(config.getDisableMechanismValue()) + "."); } + // Create logger in private method to avoid a public static field in the interface. + private static Logger logger() { + return Logger.getLogger(TelemetryClient.class.getName()); + } + private static String formatEnvValue(final String value) { if (value == null) { return ""; From 68b7529a9d9d02c0cc7a6f9fd475ce954f85c251 Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Fri, 17 Apr 2026 07:20:57 +0200 Subject: [PATCH 3/4] Fix sonar warnings --- src/test/java/com/exasol/telemetry/TelemetryClientTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/com/exasol/telemetry/TelemetryClientTest.java b/src/test/java/com/exasol/telemetry/TelemetryClientTest.java index 41b5fb7..c0b29c3 100644 --- a/src/test/java/com/exasol/telemetry/TelemetryClientTest.java +++ b/src/test/java/com/exasol/telemetry/TelemetryClientTest.java @@ -2,7 +2,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import java.net.URI; @@ -13,7 +12,7 @@ class TelemetryClientTest { // [utest~telemetry-client-disabled-tracking~1->req~tracking-controls~1] @Test - void doesNotRunSenderWhenTrackingIsDisabled() throws Exception { + void doesNotRunSenderWhenTrackingIsDisabled() { final TelemetryConfig config = TelemetryConfig.builder("project", "1.2.3").endpoint(URI.create("https://example.com")) .environment(new MapEnvironment(Map.of(TelemetryConfig.DISABLED_ENV, "true"))) .build(); From 69a31cd96d3cd37c7f5dc1fba9872f4db03fecfd Mon Sep 17 00:00:00 2001 From: Christoph Pirkl Date: Fri, 17 Apr 2026 08:38:38 +0200 Subject: [PATCH 4/4] Fix logger tests --- .../com/exasol/telemetry/StatusLoggingIT.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/test/java/com/exasol/telemetry/StatusLoggingIT.java b/src/test/java/com/exasol/telemetry/StatusLoggingIT.java index a596689..303af7a 100644 --- a/src/test/java/com/exasol/telemetry/StatusLoggingIT.java +++ b/src/test/java/com/exasol/telemetry/StatusLoggingIT.java @@ -17,12 +17,9 @@ class StatusLoggingIT { private static final String VERSION = "1.2.3"; private static final String FEATURE = "myFeature"; - @SuppressWarnings("java:S3416") // Using captured logger name by intention - private static final Logger CAPTURED_LOGGER = Logger.getLogger(TelemetryClient.class.getName()); - @Test void logsWhenTelemetryIsEnabled() throws Exception { - try (LogCapture capture = new LogCapture(CAPTURED_LOGGER); + try (LogCapture capture = new LogCapture(); RecordingHttpServer server = RecordingHttpServer.createSuccessServer(); TelemetryClient client = TelemetryClient.create(server.configBuilder(PROJECT_TAG, VERSION).build())) { final LogRecord enabledRecord = capture.await(logRecord -> logRecord.getLevel() == Level.INFO @@ -38,7 +35,7 @@ void logsWhenTelemetryIsEnabled() throws Exception { @Test void logsWhenTelemetryIsDisabledWithMechanism() throws Exception { - try (LogCapture capture = new LogCapture(CAPTURED_LOGGER); + try (LogCapture capture = new LogCapture(); RecordingHttpServer server = RecordingHttpServer.createSuccessServer(); TelemetryClient client = TelemetryClient.create(server.configBuilder(PROJECT_TAG, VERSION) .environment(new MapEnvironment(Map.of(TelemetryConfig.DISABLED_ENV, "disabled"))) @@ -50,7 +47,7 @@ void logsWhenTelemetryIsDisabledWithMechanism() throws Exception { assertThat(envRecord.getMessage(), containsString("EXASOL_TELEMETRY_DISABLE='disabled'")); } - try (LogCapture capture = new LogCapture(CAPTURED_LOGGER); + try (LogCapture capture = new LogCapture(); RecordingHttpServer server = RecordingHttpServer.createSuccessServer(); TelemetryClient client = TelemetryClient.create(server.configBuilder(PROJECT_TAG, VERSION) .environment(new MapEnvironment(Map.of(TelemetryConfig.CI_ENV, "github-actions"))) @@ -65,7 +62,7 @@ void logsWhenTelemetryIsDisabledWithMechanism() throws Exception { @Test void logsSentMessageCount() throws Exception { - try (LogCapture capture = new LogCapture(CAPTURED_LOGGER); + try (LogCapture capture = new LogCapture(); RecordingHttpServer server = RecordingHttpServer.createSuccessServer()) { final TelemetryClient client = TelemetryClient.create(server.configBuilder(PROJECT_TAG, VERSION) .build()); @@ -84,7 +81,7 @@ void logsSentMessageCount() throws Exception { @Test void logsWhenTelemetrySendingFails() throws Exception { - try (LogCapture capture = new LogCapture(CAPTURED_LOGGER); + try (LogCapture capture = new LogCapture(); RecordingHttpServer server = RecordingHttpServer.createFlakyServer(1)) { final TelemetryClient client = TelemetryClient.create(server.configBuilder(PROJECT_TAG, VERSION) .retryTimeout(Duration.ofMillis(500)) @@ -108,7 +105,7 @@ void logsWhenTelemetrySendingFails() throws Exception { @Test void logsWhenTelemetryStops() throws Exception { - try (LogCapture capture = new LogCapture(CAPTURED_LOGGER); + try (LogCapture capture = new LogCapture(); RecordingHttpServer server = RecordingHttpServer.createSuccessServer()) { final TelemetryClient client = TelemetryClient.create(server.configBuilder(PROJECT_TAG, VERSION).build()); try { @@ -128,8 +125,8 @@ private static final class LogCapture extends Handler implements AutoCloseable { private final Level originalLevel; private final boolean originalUseParentHandlers; - private LogCapture(final Logger logger) { - this.logger = logger; + private LogCapture() { + this.logger = Logger.getLogger("com.exasol.telemetry"); this.originalLevel = logger.getLevel(); this.originalUseParentHandlers = logger.getUseParentHandlers(); logger.setLevel(Level.ALL);