|
12 | 12 | // See the License for the specific language governing permissions and |
13 | 13 | // limitations under the License. |
14 | 14 |
|
| 15 | +#include <algorithm> |
15 | 16 | #include <cerrno> |
16 | 17 | #include <chrono> |
17 | 18 | #include <cinttypes> |
|
23 | 24 | #include "rcpputils/filesystem_helper.hpp" |
24 | 25 | #include "rcpputils/env.hpp" |
25 | 26 | #include "rcutils/allocator.h" |
| 27 | +#include "rcutils/env.h" |
26 | 28 | #include "rcutils/logging.h" |
27 | 29 | #include "rcutils/process.h" |
28 | 30 | #include "rcutils/snprintf.h" |
29 | 31 | #include "rcutils/time.h" |
30 | 32 |
|
31 | 33 | #include "spdlog/spdlog.h" |
32 | 34 | #include "spdlog/sinks/basic_file_sink.h" |
| 35 | +#include "spdlog/sinks/rotating_file_sink.h" |
33 | 36 |
|
34 | 37 | #include "rcl_logging_interface/rcl_logging_interface.h" |
35 | 38 |
|
36 | 39 | static std::mutex g_logger_mutex; |
37 | 40 | static std::shared_ptr<spdlog::logger> g_root_logger = nullptr; |
38 | 41 |
|
| 42 | +constexpr std::size_t DEFAULT_ROTATING_FILE_SIZE_BYTES = 100 * 1024 * 1024; |
| 43 | +constexpr std::size_t DEFAULT_ROTATING_MAX_NUM_FILES = 5; |
| 44 | + |
| 45 | +using std::size_t; |
| 46 | + |
39 | 47 | static spdlog::level::level_enum map_external_log_level_to_library_level(int external_level) |
40 | 48 | { |
41 | 49 | spdlog::level::level_enum level = spdlog::level::level_enum::off; |
@@ -91,6 +99,78 @@ get_should_use_old_flushing_behavior() |
91 | 99 |
|
92 | 100 | } // namespace |
93 | 101 |
|
| 102 | +static std::string get_string_env_var(const char * env_var_name) |
| 103 | +{ |
| 104 | + const char * env_var_value; |
| 105 | + const char * error_str; |
| 106 | + error_str = rcutils_get_env(env_var_name, &env_var_value); |
| 107 | + if (error_str != nullptr) { |
| 108 | + throw std::runtime_error( |
| 109 | + std::string( |
| 110 | + "Failed to get env var '") + env_var_name + "': " + error_str); |
| 111 | + } |
| 112 | + return env_var_value; // Returns empty string for unset or empty env vars |
| 113 | +} |
| 114 | + |
| 115 | +static bool contained_in( |
| 116 | + const std::vector<std::string> & check_strings, |
| 117 | + const std::string & value_str) |
| 118 | +{ |
| 119 | + return std::find(check_strings.begin(), check_strings.end(), value_str) != check_strings.end(); |
| 120 | +} |
| 121 | + |
| 122 | +static std::string join_quoted( |
| 123 | + const std::string & sep, const std::string & quote_chars, |
| 124 | + const std::vector<std::string> & list) |
| 125 | +{ |
| 126 | + if (list.empty()) { |
| 127 | + return ""; |
| 128 | + } |
| 129 | + |
| 130 | + std::string joined = quote_chars + *(list.begin()) + quote_chars; |
| 131 | + std::for_each( |
| 132 | + list.begin() + 1, list.end(), |
| 133 | + [&](const auto & entry) {joined += sep + quote_chars + entry + quote_chars;}); |
| 134 | + |
| 135 | + return joined; |
| 136 | +} |
| 137 | + |
| 138 | +static bool get_bool_env_var(const char * env_var_name) |
| 139 | +{ |
| 140 | + std::string value_str = get_string_env_var(env_var_name); |
| 141 | + std::vector<std::string> true_strings = {"1", "true", "TRUE"}; |
| 142 | + std::vector<std::string> false_strings = {"0", "false", "FALSE", ""}; |
| 143 | + |
| 144 | + if (contained_in(false_strings, value_str)) { |
| 145 | + return false; |
| 146 | + } else if (contained_in(true_strings, value_str)) { |
| 147 | + return true; |
| 148 | + } else { |
| 149 | + throw std::runtime_error( |
| 150 | + std::string( |
| 151 | + "Unrecognized value for '") + env_var_name + "': '" + value_str + "'. " + |
| 152 | + "Valid truthy values: " + join_quoted(", ", "'", true_strings) + ". " + |
| 153 | + "Valid falsy values: " + join_quoted(", ", "'", false_strings) + ". " |
| 154 | + ); |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +static size_t get_size_t_env_var(const char * env_var_name, const size_t default_val) |
| 159 | +{ |
| 160 | + std::string value_str = get_string_env_var(env_var_name); |
| 161 | + if (value_str.empty()) { |
| 162 | + return default_val; |
| 163 | + } else { |
| 164 | + int value = std::stoi(value_str); |
| 165 | + if (value < 0) { |
| 166 | + throw std::runtime_error( |
| 167 | + std::string("Env var must be positive '") + env_var_name + "': '" + value_str + "'. " |
| 168 | + ); |
| 169 | + } |
| 170 | + return value; |
| 171 | + } |
| 172 | +} |
| 173 | + |
94 | 174 | rcl_logging_ret_t rcl_logging_external_initialize( |
95 | 175 | const char * config_file, |
96 | 176 | rcutils_allocator_t allocator) |
@@ -174,7 +254,22 @@ rcl_logging_ret_t rcl_logging_external_initialize( |
174 | 254 | return RCL_LOGGING_RET_ERROR; |
175 | 255 | } |
176 | 256 |
|
177 | | - auto sink = std::make_unique<spdlog::sinks::basic_file_sink_mt>(name_buffer, false); |
| 257 | + std::unique_ptr<spdlog::sinks::sink> sink; |
| 258 | + if (get_bool_env_var("RCL_LOGGING_SPDLOG_ROTATE_FILES") == true) { |
| 259 | + size_t max_size = |
| 260 | + get_size_t_env_var( |
| 261 | + "RCL_LOGGING_SPDLOG_ROTATING_FILE_SIZE_BYTES", |
| 262 | + DEFAULT_ROTATING_FILE_SIZE_BYTES); |
| 263 | + size_t max_files = |
| 264 | + get_size_t_env_var( |
| 265 | + "RCL_LOGGING_SPDLOG_MAX_NUM_FILES", |
| 266 | + DEFAULT_ROTATING_MAX_NUM_FILES); |
| 267 | + sink = |
| 268 | + std::make_unique<spdlog::sinks::rotating_file_sink_mt>(name_buffer, max_size, max_files); |
| 269 | + } else { |
| 270 | + sink = std::make_unique<spdlog::sinks::basic_file_sink_mt>(name_buffer, false); |
| 271 | + } |
| 272 | + |
178 | 273 | g_root_logger = std::make_shared<spdlog::logger>("root", std::move(sink)); |
179 | 274 | if (!should_use_old_flushing_behavior) { |
180 | 275 | // in this case we should do the new thing (until config files are supported) |
|
0 commit comments