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
Callinit() 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 laterinit() 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 withinit() 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
Bothtrace 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. Useretries=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
Putclassmethod, 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
Usestrict=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.