Log from your code

The stack logs through core_logger, not through RCLCPP_*, printf or std::cout. One logger means one place to set levels, one format, and output that reaches both the console and rosout.

Note

core_utils is the deliberate exception — it sits below the logger and cannot depend on it.

Basic use

#include <core_logger/log.hpp>

LOG_INFO("arm enabled");
LOG_INFO_STREAM("moving to destination " << destination);
LOG_WARN_STREAM("retrying after " << delay_s << " s");
LOG_ERROR_STREAM("plan failed: " << ErrorCodes::ToString(code));

Levels, quietest to loudest: FATAL, ERROR, WARN, INFO, DEBUG, VERBOSE. Each has a plain and a _STREAM form, and a _NAMED / _STREAM_NAMED variant that takes an explicit logger name.

Set the level at runtime:

CORE_LOG_LEVEL=VERBOSE ros2 launch <app>_bringup <app>.launch.py

Choosing a level

The test is who reads the line and how often it fires.

Level

Use for

ERROR

The operation failed and the caller must handle it.

WARN

Something unexpected that the system recovered from. Not for outcomes that are routine — a planner returning “start and goal too close” when the arm is already at the goal is expected, and belongs at INFO.

INFO

Milestones an operator would want in a normal run. One line per phase, not per trajectory point.

DEBUG

Detail for diagnosing a specific subsystem.

VERBOSE

Per-iteration and per-message detail, including protocol traffic.

Warning

Anything that fires per trajectory point or per network message belongs at VERBOSE. A per-point INFO line makes the console unusable and hides the lines that matter.

In headers

The bare LOG_* macros do not work in a header that is included from another package. They resolve ::logger, a non-dependent name that may not exist at the include site. Use the _NAMED form and pass the logger explicitly:

// in a header
LOG_INFO_STREAM_NAMED("my_component", "value: " << value);

Inside ROS nodes

core_logger’s RclutilsAppender bridges log output onto rosout, so ros2 topic echo /rosout and Foxglove see the same lines as the console.

Warning

Detach the appender before the node shuts down or the plugin unloads. dlclose on a plugin and rclcpp shutdown both segfault the host process if an attached appender outlives the context it writes to.

ScopedRootLogger handles this — it attaches on construction and detaches on destruction. Prefer it to attaching by hand.

Checking your work

Before opening a PR, confirm nothing slipped back in:

grep -rnE 'RCLCPP_(INFO|WARN|ERROR|DEBUG|FATAL)|printf\(|std::cout' \
  --include=*.cpp --include=*.hpp src/ | grep -v core_utils