feat: support custom HTTP headers (API gateway / IBM APIC) - #629
Conversation
The distributed/streaming ingestion client (`IngestTraces`) builds its own httpx client and bypasses galileo-core's `ApiClient`, so it did not carry the custom headers added in galileo-core. Wire `extra_headers` from the config through to `IngestTraces` so gateway headers (e.g. IBM APIC's `ibm_client_id` / `ibm_client_secret`) are sent on every ingestion request too. - `IngestTraces` now accepts `extra_headers` and merges them into its request headers, with Galileo's own headers taking precedence on key collisions. - `GalileoLogger._create_traces_client` passes `config.extra_headers`. - `GalileoPythonConfig` inherits the `extra_headers` field from galileo-core, so the standard API and batch-ingestion paths are already covered. Requires galileo-core with `extra_headers` support. Co-Authored-By: Claude <noreply@anthropic.com>
CI (and users) may resolve a galileo-core version that predates the extra_headers field. Use getattr with a None fallback so trace-client creation does not raise AttributeError against older core releases. Co-Authored-By: Claude <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #629 +/- ##
==========================================
+ Coverage 83.53% 83.62% +0.09%
==========================================
Files 124 124
Lines 11029 11037 +8
==========================================
+ Hits 9213 9230 +17
+ Misses 1816 1807 -9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
✅ End-to-end validation against a live Galileo backendBefore release, this change was validated end-to-end through a mock IBM APIC gateway fronting a real Galileo API, proving the SDK carries the custom headers on every egress path. The gateway enforces Results — all PASS
Proof — gateway log shows every SDK egress path carried the headersEvery pre-auth ( Validated with the local working copies of both |
✅ Trace logging verified end-to-end through DevStack mock APICBuilding on the earlier gateway validation, I took the simplest run-Galileo demo ( The pushGateway log — every logging call routed through APIC → Galileo APIEach call required valid IBM headers to pass the gateway and was forwarded to the real Galileo API. Persistence confirmed — queried the trace back through the gatewayThe exact record pushed — input, tags, and metadata — was read back, proving the trace was truly logged, not merely accepted. TakeawayThe full customer scenario works: the Galileo SDK, configured with Ingestion used the standard |
…e collision Addresses baz-reviewer findings on PR #629: - Forward GALILEO_EXTRA_HEADERS on the /ingest/healthz availability probe so a gateway that enforces IBM APIC headers on every request (health checks included) doesn't 401 the probe and make the ingest service look unavailable. - Drop extra headers that case-insensitively collide with Galileo's reserved headers (Content-Type / Galileo-API-Key / X-Galileo-SDK) so a differently-cased duplicate can't put two copies on the wire. - Doc + tests updated accordingly. Co-Authored-By: Claude <noreply@anthropic.com>
Addressed review feedback (commit ecb412c)Thanks @baz-reviewer — all three findings were valid and are now fixed. 1 & 2 — Headerless 3 — Case-insensitive header collision (Security, low). The identical case-insensitive guard was applied to Tests: |
No code changes. All three baz-reviewer findings were addressed in ecb412c (the bot posted follow-up comments confirming each as addressed); this empty commit fires a fresh PR synchronize event to re-run the review check. Co-Authored-By: Claude <noreply@anthropic.com>
Addresses baz-reviewer findings on PR #629: - The /ingest/healthz result was cached in a single process-wide slot, so a probe made under one api_url/extra_headers kept forcing the standard Traces client even after GalileoPythonConfig.reset() or a later get(extra_headers=...) changed where or how we probe. Key the cache by (api_url, sorted extra_headers) via _ingest_cache_key so a config change re-probes instead of reusing a stale result. Added test_is_ingest_service_available_reprobes_when_config_changes. - Tidy the Given/When/Then comment style in the ingest-probe test per AGENTS.md. Co-Authored-By: Claude <noreply@anthropic.com>
Addressed (commit 198514b)
47 tests pass across |
fercor-cisco
left a comment
There was a problem hiding this comment.
🤖 This review was generated by the Astra agent (claude-opus-4-8). It may contain mistakes.
Verdict: approve — Well-implemented, thoroughly tested feature; all prior review comments addressed and verified. Only a non-blocking dependency-pin concern remains.
General Comments
- 🟡 minor (design): This feature is only functional when
galileo-coreactually provides theextra_headersfield, butpyproject.tomlstill pinsgalileo-core = "^4.4.0"and the code usesgetattr(config, "extra_headers", None)fallbacks everywhere. If a user resolves/locks to a core release without the field, settingGALILEO_EXTRA_HEADERSsilently no-ops on every path (distributed ingest via the getattr fallback; resource-management/batch/login via core) with no warning or error — a hard-to-diagnose failure mode for a gateway-credentials feature, since the gateway will simply return 401. The PR body acknowledges the release ordering, but the version constraint doesn't enforce it. Recommend bumping thegalileo-coreminimum to the release that addsextra_headers(in both the main and test dependency entries) once it's published, so a fresh resolve can't pick an incompatible core.
Follow-ups
Suggested follow-up work that could be tracked as Jira tickets:
pyproject.toml:39-57: After the corresponding galileo-core change is published to PyPI, bump thegalileo-coreversion constraint (lines 39 and 57) from^4.4.0to the release that introduces theextra_headersfield, so the SDK can't resolve to a core that silently ignores GALILEO_EXTRA_HEADERS. At that point the defensivegetattr(config, "extra_headers", None)fallbacks in logger.py/traces.py could also be simplified to direct attribute access.
User description
Summary
Adds support for sending customer-defined HTTP headers on every request the SDK makes to the Galileo API. This is required when the Galileo API is fronted by an API gateway (e.g. IBM APIC) that expects client credentials such as
ibm_client_id/ibm_client_secreton each call.The feature is header-agnostic: the customer defines both the header names and values via a single config knob.
What changed (this repo)
The distributed/streaming ingestion client (
IngestTraces) builds its ownhttpx.AsyncClientand bypassesgalileo-core'sApiClient, so it needs the headers wired in explicitly:IngestTracesnow acceptsextra_headersand merges them into its request headers, with Galileo's own headers (Galileo-API-Key,Content-Type,X-Galileo-SDK) taking precedence on key collisions.GalileoLogger._create_traces_clientpassesconfig.extra_headers.GalileoPythonConfiginherits the newextra_headersfield fromgalileo-core, so the standard API and batch-ingestion paths are already covered by the core change.Configuration (customer-facing)
or programmatically:
Dependency
galileo-corechange: rungalileo/orbit#1376 (feat(core): support custom extra headers on every request). That PR adds theextra_headersfield toApiClient/GalileoConfigand applies it to all core egress paths (resource management, batch ingestion, and pre-auth login/healthcheck). This SDK PR only covers the distributed-ingestion path that bypasses core, and must land/release after the core change is published to PyPI.Testing
TestIngestTracesExtraHeadersintests/test_traces_client_headers.pycovering: extra headers present, Galileo headers win on collision, and default (no extra headers).🤖 Generated with Claude Code
Generated description
Below is a concise technical summary of the changes proposed in this PR:
Add support for customer-defined HTTP headers across the SDK's distributed ingest flow by wiring
extra_headersintoIngestTraces,GalileoLogger, and the ingest health probe. Update the configuration docs and tests so gateway credentials likeibm_client_id/ibm_client_secretare sent on every relevant request while Galileo's own headers still win on collisions.IngestTracesandGalileoLogger, and key the ingest health check cache byapi_urlplusextra_headersso gateway-authenticated probes rerun correctly.Modified files (3)
Latest Contributors(2)
Modified files (2)
Latest Contributors(2)