Introducing: `ros2_fmt_logger`

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 of RCLCPP_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 _STREAM macros?
    • Yes, but it’s longer :smiley:
      double progress = 0.756;
      logger.info("Progress: {:.1} %", progress);
      RCLCPP_INFO_STREAM(logger, "Progress: " << std::setprecision(1) << progress << " %");
      

  1. This is a direct violation of REP-103 ↩︎

3 Likes

I thought I was having déjà vu, but it turns out @facontidavide released something similar a few years ago: GitHub - facontidavide/ros2_logging_fmt.

I would love to see this style of logging be adopted in rclcpp.

2 Likes

Indeed! I did encounter that during my search for it.
But I wanted to avoid using the macros altogether.

Perhaps I should have named it YAfmt_logger :smiley: