Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
### v3.25.0 (2026-07-01)
* * *

### New Features
* Added an optional telemetry adapter hook for tracing Chargebee API calls via OpenTelemetry (or any APM). Configure it via `Chargebee(..., telemetry_adapter=adapter)`. When unconfigured, the SDK skips all telemetry work — no behavior change for existing integrations.
* Each API call emits one CLIENT span (`chargebee.{resource}.{operation}`) with OpenTelemetry HTTP semantic-convention attributes plus `chargebee.*` attributes. Adapters may inject W3C trace context (`traceparent`) into outbound request headers for distributed tracing.
* Exposed the telemetry types (`TelemetryAdapter`, `RequestTelemetryContext`, `RequestTelemetryResult`, `RequestTelemetryError`), the `TelemetrySupport` helpers, and the `TelemetryAttributeKeys` constants under `chargebee.telemetry`. OpenTelemetry is not bundled with the SDK; bring your own OTel packages in the host application.
### v3.24.0 (2026-06-12)
* * *
### New Resources:
Expand Down
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,85 @@ cb_client.update_retry_config(retry_config)

```

### Telemetry (OpenTelemetry)

**Optional add-on.** Existing integrations do not need any changes — if you never set a telemetry adapter, API calls behave exactly as before.

Pass a `telemetry_adapter` when you want Chargebee API calls traced in your observability stack (Datadog, Splunk, Honeycomb, Jaeger, etc.). OpenTelemetry is **not** bundled with `chargebee` — install and configure OTel (or your APM SDK) in your application, implement `TelemetryAdapter`, and wire it on the client.

The SDK builds standardized span attributes (`context.start_attributes`, `result.end_attributes`) following stable [OpenTelemetry HTTP semantic conventions](https://opentelemetry.io/docs/specs/semconv/http/http-spans/) (`url.full`, `http.request.method`, `http.response.status_code`, `server.address`, `error.type`) plus Chargebee-specific `chargebee.*` attributes (see `chargebee.telemetry.TelemetryAttributeKeys`).

Span names follow `chargebee.{resource}.{operation}` (e.g. `chargebee.subscription.create`). One span is created per SDK API call; retries reuse the same span. Adapter failures are logged and never affect the underlying API request.

Pass `telemetry_adapter` when constructing `Chargebee`, or call `update_telemetry_adapter()` on an existing client. Each new `Chargebee(...)` instance gets its own environment — set the adapter on every client you use for telemetry.

To pass custom `chargebee-*` headers (promoted to `http.request.header.chargebee-*` span attributes), include them in the `headers` argument on resource methods.

#### OpenTelemetry example

```sh
pip install chargebee opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-grpc
```

Configure OpenTelemetry at app startup, then pass your adapter:

```python
# App startup — configure once
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

provider = TracerProvider(resource=Resource.create({"service.name": "billing-service"}))
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)
```

```python
from chargebee import Chargebee, RequestTelemetryContext, RequestTelemetryResult, TelemetryAdapter
from opentelemetry import trace
from opentelemetry.trace import SpanKind, Status, StatusCode


class OtelTelemetryAdapter:
def __init__(self, tracer):
self._tracer = tracer

def on_request_start(self, context: RequestTelemetryContext, request_headers: dict):
span = self._tracer.start_span(
context.span_name,
kind=SpanKind.CLIENT,
attributes=dict(context.start_attributes),
)
# Inject W3C trace context into outbound headers
from opentelemetry.propagate import inject
from opentelemetry.trace import set_span_in_context

inject(request_headers, context=set_span_in_context(span))
return span

def on_request_end(self, handle, result: RequestTelemetryResult):
if handle is None:
return
span = handle
for key, value in result.end_attributes.items():
span.set_attribute(key, value)
if result.error:
span.set_status(Status(StatusCode.ERROR, result.error.message))
else:
span.set_status(Status(StatusCode.OK))
span.end()


