cyclopes_
sign in sign up

Docs / Servers

Python

Standard library only, sends on a background thread, and never raises into your application. Django, WSGI and stdlib logging are wired for you.

What you need first

A project's DSN. Create a project, then copy it from that project's settings page: it carries the host and the project id, so it is the only thing this SDK needs to be pointed at.

https://<key>@cyclopes.localhost.co.zw/ingest/<project-id>

The key is write-only: it can send events and nothing else, so shipping it inside a mobile app is fine. Read it from your environment rather than committing it.

Install

Served from this install's own package index rather than PyPI.

$ pip install cyclopes --extra-index-url https://cyclopes.localhost.co.zw/pypi/simple/

This server is serving 0.4.0. Zero runtime dependencies, so it cannot collide with anything you already have.

Wire it up

import cyclopes

cyclopes.init(dsn="https://<key>@cyclopes.localhost.co.zw/ingest/<project-id>", environment="production")

# unhandled exceptions are now reported automatically; or capture manually:
try:
    risky()
except Exception:
    cyclopes.capture_exception()

On a server, read the next two sections before you ship: a process that initialises once and serves everybody needs a session per request, or every user's journey merges into one.

Django

One middleware and one setting. The middleware initialises the client from settings.CYCLOPES the first time it runs, so there is nothing to call in ready().

# settings.py
MIDDLEWARE = ["cyclopes.integrations.django.CyclopesMiddleware", ...]

CYCLOPES = {
    "dsn": "https://<key>@cyclopes.localhost.co.zw/ingest/<project-id>",
    "environment": "production",
}

Put it high in the list. Middleware below it that raises is still caught; middleware above it is not.

Any WSGI app

Flask, Pyramid, Bottle, or a bare callable: wrap the application object.

from cyclopes.integrations.wsgi import CyclopesWsgiMiddleware

cyclopes.init(dsn="...")
app = CyclopesWsgiMiddleware(app)

Logs and journeys

Two more streams, kept apart from errors on the server but related through the session id and the user you identify. Items are batched, 30 of them or five seconds, and sent in the background.

# who is this? attaches to logs, activity and errors from now on
cyclopes.identify({"id": "u-42", "email": "dev@example.com"})

# diagnostic log stream (the Logs page)
cyclopes.logger.info("cache warmed", keys=1200)
cyclopes.logger.error("upstream timed out", service="payments")

# activity stream (the Journeys page): screens and named steps
cyclopes.screen("Cart", {"items": 3})
cyclopes.track("checkout_started", {"total": 99.9})

On a multi-user server prefer per-request cyclopes.set_user(...), which overrides the global identify() for events and log items on that thread. cyclopes.flush() drains everything.

screen() records where the user is and every track() after it says so. For an arrival nothing announces, say so with cyclopes.set_current_screen("PromoSheet"). It is thread-local, like the scoped session.

Bridging stdlib logging

Sub-error logs become breadcrumbs and error-and-above become events, so the trail leading up to a failure arrives with it:

import logging
from cyclopes.integrations.logging import CyclopesLogHandler

logging.getLogger().addHandler(CyclopesLogHandler(level=logging.INFO))

To mirror the same records into the log stream as well:

logging.getLogger().addHandler(CyclopesLogHandler(level=logging.INFO, stream=True))

Sessions

A session is one continuous sitting. The SDK makes an id at init() and rotates it after 30 idle minutes (session_timeout=1800; 0 disables).

cyclopes.new_session()          # rotate explicitly, e.g. on logout
cyclopes.set_session("abc")     # pin a known id

On a server, scope one per request. A process initialises the SDK once and serves every user, so without a scope they all share one session id and their journeys merge into a single meaningless blob:

with cyclopes.session(request_id):
    cyclopes.identify({"id": user.id})
    cyclopes.track("checkout_started")

The Django and WSGI middlewares do this for you.

Scrubbing

Every field, including source snippets, local variables, breadcrumbs and contexts, passes through before_send(event) -> event | None before it leaves your process:

cyclopes.init(dsn=..., before_send=my_scrubber)        # redact or drop events
cyclopes.init(dsn=..., include_local_variables=False)  # never capture locals

Local variables are captured by default because they are usually what makes a traceback answerable. On a service handling card numbers, they are not.