33#include < rcl/log_level.h>
44#include < rcutils/logging.h>
55
6+ #include < spdlog/sinks/basic_file_sink.h>
7+ #include < spdlog/spdlog.h>
8+
9+ #include < unistd.h>
10+
11+ #include < chrono>
12+ #include < cstdlib>
13+ #include < filesystem>
14+ #include < iomanip>
15+ #include < sstream>
16+
617#include " agnocast/agnocast_tracepoint_wrapper.h"
718
819namespace agnocast
@@ -11,6 +22,48 @@ namespace agnocast
1122Context g_context;
1223std::mutex g_context_mtx;
1324
25+ // / Generate a ROS2-style log directory path: ~/.ros/log/<timestamp-hostname-pid>/
26+ static std::string generate_log_directory ()
27+ {
28+ std::string base_dir;
29+ const char * ros_log_dir = std::getenv (" ROS_LOG_DIR" );
30+ const char * ros_home = std::getenv (" ROS_HOME" );
31+ if (ros_log_dir && ros_log_dir[0 ] != ' \0 ' ) {
32+ base_dir = ros_log_dir;
33+ } else if (ros_home && ros_home[0 ] != ' \0 ' ) {
34+ base_dir = std::string (ros_home) + " /log" ;
35+ } else {
36+ const char * home = std::getenv (" HOME" );
37+ base_dir = std::string (home ? home : " /tmp" ) + " /.ros/log" ;
38+ }
39+
40+ auto now = std::chrono::system_clock::now ();
41+ auto time_t_now = std::chrono::system_clock::to_time_t (now);
42+ auto us =
43+ std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch ()).count () % 1000000 ;
44+ std::tm tm_buf{};
45+ localtime_r (&time_t_now, &tm_buf);
46+
47+ char hostname[256 ] = {};
48+ gethostname (hostname, sizeof (hostname));
49+
50+ std::ostringstream oss;
51+ oss << base_dir << " /" << std::put_time (&tm_buf, " %Y-%m-%d-%H-%M-%S" ) << " -"
52+ << std::setfill (' 0' ) << std::setw (6 ) << us << " -" << hostname << " -" << getpid ();
53+ return oss.str ();
54+ }
55+
56+ static void initialize_spdlog (const std::string & log_dir)
57+ {
58+ std::filesystem::create_directories (log_dir);
59+ std::string log_file = log_dir + " /agnocast.log" ;
60+ spdlog::drop (" agnocast" );
61+ auto logger = spdlog::basic_logger_mt (" agnocast" , log_file);
62+ logger->set_pattern (" [%Y-%m-%d %H:%M:%S.%e] [%l] %v" );
63+ logger->flush_on (spdlog::level::warn);
64+ spdlog::set_default_logger (logger);
65+ }
66+
1467static void noop_log_output_handler (
1568 const rcutils_log_location_t *, int , const char *, rcutils_time_point_value_t , const char *,
1669 va_list *)
@@ -48,11 +101,11 @@ void Context::init(int argc, char const * const * argv)
48101 rcl_reset_error ();
49102 }
50103
51- // Apply --disable-stdout-logs and detect --enable-rosout-logs within --ros-args scope.
52- // There is no public rcl API to extract these flags from rcl_arguments_t, so we scan argv
53- // directly. rcl_logging_configure() cannot be used as it would initialize spdlog and attempt to
54- // set up a rosout publisher (which requires rcl_node_t), neither of which exist in agnocast.
104+ // Scan argv for flags that have no public rcl getter API.
105+ // rcl_logging_configure() is not used because it would replace the rcutils output handler
106+ // and couple agnocast to rcl's logging lifecycle.
55107 bool in_ros_args = false ;
108+ bool disable_external_lib_logs = false ;
56109 for (const auto & arg : args) {
57110 if (arg == " --ros-args" ) {
58111 in_ros_args = true ;
@@ -63,10 +116,17 @@ void Context::init(int argc, char const * const * argv)
63116 rcutils_logging_set_output_handler (noop_log_output_handler);
64117 } else if (arg == " --enable-rosout-logs" ) {
65118 enable_rosout_logs_ = true ;
119+ } else if (arg == " --disable-external-lib-logs" ) {
120+ disable_external_lib_logs = true ;
66121 }
67122 }
68123 }
69124
125+ // Initialize spdlog file logging unless explicitly disabled.
126+ if (!disable_external_lib_logs) {
127+ initialize_spdlog (generate_log_directory ());
128+ }
129+
70130 initialized_ = true ;
71131
72132 TRACEPOINT (agnocast_init, static_cast <const void *>(this ));
0 commit comments