Monitoring
Python SDK
Part of the `sillydev` package on PyPI. `import sillydev` does not load it: import `monitoring` from it when you want it.
← Monitoring docsUpdated
Browse the docs
init()
Call it once, as early as you can. Every argument is optional. Without a key (as an argument or in SILLYDEV_MONITORING_KEY) it does nothing. Calling it again replaces the previous setup.
runtime_metrics sends CPU, memory, garbage collection and thread counts, and the heartbeat. capture_unhandled records exceptions nothing caught, in the main thread and in other threads, without changing what Python does with them. sample_rate is the share of errors and events sent, from 0 to 1. before_send is called with the kind ("error", "event" or "slow") and the item, and returns the item, changed or not, or None to drop it. debug logs every payload to the sillydev.monitoring logger.
Python
from sillydev import monitoring
def scrub(kind, item):
if kind == "error" and "card" in item.get("message", ""):
return None # never send this one
return item
monitoring.init(
key=None, # default: SILLYDEV_MONITORING_KEY
service="orders-bot", # default: SILLYDEV_MONITORING_SERVICE, then "default"
environment="production", # default: SILLYDEV_MONITORING_ENVIRONMENT, then "production"
release="1.4.2", # default: SILLYDEV_MONITORING_RELEASE
runtime_metrics=True,
capture_unhandled=True,
sample_rate=1.0,
before_send=scrub,
debug=False,
)Metrics
increment(name, value=1, tags=None) adds to a counter, gauge(name, value, tags=None) records the latest value of something, and timing(name, ms, tags=None) records a duration in milliseconds. with monitoring.timer(name): times a block and @monitoring.timed(name) times every call of a function or coroutine; a timed call slower than a second is also kept as a slow operation.
Names are lowercase letters, digits, dots and underscores, starting with a letter. Tags are up to 8 string pairs. Each distinct name and set of tags is one series, and a project has a limit on series, so tag by things with few values (a command, a plan) and never by a user or message id.
Python
monitoring.increment("orders.created", tags={"plan": "free"})
monitoring.gauge("queue.length", len(queue))
monitoring.timing("payment.duration", 182.5, tags={"provider": "stripe"})
with monitoring.timer("reports.build"):
build_report()
@monitoring.timed("orders.sync")
async def sync_orders():
...Errors and events
capture_exception(exc=None, tags=None, context=None) reports an exception; with no argument, the one being handled. capture_message(message) reports something that is not an exception as an error. Context is any JSON-friendly dictionary shown on the issue page; values whose names look like passwords, tokens or cookies are replaced with [redacted].
event(name, attributes=None) records something that happened, such as a deploy or a sign-up. Events are listed on the Events tab, and a deploy event is marked on the Overview charts.
Python
try:
charge(order)
except PaymentError:
monitoring.capture_exception(tags={"provider": "stripe"}, context={"order": order.id})
monitoring.capture_message("Stock fell below ten")
monitoring.event("deploy", {"version": "1.4.2"})Heartbeats, flushing and shutting down
With runtime metrics on, every send is a heartbeat. With them off, call heartbeat() from your main loop so the project knows the app is alive; an alert can tell you when it stops.
The SDK sends from a background thread every ten seconds, and at exit. flush(timeout=2.0) sends everything now and returns whether it all went; shutdown() flushes and stops. A short script that exits straight after its work should call flush() first.
For asyncio apps that do not use one of the integrations, monitoring.instrument_loop() measures the running loop's lag and records exceptions in tasks nothing awaited.
Read next