Skip to main content

Automatic HTTP request monitoring for Python applications

Project description

API Monitor SDK — Python

PyPI version Python Versions License: MIT Status: Beta

Zero-code HTTP monitoring and real-time API validation for Python web applications.
Works with Django, Flask, FastAPI, and any code that uses requests, httpx, or urllib3 — no code changes required.


Table of Contents


Overview

The API Monitor SDK automatically intercepts every inbound and outbound HTTP request made by your application and sends rich telemetry — latency, status codes, headers, request/response bodies, caller identity — to the API Monitor platform.

Capability Details
Zero-code instrumentation One init() call at startup, no decorators or middleware to add
Framework auto-detection Django, Flask, FastAPI / Starlette patched automatically
Library interception requests, httpx, urllib3 patched transparently
Validation gate Block / rate-limit requests based on platform rules, <2 ms overhead
Async batching Metrics sent in background — zero latency impact on your API
Offline mode Metrics cached in local SQLite when the platform is unreachable
PII scrubbing Emails, credit cards, phone numbers, API keys redacted before send
Identity resolution Caller identified via JWT, headers, body fields, or IP hash
Live config reload Sampling rate, batch size, exclusions updated from the platform without restart
Fork safe Re-initialises cleanly after os.fork() (Gunicorn, uWSGI)

Installation

pip install api-monitor-sdk

Python 3.8+ required. No other mandatory dependencies — requests is already installed in almost every Python project.


Quick Start

Ingest-only (default — metrics, no request blocking)

import apimonitor

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

That is the entire integration. Every HTTP request your server handles is now captured and streamed to the platform.

With request validation (rate-limiting / blocking)

import apimonitor

apimonitor.init(
    api_key="your-api-key",
    enable_validation=True,   # enable the sync validation gate
)

When validation is enabled, a single /validate call to the platform acts as both the gate decision and the metric record — no extra network round-trip.

Both disabled (dry-run / testing)

apimonitor.init(
    api_key="your-api-key",
    enable_ingest=False,
    enable_validation=False,
)

How It Works — Two Flows

Incoming request
       │
       ▼
 ┌─────────────────────────────────────────┐
 │  Flow 1 — Validation Gate (optional)   │
 │                                         │
 │  1. Check in-process LRU cache         │  ← sub-ms, no network
 │  2. Check circuit breaker state        │  ← sub-ms, no network
 │  3. POST /validate   ──────────────────┼──► Platform decides allow/deny
 │     (with metric piggybacked)          │    AND saves metric in one call
 │                                         │
 │  If denied → return 403/429 immediately │
 └─────────────────────────────────────────┘
       │  (allowed)
       ▼
 Your application handles the request
       │
       ▼
 ┌─────────────────────────────────────────┐
 │  Flow 2 — Async Metric Ingest          │
 │  (only when enable_validation=False)   │
 │                                         │
 │  Metric queued in-memory               │
 │  Background thread batches & sends     │
 │       → POST /ingest (batch)           │
 │  On failure → saved to SQLite offline  │
 └─────────────────────────────────────────┘

Key efficiency principle: When enable_validation=True, the metric is embedded inside the /validate payload. The platform processes both the allow/deny decision and the metric storage in a single HTTP call. /ingest is never called separately.


Framework Integration

Django

Add the init() call at the top of your settings.py or in your AppConfig.ready() method.

settings.py

import apimonitor

apimonitor.init(
    api_key="your-api-key",
    framework="django",
    environment="production",
    exclude=["/health/", "/metrics/"],
)

apps.py (recommended for Django apps)

from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        import apimonitor
        apimonitor.init(
            api_key="your-api-key",
            framework="django",
            environment="production",
        )

The SDK patches django.core.handlers.base.BaseHandler.get_response — every request/response cycle is captured with zero changes to your views.


Flask

Call init() before app.run() or before WSGI server startup.

from flask import Flask
import apimonitor

app = Flask(__name__)

apimonitor.init(
    api_key="your-api-key",
    framework="flask",
    environment="production",
    sampling_rate=0.5,          # capture 50% of requests
    exclude=["/healthz", "/ping"],
)

