Official Python SDK for CloseYourIt observability
Project description
CloseYourIt Python SDK
SDK Python ufficiale per inviare errori, log e metriche a CloseYourIt da applicazioni server-side. Il repository è privato e il package è in fase pre-alpha.
Stato
Lo scaffold, la configurazione fail-safe, lo scope isolato, lo scrubbing PII, i builder tipizzati,
il transport asincrono, gli hook Python standard e le integrazioni Celery, WSGI, ASGI, Django,
Flask, HTTP e SQLAlchemy sono disponibili. Le altre integrazioni vengono aggiunte in TDD attraverso
i ticket del progetto CloseYourIt CYPY.
Requisiti
- Python 3.11 o successivo
- mise per il runtime locale
Setup
mise install
mise exec -- python -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install -e ".[dev]"
Il core WSGI/ASGI non aggiunge dipendenze runtime. Le integrazioni opzionali si installano per singolo stack:
python -m pip install "closeyourit[django]"
python -m pip install "closeyourit[flask]"
python -m pip install "closeyourit[celery]"
python -m pip install "closeyourit[requests]"
python -m pip install "closeyourit[httpx]"
python -m pip install "closeyourit[sqlalchemy]"
Verifica
.venv/bin/ruff check .
.venv/bin/ruff format --check .
.venv/bin/mypy
.venv/bin/python -m pytest
.venv/bin/python -m pip check
.venv/bin/python -m build
.venv/bin/python -m twine check dist/*
actionlint
La suite applica coverage line e branch con soglia minima del 90%.
Configurazione
Le variabili seguenti appartengono alle applicazioni che consumano l'SDK e non sono necessarie per installare o sviluppare il package:
| Variabile | Descrizione |
|---|---|
CLOSEYOURIT_ENDPOINT_URL |
URL del servizio ingest |
CLOSEYOURIT_TOKEN |
Token Bearer server-side; non deve essere esposto in client pubblici |
CLOSEYOURIT_PROJECT_ID |
Identificativo del progetto CloseYourIt |
CLOSEYOURIT_ENVIRONMENT |
Ambiente applicativo |
CLOSEYOURIT_RELEASE |
Versione dell'applicazione monitorata |
I valori reali devono essere gestiti fuori dal repository e caricati tramite doppler run.
from closeyourit import Configuration
configuration = Configuration()
if not configuration.enabled:
print(configuration.disabled_reason)
Configurazioni incomplete o non valide disabilitano l'SDK senza sollevare eccezioni. In
production l'endpoint deve usare HTTPS; il token deve essere server-side e iniziare con cyi_.
Scope e protezione dati
Lo scope usa ContextVar: task asincroni e thread non condividono mutazioni accidentali, mentre
with_scope() eredita lo stato corrente e lo ripristina al termine del blocco.
from closeyourit import current_scope, with_scope
current_scope().set_user({"id": "account-123", "email": "private@example.com"})
current_scope().set_tag("tenant", "acme")
with with_scope() as scope:
scope.set_extra("operation", "checkout")
event_context = scope.snapshot().to_event_data()
Per default lo snapshot conserva soltanto user.id; send_pii=True deve essere una scelta
esplicita dell'applicazione consumer. Password, token, credenziali, cookie, dati personali, query
sensibili e header di autenticazione vengono filtrati ricorsivamente prima del trasporto. Gli
snapshot sono copie profondamente immutabili e non cambiano se il consumer modifica gli oggetti
originali.
Eventi, log e metriche
EventBuilder produce payload conformi al contratto wire senza effettuare rete. Client applica
sampling, before_send, breadcrumb e protezione dati prima di consegnare il payload a un
EventSink. Con una configurazione valida, il sink predefinito è il transport asincrono di
produzione; una configurazione incompleta resta un no-op.
from closeyourit import Configuration, EventBuilder
configuration = Configuration(environment="production", release="v1.4.2")
builder = EventBuilder(configuration)
try:
raise RuntimeError("checkout failed")
except RuntimeError as error:
payload = builder.exception(error, handled=True)
Le eccezioni conservano cause, stack frame, handled, runtime, release e scope. Messaggi e log
vengono scrubbati prima della consegna; le metriche slow_method usano UUID idempotenti e durata
monotona. I breadcrumb sono limitati, isolati tramite ContextVar e mantengono soltanto gli ultimi
N elementi.
Transport e shutdown
Il transport usa soltanto la standard library, serializza il payload prima dell'accodamento e non
blocca il thread applicativo sulla rete. La coda è limitata, il worker parte soltanto al primo evento
accettato e gli eventi eccedenti vengono scartati in modo diagnosticabile. Retry limitati coprono
errori di rete, timeout, 408, 425, 429 con Retry-After e risposte 5xx.
from closeyourit import Client, Configuration, Transport
configuration = Configuration()
transport = Transport(configuration, max_queue=100)
client = Client(configuration, sink=transport)
client.capture_message("worker started")
client.flush(timeout=2.0)
print(transport.stats)
client.close(timeout=2.0)
flush() attende che ogni evento accettato raggiunga uno stato terminale; close() impedisce nuovi
accodamenti, drena la coda ed è idempotente. Durante un fork lo stato ereditato viene scartato e il
processo figlio crea una nuova coda e un nuovo worker. I redirect conservano metodo e body, ma il
Bearer viene inviato soltanto alla stessa authority e mai dopo un cambio host, porta o protocollo.
Logging ed errori non gestiti
CloseYourItHandler inoltra i record del modulo logging a partire da una soglia configurabile.
Gli attributi aggiunti con extra vengono scrubbati come ogni altro payload; i logger interni
closeyourit sono esclusi per impedire loop. Installazione, rimozione e chiusura sono idempotenti.
import logging
from closeyourit import Client, CloseYourItHandler, Configuration
client = Client(Configuration())
handler = CloseYourItHandler(client, level=logging.WARNING).install()
logging.getLogger().warning("retry", extra={"attempt": 2})
handler.close()
ErrorHooks integra sys.excepthook, threading.excepthook e, quando fornito, l'exception handler
di un loop asyncio. Ogni errore viene registrato come fatal e non gestito, poi il gestore
preesistente viene sempre richiamato. Cancellazioni e terminazioni intenzionali non vengono
catturate; gli errori interni dell'SDK restano fail-safe.
import asyncio
from closeyourit import Client, Configuration, ErrorHooks
client = Client(Configuration())
hooks = ErrorHooks(client).install()
async def main() -> None:
hooks.install(asyncio.get_running_loop())
# avvio dell'applicazione
asyncio.run(main())
hooks.close(timeout=2.0)
uninstall() ripristina soltanto i gestori ancora posseduti dall'istanza, senza sovrascrivere hook
installati successivamente dall'applicazione. close() esegue anche la chiusura idempotente del
client e del transport.
Celery
L'integrazione Celery è opzionale e usa esclusivamente i signal ufficiali. Registra durata di
esecuzione e latenza di coda come metriche slow_method, con task name, queue, retry count, stato,
release e soli header di correlazione esplicitamente allowlisted.
from closeyourit import CeleryIntegration, Client, Configuration
client = Client(Configuration())
celery_integration = CeleryIntegration(
client,
task_threshold_ms=500,
queue_threshold_ms=250,
correlation_headers=("traceparent", "x-request-id"),
).install()
# Allo shutdown dell'applicazione:
celery_integration.close()
client.close(timeout=2.0)
Errori, retry e task revocati vengono catturati senza modificare la politica di retry Celery.
args, kwargs e risultati non vengono mai letti né inviati. Un timestamp tecnico aggiunto al
messaggio permette di misurare la coda anche tra processi; eventuale clock skew negativo viene
azzerato. In modalità eager, dove non avviene una pubblicazione sul broker, resta disponibile la
durata del task ma non viene inventata una latenza di coda.
I correlation ID accettano soltanto identificatori ASCII con forma limitata a 128 byte;
traceparent deve rispettare il formato W3C versione 00, inclusi trace ID e parent ID non nulli.
Valori duplicati, malformati, sovradimensionati o con forma compatibile con PII vengono scartati.
Installazione, rimozione e chiusura sono idempotenti; se Celery non è installato l'adapter resta un
no-op fail-safe. La chiusura dell'integrazione non chiude il client, che può essere condiviso con
gli adapter web, HTTP e SQLAlchemy.
Applicazioni web
WSGIMiddleware e ASGIMiddleware usano esclusivamente la standard library. Creano uno scope per
richiesta, mantengono lo streaming lazy, misurano la durata monotona e catturano le eccezioni non
gestite senza alterare risposta o propagazione. Il body non viene mai letto. Il contesto include
metodo, path e URL senza query; gli header provengono soltanto da
Configuration.request_header_allowlist e quelli sensibili restano esclusi anche se aggiunti per
errore all'allowlist.
from closeyourit import ASGIMiddleware, Client, Configuration, WSGIMiddleware
client = Client(Configuration())
wsgi_application = WSGIMiddleware(wsgi_application, client)
asgi_application = ASGIMiddleware(asgi_application, client)
Il middleware riusa X-Request-ID come trace_id soltanto se è un identificatore ASCII sicuro e
limitato a 128 caratteri; altrimenti genera un UUID. A risposta completata registra status e durata
nello scope; oltre slow_request_threshold_ms emette una metrica slow_request, usando la route
templated fornita dal framework quando esiste. L'URL della metrica viene sempre ricostruito senza
userinfo, query o fragment.
Per Django inserire il middleware tra i primi elementi, così che avvolga l'applicazione:
MIDDLEWARE = [
"closeyourit.django.DjangoMiddleware",
# middleware dell'applicazione
]
Per Flask l'integrazione avvolge lo stack WSGI corrente e registra route ed eccezioni tramite gli hook ufficiali del framework:
from closeyourit import Client, Configuration, FlaskIntegration
client = Client(Configuration())
FlaskIntegration(app, client=client)
Gli adapter sono importabili anche quando Django o Flask non sono installati; le dipendenze opzionali vengono caricate soltanto quando l'integrazione corrispondente viene inizializzata.
Integrazioni HTTP e SQLAlchemy
Requests, HTTPX e SQLAlchemy restano dipendenze opzionali. Installare soltanto gli extra usati dall'applicazione:
python -m pip install "closeyourit[requests]"
python -m pip install "closeyourit[httpx]"
python -m pip install "closeyourit[sqlalchemy]"
L'instrumentation è locale all'istanza, usa gli hook/eventi ufficiali disponibili e restituisce
sempre un handle uninstrument() idempotente. Non vengono applicati monkeypatch globali.
import httpx
import requests
from closeyourit import Client, Configuration, instrument_httpx, instrument_requests
client = Client(Configuration())
session = requests.Session()
requests_instrumentation = instrument_requests(session, client)
http_client = httpx.Client()
httpx_instrumentation = instrument_httpx(http_client, client)
requests_instrumentation.uninstrument()
httpx_instrumentation.uninstrument()
Ogni chiamata produce un breadcrumb HTTP; timeout e 5xx sono marcati come warning. Le chiamate
oltre slow_external_threshold_ms generano slow_external_http, mentre richieste ripetute allo
stesso metodo, host e path templatizzato generano un solo repeated_http alla soglia. URL, query,
fragment e credenziali non arrivano mai nel payload: UUID, identificativi numerici e token-like nel
path vengono normalizzati. http_capture_hosts permette di limitare ulteriormente gli host
osservati.
La propagazione W3C traceparent è disabilitata per default e richiede sia
trace_propagation_enabled=True sia una trace_propagation_hosts esplicita. L'allowlist viene
ricontrollata a ogni redirect e il trace header viene rimosso passando a un host non consentito.
configuration = Configuration(
trace_propagation_enabled=True,
trace_propagation_hosts=("api.example.com",),
)
SQLAlchemy usa before_cursor_execute, after_cursor_execute e handle_error sull'engine sync o
async. Ogni query viene trasformata in un fingerprint privo di literal, commenti e bind raw. Le
query lente sono puntuali; profile() delimita la finestra per conteggio totale e rilevazione N+1.
from closeyourit import instrument_sqlalchemy
sqlalchemy_instrumentation = instrument_sqlalchemy(engine, client)
with sqlalchemy_instrumentation.profile(route="orders.index"):
load_orders()
sqlalchemy_instrumentation.uninstrument()
I profili usano ContextVar, quindi richieste concorrenti, task async e profili annidati non
condividono conteggi. Le API adottate sono documentate dai progetti upstream:
Requests hooks,
HTTPX event hooks e
SQLAlchemy connection events.
Packaging e release
Il progetto usa pyproject.toml, build backend Hatchling e layout src/. I tag stabili
vMAJOR.MINOR.PATCH avviano .github/workflows/publish.yml, che verifica versione, changelog,
compatibilità Python, wheel e source distribution prima di pubblicare su PyPI.
Il gate esegue anche il contratto golden vendorizzato in contracts/ingest/v1: schema, fixture dei
producer, classificazione HTTP e checksum devono restare allineati allo snapshot canonico del
repository closeyourit-docs, identificato da contracts/ingest/LOCK.json.
La pubblicazione usa il GitHub environment pypi e Trusted Publishing OIDC: non esistono token
PyPI permanenti nel repository. Prima di creare un tag, spostare le modifiche rilevanti da
[Unreleased] alla sezione della versione corrispondente.
Repository e tracker
- GitHub: https://github.com/bussolabs/closeyourit-python
- CloseYourIt: progetto
CYPY - Contratto wire condiviso:
contracts/ingest/v1
Project details
Release history Release notifications | RSS feed
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 closeyourit-0.1.0.tar.gz.
File metadata
- Download URL: closeyourit-0.1.0.tar.gz
- Upload date:
- Size: 74.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c703ff310594323f8c50553688943e4c8d2a4c8edefb932741fad36e47153e72
|
|
| MD5 |
3b87599a8a8b3948807d414bf4405a65
|
|
| BLAKE2b-256 |
5375bcb5006862f71d73c08b66a08c0a27029dfe1bd285a2b0e646cbf8699339
|
Provenance
The following attestation bundles were made for closeyourit-0.1.0.tar.gz:
Publisher:
publish.yml on bussolabs/closeyourit-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
closeyourit-0.1.0.tar.gz -
Subject digest:
c703ff310594323f8c50553688943e4c8d2a4c8edefb932741fad36e47153e72 - Sigstore transparency entry: 2164169097
- Sigstore integration time:
-
Permalink:
bussolabs/closeyourit-python@869c8abe3ef8db2ccf5345f53dbfd76d57e382c1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bussolabs
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@869c8abe3ef8db2ccf5345f53dbfd76d57e382c1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file closeyourit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: closeyourit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 46.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7856ef41b0d193cc8082f5645491614960c8677c90e04f3ada51a049a4358ecb
|
|
| MD5 |
31f64a597514eb0b58a83f41ba38c704
|
|
| BLAKE2b-256 |
bc1173e4f3e077fe31d2f219c4787e50f16cda6b445c013301c9d19d6139b51e
|
Provenance
The following attestation bundles were made for closeyourit-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on bussolabs/closeyourit-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
closeyourit-0.1.0-py3-none-any.whl -
Subject digest:
7856ef41b0d193cc8082f5645491614960c8677c90e04f3ada51a049a4358ecb - Sigstore transparency entry: 2164169123
- Sigstore integration time:
-
Permalink:
bussolabs/closeyourit-python@869c8abe3ef8db2ccf5345f53dbfd76d57e382c1 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bussolabs
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@869c8abe3ef8db2ccf5345f53dbfd76d57e382c1 -
Trigger Event:
push
-
Statement type: