Skip to main content

RunGuard SDK – capture runtime errors and send them to RunGuard

Project description

RunGuard Python SDK

Capture runtime errors and send them to RunGuard. Graceful: never raises, never blocks longer than the configured timeout (default 3 s).

Read-only mirror. This repo is a snapshot of the SDK source published to PyPI as runguard-sdk. Pull requests cannot be merged here — please open an issue describing the problem or change, and we'll work it back into the upstream source. The SDK is developed in a private monorepo by Robion Systems.

Prerequisites

  1. A RunGuard account — sign up at runguard.ai.
  2. Install the RunGuard GitHub App on your repos: github.com/apps/runguard-app.
  3. Get an API key from the RunGuard dashboard.

Install

pip install runguard-sdk

Requires Python 3.9+. Zero runtime dependencies — uses only the standard library.

Usage

from runguard_sdk import init, capture

init(api_key="your-api-key")

try:
    do_work()
except Exception as err:
    capture(err)
    raise

In asyncio apps (FastAPI, Starlette, aiohttp, Sanic), use acapture so the event loop is not blocked on the HTTP send:

from runguard_sdk import init, acapture

init(api_key="your-api-key")

@app.exception_handler(Exception)
async def on_error(request, exc):
    await acapture(exc, metadata={"path": request.url.path})
    raise

In hot paths where you want best-effort reporting without paying the HTTP round-trip cost on the request path, use capture_sync:

from runguard_sdk import capture_sync

capture_sync(err)   # returns immediately; HTTP send runs in a daemon thread

API

  • init(*, api_key, base_url=None, timeout=None) — configure the SDK. May be called multiple times; the last call wins. Empty api_key is allowed and short-circuits capture() without making a network call. timeout is in seconds.
  • capture(error, *, service=None, env=None, timestamp=None, metadata=None) -> bool — synchronous. Returns True on HTTP 2xx, False on anything else. Never raises.
  • await acapture(error, ...) -> bool — async variant; same arguments. Runs the blocking HTTP send in a worker thread via asyncio.to_thread so the event loop is never blocked.
  • capture_sync(error, ...) -> None — fire-and-forget. Spawns a daemon thread and returns immediately.

Environment variables

