My team at Amazon is looking at extending the existing logging functionality in ROS2. I’ve described below what our initial plans are for logging and the justifications. I welcome any feedback on the plan.
ROS 2 Logging Current State
Currently in ROS2 most of the logging implementation exists in the rcutils library. The interface into it is a set of macros that get generated during the build and a few functions in rcutils that back them. A string representing the logger name is used as input to these functions to identify which logger is being used/modified. Each implementation of the RCL then provides its own logging interface. In the RCLCPP library, for example, it is again a set of generated macros that are backed by the rcutils macros. When a logger is created in one of the language RCL libraries, it does not call down into the rcutils library to initialize any state for that logger.
Loggers can be created in association with a node or on their own. Loggers associated with a node are identical to loggers not associated with the node except that the name of the logger is automatically set based on the associated node’s name/namespace. Logs can be created in a hierarchy based on their name. The hierarchy can be used to adjust the severity level at which a logger operates.
The logging functionality in the rcutils library currently allows for only a single output function to be set for all logs. It defines a typedef for the output function header and allows the output function to be changed by calling a setter function with a pointer to a new output function. The rcutils library also includes one output function implementation which sends logs to stdout. The output function cannot be changed for different loggers in the hierarchy. Every logger uses the same output function.
These are the changes my team is looking to implement.
- Increase the number of output functions that can be set in the rcutils layer. We will add new functions to add and remove functions from a list of output functions in the rcutils library, but will not add this capability to the RCL interface. For the initial implementation, we will not add the ability to change the output functions at different logger hierarchies.
- Write a new log output function in the rcutils library that will forward all logs from a logger associated with a Node to a
rosouttopic on that node.
- Define an interface for a shared library that wraps more power open source logging libraries such as log4cxx. The interface will include initialize/teardown functions, call throughs for when logging metadata changes (such as log levels and formats), and functions to output logs from the rcutils library. We will also define a way of passing through a configuration file location so that the shared library implementation can use the standard config format defined by the library that backs.
- Write a new log output function in rcutils that will call out to a shared library that implements the above interface.
- Write an implementation of that shared library that wraps the log4cxx library. This implementation will include a default configuration that will send all logs to a file.
- Modify the rcutils library so that when it initializes it will hook up the three output functions that now exist in the rcutils (stdout, rosout, and shared library). These will each be able to be individually enabled/disabled via an environment variable.
- Why not add the new interfaces for adding log output functions to the RCL layer so that people implementing their own nodes can hook up additional loggers?
- If someone really wants to do this it will be possible by the RCL handle for their node and directly using the rcutils library. The reason for not adding to the RCL interface to make it easy to do in every language is that we do not want to encourage node developers to add their own custom logging in this way. It is better to conform to relying on only the outputs in the rcl/rcutils libraries so that your node can be more easily integrated into other applications. If an application developer wishes to use your node in their application, but wants total control over how logging is handled, it is a better experience for them to only need to adjust the standard output mechanisms natively provided by ROS 2.
- Why not also add the ability to set different output functions for different logger hierarchies?
- We are not providing this as part of the native rcutils library as a simplifier for our initial work. This functionality would be available as part of the shared library output that we are proposing. So anyone who really wanted that level of control could get it for both stdout and file logging by adjusting the configuration file for the shared library logger.
- Why not send all logs to the rosout topic instead of only those sent to loggers associated with nodes?
- Because of the way ROS 2 associates DDS concepts with ROS Nodes, there didn’t seem to be a clean way to setup a general topic on a process that wasn’t associated with an existing Node. Since a process can also contain multiple ROS Nodes, we didn’t think it would be good design to just pick one to have everything published to.
- Why the shared library?
- We went with the shared library approach in order to provide a way to hook into an existing logging library without tying ROS 2 to only using that library. Wrapping an existing logging library will give us the ability to have features such as file logging, log file rotation, and a hierarchy of output sinks without the need to implement all of that in the ROS codebase.
- Why keep the existing stdout logger implementation in rcutils instead of relying on the shared library to provide the stdout implementation, since most major logging libraries already have those created.
- We decided to keep the existing stdout output function because it provides a standard in the cases where someone does not want to rely on the shared library logger. Since you can enable/disable any of the three natively provided log output functions it is easy for a user who wants more control to disable the ROS provided stdout and rely on the
- Since the shared library will be used in the rcutils layer will it integrate with the custom allocators that the rcl/rcutiles use?
- No, we are not planning to provide a hook into the custom allocator for the initial implementation. This could be added later, however, most of the open source logging implementations that are well supported and provide rich features do not provide interfaces for providing your own allocator. We did not want to pass the allocator into the shared library when the shared library wouldn’t actually be able to use it. In the case where someone needs the control over allocation they would need to recompile anyways and at that point could swap out the shared library implementation with a logger of their choosing that provides more control over memory allocation.