Traces
Traces let you follow the lifecycle of an operation across your robot's software stack. Every trace is made up of spans — individual units of work with timing and attributes.
Your first trace
import botpulse
import time
pulse = botpulse.init(
service_name="navigation-node",
robot_id="robot-01",
)
# A simple trace
with pulse.trace("process_lidar_data") as span:
# Simulate processing
time.sleep(0.05)
span.set_attribute("points_received", 1024)
span.set_attribute("algorithm", "icp")
print("Done — trace sent to collector")
Running this creates a trace with a single span called process_lidar_data.
Nested spans
Real operations have sub-steps. Nest spans to see the breakdown:
import botpulse
import time
pulse = botpulse.init(
service_name="navigation-node",
robot_id="robot-01",
)
with pulse.trace("navigate_to_waypoint") as root:
root.set_attribute("waypoint.x", 12.5)
root.set_attribute("waypoint.y", 3.2)
with pulse.child_span("plan_path") as span:
time.sleep(0.02)
span.set_attribute("algorithm", "a_star")
span.set_attribute("path_length_m", 14.8)
with pulse.child_span("execute_trajectory") as span:
time.sleep(0.1)
span.set_attribute("duration_target_ms", 5000)
span.set_attribute("max_velocity", 0.5)
This produces a trace with three spans: the root and two children. You can see exactly how much time path planning vs. trajectory execution took.
Adding events
Events are timestamped markers within a span. Use them to record significant moments:
with pulse.trace("pick_and_place") as span:
span.add_event("gripper_opened")
# ... move to object ...
span.add_event("object_grasped", {"object_id": "block-A"})
# ... move to target ...
span.add_event("object_released", {"target_slot": 3})
Recording errors
Mark a span as an error when something goes wrong:
try:
with pulse.trace("call_motor_driver") as span:
result = motor_driver.set_speed(100)
if result.error:
span.set_status("error", result.error_message)
span.record_exception(result.error)
except Exception as e:
span.set_status("error", str(e))
span.record_exception(e)
Cross-service traces
When your robot has multiple nodes (e.g., a ROS2 system with separate processes), Bot Pulse propagates trace context automatically so you see the full picture:
import botpulse
pulse = botpulse.init(service_name="perception", robot_id="robot-01")
with pulse.trace("detect_objects") as span:
# ... run detection ...
span.set_attribute("objects_found", 3)
import botpulse
pulse = botpulse.init(service_name="planning", robot_id="robot-01")
# The trace context is propagated if both nodes share the same collector
with pulse.trace("plan_navigation") as span:
span.set_attribute("objects", 3)
# ... plan path ...
Attributes reference
Attributes are key-value pairs attached to spans. Use these types:
| Value type | Example |
|---|---|
str | "a_star" |
int | 1024 |
float | 0.5 |
bool | True |
Best practices
- Name spans by operation, not by implementation (
"navigate_to_waypoint"not"run_a_star_and_publish_cmd_vel") - Keep span count reasonable — one span per logical step, not per line of code
- Set attributes early — they're most useful when they describe what the span is about
- Always record exceptions — it makes debugging production issues much easier