Skip to content

feat: add diagnostics API#219

Open
vazarkevych wants to merge 1 commit into
mainfrom
feat/add-diagnostics-api
Open

feat: add diagnostics API#219
vazarkevych wants to merge 1 commit into
mainfrom
feat/add-diagnostics-api

Conversation

@vazarkevych

@vazarkevych vazarkevych commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR adds a diagnostics API for GrowthBookClient.

The new API exposes a read-only snapshot of the current SDK state:

Diagnostics diagnostics = client.getDiagnostics();
String json = diagnostics.toJson();

The goal is to make SDK support and debugging easier by answering common runtime questions:

  • Is the client initialized?
  • Is feature data loaded?
  • When were features last refreshed?
  • Is the current feature data stale?
  • Is caching enabled and when was it updated?
  • Is streaming configured, connected, or interrupted?
  • What SDK configuration is currently active?
  • What was the last refresh error?
  • Are refreshes succeeding or repeatedly failing?

Motivation

Before this change, users had very limited visibility into the internal SDK state.

If feature evaluations behaved unexpectedly, support and debugging usually required manually checking configuration, logs, cache state, refresh behavior, and streaming behavior separately.

This PR introduces a structured diagnostics snapshot that can be inspected programmatically or copied as JSON into a support ticket.

Public API

A new method is available on GrowthBookClient:

Diagnostics diagnostics = client.getDiagnostics();

The returned object is immutable and read-only.

Calling getDiagnostics() does not trigger:

  • feature evaluation
  • feature refresh
  • network calls
  • state mutation

A JSON helper is also available:

String diagnosticsJson = diagnostics.toJson();

Diagnostics Response

The diagnostics response is organized into several sections.

sdk

SDK metadata.

{
  "sdk": {
    "version": "..."
  }
}

health

A high-level derived health summary.

{
  "health": {
    "state": "READY",
    "issues": []
  }
}

Possible health states:

  • READY
  • NOT_READY
  • DEGRADED
  • ERROR
  • SHUTDOWN

Health issues include stable machine-readable codes, severity, message, and timestamp.

Example:

{
  "code": "FEATURES_STALE",
  "severity": "WARNING",
  "message": "Feature data is past its cache expiry.",
  "timestampMillis": 123456789
}

config

Sanitized SDK configuration metadata.

{
  "config": {
    "apiHost": "https://custom.growthbook.io",
    "featuresEndpoint": "https://custom.growthbook.io/api/features/cust..._key",
    "eventsEndpoint": "https://custom.growthbook.io/sub/cust..._key",
    "clientKeyMasked": "cust..._key",
    "encryptionConfigured": true,
    "swrTtlSeconds": 60,
    "backgroundFetchIntervalMillis": null,
    "retryMaxAttempts": 5,
    "enabled": true,
    "qaMode": false,
    "stickyBucketingEnabled": false
  }
}

Sensitive values are not exposed:

  • clientKey is masked
  • encryption/decryption keys are never returned
  • encryption state is exposed only as encryptionConfigured

client

Client lifecycle state.

{
  "client": {
    "initialized": true,
    "shutdown": false,
    "remoteEvalEnabled": false
  }
}

features

Current feature data state.

{
  "features": {
    "state": "LOADED",
    "source": "LOCAL_REPOSITORY",
    "available": true,
    "activeFeatureCount": 12,
    "totalFeatureCount": 12,
    "keys": {
      "available": true,
      "totalCount": 12,
      "sampleLimit": 20,
      "sample": ["checkout-flow", "payment-config"],
      "truncated": false,
      "sha256": "..."
    }
  }
}

Feature keys are sampled and hashed to make it easier to compare feature snapshots without dumping the full feature payload.

refresh

Feature refresh state, freshness, errors, and counters.

