Observability
The SDK emits structured events for every request, retry, and error. Out of the box it’s quiet; turn up verbosity when you need to.
Standard logging
Section titled “Standard logging”The Python wrapper logs to the openapp_sdk logger. Configure it like any
other stdlib logger:
import logging
logging.basicConfig(level=logging.INFO)logging.getLogger("openapp_sdk").setLevel(logging.DEBUG)Records include openapp_request_id, HTTP method and path, and status code.
The token is never logged.
Verbose logging via environment variables
Section titled “Verbose logging via environment variables”For low-level introspection, the native wheel supports additional log
filtering through environment variables. These are forwarded to the
openapp_sdk.core logger, so they compose with stdlib logging.
OPENAPP_SDK_LOG=info python my_script.pyOPENAPP_SDK_LOG=openapp_sdk=debug python my_script.pyFilters use target=level, comma-separated (for example
openapp_sdk=debug,http=warn). Valid levels are trace, debug, info,
warn, error, and off.
OpenTelemetry
Section titled “OpenTelemetry”Install the optional OTEL extra:
pip install "openapp-sdk[opentelemetry]"When OpenTelemetry is available and a tracer provider is configured, the SDK:
- Wraps each request in a span named
openapp.<tag>.<operation>. - Sets attributes:
http.method,http.url,http.status_code,openapp.request_id,openapp.retry_count. - Propagates
traceparent/tracestateheaders on outbound requests.
from opentelemetry import tracefrom opentelemetry.sdk.trace import TracerProvider
trace.set_tracer_provider(TracerProvider())# ... add exporter(s) ...
from openapp_sdk import Clientclient = Client.connect(api_key="...")client.orgs.list() # emits a spanInterceptors
Section titled “Interceptors”For cross-cutting concerns that don’t fit into logging / tracing, implement
the Interceptor protocol from openapp_sdk.interceptor:
from openapp_sdk.interceptor import Interceptor, RequestSpec, ResponseView
class AddCorrelationId(Interceptor): def __init__(self, correlation_id: str) -> None: self._cid = correlation_id
async def on_request(self, req: RequestSpec) -> RequestSpec: return req.with_header("X-Correlation-Id", self._cid)
async def on_response(self, req: RequestSpec, res: ResponseView) -> None: # read-only observation return None
client = Client.connect( api_key="...", interceptors=[AddCorrelationId("abc123")],)Interceptors run in the order provided, and can:
- Add / modify headers (
req.with_header,req.with_query). - Observe responses (
res.status,res.headers,res.duration_ms). - Not short-circuit the request — if you need that, open an issue.
Debugging failed requests
Section titled “Debugging failed requests”- Bump the logger to
DEBUG. - Re-run the failing call.
- Grab the
openapp_request_idfrom the log line (or the raised exception’s.request_id). - Include it when filing a support ticket — backend logs are keyed on it.