Python SDK
Package linearjeph 1.0.0, MIT licensed, Python 3.8+, no dependencies.
Install
pip install https://linearjeph.cc/sdk/linearjeph-1.0.0.tar.gz
# or from the public source repository
pip install "git+https://github.com/kingtechies/linearjeph-sdk@python/v1.0.0#subdirectory=python"Quickstart
import linearjeph
linearjeph.init("lj_app_YOUR_KEY", release="1.4.2", environment="production")
try:
risky()
except Exception as exc:
linearjeph.capture_exception(exc) # or just let it crash: unhandled exceptions are captured tooGet the key from Dashboard > Maintenance > your application > Reveal key. Prefer reading it from the environment: linearjeph.init(os.environ["LINEARJEPH_KEY"], ...).
init installs sys.excepthook and threading.excepthook (pass hooks=False to skip) and registers an atexit flush. It never raises and returns True when the SDK is enabled.
init
init(key, release=None, environment="production", endpoint="https://linearjeph.cc", **options) -> bool| Argument | Type | Default | Meaning |
|---|---|---|---|
key | str | required | Application ingest key. An empty key leaves the SDK disabled. |
release | str | None | Version or build identifier, up to 100 chars. Drives release tracking and regression detection. |
environment | str | "production" | Up to 40 chars, for example staging. |
endpoint | str | https://linearjeph.cc | Base URL; change only for a private deployment. |
sample_rate | float | 1.0 | Fraction of errors and messages sent, 0.0 to 1.0. |
traces_sample_rate | float | 1.0 | Fraction of transactions sent. |
before_send | callable(event) -> event or None | None | Change the event dict or return None to drop it. If it raises, the event is dropped. |
scrub_keys | list[str] | [] | Extra tag-key substrings to filter, case-insensitive. |
max_queue | int | 100 | Items waiting to be sent. When full, new items are dropped (never blocks). |
batch_size | int | 20 | Events per request, 1 to 100. |
retries | int | 4 | Retries after the first attempt for 429, 5xx and network errors. |
backoff | float | 0.5 | Base delay in seconds; doubles each attempt with jitter. A Retry-After header on 429 is used instead (max 60 s). |
timeout | float | 10.0 | Seconds per HTTP request. |
flush_timeout | float | 5.0 | Longest wait for the flush at interpreter exit. |
in_app_exclude | tuple[str] | site-packages, dist-packages, /lib/python | Filename fragments for frames that are not your code (affects grouping). |
hooks | bool | True | Install the uncaught exception hooks. |
debug | bool | False | Log SDK problems to the linearjeph logger at DEBUG. |
Calling init again replaces the configuration (the previous queue is flushed first).
Functions
| Function | Description |
|---|---|
capture_exception(exc=None, tags=None) | Report an exception. With no argument it reports the exception being handled. Accepts an exception instance or a sys.exc_info() tuple. |
capture_message(message, level="info", tags=None) | Report a message as an event of type Message. The level is added as the tag level. |
capture_transaction(name, duration_ms, status="ok", tags=None) | Report one timed operation. |
start_transaction(name) / transaction(name) | Context manager and decorator that times a block and reports it. Status is error if the block raises (the exception is not swallowed). |
heartbeat(name, status="ok", interval_s=None) | Check in. status is ok or error; interval_s is 30 to 2,592,000. name must match [A-Za-z0-9._-]{1,64}, otherwise the call is ignored. |
monitor(name, interval_s=None, capture=False) | Context manager and decorator for cron jobs: heartbeat ok on success, error if the block raises (then the exception continues). capture=True also reports the exception. |
report_packages() | Send the installed distributions to the dependency audit. Returns the number queued. |
set_tag(key, value) | Add a tag to every later event. At most 20 tags per event. |
set_user(user_id) | Attach a user id (a string you choose; None clears it). |
scrub(data) | Return a copy of a dict, list or string with sensitive values replaced by [Filtered]. |
flush(timeout=5.0) | Block until the queue is delivered or the timeout passes. Returns True when empty. |
close(timeout=5.0) | Flush and stop the sender. |
serialize_exception(type, value, tb) | Build the raw error event (used by tests and custom integrations). |
All functions return quickly and swallow their own errors.
Django
Add the middleware near the top of MIDDLEWARE and initialise in settings.py (or an AppConfig.ready):
# settings.py
import linearjeph, os
linearjeph.init(os.environ["LINEARJEPH_KEY"], release=os.environ.get("RELEASE"),
environment=os.environ.get("ENVIRONMENT", "production"))
MIDDLEWARE = [
"linearjeph.integrations.django.LinearjephMiddleware",
# ...
]
LINEARJEPH_IGNORE_PATHS = ("/health", "/static/") # optional, path prefixes not timedThe middleware times every request using the URL route (GET /items/<int:pk>/) and captures exceptions raised in views.
Flask
import linearjeph
from flask import Flask
from linearjeph.integrations.flask import init_app
linearjeph.init("lj_app_YOUR_KEY", release="1.4.2")
app = Flask(__name__)
init_app(app, ignore_paths=("/health",))Request timings use the route rule (/items/<int:id>); unhandled exceptions are captured through Flask's got_request_exception signal.
FastAPI, Starlette and any ASGI app
import linearjeph
from fastapi import FastAPI
from linearjeph.integrations.asgi import LinearjephASGIMiddleware
linearjeph.init("lj_app_YOUR_KEY", release="1.4.2")
app = FastAPI()
app.add_middleware(LinearjephASGIMiddleware, ignore_paths=("/health",))Transactions are named after the matched route when the framework provides it, otherwise the raw path. Exceptions that escape the app are captured and re-raised.
Performance beyond web requests
with linearjeph.start_transaction("nightly-import"):
import_everything()
@linearjeph.transaction("send-invoice")
def send_invoice(order): ...Transactions need the Professional plan or above.
Cron jobs and heartbeats
import linearjeph
linearjeph.init("lj_app_YOUR_KEY")
@linearjeph.monitor("nightly-report", interval_s=86400)
def main():
build_report()
if __name__ == "__main__":
main() # the atexit flush delivers the heartbeat before the interpreter exitsThe first check-in creates the monitor. If the job fails, or does not check in within the interval plus 5 minutes, an incident opens and alerts are sent. As a context manager: with linearjeph.monitor("sync", interval_s=900): .... Plain function: linearjeph.heartbeat("worker", "ok", interval_s=60).
Dependency audit
linearjeph.report_packages() # call on startup or deploy, not per requestSends every installed distribution (normalised lowercase name and version). The list is replaced on each call. In CI you can run it from a script after pip install, with the same init call.
Release tracking in CI
# .github/workflows/deploy.yml
env:
RELEASE: ${{ github.ref_name }}+${{ github.run_number }}linearjeph.init(os.environ["LINEARJEPH_KEY"], release=os.environ["RELEASE"])A new release string on each deploy lets LINEARJEPH show which release introduced an issue and flag regressions of resolved issues.
Sampling
linearjeph.init(key, sample_rate=0.5, traces_sample_rate=0.1) # half the errors, a tenth of transactionsSampled-out events never leave the process. Sampling applies before before_send.
Scrubbing and before_send
Tag values under sensitive keys are replaced by [Filtered]: any key containing password, secret, token, authorization, auth, cookie, api_key, session, csrf, bearer, credential or private_key, plus your scrub_keys. Text such as password=hunter2 or Bearer eyJ... in exception and message text is masked too. The SDK does not read or send request headers, bodies, cookies or local variables.
def before_send(event):
if event["type"] == "error" and "HealthCheckError" in event["exception"]["type"]:
return None # drop
event["tags"]["tenant"] = current_tenant()
return event
linearjeph.init(key, before_send=before_send, scrub_keys=["ssn", "iban"])Remember that anything you put in a tag is sent; use linearjeph.scrub(...) for your own structures.
Fork-based servers
gunicorn and uWSGI workers that fork after init are handled: each process starts its own sender thread and queue on first use. Call init once in the master or in each worker.
Troubleshooting
- Turn on
debug=Trueand attach a log handler for thelinearjephlogger. - A 401 logs "ingest key rejected" once and stops sending: check the key and that the application is active.
initreturnsFalsewhen the key is empty.- Heartbeat names with spaces or other characters are ignored; use letters, digits, dot, dash, underscore.
capture_exception()with no active exception does nothing.- For
python -cor other very short scripts, calllinearjeph.flush()before exiting throughos._exit. - See also Troubleshooting and the Ingest HTTP API.