{
  "refresh": {
    "strategy": "STALE_WHILE_REVALIDATE",
    "lastSuccessfulRefreshAtMillis": 123456789,
    "lastSuccessfulRefreshAgeMillis": 1000,
    "nextCacheExpiresAtMillis": 123516789,
    "millisUntilCacheExpiry": 59000,
    "stale": false,
    "successCount": 1,
    "failureCount": 0,
    "consecutiveFailures": 0,
    "lastFailureAtMillis": null,
    "lastLoadedFromCache": false,
    "lastError": null
  }
}

This makes it possible to distinguish between:

  • features were never refreshed
  • features were recently refreshed
  • current feature data is stale
  • refresh is repeatedly failing
  • SDK is falling back to cached data

cache

Cache status and freshness.

{
  "cache": {
    "state": "AVAILABLE",
    "mode": "AUTO",
    "enabled": true,
    "lastUpdatedMillis": 123456789,
    "ageMillis": 1000
  }
}

streaming

Streaming / SSE state.

{
  "streaming": {
    "state": "CONNECTED",
    "reconnectAttempts": 0
  }
}

Possible streaming states include:

  • DISABLED
  • INITIALIZING
  • CONNECTED
  • INTERRUPTED
  • UNSUPPORTED
  • SHUTDOWN

remoteEval

Remote evaluation configuration and status.

{
  "remoteEval": {
    "enabled": false,
    "ready": false,
    "cacheConfigured": false,
    "cacheSize": 1000,
    "cacheTtlSeconds": null
  }
}

Note: cacheSize and cacheTtlSeconds currently represent configured remote-eval cache values, not live cache occupancy.

Security

Diagnostics output is intended to be safe for support workflows.

This PR avoids exposing sensitive values:

  • raw clientKey is masked
  • full feature and SSE endpoints are masked
  • decryptionKey / legacy encryptionKey values are never returned
  • refresh error messages are sanitized before being exposed
  • toJson() serializes already-sanitized diagnostics data

Example:

https://custom.growthbook.io/api/features/custom_key using test_key

becomes:

https://custom.growthbook.io/api/features/cust..._key using [redacted]

Implementation Notes

  • Diagnostics are built as a fresh snapshot on each getDiagnostics() call.
  • The API is read-only and does not affect feature evaluation.
  • Health is derived from lower-level diagnostics sections.
  • Feature key diagnostics include a sorted sample and SHA-256 hash.
  • Refresh counters are tracked in FeatureRefreshNotifier, even when no listeners are registered.
  • Shared endpoint and default constants were moved to SDKConstants.

esEndpoint() != null) {
lib/src/main/java/growthbook/sdk/java/diagnostics/provider/helper/ConfigDiagnosticsMapper.java:89: return repository.getFeaturesEndpoint();
lib/src/main/java/growthbook/sdk/java/diagnostics/provider/helper/ConfigDiagnosticsMapper.java:100: if (repository != null && repository.getEventsEndpoint() != null) {
lib/src/main/java/growthbook/sdk/java/diagnostics/provider/helper/ConfigDiagnosticsMapper.java:101: return repository.getEventsEndpoint();

Tests

Added dedicated diagnostics coverage for:

  • diagnostics before initialization
  • diagnostics after initialization
  • shutdown state
  • failed initialization errors
  • sanitized error messages in toJson()
  • stale feature data health issue
  • streaming derived states
  • remote-eval diagnostics

Also moved diagnostics-related GrowthBookClient tests into a dedicated
GrowthBookClientDiagnosticsTest class and extracted shared test fixtures.

Adds a read-only Diagnostics API to GrowthBookClient exposing SDK health,
config, feature, refresh, cache, streaming and remote-eval state:

- New diagnostics package with models, enums and provider helpers
  (health resolver, config/feature/error mappers, secret masker).
- getDiagnostics() on GrowthBookClient returning a sanitized snapshot
  (client keys masked, secrets/URL credentials redacted).
- Repository diagnostics accessors and refresh success/failure counters
  in GBFeaturesRepository, plus SSE connection state tracking.
- Shared SDKConstants for default endpoints and SWR TTL.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant