Skip to main content
The Python distribution and import are named vexlo. It requires Python 3.10 or newer and has no runtime package dependencies. For installation and an application example, use Python SDK.

Public API index

Initialization and configuration

Call init() once at application startup, before instrumented functions execute. An explicit argument takes precedence over its environment variable, except that spool_dir="" falls back to the environment or default directory. An omitted environment-backed argument is None in the Python signature and resolves to the effective default below. The signature accepts positional or keyword arguments. Use keywords to make the configuration readable. Path denotes pathlib.Path:
Boolean environment values are stripped and lowercased. 0, false, no, off and an empty string mean false; an unset or unrecognized value means true. An invalid sample-rate string, an out-of-range rate, an invalid service name, an unresolved default home directory, or a tag_keys/redact_keys conflict raises a VexloError during init(). An explicitly empty service name is invalid even when VEXLO_SERVICE is set. Passing arguments of the wrong Python type can raise built-in exceptions. Type errors in a configuration call are outside the runtime capture boundary.

Reinitialization

A later init() replaces configuration, assigns a new instance ID and replaces the local writer. It does not reset cumulative diagnostic counters. Initialize before handling work; reinitialization while calls are active can leave their recording without a completed call. Use deployment policy for supported runtime reductions in capture.

Runtime policy

The SDK refreshes the local service policy at most once a second. It combines that policy with init() by taking the lower setting for enabled, value_sample_rate, capture_logs, capture_values and capture_error_message. Policy cannot enable a feature disabled locally. A missing, unreadable, malformed or stale policy restores the initialization settings. A policy is stale when its updated_at is more than 300 seconds old. The Python policy reader parses max_value_bytes, but capture does not apply it; the fixed slot limit remains in effect. See Capture controls for the supported-effect matrix and Configuration for administration.

Markers and callable behavior

Both trace and step accept a function directly, or a keyword-only retries argument whose default is False. Bare @vexlo.step and called @vexlo.step(retries=True) preserve the callable’s signature and return type for callers and type checkers. A captured input uses bound parameter names when a signature is available. The first parameter named self is omitted. A callable with no usable signature or an unsuccessful binding falls back to args and kwargs, so do not assume every input has named parameters.

Retry grouping

Inferred grouping requires consecutive recorded child calls under the same parent, the same function, matching complete input captures, and failures with the same error class. An undeclared call that succeeds without emitting marked descendants is assigned a new step. Use retries=True when a successful recovery must remain an attempt of the failed step. Declared grouping relaxes the input and error-class checks; the preceding recorded child must still have failed. An inferred candidate can already have emitted children before its result is known. If undoing its tentative path would orphan those children, the SDK retains that path without confirming a retry continuation. Declare known retries rather than relying on this inference boundary. Without input capture, or with a truncated input, Vexlo cannot infer that match. Declared retry grouping remains available with capture_values=False. Sampling values out still captures the input used for inference.

Supported callable forms

A coroutine created but never awaited produces no record. For all suspended forms, argument binding happens on first resumption. Generator duration includes the consumer’s time between resumptions. Close an async generator within its trace if iteration ends early; otherwise its finalization can occur after the trace closes and leave a call without completion data. enabled=False disables recording. A marked async generator still passes its items through the wrapper; removing its marker removes that remaining wrapper path.

Decorator ordering and setup errors

Put classmethod, property, cached_property or partialmethod above the Vexlo marker. Applying a marker to the descriptor object raises a VexloError; applying it to a non-callable also raises. A class is returned unchanged and increments markers_declined: instrument its methods or __init__ instead. Applying another marker to an already marked function creates a redundant nested step.

Tags and execution context

tag(**kwargs) adds key/value pairs to the current trace while it remains active. It does nothing without a trace or while capture is disabled. Invalid pairs are ignored; a redacted key is dropped and counted in redacted_tags. Values accept the exact built-in types str, int, float, bool and None. They become searchable text: for example, integer 42 becomes "42", True becomes "true", and None becomes "null". Other objects, including subclasses of those types, are ignored rather than converted with str(). The same conversion applies to arguments harvested by tag_keys. Keys and rendered values must be printable ASCII. Keys longer than 64 characters are ignored; values longer than 256 characters are truncated. Prefer init(tag_keys=[...]) for known entry-point arguments. Those tags are harvested before the trace-opening record is queued. Runtime tag() changes reach the server only if they happen before that record is serialized to local disk; late changes can be lost without a counter. This is not a durable update API for a trace that has already been sent. With correlate_otel=True, an active span’s trace ID is recorded as otel_trace_id when a recording opens. Setting it to False skips the optional import. This correlation does not propagate context over HTTP or other transports. carrier() returns a frozen Carrier containing the active trace ID and call-stack snapshot. In an uninstrumented context with no trace or step frame, it still returns a Carrier, with trace_id=None and an empty step tuple. attach() accepts that object and returns a context manager. A with block yields None, restores the snapshot for the block, then restores the receiving context on exit. Use with vexlo.attach(context): in a receiving thread. Context follows synchronous calls and await, with separate context in concurrent asyncio tasks; thread pools and bare threads need an explicit handoff. A carrier is an in-process snapshot, not a network or subprocess propagation format. Child work must finish before the entry point completes if its result is to be recorded in that trace.

Flushing and diagnostics

flush() requests a synchronous local drain with a nominal two-second deadline and returns None. This is not a hard wall-clock return bound: filesystem writes, flushes and fsync can exceed the deadline. It is a no-op without a configured writer. Outside strict mode, faults at that boundary are counted instead of raised. A return does not confirm that all bytes reached disk, and it does not acknowledge server ingestion. The process-exit drain uses the same nominal deadline and has the same filesystem exposure. The deadline limits drain-loop progress and lock waits; it does not preempt disk operations. Local write losses also have outbox/relay loss accounting; Diagnostics is not a complete disk-delivery receipt. Verify an end-to-end recording through the server.

Diagnostic counters

diagnostics() returns an immutable snapshot. Counters accumulate over the process lifetime, and a new init() does not reset them. Concurrent updates can fall between individual counter reads; the snapshot is not an atomic multi-counter transaction. Do not require all counters to be zero. For example, a deliberately withheld tag raises redacted_tags, and child work that outlives a trace raises its corresponding lifetime counter.

Strict mode and warnings

Use strict=True for tests that need internal faults to raise on strict paths. Internal faults while recording an application’s exception are counted regardless of strict mode, so an ordinary recording failure does not replace that exception. Setup validation still raises outside strict mode. Application callbacks that raise KeyboardInterrupt or SystemExit are not swallowed by guards that catch Exception. Fault warnings are rate-limited to one per minute per fault location. They use the application’s logging handlers on the thread encountering the fault. Setting the vexlo logger to ERROR silences those warnings while preserving counters.

Capture bounds and failure behavior

A structural limit truncates the captured representation and marks it truncated. Functions continue running after the step cap, but new calls are not recorded. Call-record pressure evicts a trace’s buffered records together; held-value pressure removes its values while retaining its call records. The 16 MiB accounting excludes mutable trace tags, which are bounded separately by record and tag-count limits. These budgets do not establish a total process-memory ceiling. Adapters, exception-message formatting and log formatting can execute application code. The value-walk deadline does not preempt a slow serializer, __str__, or logging handler. The SDK uses a background writer for local files and sends no network traffic of its own. It does not delete its local queue: see Retention for relay-side cleanup.