From aad6d7f5cc5d72c2ac5b0ccdb6df475c931a4dfc Mon Sep 17 00:00:00 2001 From: jgjesdal Date: Wed, 30 Sep 2026 10:22:47 +0200 Subject: [PATCH] fix(sdk): log spool warnings through SLF4J, not System.err The SDK runs inside someone else's application, so dropped or unreadable spooled data belongs in that application's logging backend, where it can be routed and alerted on. Co-Authored-By: Claude Opus 5.5 (1M context) Signed-off-by: jgjesdal --- datahub-java-sdk/AGENTS.md | 4 +++- datahub-java-sdk/build.gradle | 4 ++++ .../ai/intellistream/datahub/sdk/ingest/DurableSpool.java | 8 ++++++-- .../intellistream/datahub/sdk/services/EventService.java | 6 +++++- .../datahub/sdk/services/TimeseriesService.java | 6 +++++- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/datahub-java-sdk/AGENTS.md b/datahub-java-sdk/AGENTS.md index b5b34f7a..255688b2 100644 --- a/datahub-java-sdk/AGENTS.md +++ b/datahub-java-sdk/AGENTS.md @@ -6,7 +6,7 @@ Thin, synchronous Java client for the DataHub Platform REST API, published as ## Hard constraints - **No server stack.** Built on the JDK `java.net.http.HttpClient`; depends only on - `datahub-api-model` (the wire contract) plus Jackson 3 and zstd-jni. No Spring, no Feign, no + `datahub-api-model` (the wire contract) plus Jackson 3, zstd-jni and slf4j-api. No Spring, no Feign, no Vault client — the Spring Boot plugins in `build.gradle` exist only for BOM version management. Keep the dependency surface at zero-/tiny-transitive jars; this artifact ships to external users. zstd-jni is the one native library, and it is there because the binary datapoint path requires @@ -27,6 +27,8 @@ Thin, synchronous Java client for the DataHub Platform REST API, published as token is `change-request` — that request will never work as it stands — yet holding the data while someone renews the credential is exactly what the spool is for. Going by `retry` in `isBufferable` would stop buffering 401/403 and defeat the feature. +- **Log through SLF4J, never `System.err`/`System.out`.** The SDK runs inside someone else's + application, and its warnings belong in that application's logging backend. - **Wire types come from `datahub-api-model`** — never redefine request/response DTOs here. In-tree it is a project dependency (`api project(':datahub-api-model')`); the published POM pins resolved versions so non-Spring consumers work. diff --git a/datahub-java-sdk/build.gradle b/datahub-java-sdk/build.gradle index e60d13ee..7e515c41 100644 --- a/datahub-java-sdk/build.gradle +++ b/datahub-java-sdk/build.gradle @@ -29,6 +29,10 @@ dependencies { // has no zstd). api-model's ZstdPayloadCodec compiles against it but does not carry it. implementation 'com.github.luben:zstd-jni:1.5.7-17' + // The SDK's own warnings (spooled data dropped or unreadable) go to the caller's logging + // backend. api-model already brings slf4j-api; declared here because the SDK uses it directly. + implementation 'org.slf4j:slf4j-api' + // JUnit (version managed by the Spring Boot BOM) is supplied by java-common-conventions. } diff --git a/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/ingest/DurableSpool.java b/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/ingest/DurableSpool.java index 9f7e6de6..b7d5e1b3 100644 --- a/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/ingest/DurableSpool.java +++ b/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/ingest/DurableSpool.java @@ -3,6 +3,8 @@ import tools.jackson.databind.JavaType; import tools.jackson.databind.json.JsonMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.BufferedOutputStream; import java.io.BufferedReader; @@ -53,6 +55,8 @@ */ public final class DurableSpool { + private static final Logger log = LoggerFactory.getLogger(DurableSpool.class); + /** Sends one chunk of records; returns {@code true} if the server accepted them (so they can be dropped). */ @FunctionalInterface public interface ChunkSender { @@ -299,7 +303,7 @@ record = mapper.readValue(line, elementType); } } } catch (IOException e) { - System.err.println("DataHub SDK: ignoring unreadable spool segment " + segment.path + ": " + e.getMessage()); + log.warn("Ignoring unreadable spool segment {}: {}", segment.path, e.getMessage()); } } @@ -323,7 +327,7 @@ private void recover() { } }); } catch (IOException e) { - System.err.println("DataHub SDK: could not list spool dir " + dir + ": " + e.getMessage()); + log.warn("Could not list spool dir {}: {}", dir, e.getMessage()); return; } deleteQuietly(temps); diff --git a/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/services/EventService.java b/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/services/EventService.java index c702bcae..b9b29be0 100644 --- a/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/services/EventService.java +++ b/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/services/EventService.java @@ -16,6 +16,8 @@ import ai.intellistream.datahub.sdk.util.UuidV7; import ai.intellistream.datahub.models.SearchBody; import tools.jackson.databind.JavaType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -25,6 +27,8 @@ /** Events — record and query operational events. Mirrors the {@code /events} endpoints. */ public final class EventService { + private static final Logger log = LoggerFactory.getLogger(EventService.class); + private static final String CREATE_PATH = "/events/create"; private final ApiHttp http; @@ -278,7 +282,7 @@ private boolean sendChunk(List chunk, IngestOptions options) { if (result.isBufferable()) { return false; // stop draining; server unreachable or auth not yet restored } - System.err.println("DataHub SDK: dropping spooled events after a terminal error: " + result.errors()); + log.warn("Dropping spooled events after a terminal error: {}", result.errors()); return true; // terminal: drop so the spool can't get stuck forever } diff --git a/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/services/TimeseriesService.java b/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/services/TimeseriesService.java index 5a9e16a0..6d1093e3 100644 --- a/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/services/TimeseriesService.java +++ b/datahub-java-sdk/src/main/java/ai/intellistream/datahub/sdk/services/TimeseriesService.java @@ -29,6 +29,8 @@ import ai.intellistream.datahub.models.SearchBody; import tools.jackson.databind.JavaType; import tools.jackson.databind.type.TypeFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -45,6 +47,8 @@ */ public final class TimeseriesService { + private static final Logger log = LoggerFactory.getLogger(TimeseriesService.class); + private static final String DATA_PATH = "/timeseries/data"; private final ApiHttp http; @@ -383,7 +387,7 @@ private boolean sendChunk(List chunk, IngestOptions option if (result.isBufferable()) { return false; // stop draining; server unreachable or auth not yet restored } - System.err.println("DataHub SDK: dropping spooled datapoints after a terminal error: " + result.errors()); + log.warn("Dropping spooled datapoints after a terminal error: {}", result.errors()); return true; // terminal: drop so the spool can't get stuck forever }