# PyObs Desk > Audit a Python service's observability before it ships - would you see it if it failed, and is its telemetry safe - then instrument it. The free check runs in the browser and uploads nothing: it lexes pasted Python (FastAPI, Flask, Django, Celery or a plain worker; one or more files), rebuilds the block structure, inventories every log call, prometheus_client metric, OpenTelemetry span, except block, outbound HTTP call and request or task handler, and runs 25 deterministic checks drawn from Python observability practice. The paid audit rates logs, metrics, traces and correlation, confirms or dismisses every flag and gives exact before/after edits per finding; the paid instrument lane writes the whole instrumented file(s) and a signals list. The page checks every quote and line against the paste, applies every edit and file back into the paste and re-runs its checks, and calls out a reply that repeats a credential from the paste. App: https://pyobs-desk.skillsafe.ai/ API tutorial: https://pyobs-desk.skillsafe.ai/api.html ## Input - The code pasted into one box, each file introduced by a `# file: ` line; dropped files or a dropped package folder are joined that way automatically. A paste with no `# file:` line is read as one file (app.py). - Nothing is imported or run: every flag is a reading of the text, with the file and line it points at. Strings and comments are lexed out first, so a string that mentions print( or logger.info( is never a finding. - Middleware, a logging configuration or instrumentation in a module that was not pasted is outside the check (PO-26 says so); backends, dashboards and alerts are outside the paste and the paid lanes ask rather than assume. ## Flags the free check raises - PO-01 (medium): print() used as the service's log (outside an if __name__ == "__main__" block). - PO-02 (low): a log message built with an f-string, % or .format instead of fields. - PO-03 (low): the root logger (logging.info(...)) or the deprecated .warn(). - PO-04 (high for a broad or bare except, medium otherwise): an exception swallowed - nothing logged, nothing re-raised. - PO-05 (medium): a broad except that logs without the traceback and does not re-raise. - PO-06 (high for credentials, medium for personal data): a secret or personal data in a log call or span attribute - including inside f-string placeholders and extra={...}. - PO-07 (medium): a whole request body, payload or header map logged. - PO-08 (low): ERROR level on an expected client-error (4xx) path. - PO-09 (high): a prometheus_client metric created inside a function - ValueError: Duplicated timeseries on the second call. - PO-10 (high): an invalid metric name or no documentation argument; (low) not snake_case, a duration not in seconds, reserved suffixes. - PO-11 (high): a high-cardinality label name (an id, email, URL, IP, ...). - PO-12 (medium): an unbounded label value (the raw request path, the exception text, an f-string, an id). - PO-13 (high): a metric recorded against its declared labels - wrong names or count, a labelled metric without .labels(), .labels() on an unlabelled one, Counter.dec(). - PO-14 (low): a Summary where a Histogram aggregates across replicas. - PO-15 (low): a duration measured with time.time(). - PO-16 (high): a span that is never ended (start_span without end, start_as_current_span never entered). - PO-17 (medium): a span name built from request data. - PO-18 (medium): an exception handled inside a span without record_exception or set_status. - PO-19 (low): a TracerProvider without service.name. - PO-20 (low): a traced service whose logs carry no trace id. - PO-21 (medium when traced, low with a correlation id): an outbound HTTP call that does not propagate the context. - PO-22 (medium): request handlers that log, with no correlation or request id anywhere. - PO-23 (low): a metric declared but never recorded. - PO-24 (medium): handlers without a latency or traffic signal (no metric, no span, no instrumentation middleware). - PO-25 (medium): a service with handlers and no log call. - PO-26 (info): context - framework, libraries, files not checked. ## Free extras - Every handler with its method, route and whether anything measures it; every log call with level, library, message style and fields; every metric with type, labels and how many places record it; every span. - Put any audit finding (or all of them, or the instrumented files) back into the paste and re-run the check; undo puts the original back. - Exports: the check as Markdown, the flags as CSV, the edited or instrumented code, the changes as a unified diff (.patch) for git apply, the findings or signals as CSV, the whole result as JSON. ## Paid lanes (the `task` field) - `audit`: `{task, verdict: blocked|needs_work|ready, headline, service, signals: [{signal: logs|metrics|traces|correlation, status: good|partial|missing, note}], findings: [{id, flags, file, line, severity, pillar, title, evidence, why, edits: [{before, after}]}], flag_answers: [{flag, status: confirmed|dismissed, reason}], plan, questions, summary}`. `before` is an exact slice of the file, applied in order. - `instrument`: `{task, verdict: instrumented|partial, headline, files: [{path, content}], signals: [{kind: metric|span|log, name, type, labels, where, why}], setup, open: [{item, reason}], questions, summary}`. `content` is the whole file. - Run input: `{task, code, facts, note?, audit_handoff?, retry_note?}` - all strings; `facts` is the page's analysis as a JSON string. The audit hands its findings to the instrument lane with every edit applied to `code`. - Model: gpt-terra, 10% publisher markup. A run needs a signed-in account; the reservation shown before a run is a hold, and only what the run uses is charged. ## Sources - python-observability by Seth Hobson, wshobson/agents (github.com/wshobson/agents, plugins/python-development/skills/python-observability), MIT - https://skillsafe.ai/skill/@wshobson/python-observability - The checks and the reconciliation are original to this app. Prometheus and OpenTelemetry are CNCF projects; this app is not affiliated with them.