logiq-sdk
Official Python SDK for LogIQ — batches structured log/error events on a background thread and ships them to your LogIQ project over HTTP, so you can point your app at the platform with one import and a few lines of setup.
pip install logiq-sdk
The distribution is named logiq-sdk; the importable module is logiq:
from logiq import Monitor
- Quick start
- How it works
- Configuring
Monitor - Sending logs
- Capturing exceptions
- Timing operations with
trace() - Catching unhandled exceptions process-wide
- Correlation IDs
- ASGI middleware (FastAPI / Starlette)
- Flask middleware
- Heartbeats (Servers dashboard)
- Manual flush, shutdown, and delivery guarantees
- Full FastAPI example
- API reference summary
Quick start
from logiq import Monitor
monitor = Monitor(
api_key="<YOUR_LOGIQ_API_KEY>",
base_url="https://your-logiq-backend.example.com",
service_name="checkout-service",
)
monitor.info("Order placed", operation="create_order", metadata={"order_id": 123})
try:
charge_card(order)
except Exception as exc:
monitor.capture_exception(exc, operation="create_order")
Get api_key/base_url from your LogIQ project's onboarding page (project creation mints the key once — store it securely, it isn't retrievable again).
How it works
- Every
log()/info()/warn()/error()/debug()/capture_exception()call appends an event to an in-memory buffer — it never blocks on network I/O. - A background thread flushes the buffer to
POST {base_url}/api/v1/logseither everyflush_intervalseconds, or immediately once the buffer reachesbatch_sizeevents. - Failed sends are retried with exponential backoff; if all retries are exhausted the batch is moved to an in-memory dead-letter list rather than raised as an exception.
atexitis registered automatically — on normal process exit,close()runs and performs one final flush.
Configuring Monitor
monitor = Monitor(
api_key="...", # required — your project's API key (X-API-Key header)
base_url="...", # required — your LogIQ backend origin, no trailing slash needed
service_name="checkout", # default service_name attached to every event, can be overridden per-call
source="sdk", # default `source` field attached to every event
batch_size=50, # flush once this many events are buffered
flush_interval=2.0, # seconds between scheduled background flushes
timeout_seconds=5.0, # per-request HTTP timeout
max_retries=3, # retries per batch before it's moved to the dead-letter list
retry_backoff_seconds=0.5, # base backoff; actual wait = retry_backoff_seconds * 2**(attempt-1)
min_level="WARN", # events below this level are silently dropped, see Log levels below
session=None, # pass a pre-configured requests.Session() to reuse connections/pooling
start_background=True, # False = don't start the flush thread (you'll drive flush() yourself)
)
Log levels & min_level. Levels, lowest to highest priority: DEBUG < INFO < WARN/WARNING < ERROR < CRITICAL. The default min_level="WARN" means .info() and .debug() calls are silently dropped unless you lower it:
monitor = Monitor(api_key=..., base_url=..., min_level="INFO") # or "DEBUG" to see everything
Sending logs
monitor.info("Cache warmed", operation="startup")
monitor.warn("Upstream latency high", operation="charge", metadata={"latency_ms": 1200})
monitor.error("Payment declined", operation="charge", error_type="CardDeclined")
monitor.debug("Computed discount", operation="pricing", metadata={"discount_pct": 10})
# Or call log() directly for full control:
monitor.log(
"Custom event",
level="ERROR",
operation="reconcile",
status="failed",
error_type="ReconciliationMismatch",
metadata={"expected": 100, "actual": 97},
correlation_id="req-abc-123", # falls back to the active correlation ID if omitted, see below
service_name="billing-service", # overrides the Monitor-level default for this one event
source="worker", # overrides the Monitor-level default for this one event
)
message is the only required field for every call — an empty/falsy message is silently dropped without raising.
Capturing exceptions
try:
process_payment(order)
except Exception as exc:
monitor.capture_exception(exc, operation="process_payment", metadata={"order_id": order.id})
This always logs at ERROR, sets error_type to the exception's class name, status="error", and stores the full formatted traceback under metadata["traceback"].
Timing operations with trace()
A context manager that logs a start (DEBUG) event on entry and either a completed (INFO, with duration_ms) event on clean exit, or routes to capture_exception (also with duration_ms in metadata) if the block raises:
with monitor.trace("charge_card", metadata={"order_id": order.id}):
charge_card(order) # any exception here is captured automatically, with duration_ms attached
Catching unhandled exceptions process-wide
monitor.install_excepthook()
Wraps sys.excepthook: any unhandled exception is captured (operation="unhandled_exception"), flushed immediately, then passed on to whatever exception hook was previously installed (so default traceback printing still happens).
Correlation IDs
Correlation IDs use contextvars, so they propagate correctly across async/await and threads started via the standard concurrency primitives, scoped to the current request/task:
from logiq import set_correlation_id, get_correlation_id, reset_correlation_id
token = set_correlation_id("req-abc-123")
try:
monitor.info("Handling request") # picks up correlation_id="req-abc-123" automatically
downstream_call() # anything reading get_correlation_id() sees the same ID
finally:
reset_correlation_id(token)
log()/info()/warn()/error()/debug()/capture_exception() all use get_correlation_id() as the fallback whenever you don't pass correlation_id= explicitly. The ASGI and Flask middleware (below) set this for you automatically for every request.
ASGI middleware (FastAPI / Starlette)
from fastapi import FastAPI
from logiq import MonitorASGIMiddleware
app = FastAPI()
app.add_middleware(MonitorASGIMiddleware, monitor=monitor)
For every HTTP request, this:
- Reads a correlation ID from the
X-Request-IDorX-Correlation-IDrequest header, generating a new UUID if neither is present. - Sets it as the active correlation ID for the duration of the request (propagates into any
monitor.*calls made by your handler). - After the response, logs one event:
INFOfor status< 500,ERRORotherwise, withmetadata={"method", "path", "duration_ms"}. - If the handler raises, calls
capture_exception(same metadata) and re-raises — the exception isn't swallowed. - Resets the correlation ID afterward so it doesn't leak into unrelated requests.
- Only instruments
httpscope requests — websocket/lifespan scopes pass straight through untouched.
Flask middleware
from flask import Flask
from logiq import attach_flask_middleware
app = Flask(__name__)
attach_flask_middleware(app, monitor)
Same behavior as the ASGI middleware (correlation ID from X-Request-ID/X-Correlation-ID, one request-lifecycle log event, INFO/ERROR by status code), implemented via before_request/after_request hooks. Flask is an optional dependency — installing logiq-sdk alone does not require Flask; it's only imported when attach_flask_middleware() is actually called (raises a clear RuntimeError if Flask isn't installed).
Heartbeats (Servers dashboard)
Heartbeats register your service with LogIQ's Servers dashboard even before any real log traffic occurs, and bypass min_level entirely:
monitor.heartbeat() # one-off, e.g. right after startup
monitor.start_heartbeat_loop(30) # background thread, sends every 30s until monitor.close()
start_heartbeat_loop() runs on its own daemon thread and stops automatically when monitor.close() is called (including via the automatic atexit handler).
Manual flush, shutdown, and delivery guarantees
monitor.flush() # force-send everything currently buffered, right now
monitor.close() # stop background threads, then flush one final time
-
close()is also called automatically on normal interpreter exit viaatexit, so buffered-but-unsent events generally get a last delivery attempt even if you never call it yourself. -
Events that still fail after
max_retriesattempts land in an in-memory dead-letter list — nothing is raised into your application code. Inspect it with:failed_events = monitor.dead_letter()
This list is not persisted — read it before the process exits if you need to react to permanent delivery failures (e.g. write it to disk or re-queue it yourself).
-
If you need to control the flush thread's lifecycle yourself (e.g. in a short-lived script or a test), pass
start_background=Falseand callmonitor.flush()explicitly when you want events sent.
Full FastAPI example
from fastapi import FastAPI
from logiq import Monitor, MonitorASGIMiddleware
monitor = Monitor(
api_key="<YOUR_LOGIQ_API_KEY>",
base_url="https://your-logiq-backend.example.com",
service_name="checkout-service",
min_level="INFO",
)
monitor.start_heartbeat_loop(30)
monitor.install_excepthook()
app = FastAPI()
app.add_middleware(MonitorASGIMiddleware, monitor=monitor)
@app.post("/orders")
def create_order(order: OrderIn):
with monitor.trace("create_order", metadata={"sku": order.sku}):
return place_order(order)
API reference summary
| Import | Purpose |
|---|---|
Monitor(...) |
The client — construct once per process/service and reuse it |
monitor.log(message, *, level=, operation=, status=, error_type=, metadata=, correlation_id=, service_name=, source=) |
Core event method — all convenience methods below call this |
monitor.info/warn/error/debug(message, **kwargs) |
Shortcuts for log(message, level=..., **kwargs) |
monitor.capture_exception(exc, *, operation=, metadata=, correlation_id=) |
Log an exception with traceback captured in metadata["traceback"] |
monitor.trace(operation, *, metadata=) |
Context manager: logs start/completion (or captures the exception) with duration_ms |
monitor.install_excepthook() |
Route uncaught exceptions process-wide through capture_exception |
monitor.heartbeat(service_name=None) |
One-off "service is up" event, bypasses min_level |
monitor.start_heartbeat_loop(interval=30.0) |
Background periodic heartbeat, stops on close() |
monitor.flush() |
Force-send everything buffered right now |
monitor.close() |
Stop background threads and flush once more (also runs automatically via atexit) |
monitor.dead_letter() |
List of batches that failed delivery after all retries |
MonitorASGIMiddleware |
FastAPI/Starlette middleware — correlation IDs + one log event per request |
attach_flask_middleware(app, monitor) |
Same, for Flask |
set_correlation_id(id) / get_correlation_id() / reset_correlation_id(token) |
Manual correlation ID propagation via contextvars |
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file logiq_sdk-1.0.1.tar.gz.
File metadata
- Download URL: logiq_sdk-1.0.1.tar.gz
- Upload date:
- Size: 14.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06857e716b36f12dfaaa635df29e844ec827916294d75b37bd054c346a819bd4
|
|
| MD5 |
5eee6b35fdf4174061679b33e4c29b81
|
|
| BLAKE2b-256 |
46005da3792af9b7251864f06adb51a5a63fa995828a9e5387ce81753166c993
|
File details
Details for the file logiq_sdk-1.0.1-py3-none-any.whl.
File metadata
- Download URL: logiq_sdk-1.0.1-py3-none-any.whl
- Upload date:
- Size: 10.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85504b52756753afa71eb6ca72d176aaa57528322d338f25578f33164fe53ce7
|
|
| MD5 |
b3274626dc21689fc3b3278211668a0a
|
|
| BLAKE2b-256 |
07c54459ad30e66eb49132f636862dc7c76deaffd80a3f4f4850debbcc7f7fc8
|