Tired of fixing compile issues like:
warning: format ‘%d’ expects argument of type ‘int’, but argument has type ‘std::vector<int>::size_type’ {aka ‘long unsigned int’} [-Wformat=]
when you want to print a vector size?
Or writing .c_str() when printing an std::string?
Confused when using milliseconds to throttle?[1]
Introducing:
ros2_fmt_logger
A modern, ROS 2 logging library that provides fmt-style formatting as a replacement for RCLCPP logging macros.
Features
- Function calls instead of macros:
logger.info("Hello, {}!", name)instead ofRCLCPP_INFO(logger, "Hello, %s", name.c_str()) - Additional
.on_change()method for logging changes in values - chrono syntax for throttling:
logger.warn_throttle(1s, "Warning: {}", value) - backwards compatible with the macros, so easy to start using in existing projects without a full rewrite of the current log statements
Examples
Once-only logging
logger.info_once("This message will only appear once, no matter how many times called");
Throttled logging
using std::chrono_literals::operator""s;
logger.warn_throttle(1s, "This warning appears at most once per second: {}", value);
Change-based logging
// Log only when the value changes
logger.info_on_change(sensor_value, "Sensor reading changed to: {}", sensor_value);
// Log only when change exceeds threshold
logger.error_on_change(temperature, 5.0, "Temperature changed significantly: {:.1f}°C", temperature);
Format String Syntax
Uses the powerful fmt library format syntax:
// Basic formatting
logger.info("Hello, {}!", name);
// Positional arguments
logger.info("Processing {1} of {0} items", total, current);
// Format specifiers
logger.info("Progress: {:.1%}", progress); // Percentage with 1 decimal
logger.info("Value: {:08.2f}", value); // Zero-padded floating point
logger.info("Hex: {:#x}", number); // Hexadecimal with 0x prefix
// Container formatting (requires fmt/ranges.h)
logger.info("Values: {}", std::vector{1, 2, 3, 4});
See demo_ros2_fmt_logger.cpp for more examples.
NAQ
- Why not use std::format?
- fmt is still more powerful, like when printing ranges. Also rclcpp already depends on it indirectly, so it’s kind of free.
- Isn’t this why we have
_STREAMmacros?- Yes, but it’s longer

double progress = 0.756; logger.info("Progress: {:.1} %", progress); RCLCPP_INFO_STREAM(logger, "Progress: " << std::setprecision(1) << progress << " %");
- Yes, but it’s longer