@app.route("/api/users")
def users():
    return {"users": []}

if __name__ == "__main__":
    app.run()

The SDK patches Flask.full_dispatch_request — works with Flask blueprints, extensions, and all route patterns automatically.


FastAPI

Call init() before creating the FastAPI() application, or in a startup event.

from fastapi import FastAPI
import apimonitor

# Option A: Before app creation (recommended)
apimonitor.init(
    api_key="your-api-key",
    framework="fastapi",
    environment="production",
)

app = FastAPI()

@app.get("/api/items")
def get_items():
    return []
# Option B: Startup event
from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    apimonitor.init(api_key="your-api-key", framework="fastapi")
    yield
    apimonitor.stop()

app = FastAPI(lifespan=lifespan)

The SDK patches starlette.applications.Starlette.__call__ and correctly reads the ASGI request body, then replays it to the actual handler — your endpoint sees the full body normally.


Generic (requests / httpx)

No web framework? The SDK still intercepts all outgoing HTTP calls made by your code.

import apimonitor
import requests

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

# This call is automatically monitored
resp = requests.get("https://api.example.com/data")

httpx and direct urllib3 usage are intercepted in the same way.


Configuration Reference

All parameters can be passed to init() or set via environment variables (env vars take lower precedence than explicit kwargs).

init() Parameters

