Skip to content
OAOpenAppPhysical Security as a Service
Login

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.

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.

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.

Terminal window
OPENAPP_SDK_LOG=info python my_script.py
OPENAPP_SDK_LOG=openapp_sdk=debug python my_script.py

Filters use target=level, comma-separated (for example openapp_sdk=debug,http=warn). Valid levels are trace, debug, info, warn, error, and off.

Install the optional OTEL extra:

Terminal window
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 / tracestate headers on outbound requests.
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
trace.set_tracer_provider(TracerProvider())
# ... add exporter(s) ...
from openapp_sdk import Client
client = Client.connect(api_key="...")
client.orgs.list() # emits a span

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.
  1. Bump the logger to DEBUG.
  2. Re-run the failing call.
  3. Grab the openapp_request_id from the log line (or the raised exception’s .request_id).
  4. Include it when filing a support ticket — backend logs are keyed on it.