diff --git a/.gitattributes b/.gitattributes index 98fe6dd..6227c29 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,5 +12,5 @@ doc/changes/changelog.md linguist-generated=true .settings/org.eclipse.jdt.core.prefs linguist-generated=true .settings/org.eclipse.jdt.ui.prefs linguist-generated=true -specs/_plans/ linguist-generated=true -specs/_recorded linguist-generated=true +specs/_plans/** linguist-generated=true +specs/_recorded/** linguist-generated=true diff --git a/AGENTS.md b/AGENTS.md index fc63176..4762d3c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,8 +5,11 @@ - The project uses the `speq` skill, see https://github.com/marconae/speq-skill - The project uses OpenFastTrace (OFT). - Reference skill: https://github.com/itsallcode/openfasttrace/blob/main/skills/openfasttrace-skill/SKILL.md +- The project uses [project-keeper](https://github.com/exasol/project-keeper) for managing project structure -## OpenFastTrace Artifact Types +## OpenFastTrace + +### OpenFastTrace Artifact Types - `feat`: high level features in the mission - `req`: requirements in the speq spec files @@ -14,10 +17,14 @@ - `utest`: unit tests - `itest`: integration tests -## Tag Maintenance +### Tag Maintenance - Always add or update OpenFastTrace tags for features, requirements, and code tags when updating the mission or speq spec files. -## Validation +### Validation - Ensure that all requirements are covered by running `mvn generate-sources org.itsallcode:openfasttrace-maven-plugin:trace`. + +## Project Keeper + +If `mvn verify` fails with project keeper errors, run `mvn project-keeper:fix` and try again. diff --git a/README.md b/README.md index ee405d5..19b9cea 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ The project uses `speq` skills and recorded specs to drive planning, implementat - Protocol-compatible message format with `category`, protocol `version`, `productVersion`, `timestamp`, and `features` - Exponential backoff with retry timeout - Clean shutdown via `AutoCloseable` -- Environment-variable control for disabling telemetry and overriding the endpoint +- Host-configuration and environment-variable control for disabling telemetry, plus environment-variable endpoint override - Lifecycle log messages for enabled, disabled, sent-count, and stopped telemetry states - Zero runtime dependencies @@ -57,6 +57,8 @@ try (TelemetryClient client = TelemetryClient.create(config)) { The default endpoint is `https://metrics.exasol.com`. +Applications can also disable telemetry explicitly in code with `TelemetryConfig.builder(...).disableTracking()`. + ## Documentation * [Developer Guide](doc/developer-guide.md) diff --git a/doc/app-user-guide.md b/doc/app-user-guide.md index 4f15c64..e296e1a 100644 --- a/doc/app-user-guide.md +++ b/doc/app-user-guide.md @@ -34,6 +34,7 @@ For Exasol's general privacy information, see the [Exasol Privacy Policy](https: Applications can use lifecycle log messages from the library to indicate whether telemetry is enabled or disabled. When telemetry is disabled, the library does not queue or send usage events. +Applications may disable telemetry through their own configuration and report that state in logs as `Telemetry is disabled via host configuration.`. ## How To Disable Telemetry diff --git a/doc/changes/changes_0.2.0.md b/doc/changes/changes_0.2.0.md index 56af214..b47f70d 100644 --- a/doc/changes/changes_0.2.0.md +++ b/doc/changes/changes_0.2.0.md @@ -1,10 +1,11 @@ -# Telemetry Java 0.2.0, released 2026-??-?? +# Telemetry Java 0.2.0, released 2026-04-?? -Code name: +Code name: Explicitly Disable Tracking ## Summary -## Features +The new release lets host applications disable telemetry directly in code. -* ISSUE_NUMBER: description +## Features +* #9: Add explicit host-controlled telemetry disablement via `TelemetryConfig.Builder.disableTracking()` diff --git a/doc/integration-guide.md b/doc/integration-guide.md index 64e21f5..c3f28d8 100644 --- a/doc/integration-guide.md +++ b/doc/integration-guide.md @@ -17,6 +17,7 @@ try (TelemetryClient client = TelemetryClient.create(config)) { - A project short tag and a product/library version at startup. The library adds the project tag as the telemetry category and includes the configured productVersion in every accepted telemetry event. - An optional HTTP endpoint for JSON `POST` delivery. If omitted, the default endpoint is `https://metrics.exasol.com`. +- An optional host-controlled disable switch via `TelemetryConfig.Builder.disableTracking()` when the integrating application exposes its own telemetry setting. ## Required Documentation @@ -52,6 +53,18 @@ For details on what is collected and how to disable telemetry, see the [document - `CI` Disables telemetry automatically when set to any non-empty value. +## Host-Controlled Disablement + +If the integrating application already has its own telemetry switch, map that setting to `TelemetryConfig.Builder.disableTracking()`. + +```java +TelemetryConfig config = TelemetryConfig.builder("MyApp", "1.2.3") + .disableTracking() + .build(); +``` + +When telemetry is disabled this way, the lifecycle `INFO` log reports `Telemetry is disabled via host configuration.`. + ### UDF Integration Tests For Exasol UDF integration tests, disable telemetry explicitly so test executions never emit usage data. In UDF script definitions, set `%env EXASOL_TELEMETRY_DISABLE=1;`. diff --git a/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/config/tracking-controls/spec.md b/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/config/tracking-controls/spec.md new file mode 100644 index 0000000..ca0ed5e --- /dev/null +++ b/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/config/tracking-controls/spec.md @@ -0,0 +1,18 @@ +# Feature: tracking-controls + +Adds an explicit host-configured disable option while preserving existing environment-based tracking controls. + +## Background + +Host applications may expose their own telemetry opt-out setting and need a direct way to propagate that choice into `telemetry-java`. Environment variables still disable telemetry automatically in deployment and CI environments, and `EXASOL_TELEMETRY_ENDPOINT` continues to override the configured endpoint independently of disablement. + +## Scenarios + + +### Scenario: Disables tracking via explicit host configuration + +* *GIVEN* the host application configures telemetry as disabled in code +* *WHEN* the host application initializes the library and records feature usage +* *THEN* the library SHALL disable telemetry collection and delivery +* *AND* the library MUST NOT enqueue or send usage events while disabled + diff --git a/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/lifecycle/status-logging/spec.md b/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/lifecycle/status-logging/spec.md new file mode 100644 index 0000000..ef05c4e --- /dev/null +++ b/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/lifecycle/status-logging/spec.md @@ -0,0 +1,19 @@ +# Feature: status-logging + +Updates disabled lifecycle logging so it reports host-configured telemetry disablement distinctly from environment-based disablement. + +## Background + +The telemetry lifecycle log already reports when telemetry is enabled, disabled, sending, or stopped. Once host applications can disable telemetry explicitly in code, the disabled message needs to tell users whether disablement came from host configuration or from `EXASOL_TELEMETRY_DISABLE` or `CI`. + +## Scenarios + + +### Scenario: Logs when telemetry is disabled + +* *GIVEN* the telemetry client is created and telemetry is disabled +* *WHEN* initialization completes +* *THEN* the library SHALL log at `INFO` level that telemetry is disabled +* *AND* the library SHALL identify whether telemetry was disabled by host configuration, `EXASOL_TELEMETRY_DISABLE`, or `CI` +* *AND* the library SHALL include the actual env-var value that caused disablement when disablement came from `EXASOL_TELEMETRY_DISABLE` or `CI` + diff --git a/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/plan.md b/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/plan.md new file mode 100644 index 0000000..feddcd7 --- /dev/null +++ b/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/plan.md @@ -0,0 +1,100 @@ +# Plan: change-tracking-controls-explicit-disable-option + +## Summary + +This plan extends `tracking-controls` with an explicit host-configured disable option so applications and libraries can turn telemetry off from their own configuration model without relying on process-level environment variables. It also updates status logging so the disabled message tells users whether telemetry was disabled by host configuration or by environment-based controls. + +## Design + +### Context + +The library currently disables telemetry only when `EXASOL_TELEMETRY_DISABLE` or `CI` is set in the environment, and the disabled log message only reports those environment-driven mechanisms. Issue `#9` requires a host-facing configuration option because some integrating applications expose their own telemetry flag, for example `TELEMETRY=false`, and need a direct way to map that setting into `telemetry-java` while still producing an accurate lifecycle log message. + +- **Goals** — Add an explicit disable control to the public configuration surface, keep integration simple for host applications, preserve existing environment-driven disable behavior, and make the disabled lifecycle log identify the correct disable mechanism. +- **Non-Goals** — Add a general configuration framework, allow explicit configuration to re-enable telemetry when environment-based disablement is active, or change payload shape, retry behavior, or endpoint override semantics beyond the disabled log message. + +### Decision + +Add a builder-level disable switch to `TelemetryConfig` and define disabled tracking as the logical OR of explicit configuration, `EXASOL_TELEMETRY_DISABLE`, and `CI`. The explicit option gives host applications a stable integration point for their own settings, while environment variables remain authoritative opt-out mechanisms for deployment environments and CI, and the status log reports whichever mechanism actually disabled telemetry. + +#### Architecture + +`host app config -> TelemetryConfig.Builder.disableTracking(...) -> TelemetryConfig -> TelemetryClient.create(...) -> disabled no-op client or async client` + +#### Patterns + +| Pattern | Where | Why | +|---------|-------|-----| +| Builder option | `TelemetryConfig.Builder` | Matches the existing public configuration surface and keeps host integration explicit | +| Monotonic disable resolution | `TelemetryConfig` | Multiple disable mechanisms compose safely without creating override ambiguity | +| No-op runtime branch | `TelemetryClient.create(...)` | Reuses the existing disabled-client path instead of adding new runtime states | +| Mechanism-aware lifecycle log | `TelemetryClient.logDisabled(...)` | Keeps the disabled status message aligned with the configured disable source | + +### Consequences + +| Decision | Alternatives Considered | Rationale | +|----------|------------------------|-----------| +| Add explicit disablement on `TelemetryConfig.Builder` | Add a separate factory or system-property-based switch | The builder is already the public configuration entry point and keeps the API small | +| Treat disablement as explicit-config OR environment OR CI | Let explicit config override environment disablement | Environment and CI opt-out remain the safer, deployment-controlled mechanisms | +| Update the disabled log to report host configuration when applicable | Leave the log environment-specific or make it generic | Users should see why telemetry is disabled without losing the distinction between host-configured and environment-configured disablement | +| Keep endpoint override and identity behavior unchanged | Revisit all tracking control rules together | Issue `#9` is scoped to host-controlled disablement only | + +## Features + +| Feature | Status | Spec | +|---------|--------|------| +| tracking-controls | CHANGED | `config/tracking-controls/spec.md` | +| status-logging | NEW | `lifecycle/status-logging/spec.md` | + +## Requirements + +| Requirement | Details | +|-------------|---------| +| Explicit disable API | The public configuration surface SHALL provide a host-controlled way to disable telemetry without environment variables | +| Disable precedence | Explicit configuration, `EXASOL_TELEMETRY_DISABLE`, and `CI` SHALL each disable tracking independently | +| No-op behavior | Explicitly disabled tracking SHALL keep collection and delivery as no-ops | +| Disabled log accuracy | The disabled lifecycle log SHALL identify whether disablement came from host configuration, `EXASOL_TELEMETRY_DISABLE`, or `CI` | +| Compatibility | Existing endpoint override and environment-based disable scenarios SHALL remain unchanged | + +## Implementation Tasks + +1. Add a builder-level disable option to `TelemetryConfig` and include it in disabled-state resolution. +2. Extend the `tracking-controls` spec with a scenario for explicit host-configured disablement. +3. Extend `status-logging` so the disabled `INFO` message distinguishes host-configured disablement from `EXASOL_TELEMETRY_DISABLE` and `CI`. +4. Add unit coverage for disabled-state resolution when the explicit option is set alone and alongside environment variables. +5. Add or extend integration coverage to verify that explicitly disabled telemetry does not emit requests and logs the correct disable mechanism. +6. Update user-facing docs where configuration-based disablement is described for integrators or end users. + +## Dead Code Removal + +| Type | Location | Reason | +|------|----------|--------| +| None | N/A | The change extends existing tracking-control behavior without removing a feature | + +## Verification + +### Scenario Coverage + +| Scenario | Test Type | Test Location | Test Name | +|----------|-----------|---------------|-----------| +| Disables tracking via explicit host configuration | Integration | `src/test/java/com/exasol/telemetry/TrackingControlsIT.java` | `disablesTrackingViaExplicitConfiguration` | +| Disables tracking via environment variables | Integration | `src/test/java/com/exasol/telemetry/TrackingControlsIT.java` | `disablesTrackingViaEnvironmentVariables` | +| Disables tracking automatically in CI | Integration | `src/test/java/com/exasol/telemetry/TrackingControlsIT.java` | `disablesTrackingAutomaticallyWhenCiIsNonEmpty` | +| Overrides the configured endpoint via environment variable | Integration | `src/test/java/com/exasol/telemetry/TrackingControlsIT.java` | `overridesConfiguredEndpointViaEnvironmentVariable` | +| Logs when telemetry is disabled | Integration | `src/test/java/com/exasol/telemetry/StatusLoggingIT.java` | `logsWhenTelemetryIsDisabledWithMechanism` | + +### Manual Testing + +| Feature | Command | Expected Output | +|---------|---------|-----------------| +| tracking-controls | `mvn verify` | Tests confirm explicit disablement and environment-driven controls keep telemetry disabled and preserve endpoint override behavior | +| status-logging | `mvn verify` | Tests confirm the disabled `INFO` log reports host configuration, `EXASOL_TELEMETRY_DISABLE`, or `CI` as the disable mechanism | + +### Checklist + +| Step | Command | Expected | +|------|---------|----------| +| Build | `mvn package` | Exit 0 | +| Test | `mvn test` | 0 failures | +| Lint | `mvn verify` | Exit 0 | +| Format | `mvn verify` | Exit 0; no dedicated formatter command is defined in the mission | diff --git a/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/tasks.md b/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/tasks.md new file mode 100644 index 0000000..6b0f074 --- /dev/null +++ b/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/tasks.md @@ -0,0 +1,17 @@ +# Tasks: change-tracking-controls-explicit-disable-option + +## Phase 2: Implementation +- [x] 2.1 Add a builder-level disable option to `TelemetryConfig` and include it in disabled-state resolution. +- [x] 2.2 Extend the `tracking-controls` spec with a scenario for explicit host-configured disablement. +- [x] 2.3 Extend `status-logging` so the disabled `INFO` message distinguishes host-configured disablement from `EXASOL_TELEMETRY_DISABLE` and `CI`. +- [x] 2.4 Add unit coverage for disabled-state resolution when the explicit option is set alone and alongside environment variables. +- [x] 2.5 Add or extend integration coverage to verify that explicitly disabled telemetry does not emit requests and logs the correct disable mechanism. +- [x] 2.6 Update user-facing docs where configuration-based disablement is described for integrators or end users. + +## Phase 3: Verification +- [x] 3.1 Run targeted RED/GREEN tests for changed behavior. +- [x] 3.2 Run `mvn package`. +- [x] 3.3 Run `mvn test`. +- [x] 3.4 Run `mvn verify`. +- [x] 3.5 Audit scenario coverage against the plan. +- [x] 3.6 Produce `verification-report.md`. diff --git a/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/verification-report.md b/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/verification-report.md new file mode 100644 index 0000000..b607eef --- /dev/null +++ b/specs/_recorded/2026-04-17-change-tracking-controls-explicit-disable-option/verification-report.md @@ -0,0 +1,72 @@ +# Verification Report: change-tracking-controls-explicit-disable-option + +**Generated:** 2026-04-17 + +## Verdict + +| Result | Details | +|--------|---------| +| **PASS** | The explicit host-disable feature, disabled log message update, targeted tests, `mvn package`, `mvn test`, `mvn verify`, and OpenFastTrace trace all passed. | + +| Check | Status | +|-------|--------| +| Build | ✓ | +| Tests | ✓ | +| Lint | ✓ | +| Format | ✓ | +| Scenario Coverage | ✓ | +| Manual Tests | ✓ | + +## Test Evidence + +### Coverage + +| Type | Coverage % | +|------|------------| +| Unit | N/A | +| Integration | N/A | + +### Test Results + +| Type | Run | Passed | Ignored | +|------|-----|--------|---------| +| Unit + Integration | `mvn -Dtest=TelemetryConfigTest,TrackingControlsIT,StatusLoggingIT test` | 20 | 0 | +| Unit | `mvn test` | 27 | 0 | +| Traceability | `mvn generate-sources org.itsallcode:openfasttrace-maven-plugin:trace` | 60 traced items, 0 defects | 0 | + +### Manual Tests + +| Test | Result | +|------|--------| +| `mvn verify` for `tracking-controls` and `status-logging` | ✓ | + +## Tool Evidence + +### Linter + +```text +mvn verify +[INFO] BUILD SUCCESS +``` + +### Formatter + +```text +No dedicated formatter command is defined in the mission. The checklist maps format to `mvn verify`, which now passes. +``` + +## Scenario Coverage + +| Domain | Feature | Scenario | Test Location | Test Name | Passes | +|--------|---------|----------|---------------|-----------|--------| +| config | tracking-controls | Disables tracking via explicit host configuration | `src/test/java/com/exasol/telemetry/TrackingControlsIT.java` | `disablesTrackingViaExplicitConfiguration` | Pass | +| config | tracking-controls | Disables tracking via environment variables | `src/test/java/com/exasol/telemetry/TrackingControlsIT.java` | `disablesTrackingViaEnvironmentVariables` | Pass | +| config | tracking-controls | Disables tracking automatically in CI | `src/test/java/com/exasol/telemetry/TrackingControlsIT.java` | `disablesTrackingAutomaticallyWhenCiIsNonEmpty` | Pass | +| config | tracking-controls | Overrides the configured endpoint via environment variable | `src/test/java/com/exasol/telemetry/TrackingControlsIT.java` | `overridesConfiguredEndpointViaEnvironmentVariable` | Pass | +| lifecycle | status-logging | Logs when telemetry is disabled | `src/test/java/com/exasol/telemetry/StatusLoggingIT.java` | `logsWhenTelemetryIsDisabledViaHostConfiguration`, `logsWhenTelemetryIsDisabledViaEnvironmentVariable`, `logsWhenTelemetryIsDisabledViaCi` | Pass | + +## Notes + +- `TelemetryConfig.Builder.disableTracking()` was added as the explicit host-controlled opt-out API. +- Disabled logging now emits `Telemetry is disabled via host configuration.` when telemetry is disabled in code and still prefers `EXASOL_TELEMETRY_DISABLE` or `CI` when those environment variables are set. +- An initial `mvn test` run failed because stale build outputs left `TelemetryEventTest` unresolved; `mvn clean test` fixed the workspace state and subsequent `mvn test` and `mvn verify` passed. diff --git a/specs/config/tracking-controls/spec.md b/specs/config/tracking-controls/spec.md index aed3fbb..d440346 100644 --- a/specs/config/tracking-controls/spec.md +++ b/specs/config/tracking-controls/spec.md @@ -32,6 +32,13 @@ Tracking can be deactivated by `EXASOL_TELEMETRY_DISABLE` or automatically by `C * *THEN* the library SHALL disable telemetry collection and delivery * *AND* the library MUST NOT enqueue or send usage events while disabled +### Scenario: Disables tracking via explicit host configuration + +* *GIVEN* the host application configures telemetry as disabled in code +* *WHEN* the host application initializes the library and records feature usage +* *THEN* the library SHALL disable telemetry collection and delivery +* *AND* the library MUST NOT enqueue or send usage events while disabled + ### Scenario: Overrides the configured endpoint via environment variable * *GIVEN* the host application configures an endpoint, project tag, and `productVersion` in code diff --git a/specs/lifecycle/status-logging/spec.md b/specs/lifecycle/status-logging/spec.md index 2c6ea85..2ee9e79 100644 --- a/specs/lifecycle/status-logging/spec.md +++ b/specs/lifecycle/status-logging/spec.md @@ -31,8 +31,8 @@ These log messages are informational lifecycle messages only. They do not includ * *GIVEN* the telemetry client is created and telemetry is disabled * *WHEN* initialization completes * *THEN* the library SHALL log at `INFO` level that telemetry is disabled -* *AND* the library SHALL identify whether telemetry was disabled by `EXASOL_TELEMETRY_DISABLE` or `CI` -* *AND* the library SHALL include the actual env-var value that caused disablement +* *AND* the library SHALL identify whether telemetry was disabled by host configuration, `EXASOL_TELEMETRY_DISABLE`, or `CI` +* *AND* the library SHALL include the actual env-var value that caused disablement when disablement came from `EXASOL_TELEMETRY_DISABLE` or `CI` ### Scenario: Logs message counts when telemetry is sent diff --git a/src/main/java/com/exasol/telemetry/TelemetryClient.java b/src/main/java/com/exasol/telemetry/TelemetryClient.java index 57a0e1a..a10dc26 100644 --- a/src/main/java/com/exasol/telemetry/TelemetryClient.java +++ b/src/main/java/com/exasol/telemetry/TelemetryClient.java @@ -48,8 +48,13 @@ private static void logEnabled(final TelemetryConfig config) { // [impl~telemetry-client-log-disabled~1->req~status-logging~1] private static void logDisabled(final TelemetryConfig config) { - logger().info(() -> "Telemetry is disabled via " + config.getDisableMechanism() + "=" - + formatEnvValue(config.getDisableMechanismValue()) + "."); + final String mechanism = config.getDisableMechanism(); + final String mechanismValue = config.getDisableMechanismValue(); + if (mechanismValue == null) { + logger().info(() -> "Telemetry is disabled via " + mechanism + "."); + return; + } + logger().info(() -> "Telemetry is disabled via " + mechanism + "=" + formatEnvValue(mechanismValue) + "."); } // Create logger in private method to avoid a public static field in the interface. diff --git a/src/main/java/com/exasol/telemetry/TelemetryConfig.java b/src/main/java/com/exasol/telemetry/TelemetryConfig.java index fb1a848..087e996 100644 --- a/src/main/java/com/exasol/telemetry/TelemetryConfig.java +++ b/src/main/java/com/exasol/telemetry/TelemetryConfig.java @@ -32,6 +32,7 @@ public final class TelemetryConfig { private final URI endpoint; private final String disabledEnvValue; private final String ciEnvValue; + private final boolean explicitlyDisabled; private final int queueCapacity; private final Duration retryTimeout; private final Duration initialRetryDelay; @@ -47,6 +48,7 @@ private TelemetryConfig(final Builder builder) { this.environment = requireNonNull(builder.environment, "environment"); this.disabledEnvValue = environment.getenv(DISABLED_ENV); this.ciEnvValue = environment.getenv(CI_ENV); + this.explicitlyDisabled = builder.trackingDisabled; this.endpoint = resolveEndpoint(builder.endpoint, environment); this.queueCapacity = positive(builder.queueCapacity, "queueCapacity"); this.retryTimeout = positive(builder.retryTimeout, "retryTimeout"); @@ -54,13 +56,13 @@ private TelemetryConfig(final Builder builder) { this.maxRetryDelay = positive(builder.maxRetryDelay, "maxRetryDelay"); this.connectTimeout = positive(builder.connectTimeout, "connectTimeout"); this.requestTimeout = positive(builder.requestTimeout, "requestTimeout"); - this.trackingDisabled = isDisabled(disabledEnvValue) || isDisabled(ciEnvValue); + this.trackingDisabled = explicitlyDisabled || isDisabled(disabledEnvValue) || isDisabled(ciEnvValue); } /** * Creates a builder for a telemetry configuration bound to the given project tag and product version. * - * @param projectTag project identifier attached to emitted telemetry messages as {@code category} + * @param projectTag project identifier attached to emitted telemetry messages as {@code category} * @param productVersion host product or library version attached to emitted telemetry messages as {@code productVersion} * @return configuration builder */ @@ -123,6 +125,9 @@ String getDisableMechanism() { if (isDisabled(ciEnvValue)) { return CI_ENV; } + if (explicitlyDisabled) { + return "host configuration"; + } return null; } @@ -180,6 +185,7 @@ public static final class Builder { private final String projectTag; private final String productVersion; private URI endpoint; + private boolean trackingDisabled; private int queueCapacity = 256; private Duration retryTimeout = Duration.ofSeconds(5); private Duration initialRetryDelay = Duration.ofMillis(100); @@ -199,6 +205,16 @@ Builder endpoint(final URI endpoint) { return this; } + /** + * Disable telemetry collection and delivery explicitly in host configuration. + * + * @return this builder + */ + public Builder disableTracking() { + this.trackingDisabled = true; + return this; + } + Builder queueCapacity(final int queueCapacity) { this.queueCapacity = queueCapacity; return this; diff --git a/src/test/java/com/exasol/telemetry/StatusLoggingIT.java b/src/test/java/com/exasol/telemetry/StatusLoggingIT.java index 3e2a31b..67fea24 100644 --- a/src/test/java/com/exasol/telemetry/StatusLoggingIT.java +++ b/src/test/java/com/exasol/telemetry/StatusLoggingIT.java @@ -34,9 +34,24 @@ void logsWhenTelemetryIsEnabled() throws Exception { } } + @Test + void logsWhenTelemetryIsDisabledViaHostConfiguration() throws Exception { + try (LogCapture capture = new LogCapture(); + RecordingHttpServer server = RecordingHttpServer.createSuccessServer(); + TelemetryClient client = TelemetryClient.create(server.configBuilder(PROJECT_TAG, VERSION) + .disableTracking() + .build())) { + client.track(FEATURE); + final LogRecord hostRecord = capture.await(logRecord -> logRecord.getLevel() == Level.INFO + && logRecord.getMessage().contains("Telemetry is disabled via host configuration."), Duration.ofSeconds(1)); + + assertThat(hostRecord.getMessage(), is("Telemetry is disabled via host configuration.")); + } + } + @Test // [itest~status-logging-disabled~1->req~status-logging~1] - void logsWhenTelemetryIsDisabledWithMechanism() throws Exception { + void logsWhenTelemetryIsDisabledViaEnvironmentVariable() throws Exception { try (LogCapture capture = new LogCapture(); RecordingHttpServer server = RecordingHttpServer.createSuccessServer(); TelemetryClient client = TelemetryClient.create(server.configBuilder(PROJECT_TAG, VERSION) @@ -48,7 +63,10 @@ void logsWhenTelemetryIsDisabledWithMechanism() throws Exception { assertThat(envRecord.getMessage(), containsString("EXASOL_TELEMETRY_DISABLE='disabled'")); } + } + @Test + void logsWhenTelemetryIsDisabledViaCi() throws Exception { try (LogCapture capture = new LogCapture(); RecordingHttpServer server = RecordingHttpServer.createSuccessServer(); TelemetryClient client = TelemetryClient.create(server.configBuilder(PROJECT_TAG, VERSION) diff --git a/src/test/java/com/exasol/telemetry/TelemetryConfigTest.java b/src/test/java/com/exasol/telemetry/TelemetryConfigTest.java index c543786..791584f 100644 --- a/src/test/java/com/exasol/telemetry/TelemetryConfigTest.java +++ b/src/test/java/com/exasol/telemetry/TelemetryConfigTest.java @@ -1,8 +1,7 @@ package com.exasol.telemetry; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import static org.junit.jupiter.api.Assertions.assertThrows; import java.net.URI; @@ -56,6 +55,32 @@ void usesEndpointOverrideAndDisableEnvironmentValues() { assertThat(config.isTrackingDisabled(), is(true)); } + // [utest~telemetry-config-explicit-disable~1->req~tracking-controls~1] + @Test + void disablesTrackingExplicitlyInHostConfiguration() { + final TelemetryConfig config = defaultBuilder() + .disableTracking() + .environment(MapEnvironment.empty()) + .build(); + + assertThat(config.isTrackingDisabled(), is(true)); + assertThat(config.getDisableMechanism(), is("host configuration")); + assertThat(config.getDisableMechanismValue(), is(nullValue())); + } + + // [utest~telemetry-config-disable-mechanism-precedence~1->req~tracking-controls~1] + @Test + void prefersEnvironmentDisableMechanismOverHostConfiguration() { + final TelemetryConfig config = defaultBuilder() + .disableTracking() + .environment(new MapEnvironment(Map.of(TelemetryConfig.DISABLED_ENV, "disabled"))) + .build(); + + assertThat(config.isTrackingDisabled(), is(true)); + assertThat(config.getDisableMechanism(), is(TelemetryConfig.DISABLED_ENV)); + assertThat(config.getDisableMechanismValue(), is("disabled")); + } + // [utest~telemetry-config-disable-in-ci~1->req~tracking-controls~1] @Test void disablesTrackingAutomaticallyInCi() { diff --git a/src/test/java/com/exasol/telemetry/TrackingControlsIT.java b/src/test/java/com/exasol/telemetry/TrackingControlsIT.java index e9d650d..2efac78 100644 --- a/src/test/java/com/exasol/telemetry/TrackingControlsIT.java +++ b/src/test/java/com/exasol/telemetry/TrackingControlsIT.java @@ -14,6 +14,20 @@ class TrackingControlsIT { private static final String VERSION = "1.2.3"; private static final String FEATURE = "myFeature"; + // [itest~tracking-controls-disable-explicit-config~1->req~tracking-controls~1] + @Test + void disablesTrackingViaExplicitConfiguration() throws Exception { + try (RecordingHttpServer server = RecordingHttpServer.createSuccessServer(); + TelemetryClient client = TelemetryClient.create(server.configBuilder(PROJECT_TAG, VERSION) + .disableTracking() + .build())) { + client.track(FEATURE); + + Thread.sleep(150); + assertThat(server.awaitRequests(1, Duration.ofMillis(150)), empty()); + } + } + // [itest~tracking-controls-disable-env~1->req~tracking-controls~1] @Test void disablesTrackingViaEnvironmentVariables() throws Exception {