Parameter Type Default Description
api_key str required Your API Monitor platform key
base_url str https://api.yourmonitor.com Platform base URL
enable_validation bool False Enable Flow 1 — synchronous request gate
enable_ingest bool True Enable Flow 2 — async metric capture
framework str None e.g. "django", "flask", "fastapi" — shown in platform agents list
environment str None e.g. "production", "staging" — shown in platform
sampling_rate float 1.0 Fraction of requests to capture (0.0–1.0). 0.1 = 10%
batch_size int 100 Flush metrics after this many are queued
batch_timeout int 10 Flush metrics after this many seconds even if batch is not full
queue_maxsize int 10000 Max in-memory queue depth (metrics dropped if exceeded)
exclude list[str] [] Paths to skip. Supports exact (/health) and wildcard prefix (/internal/*)
capture_request_body bool True Capture request bodies
capture_response_body bool True Capture response bodies
max_body_bytes int 10240 Max bytes captured per body (10 KB default)
sanitize_fields list[str] [] Extra field names to redact from bodies and headers
extra_identity_headers list[str] [] Extra headers to use for caller identity (e.g. ["x-workspace-id"])
extra_identity_fields list[str] [] Extra JSON body fields for caller identity
offline_mode bool True Buffer metrics in local SQLite when platform is unreachable
local_db_path str ~/.apimonitor_queue.db Path to the offline SQLite database
max_offline_rows int 50000 Max rows in the offline buffer (oldest are dropped when full)
fail_behavior str "open" Validation failure behavior: "open" (allow) or "closed" (block)
validate_timeout_ms int 2000 Max time (ms) for a /validate call before falling back to fail_behavior
check_cache_ttl_ms int 500 How long to cache a validation allow/deny decision (ms)
cb_failure_threshold int 5 Consecutive failures before circuit breaker opens
cb_window_seconds int 10 Rolling window for measuring failures
cb_recovery_seconds int 30 Time before circuit breaker moves to HALF-OPEN and retries
ingest_mode str "background" "background" (default) or "inline" (synchronous, for testing)
heartbeat_interval int 30 Seconds between liveness pings to the platform
retry_initial_delay float 1.0 Initial retry delay after a failed batch send (seconds)
retry_max_delay float 60.0 Maximum retry delay (exponential backoff cap)
retry_multiplier float 2.0 Backoff multiplier
debug bool False Enable verbose SDK logging
log_level str "ERROR" Python log level for SDK internals

Environment Variables

Every parameter above has a corresponding environment variable. Use these for zero-change deployments in containerised environments.

Environment Variable Corresponding Parameter
APIMONITOR_API_KEY api_key
APIMONITOR_BASE_URL base_url
APIMONITOR_ENABLE_VALIDATION enable_validation
APIMONITOR_ENABLE_INGEST enable_ingest
APIMONITOR_FRAMEWORK framework
APIMONITOR_ENVIRONMENT environment
APIMONITOR_SAMPLING_RATE sampling_rate
APIMONITOR_BATCH_SIZE batch_size
APIMONITOR_BATCH_TIMEOUT batch_timeout
APIMONITOR_QUEUE_MAXSIZE queue_maxsize
APIMONITOR_EXCLUDE exclude (comma-separated)
APIMONITOR_CAPTURE_REQ_BODY capture_request_body
APIMONITOR_CAPTURE_RESP_BODY capture_response_body
APIMONITOR_MAX_BODY_BYTES max_body_bytes
APIMONITOR_SANITIZE_FIELDS sanitize_fields (comma-separated)
APIMONITOR_IDENTITY_HEADERS extra_identity_headers (comma-separated)
APIMONITOR_IDENTITY_FIELDS extra_identity_fields (comma-separated)
APIMONITOR_OFFLINE_MODE offline_mode
APIMONITOR_LOCAL_DB_PATH local_db_path
APIMONITOR_MAX_OFFLINE_ROWS max_offline_rows
APIMONITOR_FAIL_BEHAVIOR fail_behavior
APIMONITOR_VALIDATE_TIMEOUT_MS validate_timeout_ms
APIMONITOR_CHECK_CACHE_TTL_MS check_cache_ttl_ms
APIMONITOR_CB_FAILURE_THRESHOLD cb_failure_threshold
APIMONITOR_CB_WINDOW_SECONDS cb_window_seconds
APIMONITOR_CB_RECOVERY_SECONDS cb_recovery_seconds
APIMONITOR_INGEST_MODE ingest_mode
APIMONITOR_HEARTBEAT_INTERVAL heartbeat_interval
APIMONITOR_DEBUG debug
APIMONITOR_LOG_LEVEL log_level

Docker / docker-compose example:

environment:
  - APIMONITOR_API_KEY=your-api-key
  - APIMONITOR_ENVIRONMENT=production
  - APIMONITOR_SAMPLING_RATE=0.25
  - APIMONITOR_EXCLUDE=/health,/metrics,/readyz
  - APIMONITOR_SANITIZE_FIELDS=password,ssn,credit_card

Features In Depth

Request Validation Gate (Flow 1)

When enable_validation=True, the SDK makes a synchronous call to the platform before forwarding the request to your application. The platform evaluates your rate-limit rules and returns an allow/deny decision.

Performance protections built in:

  1. In-process LRU cache — repeated requests to the same endpoint by the same caller are cached for check_cache_ttl_ms ms (default 500 ms). Cache hit = sub-millisecond, no network.
  2. Circuit breaker — if the platform becomes unreachable, the breaker opens after cb_failure_threshold consecutive failures and applies fail_behavior without making network calls. Recovers automatically after cb_recovery_seconds.
  3. Timeout — every validate call is bounded by validate_timeout_ms (default 2 s). If the platform doesn't respond in time, fail_behavior is applied.

fail_behavior options:

Value Behavior
"open" (default) Allow all requests when the platform is unreachable. Safe for production — your API keeps working.
"closed" Block all requests when unreachable. Use for high-security scenarios.

Blocked response format:

{
  "error": "Rate limit exceeded",
  "blocked_by": "apimonitor",
  "retry_after": 30
}

Async Metric Ingest (Flow 2)

When enable_validation=False (or for custom events), metrics are captured asynchronously:

  1. Metric placed on an in-memory queue (non-blocking, < 1 µs).
  2. Background thread (apimonitor-sender) drains the queue and accumulates a batch.
  3. When batch reaches batch_size or batch_timeout seconds elapses, the batch is POSTed to /ingest.
  4. On failure, the batch is sent to exponential-backoff retry, and metrics are saved to offline storage.

Tuning for high-traffic apps:

apimonitor.init(
    api_key="...",
    batch_size=500,         # larger batches, fewer HTTP calls
    batch_timeout=5,        # flush more frequently
    sampling_rate=0.1,      # capture only 10% of requests
    queue_maxsize=50_000,   # allow larger in-memory queue
)

Offline Buffering

When the platform is unreachable, metrics are persisted in a local SQLite database (WAL mode, safe for multi-process servers like Gunicorn).

  • Default path: ~/.apimonitor_queue.db
  • Custom path: local_db_path="/var/data/apimonitor.db"
  • Max rows: max_offline_rows=50_000 — when full, the oldest 10% of rows are dropped to make room
  • Auto-drain: When the platform becomes reachable again, the offline buffer is automatically drained in the background

Disable offline mode (e.g. ephemeral containers with no persistent disk):

apimonitor.init(api_key="...", offline_mode=False)

PII Sanitisation

The SDK automatically redacts the following patterns from all request/response bodies and headers before any data leaves your server:

Pattern Example Redacted to
Email addresses user@example.com [REDACTED]
Credit card numbers 4111 1111 1111 1111 [REDACTED]
Phone numbers +1 (555) 123-4567 [REDACTED]
Authorization header Bearer eyJ... [REDACTED]
x-api-key header any value [REDACTED]
cookie header any value [REDACTED]

Add your own fields to redact:

apimonitor.init(
    api_key="...",
    sanitize_fields=["password", "ssn", "secret_token", "credit_card_number"],
)

Fields are matched case-insensitively in both headers and JSON body (including nested objects).


Identity Resolution

The SDK resolves a stable, anonymised identity for every request caller using this priority chain:

  1. JWT — extracts sub, user_id, userId, email, or uid claim from Authorization: Bearer header or token / access_token cookie
  2. Identity headers — checks x-user-id, x-account-id, x-client-id, x-tenant-id, x-customer-id, x-actor-id
  3. Request body — scans top-level and one-level-deep JSON for user_id, userId, account_id, email, sub, uid, etc.
  4. IP + User-Agent hash — fallback when no other signal is available

The resolved identity is always a SHA-256 hash (truncated to 16 hex chars) — raw PII is never stored.

Extend identity detection:

apimonitor.init(
    api_key="...",
    # Additional headers to check before the IP fallback
    extra_identity_headers=["x-workspace-id", "x-org-id"],
    # Additional body fields to check
    extra_identity_fields=["workspace_id", "org_id"],
)

Sampling

Reduce data volume by capturing a fraction of requests:

apimonitor.init(
    api_key="...",
    sampling_rate=0.25,   # capture 25% of traffic
)
  • 1.0 = 100% (default) — capture everything
  • 0.0 = 0% — capture nothing (effectively disabled)
  • Sampling applies only to metric capture — validation decisions are never sampled
  • Sampling rate can be updated live from the platform dashboard without restarting your server

Path Exclusions

Skip monitoring for health checks, metrics endpoints, static files, etc.:

apimonitor.init(
    api_key="...",
    exclude=[
        "/health",          # exact match
        "/healthz",
        "/readyz",
        "/metrics",
        "/static/*",        # wildcard prefix — all paths starting with /static/
        "/internal/*",
    ],
)

Exclusion rules support:

  • Exact match: /health matches only /health
  • Wildcard prefix: /static/* matches /static/js/app.js, /static/css/main.css, etc.

Custom Events

Track business events that aren't HTTP requests:

monitor = apimonitor.init(api_key="...")

# Track a custom event with arbitrary metadata
monitor.track("user_signup", plan="pro", source="organic", country="US")
monitor.track("payment_processed", amount=99.99, currency="USD", user_id="u_123")
monitor.track("export_completed", format="csv", rows=15_420)

Custom events are batched and sent through the same async pipeline as HTTP metrics.


Diagnostics

Inspect the SDK's internal state at runtime:

monitor = apimonitor.get_monitor()
import pprint
pprint.pprint(monitor.diagnose())

Output example:

{
    "started": True,
    "pid": 12345,
    "uptime_seconds": 3600.0,
    "api_key_set": True,
    "enable_validation": True,
    "enable_ingest": True,
    "fail_behavior": "open",
    "sampling_rate": 1.0,
    "batch_size": 100,
    "batch_timeout": 10,
    "ingest_mode": "background",
    "circuit_breaker_state": "closed",
    "offline_queue_size": 0,
    "platform_base_url": "https://api.yourmonitor.com",
    "patched_frameworks": ["django"],
    "patched_libraries": ["requests", "httpx"],
    "heartbeat_interval": 30,
}

Graceful Shutdown

The SDK registers an atexit handler automatically — when your process exits normally (SIGTERM, Ctrl+C, sys.exit()), any queued metrics are flushed before shutdown.

For explicit control (e.g. in tests or when using lifespan context managers):

# Stop the SDK and flush all remaining metrics
apimonitor.stop()
# Check if SDK is running
monitor = apimonitor.get_monitor()
if monitor:
    print(monitor.diagnose())

Security Notes

  • API key — keep your API key in environment variables, never hard-code it in source files.
  • PII — the SDK scrubs known PII patterns before transmission, but you should also configure sanitize_fields for any application-specific sensitive fields.
  • Body capture — if your API handles highly sensitive data and body capture is not needed for your use case, disable it:
    apimonitor.init(
        api_key="...",
        capture_request_body=False,
        capture_response_body=False,
    )
    
  • TLS — all communication with the platform uses HTTPS. Ensure base_url is always https://.
  • Offline database — the SQLite queue file may contain request/response data. Protect it with appropriate filesystem permissions.

Troubleshooting

SDK appears to not send any data

  1. Verify api_key is correct.
  2. Enable debug logging:
    apimonitor.init(api_key="...", debug=True, log_level="DEBUG")
    
  3. Check diagnose() — look at circuit_breaker_state and offline_queue_size.
  4. Confirm base_url is reachable from your server.

Validation always allows requests even when rules are set

  • Ensure enable_validation=True is passed to init().
  • Check circuit_breaker_state in diagnose() — if "open", the platform was unreachable and fail_behavior="open" is being applied.

High memory usage

Reduce the in-memory queue size and use a lower sampling rate:

apimonitor.init(
    api_key="...",
    queue_maxsize=1_000,
    sampling_rate=0.1,
)

Request body is None in the platform (FastAPI)

Body capture requires that the request body has not already been consumed before the SDK reads it. The SDK handles this automatically by buffering and replaying the ASGI body stream. If you are using a custom ASGI middleware that reads the body first, ensure it also replays it.

Gunicorn / uWSGI workers not sending data

The SDK is fork-safe. However, ensure init() is called after the worker fork (e.g. in a post_fork hook or in AppConfig.ready()), not only in the main process.

Gunicorn post_fork hook:

# gunicorn.conf.py
def post_fork(server, worker):
    import apimonitor
    apimonitor.init(api_key="your-api-key", framework="django")

Changelog

v0.1.4

  • Combined Flow 1 + Flow 2 into a single /validate round-trip — no separate /ingest call when validation is enabled
  • Added Heartbeat class replacing separate config_sync thread — liveness ping and config change detection in one thread
  • Live circuit breaker threshold updates from the platform control panel
  • ASGI body read fixed for FastAPI — request body no longer null
  • Added framework and environment metadata to heartbeat payloads
  • max_offline_rows cap added to prevent unbounded SQLite growth
  • diagnose() now reports patched_frameworks and patched_libraries

License

MIT License — see LICENSE for details.

© 2024 riken-khadela

Project details


Download files

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

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

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

api_monitor_sdk-0.1.4-py3-none-any.whl (40.7 kB view details)

Uploaded Python 3

File details

Details for the file api_monitor_sdk-0.1.4-py3-none-any.whl.

File metadata

File hashes

Hashes for api_monitor_sdk-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 1522ddbc8d7c9b7779c422cd3adaad9591b7e9e55e445f0e200e4faae72e2a2c
MD5 0cc79a9bada15e0e8b06ce3356112161
BLAKE2b-256 9d09c99cffaa585200db4d8661a68b1f0d0ee80a9040ea511d4f9eebb37717b0

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