Logging & BlackBox API
HORUS provides two complementary recording mechanisms:
horus::log-- Structured log messages visible in thehorus logCLI. Use for operational status, warnings, and errors during normal operation.horus::blackbox-- Persistent flight recorder for post-mortem analysis. Use for events that matter after a crash or anomaly.
#include <horus/log.hpp>
Quick Reference
Logging
| Function | Level | Use Case |
|---|---|---|
horus::log::info(node, msg) | Info | Normal operation, milestones |
horus::log::warn(node, msg) | Warn | Degraded operation, approaching limits |
horus::log::error(node, msg) | Error | Failures, safety events |
BlackBox
| Function | Purpose |
|---|---|
horus::blackbox::record(node, event, data) | Record an event to the flight recorder |
horus::log
info()
void horus::log::info(const char* node, const char* msg);
void horus::log::info(const std::string& node, const std::string& msg);
Emits an informational log message. Visible in horus log with default filters.
Parameters
| Name | Type | Description |
|---|---|---|
node | const char* or const std::string& | Name of the node emitting the log. Should match the node's registered name. |
msg | const char* or const std::string& | Log message content. |
horus::log::info("motor_ctrl", "Motor controller initialized at 1000Hz");
horus::log::info("camera", "Streaming 1280x720 RGB @ 30fps");
warn()
void horus::log::warn(const char* node, const char* msg);
void horus::log::warn(const std::string& node, const std::string& msg);
Emits a warning log message. Indicates degraded operation or approaching limits.
horus::log::warn("imu", "Calibration drift detected: 0.3 deg/s");
horus::log::warn("battery", "Battery at 15% -- consider returning to base");
error()
void horus::log::error(const char* node, const char* msg);
void horus::log::error(const std::string& node, const std::string& msg);
Emits an error log message. Indicates a failure that requires attention.
horus::log::error("safety", "Emergency stop triggered by collision sensor");
horus::log::error("motor_ctrl", "Motor driver communication timeout");
Log Levels
| Level | Constant | When to Use |
|---|---|---|
| Info | 0 | Normal operation: startup, milestones, periodic status |
| Warn | 1 | Something is wrong but the system continues: sensor drift, low battery, approaching limits |
| Error | 2 | Something failed: communication loss, safety event, unrecoverable state |
The log level is passed internally as an integer to the C FFI layer (horus_log(level, node, msg)). Info = 0, Warn = 1, Error = 2.
Viewing Logs with the CLI
All log messages are visible in the horus log command:
# Stream all logs
horus log
# Filter by node
horus log --node motor_ctrl
# Filter by level
horus log --level warn
# Filter by level and node
horus log --level error --node safety
Logs appear in real-time as nodes emit them. The CLI connects to the same SHM transport that nodes use for IPC.
String Formatting
The C++ log API takes const char* or const std::string&. For formatted messages, build the string before logging:
// Using std::to_string
double temp = 72.5;
horus::log::info("sensor",
("Temperature: " + std::to_string(temp) + " C").c_str());
// Using snprintf for precise formatting
char buf[256];
std::snprintf(buf, sizeof(buf), "Position: (%.3f, %.3f, %.3f)", x, y, z);
horus::log::info("odom", buf);
// Using std::string concatenation
std::string msg = "Joint angles: [";
for (size_t i = 0; i < 6; ++i) {
if (i > 0) msg += ", ";
msg += std::to_string(angles[i]);
}
msg += "]";
horus::log::info("arm", msg);
BlackBox Recording
The blackbox is a persistent flight recorder. Unlike logs, blackbox entries survive crashes and are designed for post-mortem analysis. Think of it as the "black box" on an aircraft.
record()
void horus::blackbox::record(const char* node, const char* event, const char* data);
Records a structured event to the flight recorder.
Parameters
| Name | Type | Description |
|---|---|---|
node | const char* | Node that recorded the event. |
event | const char* | Event category (e.g., "collision", "estop", "joint_limit"). |
data | const char* | Event payload -- typically JSON or a structured string. |
horus::blackbox::record("safety", "collision",
"{\"sensor\": \"bumper_front\", \"force_n\": 45.2}");
horus::blackbox::record("motor_ctrl", "overcurrent",
"{\"motor\": 3, \"current_a\": 12.5, \"limit_a\": 10.0}");
horus::blackbox::record("nav", "goal_reached",
"{\"goal\": \"charging_station\", \"error_m\": 0.02}");
Viewing BlackBox Data
# Dump all blackbox entries
horus blackbox dump
# Filter by node
horus blackbox dump --node safety
# Filter by event type
horus blackbox dump --event collision
# Export for analysis
horus blackbox export --format json > crash_report.json
When to Use Log vs BlackBox
| Scenario | Use | Why |
|---|---|---|
| "Motor initialized at 1000Hz" | log::info | Operational status, no post-mortem value |
| "IMU drift exceeds 1 deg/s" | log::warn | Operator should see it, but not a crash event |
| "Emergency stop triggered" | Both | Operator sees it now (log), investigators see it later (blackbox) |
| "Joint hit limit at 2.35 rad" | blackbox::record | Critical for root-cause analysis after incident |
| "Collision detected, force=45N" | blackbox::record | Physical event that must be preserved |
| "Starting path to waypoint 3" | log::info | Operational, no forensic value |
| "Motor overcurrent: 12.5A" | Both | Safety event visible now and preserved |
Rule of thumb: If you would want to see it when investigating why a robot stopped working 3 hours ago, record it in the blackbox.
Example: Structured Diagnostics
A motor controller that logs operational status and records safety events for post-mortem analysis.
#include <horus/log.hpp>
#include <cstdio>
struct MotorDiagnostics {
double current_limit = 10.0; // amps
int overcurrent_count = 0;
void check_motor(int motor_id, double current_a, double temp_c) {
// Normal status -- log
char buf[256];
std::snprintf(buf, sizeof(buf),
"Motor %d: %.1fA, %.1f C", motor_id, current_a, temp_c);
horus::log::info("motor_diag", buf);
// Warning threshold -- warn
if (current_a > current_limit * 0.8) {
std::snprintf(buf, sizeof(buf),
"Motor %d approaching current limit: %.1fA / %.1fA",
motor_id, current_a, current_limit);
horus::log::warn("motor_diag", buf);
}
// Overcurrent -- error + blackbox
if (current_a > current_limit) {
overcurrent_count++;
std::snprintf(buf, sizeof(buf),
"Motor %d overcurrent: %.1fA (limit %.1fA)",
motor_id, current_a, current_limit);
horus::log::error("motor_diag", buf);
// Blackbox: structured data for post-mortem
char event_data[512];
std::snprintf(event_data, sizeof(event_data),
"{\"motor\": %d, \"current_a\": %.2f, \"limit_a\": %.2f, "
"\"temp_c\": %.1f, \"count\": %d}",
motor_id, current_a, current_limit, temp_c, overcurrent_count);
horus::blackbox::record("motor_diag", "overcurrent", event_data);
}
// Thermal warning
if (temp_c > 80.0) {
std::snprintf(buf, sizeof(buf),
"Motor %d thermal warning: %.1f C", motor_id, temp_c);
horus::log::warn("motor_diag", buf);
char event_data[256];
std::snprintf(event_data, sizeof(event_data),
"{\"motor\": %d, \"temp_c\": %.1f}", motor_id, temp_c);
horus::blackbox::record("motor_diag", "thermal_warning", event_data);
}
}
};
Best Practices
Do:
- Use the node name consistently -- match the name registered with the scheduler
- Keep messages concise -- include the key metric, not a paragraph
- Use blackbox for any event you would want during incident investigation
- Include units in numeric values (
"45.2N","12.5A","2.35rad")
Avoid:
- Logging every tick at info level -- this floods the log at 1000Hz. Log periodic summaries instead
- Logging inside tight loops without rate limiting
- Using error level for non-errors (e.g., "no new data this tick" is normal, not an error)
- Putting sensitive data (passwords, keys) in logs or blackbox