Name Purpose
RUNGUARD_API_KEY API key (used if init(api_key=...) not called)
RUNGUARD_BASE_URL Ingest base URL (default https://api.runguard.ai)
RUNGUARD_TIMEOUT_MS HTTP send timeout in milliseconds (default 3000)
RUNGUARD_SERVICE Service name override
RUNGUARD_ENV Environment override; wins over ENVIRONMENT

Service detection

When service is not passed and RUNGUARD_SERVICE is unset, the SDK tries, in order:

  1. OTEL_SERVICE_NAME
  2. K_SERVICE (Cloud Run)
  3. FLY_APP_NAME (fly.io)
  4. Falls back to "app" and prints a one-time warning to stderr.

If none of these env vars are set in your runtime, pass service explicitly:

capture(err, service="checkout-api")

Env detection

RUNGUARD_ENVENVIRONMENT (12-factor convention) → "production".

The pre-1.0 ENV fallback was removed because it collides with too many other tools (notably Heroku-style config and several shell helpers).

Auto-collected metadata

Every payload includes the following keys under metadata in addition to anything you pass:

Key Value
sdk_name "runguard-python"
sdk_version Package version
sdk_runtime python-<version> (e.g. python-3.12.7)
sdk_platform platform.platform() (Darwin-23.6.0-arm64, Linux-5.15-x86_64, …)
sdk_pid os.getpid()
sdk_hostname socket.gethostname() when available

User-supplied metadata keys override SDK auto-keys on collision. Per-payload limit: 20 keys total; values are server-side stringified and truncated to 1000 chars. Client-side the SDK pre-coerces complex objects to strings so json.dumps of the payload never throws; None values are dropped.

Graceful failure

capture() never throws and never blocks longer than timeout. It returns False when:

  • the API key is missing,
  • the server returns a non-2xx response (401, 5xx, …),
  • the network is unreachable, the DNS lookup fails, or the TLS handshake fails,
  • the send exceeds timeout (default 3 s).

No retries — the SDK is at-most-once delivery. Pair it with a structured logger if you need a local fallback for ingest outages.

Framework patterns

Django / Flask (WSGI)

capture() is thread-safe; you can call it from any view handler. With gunicorn --workers N --threads M, all N*M threads share the same module-global config.

# settings.py / app.py — once at boot
from runguard_sdk import init
init(api_key=os.environ["RUNGUARD_API_KEY"])

# views.py / handler — synchronous
from runguard_sdk import capture

def my_view(request):
    try:
        return do_work(request)
    except Exception as err:
        capture(err, metadata={"path": request.path})
        raise

FastAPI / Starlette / aiohttp / Sanic (ASGI)

Use acapture to avoid blocking the event loop:

from runguard_sdk import init, acapture

init(api_key=...)

@app.middleware("http")
async def report_errors(request, call_next):
    try:
        return await call_next(request)
    except Exception as err:
        await acapture(err, metadata={"path": str(request.url.path)})
        raise

Celery

For --pool=threads, use capture() directly inside the task body. For --pool=prefork, each worker process must init() itself (workers are forked before the user code imports the SDK in most setups; call init() at module top of celery_app.py).

from celery import Celery
from runguard_sdk import init, capture

init(api_key=os.environ["RUNGUARD_API_KEY"])
celery_app = Celery(...)

@celery_app.task(bind=True)
def send_email(self, to_addr):
    try:
        smtp.send(to_addr)
    except Exception as err:
        capture(err, metadata={"task": self.name, "to": to_addr})
        raise  # let Celery retry

AWS Lambda / Cloud Run

init() once at module top so the warm container reuses it. capture() from inside the handler.

from runguard_sdk import init, capture
init(api_key=os.environ["RUNGUARD_API_KEY"])

def handler(event, context):
    try:
        return do_work(event)
    except Exception as err:
        capture(err, metadata={"request_id": context.aws_request_id})
        raise

Privacy note

Error tracebacks contain file paths from the throwing process. On developer machines this means paths like /Users/<you>/... are sent to RunGuard. In production these typically resolve to container paths (/app/...). Pass redacted stacks or extra context via metadata= if your build embeds sensitive paths.

Development

cd packages/runguard-python
pip install -e ".[dev]"
pytest

The test suite spins up a real http.server on 127.0.0.1 and points the SDK at it for transport-level tests — no monkey-patching urllib. Framework-compatibility tests simulate Django/Flask/FastAPI/Celery via stdlib primitives (threading.Thread, asyncio, multiprocessing) rather than installing the frameworks.

Publish a new version:

pip install build twine
python -m build
twine upload dist/*

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

runguard_sdk-1.0.0.tar.gz (35.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

runguard_sdk-1.0.0-py3-none-any.whl (15.9 kB view details)

Uploaded Python 3

File details

Details for the file runguard_sdk-1.0.0.tar.gz.

File metadata

  • Download URL: runguard_sdk-1.0.0.tar.gz
  • Upload date:
  • Size: 35.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for runguard_sdk-1.0.0.tar.gz
Algorithm Hash digest
SHA256 b80c5650853fc80b8aafacb99cb99b54b9fcd6cc63eaa6b6083f58d191b7df7a
MD5 0546bbc762109bc437602d39da52df06
BLAKE2b-256 6e3979dc14213c80f9d7c5e75355e2e1111af15a205bfddb91fefcc654eef04a

See more details on using hashes here.

File details

Details for the file runguard_sdk-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: runguard_sdk-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 15.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for runguard_sdk-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e03e9e61c91b28336c5ae7bb105e5b976e7ecdeadabc7a67f42d688a6602b2f3
MD5 9d6e2dc599473559fcdcec5e02346f8a
BLAKE2b-256 a0c586565141d5691cf1882cbe2d0f44d640704588b9f1b810b2117268aa8038

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page