cb_client = Chargebee(
api_key="{{api-key}}",
site="{{site}}",
telemetry_adapter=OtelTelemetryAdapter(trace.get_tracer("chargebee-python")),
)
```

Spans are exported by your own OpenTelemetry setup, so they flow to whatever backend you've configured (Datadog, Splunk, Honeycomb, Jaeger, etc.). The Chargebee config above stays the same regardless of backend — refer to your APM vendor's OpenTelemetry/OTLP documentation for exporter endpoints.

## Feedback

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.24.0
3.25.0
8 changes: 8 additions & 0 deletions chargebee/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@
from chargebee.filters import Filters
from chargebee.main import Chargebee
from chargebee.models import *
from chargebee.telemetry import (
CHARGEBEE_SDK_NAME,
RequestTelemetryContext,
RequestTelemetryError,
RequestTelemetryResult,
TelemetryAdapter,
TelemetryAttributeKeys,
)
2 changes: 2 additions & 0 deletions chargebee/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ class Environment(object):
retry_config = RetryConfig()
enable_debug_logs = False
use_async_client = False
telemetry_adapter = None

def __init__(self, options):
self.api_key = options["api_key"]
self.site = options["site"]
self.telemetry_adapter = None

def set_api_endpoint(self):
self.api_endpoint = "%s://%s.%s/api/%s" % (
Expand Down
6 changes: 6 additions & 0 deletions chargebee/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ def __init__(
connection_time_out: int = None,
read_time_out: int = None,
use_async_client: bool = False,
telemetry_adapter=None,
):
self.env = Environment({"api_key": api_key, "site": site})
if telemetry_adapter is not None:
self.env.telemetry_adapter = telemetry_adapter
if chargebee_domain is not None:
self.update_chargebee_domain(chargebee_domain)
if protocol is not None:
Expand Down Expand Up @@ -183,3 +186,6 @@ def update_retry_config(self, retry_config):

def update_enable_debug_logs(self, enable_debug_logs):
self.env.enable_debug_logs = enable_debug_logs

def update_telemetry_adapter(self, telemetry_adapter):
self.env.telemetry_adapter = telemetry_adapter
15 changes: 15 additions & 0 deletions chargebee/models/addon/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

@dataclass
class Addon:

env: environment.Environment

class Type(Enum):
Expand Down Expand Up @@ -226,6 +227,8 @@ def create(self, params: CreateParams, headers=None) -> CreateResponse:
False,
jsonKeys,
options,
resource="addon",
operation="create",
)

def update(self, id, params: UpdateParams, headers=None) -> UpdateResponse:
Expand All @@ -246,6 +249,8 @@ def update(self, id, params: UpdateParams, headers=None) -> UpdateResponse:
False,
jsonKeys,
options,
resource="addon",
operation="update",
)

def list(self, params: ListParams = None, headers=None) -> ListResponse:
Expand All @@ -262,6 +267,8 @@ def list(self, params: ListParams = None, headers=None) -> ListResponse:
False,
jsonKeys,
options,
resource="addon",
operation="list",
)

def retrieve(self, id, headers=None) -> RetrieveResponse:
Expand All @@ -278,6 +285,8 @@ def retrieve(self, id, headers=None) -> RetrieveResponse:
False,
jsonKeys,
options,
resource="addon",
operation="retrieve",
)

def delete(self, id, headers=None) -> DeleteResponse:
Expand All @@ -296,6 +305,8 @@ def delete(self, id, headers=None) -> DeleteResponse:
False,
jsonKeys,
options,
resource="addon",
operation="delete",
)

def copy(self, params: CopyParams, headers=None) -> CopyResponse:
Expand All @@ -314,6 +325,8 @@ def copy(self, params: CopyParams, headers=None) -> CopyResponse:
False,
jsonKeys,
options,
resource="addon",
operation="copy",
)

def unarchive(self, id, headers=None) -> UnarchiveResponse:
Expand All @@ -332,4 +345,6 @@ def unarchive(self, id, headers=None) -> UnarchiveResponse:
False,
jsonKeys,
options,
resource="addon",
operation="unarchive",
)
5 changes: 5 additions & 0 deletions chargebee/models/address/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

@dataclass
class Address:

env: environment.Environment

class RetrieveParams(TypedDict):
Expand Down Expand Up @@ -44,6 +45,8 @@ def retrieve(self, params: RetrieveParams, headers=None) -> RetrieveResponse:
False,
jsonKeys,
options,
resource="address",
operation="retrieve",
)

def update(self, params: UpdateParams, headers=None) -> UpdateResponse:
Expand All @@ -62,4 +65,6 @@ def update(self, params: UpdateParams, headers=None) -> UpdateResponse:
False,
jsonKeys,
options,
resource="address",
operation="update",
)
12 changes: 12 additions & 0 deletions chargebee/models/alert/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ def create(self, params: CreateParams, headers=None) -> CreateResponse:
False,
jsonKeys,
options,
resource="alert",
operation="create",
)

def retrieve(self, id, headers=None) -> RetrieveResponse:
Expand All @@ -90,6 +92,8 @@ def retrieve(self, id, headers=None) -> RetrieveResponse:
False,
jsonKeys,
options,
resource="alert",
operation="retrieve",
)

def list(self, params: ListParams = None, headers=None) -> ListResponse:
Expand All @@ -106,6 +110,8 @@ def list(self, params: ListParams = None, headers=None) -> ListResponse:
False,
jsonKeys,
options,
resource="alert",
operation="list",
)

def update(self, id, params: UpdateParams = None, headers=None) -> UpdateResponse:
Expand All @@ -124,6 +130,8 @@ def update(self, id, params: UpdateParams = None, headers=None) -> UpdateRespons
False,
jsonKeys,
options,
resource="alert",
operation="update",
)

def delete(self, id, headers=None) -> DeleteResponse:
Expand All @@ -142,6 +150,8 @@ def delete(self, id, headers=None) -> DeleteResponse:
False,
jsonKeys,
options,
resource="alert",
operation="delete",
)

def application_alerts_for_subscription(
Expand All @@ -160,4 +170,6 @@ def application_alerts_for_subscription(
False,
jsonKeys,
options,
resource="alert",
operation="application_alertsForSubscription",
)
4 changes: 4 additions & 0 deletions chargebee/models/alert_status/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def alert_statuses_for_subscription(
False,
jsonKeys,
options,
resource="alertStatus",
operation="alertStatusesForSubscription",
)

def alert_statuses_for_alert(
Expand All @@ -53,4 +55,6 @@ def alert_statuses_for_alert(
False,
jsonKeys,
options,
resource="alertStatus",
operation="alertStatusesForAlert",
)
11 changes: 11 additions & 0 deletions chargebee/models/attached_item/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

@dataclass
class AttachedItem:

env: environment.Environment

class Type(Enum):
Expand Down Expand Up @@ -77,6 +78,8 @@ def create(self, id, params: CreateParams, headers=None) -> CreateResponse:
False,
jsonKeys,
options,
resource="attachedItem",
operation="create",
)

def update(self, id, params: UpdateParams, headers=None) -> UpdateResponse:
Expand All @@ -95,6 +98,8 @@ def update(self, id, params: UpdateParams, headers=None) -> UpdateResponse:
False,
jsonKeys,
options,
resource="attachedItem",
operation="update",
)

def retrieve(self, id, params: RetrieveParams, headers=None) -> RetrieveResponse:
Expand All @@ -111,6 +116,8 @@ def retrieve(self, id, params: RetrieveParams, headers=None) -> RetrieveResponse
False,
jsonKeys,
options,
resource="attachedItem",
operation="retrieve",
)

def delete(self, id, params: DeleteParams, headers=None) -> DeleteResponse:
Expand All @@ -129,6 +136,8 @@ def delete(self, id, params: DeleteParams, headers=None) -> DeleteResponse:
False,
jsonKeys,
options,
resource="attachedItem",
operation="delete",
)

def list(self, id, params: ListParams = None, headers=None) -> ListResponse:
Expand All @@ -145,4 +154,6 @@ def list(self, id, params: ListParams = None, headers=None) -> ListResponse:
False,
jsonKeys,
options,
resource="attachedItem",
operation="list",
)
5 changes: 5 additions & 0 deletions chargebee/models/business_entity/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

@dataclass
class BusinessEntity:

env: environment.Environment

class Status(Enum):
Expand Down Expand Up @@ -50,6 +51,8 @@ def create_transfers(
False,
jsonKeys,
options,
resource="businessEntity",
operation="createTransfers",
)

def get_transfers(
Expand All @@ -68,4 +71,6 @@ def get_transfers(
False,
jsonKeys,
options,
resource="businessEntity",
operation="getTransfers",
)
Loading
Loading