Skip to content

feat: add Auth0-Client telemetry header#103

Open
kishore7snehil wants to merge 4 commits intomainfrom
feat/telemetry
Open

feat: add Auth0-Client telemetry header#103
kishore7snehil wants to merge 4 commits intomainfrom
feat/telemetry

Conversation

@kishore7snehil
Copy link
Copy Markdown
Contributor

@kishore7snehil kishore7snehil commented Apr 21, 2026

Changes

  • Add Telemetry class (telemetry.py) that builds and caches the Auth0-Client header - a base64-encoded JSON payload with SDK name, version, and Python runtime environment
  • Add _get_http_client() helper to ServerClient, MyAccountClient, and MfaClient to inject telemetry headers into all httpx requests (17 call sites)
  • Pass telemetry headers to AsyncOAuth2Client for token exchange calls
  • Add unit tests for telemetry header format and integration

@kishore7snehil kishore7snehil requested a review from a team as a code owner April 21, 2026 19:13
arpit-jn
arpit-jn previously approved these changes Apr 24, 2026
Copy link
Copy Markdown
Contributor

@arpit-jn arpit-jn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread src/auth0_server_python/telemetry.py Outdated
self.name = name
self.version = version
self.env = env if env is not None else {"python": platform.python_version()}
self._cached_headers: Optional[dict[str, str]] = None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The headers are built lazily here, but get_headers() is only ever called once in ServerClient.__init__, where the result is immediately stored in _telemetry_headers. The cache in Telemetry is never needed.

You can directly assign it like this and use in _telemetry_headers

  def __init__(self, name, version, env=None):
      self.name = name                                                                                                                              
      self.version = version                                                                                                                        
      self.env = env if env is not None else {"python": platform.python_version()}                                                                  
      payload = {"name": self.name, "version": self.version, "env": self.env}                                                                       
      self.headers = {                   # ← built once, right here                                                                                 
          "Auth0-Client": base64.b64encode(json.dumps(payload).encode()).decode(),                                                                  
          "User-Agent": f"Python/{platform.python_version()}",                                                                                      
      }  

mock_http_client.__aenter__ = AsyncMock(return_value=mock_http_client)
mock_http_client.__aexit__ = AsyncMock(return_value=False)

mocker.patch.object(client, "_get_http_client", return_value=mock_http_client)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test patches _get_http_client and asserts it was called once but it doesn't verify the headers on the HTTP request. The test name is test_fetch_oidc_metadata_sends_telemetry, but it only checks that _get_http_client was invoked, not that telemetry headers were actually present.

Can we assert the header values here to match the test name ?

assert telemetry.version == "unknown"


class TestServerClientTelemetry:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are good tests for the _get_http_client path, but no test covering complete_interactive_login which is the only place
self._oauth.fetch_token(...) is called. That code path goes through Authlib's AsyncOAuth2Client, not through _get_http_client, so it's a different code path entirely.

Can we add a test that verifies telemetry headers are sent during the authorization code exchange ?

Base automatically changed from feat/mfa-api to main April 24, 2026 09:47
@kishore7snehil kishore7snehil dismissed arpit-jn’s stale review April 24, 2026 09:47

The base branch was changed.

Send an Auth0-Client header (base64-encoded JSON with SDK name,
version, and Python runtime) on every request to Auth0 endpoints.
This follows the standard Auth0 SDK telemetry convention.

- Add Telemetry class in telemetry.py for building and caching headers
- Add _get_http_client() helper to ServerClient, MyAccountClient, and
  MfaClient to inject telemetry headers into all httpx requests
- Pass telemetry headers to AsyncOAuth2Client for token exchange calls
- Add unit tests for telemetry header format and integration
Replace `from __future__ import annotations` with `Optional[dict[str, str]]`
syntax for the headers parameter in telemetry.py, mfa_client.py, and
my_account_client.py. This avoids triggering lint warnings on existing
Optional[X] annotations while maintaining Python 3.9 compatibility.
- Build headers eagerly in Telemetry.__init__ instead of lazy caching
- Narrow exception catch to PackageNotFoundError in Telemetry.default()
- Reverse header merge order so telemetry headers cannot be overwritten
- Fix resource leak in test by closing httpx.AsyncClient
- Replace mock-based OIDC test with direct header assertion
- Add test for AsyncOAuth2Client telemetry header propagation
- Add tests for MFA client header propagation and merge behavior
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.

3 participants