diff --git a/rcl/CMakeLists.txt b/rcl/CMakeLists.txt index 830e84ef2a..e77342158a 100644 --- a/rcl/CMakeLists.txt +++ b/rcl/CMakeLists.txt @@ -12,6 +12,9 @@ find_package(rosidl_generator_c REQUIRED) include_directories(include) +include(cmake/get_default_rc_logging_implementation.cmake) +get_default_rc_logging_implementation(RC_LOGGING_IMPL) + # Default to C11 if(NOT CMAKE_C_STANDARD) set(CMAKE_C_STANDARD 11) @@ -35,6 +38,7 @@ set(${PROJECT_NAME}_sources src/rcl/guard_condition.c src/rcl/lexer.c src/rcl/lexer_lookahead.c + src/rcl/logging.c src/rcl/node.c src/rcl/publisher.c src/rcl/rcl.c @@ -56,6 +60,7 @@ ament_target_dependencies(${PROJECT_NAME} "rmw" "rcutils" "rosidl_generator_c" + ${RC_LOGGING_IMPL} ) # Causes the visibility macros to use dllexport rather than dllimport, @@ -84,6 +89,7 @@ ament_export_dependencies(rmw_implementation) ament_export_dependencies(rmw) ament_export_dependencies(rcutils) ament_export_dependencies(rosidl_generator_c) +ament_export_dependencies(${RC_LOGGING_IMPL}) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) diff --git a/rcl/cmake/get_default_rc_logging_implementation.cmake b/rcl/cmake/get_default_rc_logging_implementation.cmake new file mode 100644 index 0000000000..84e2f89c48 --- /dev/null +++ b/rcl/cmake/get_default_rc_logging_implementation.cmake @@ -0,0 +1,46 @@ +# Copyright 2018 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# +# Get the package name of the default logging implementation. +# +# Either selecting it using the variable RC_LOGGING_IMPLEMENTATION or +# choosing a default from the available implementations. +# +# :param var: the output variable name containing the package name +# :type var: string +# +macro(get_default_rc_logging_implementation var) + + # if logging implementation already specified or RC_LOGGING_IMPLEMENTATION environment variable + # is set then use that, otherwise default to using rc_logging_log4cxx + if(NOT "${RC_LOGGING_IMPLEMENTATION}" STREQUAL "") + set(_logging_implementation "${RC_LOGGING_IMPLEMENTATION}") + elseif(NOT "$ENV{RC_LOGGING_IMPLEMENTATION}" STREQUAL "") + set(_logging_implementation "$ENV{RC_LOGGING_IMPLEMENTATION}") + else() + set(_logging_implementation rc_logging_log4cxx) + endif() + + # persist implementation decision in cache + # if it was not determined dynamically + set( + RC_LOGGING_IMPLEMENTATION "${_logging_implementation}" + CACHE STRING "Select ROS middleware implementation to link against" FORCE + ) + + find_package("${_logging_implementation}" REQUIRED) + + set(${var} ${_logging_implementation}) +endmacro() diff --git a/rcl/include/rcl/arguments.h b/rcl/include/rcl/arguments.h index a594c62d62..523c162687 100644 --- a/rcl/include/rcl/arguments.h +++ b/rcl/include/rcl/arguments.h @@ -34,12 +34,12 @@ typedef struct rcl_arguments_t struct rcl_arguments_impl_t * impl; } rcl_arguments_t; -#define RCL_LOG_LEVEL_ARG_RULE "__log_level:=" -#define RCL_EXTERNAL_LOG_CONFIG_ARG_RULE "__log_config_file:=" -#define RCL_LOG_DISABLE_STDOUT_ARG_RULE "__log_disable_stdout:=" -#define RCL_LOG_DISABLE_ROSOUT_ARG_RULE "__log_disable_rosout:=" -#define RCL_LOG_DISABLE_EXT_LIB_ARG_RULE "__log_disable_external_lib:=" -#define RCL_PARAM_FILE_ARG_RULE "__params:=" +#define RCL_LOG_LEVEL_ARG_RULE "__log_level:=" +#define RCL_EXTERNAL_LOG_CONFIG_ARG_RULE "__log_config_file:=" +#define RCL_LOG_DISABLE_STDOUT_ARG_RULE "__log_disable_stdout:=" +#define RCL_LOG_DISABLE_ROSOUT_ARG_RULE "__log_disable_rosout:=" +#define RCL_LOG_DISABLE_EXT_LIB_ARG_RULE "__log_disable_external_lib:=" +#define RCL_PARAM_FILE_ARG_RULE "__params:=" /// Return a rcl_node_t struct with members initialized to `NULL`. RCL_PUBLIC diff --git a/rcl/include/rcl/logging.h b/rcl/include/rcl/logging.h new file mode 100644 index 0000000000..bd4e0b316c --- /dev/null +++ b/rcl/include/rcl/logging.h @@ -0,0 +1,58 @@ +// Copyright 2017 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef RCL__LOGGING_H_ +#define RCL__LOGGING_H_ + +#include +#include +#include + +#include "rcl/allocator.h" +#include "rcl/arguments.h" +#include "rcl/error_handling.h" +#include "rcl/types.h" +#include "rcl/visibility_control.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/// Configures the logging system. +/** + * This function should be called during the ROS initialization process. It will + * add the enabled log output appenders to the root logger. + * + *
+ * Attribute | Adherence + * ------------------ | ------------- + * Allocates Memory | No + * Thread-Safe | No + * Uses Atomics | No + * Lock-Free | Yes + * + * \param global_args The global arguments for the system + * \return `RCL_RET_OK` if successful. + * \return `RCL_RET_ERR` if a general error occurs + */ +RCL_PUBLIC +rcl_ret_t rcl_logging_configure(const rcl_arguments_t * global_args); + +#ifdef __cplusplus +} +#endif + +#endif // RCL__LOGGING_H_ diff --git a/rcl/include/rcl/logging_external_interface.h b/rcl/include/rcl/logging_external_interface.h new file mode 100644 index 0000000000..e4ed6929f5 --- /dev/null +++ b/rcl/include/rcl/logging_external_interface.h @@ -0,0 +1,66 @@ +// Copyright 2018 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef RCL__LOGGING_EXTERNAL_INTERFACE_H_ +#define RCL__LOGGING_EXTERNAL_INTERFACE_H_ + +#include +#include "rcl/types.h" +#include "rcl/visibility_control.h" + + +/** + * \brief Initializes the external logging library. + * + * \param config_file The location of a config file that the external logging library should use to configure itself. + * If no config file is provided this will be set to an empty string. Must be a NULL terminated c string. + * \return RC_EXTERNAL_LOGGING_RET_OK if initialized successfully or an error code if not. + */ +RCL_PUBLIC +rcl_ret_t rcl_logging_external_initialize(const char * config_file); + +/** + * \brief Free the resources allocated for the external logging system. + * This puts the system into a state equivalent to being uninitialized + * + * \return RC_EXTERNAL_LOGGING_RET_OK if successfully shutdown or an error code if not. + */ +RCL_PUBLIC +rcl_ret_t rcl_logging_external_shutdown(); + +/** + * \brief Logs a message + * + * \param severity The severity level of the message being logged + * \param name The name of the logger, must be a null terminated c string or NULL. If NULL or empty the root logger will + * be used. + * \param msg The message to be logged. Must be a null terminated c string. + */ +RCL_PUBLIC +void rcl_logging_external_log(int severity, const char * name, const char * msg); + + +/** + * \brief Set the severity level for a logger. + * Sets the severity level for the specified logger. If the name provided is an empty string or NULL it will change the + * level of the root logger. + * + * \param name The name of the logger. Must be a NULL terminated c string or NULL. + * \param level - The severity level to be used for the specified logger + * \return RC_EXTERNAL_LOGGING_RET_OK if set successfully or an error code if not. + */ +RCL_PUBLIC +rcl_ret_t rcl_logging_external_set_logger_level(const char * name, int level); + +#endif // RCL__LOGGING_EXTERNAL_INTERFACE_H_ diff --git a/rcl/include/rcl/macros.h b/rcl/include/rcl/macros.h index 94ff59d49d..cbf5b89d01 100644 --- a/rcl/include/rcl/macros.h +++ b/rcl/include/rcl/macros.h @@ -27,6 +27,8 @@ extern "C" # define RCL_WARN_UNUSED _Check_return_ #endif +#define RCL_UNUSED(x) (void)(x) + #ifdef __cplusplus } #endif diff --git a/rcl/package.xml b/rcl/package.xml index 1c9de626ab..811b5b6be4 100644 --- a/rcl/package.xml +++ b/rcl/package.xml @@ -1,6 +1,6 @@ - + rcl 0.6.0 The ROS client library common implementation. @@ -27,6 +27,8 @@ rmw_implementation + rc_logging_packages + ament_cmake_gtest ament_cmake_pytest ament_lint_auto diff --git a/rcl/src/rcl/arguments.c b/rcl/src/rcl/arguments.c index f59e36af03..6db36b284f 100644 --- a/rcl/src/rcl/arguments.c +++ b/rcl/src/rcl/arguments.c @@ -261,7 +261,7 @@ rcl_parse_arguments( } RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Couldn't parse arg %d (%s) as parameter file rule. Error: %s", i, argv[i], - rcl_get_error_string()); + rcl_get_error_string().str); rcl_reset_error(); // Attempt to parse argument as remap rule @@ -272,7 +272,8 @@ rcl_parse_arguments( continue; } RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, - "Couldn't parse arg %d (%s) as remap rule. Error: %s", i, argv[i], rcl_get_error_string()); + "Couldn't parse arg %d (%s) as remap rule. Error: %s", i, argv[i], + rcl_get_error_string().str); rcl_reset_error(); // Attempt to parse argument as log level configuration @@ -283,43 +284,54 @@ rcl_parse_arguments( } RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Couldn't parse arg %d (%s) as log level rule. Error: %s", i, argv[i], - rcl_get_error_string()); + rcl_get_error_string().str); rcl_reset_error(); // Attempt to parse argument as log configuration file - if (RCL_RET_OK == _rcl_parse_external_log_config_file(argv[i], allocator, &args_impl->external_log_config_file)) { + rcl_ret_t ret = _rcl_parse_external_log_config_file(argv[i], allocator, + &args_impl->external_log_config_file); + if (RCL_RET_OK == ret) { continue; } RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Couldn't parse arg %d (%s) as log config rule. Error: %s", i, argv[i], - rcl_get_error_string()); + rcl_get_error_string().str); rcl_reset_error(); // Attempt to parse argument as log_stdout_disabled - if (RCL_RET_OK == _rcl_parse_bool_arg(argv[i], RCL_LOG_DISABLE_STDOUT_ARG_RULE, &args_impl->log_stdout_disabled)) { + if (RCL_RET_OK == + _rcl_parse_bool_arg(argv[i], RCL_LOG_DISABLE_STDOUT_ARG_RULE, + &args_impl->log_stdout_disabled)) + { continue; } RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Couldn't parse arg %d (%s) as log_stdout_disabled rule. Error: %s", i, argv[i], - rcl_get_error_string()); + rcl_get_error_string().str); rcl_reset_error(); // Attempt to parse argument as log_rosout_disabled - if (RCL_RET_OK == _rcl_parse_bool_arg(argv[i], RCL_LOG_DISABLE_ROSOUT_ARG_RULE, &args_impl->log_rosout_disabled)) { + if (RCL_RET_OK == + _rcl_parse_bool_arg(argv[i], RCL_LOG_DISABLE_ROSOUT_ARG_RULE, + &args_impl->log_rosout_disabled)) + { continue; } RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Couldn't parse arg %d (%s) as log_rosout_disabled rule. Error: %s", i, argv[i], - rcl_get_error_string()); + rcl_get_error_string().str); rcl_reset_error(); // Attempt to parse argument as log_ext_lib_disabled - if (RCL_RET_OK == _rcl_parse_bool_arg(argv[i], RCL_LOG_DISABLE_EXT_LIB_ARG_RULE, &args_impl->log_ext_lib_disabled)) { + if (RCL_RET_OK == + _rcl_parse_bool_arg(argv[i], RCL_LOG_DISABLE_EXT_LIB_ARG_RULE, + &args_impl->log_ext_lib_disabled)) + { continue; } RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Couldn't parse arg %d (%s) as log_ext_lib_disabled rule. Error: %s", i, argv[i], - rcl_get_error_string()); + rcl_get_error_string().str); rcl_reset_error(); @@ -1170,10 +1182,10 @@ _rcl_parse_external_log_config_file( { RCL_CHECK_ARGUMENT_FOR_NULL(arg, RCL_RET_INVALID_ARGUMENT); - const size_t param_prefix_len = sizeof(RCL_EXTERNAL_LOG_CONFIG_ARG_RULE); + const size_t param_prefix_len = sizeof(RCL_EXTERNAL_LOG_CONFIG_ARG_RULE) - 1; if (strncmp(RCL_EXTERNAL_LOG_CONFIG_ARG_RULE, arg, param_prefix_len) == 0) { size_t outlen = strlen(arg) - param_prefix_len; - log_config_file = allocator.allocate(sizeof(char) * (outlen + 1), allocator.state); + *log_config_file = allocator.allocate(sizeof(char) * (outlen + 1), allocator.state); if (NULL == log_config_file) { RCUTILS_SAFE_FWRITE_TO_STDERR("Failed to allocate memory for parameters file path\n"); return RCL_RET_BAD_ALLOC; @@ -1213,8 +1225,8 @@ _atob( { RCL_CHECK_ARGUMENT_FOR_NULL(str, RCL_RET_INVALID_ARGUMENT); RCL_CHECK_ARGUMENT_FOR_NULL(val, RCL_RET_INVALID_ARGUMENT); - const char * true_values[] = {"y", "Y", "yes", "Yes", "t", "T", "true", "True", "1" }; - const char * false_values[] = {"n", "N", "no", "No", "f", "F", "false", "False", "0" }; + const char * true_values[] = {"y", "Y", "yes", "Yes", "t", "T", "true", "True", "1"}; + const char * false_values[] = {"n", "N", "no", "No", "f", "F", "false", "False", "0"}; for (size_t idx = 0; idx < sizeof(true_values) / sizeof(char *); idx++) { if (0 == strncmp(true_values[idx], str, strlen(true_values[idx]))) { diff --git a/rcl/src/rcl/arguments_impl.h b/rcl/src/rcl/arguments_impl.h index 46bca1bcd0..43e2d0086d 100644 --- a/rcl/src/rcl/arguments_impl.h +++ b/rcl/src/rcl/arguments_impl.h @@ -46,8 +46,11 @@ typedef struct rcl_arguments_impl_t int log_level; /// A file used to configure the external logging library char * external_log_config_file; + /// A boolean value indicating if the standard out handler should be used for log output bool log_stdout_disabled; + /// A boolean value indicating if the rosout topic handler should be used for log output bool log_rosout_disabled; + /// A boolean value indicating if the external lib handler should be used for log output bool log_ext_lib_disabled; /// Allocator used to allocate objects in this struct diff --git a/rcl/src/rcl/logging.c b/rcl/src/rcl/logging.c new file mode 100644 index 0000000000..e35d581e29 --- /dev/null +++ b/rcl/src/rcl/logging.c @@ -0,0 +1,139 @@ +// Copyright 2017 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include +#include +#include + +#include "./arguments_impl.h" +#include "rcl/allocator.h" +#include "rcl/error_handling.h" +#include "rcl/logging.h" +#include "rcl/logging_external_interface.h" +#include "rcl/macros.h" +#include "rcutils/logging.h" +#include "rcutils/time.h" + +#define RCL_LOGGING_MAX_OUTPUT_FUNCS (4) + +static rcutils_logging_output_handler_t g_rcl_logging_out_handlers[ + RCL_LOGGING_MAX_OUTPUT_FUNCS] = {0}; +static uint8_t g_rcl_logging_num_out_handlers = 0; + +/** + * An output function that sends to multiple output appenders + */ +static void rcl_logging_multiple_output_handler( + const rcutils_log_location_t * location, + int severity, const char * name, rcutils_time_point_value_t timestamp, + const char * log_str); + +/** + * An output function that sends to the external logger library + */ +static void rcl_logging_ext_lib_output_handler( + const rcutils_log_location_t * location, + int severity, const char * name, rcutils_time_point_value_t timestamp, + const char * log_str); + +/** + * An output function that sends to the rosout topic + */ +static void rcl_logging_rosout_output_handler( + const rcutils_log_location_t * location, + int severity, const char * name, rcutils_time_point_value_t timestamp, + const char * log_str); + +rcl_ret_t rcl_logging_configure(const rcl_arguments_t * global_args) +{ + RCUTILS_LOGGING_AUTOINIT + int default_level = global_args->impl->log_level; + const char * config_file = global_args->impl->external_log_config_file; + bool enable_stdout = !global_args->impl->log_stdout_disabled; + bool enable_rosout = !global_args->impl->log_rosout_disabled; + bool enable_ext_lib = !global_args->impl->log_ext_lib_disabled; + rcl_ret_t status = RCL_RET_OK; + g_rcl_logging_num_out_handlers = 0; + + if (default_level >= 0) { + rcutils_logging_set_default_logger_level(default_level); + } + if (enable_stdout) { + g_rcl_logging_out_handlers[g_rcl_logging_num_out_handlers++] = + rcutils_logging_console_output_handler; + } + if (enable_rosout) { + g_rcl_logging_out_handlers[g_rcl_logging_num_out_handlers++] = + rcl_logging_rosout_output_handler; + } + if (enable_ext_lib) { + status = rcl_logging_external_initialize(config_file); + if (RCL_RET_OK == status) { + rcl_logging_external_set_logger_level(NULL, default_level); + g_rcl_logging_out_handlers[g_rcl_logging_num_out_handlers++] = + rcl_logging_ext_lib_output_handler; + } + } + rcutils_logging_set_output_handler(rcl_logging_multiple_output_handler); + return status; +} + + +static void rcl_logging_multiple_output_handler( + const rcutils_log_location_t * location, + int severity, const char * name, rcutils_time_point_value_t timestamp, + const char * log_str) +{ + for (uint8_t i = 0; + i < g_rcl_logging_num_out_handlers && NULL != g_rcl_logging_out_handlers[i]; ++i) + { + g_rcl_logging_out_handlers[i](location, severity, name, timestamp, log_str); + } +} + +static void rcl_logging_ext_lib_output_handler( + const rcutils_log_location_t * location, + int severity, const char * name, rcutils_time_point_value_t timestamp, + const char * log_str) +{ + RCL_UNUSED(location); + RCL_UNUSED(severity); + RCL_UNUSED(name); + RCL_UNUSED(timestamp); + rcl_logging_external_log(severity, name, log_str); +} + +static void rcl_logging_rosout_output_handler( + const rcutils_log_location_t * location, + int severity, const char * name, rcutils_time_point_value_t timestamp, + const char * log_str) +{ + // TODO(nburek): Placeholder for rosout topic feature + RCL_UNUSED(location); + RCL_UNUSED(severity); + RCL_UNUSED(name); + RCL_UNUSED(timestamp); + RCL_UNUSED(log_str); +} + + +#ifdef __cplusplus +} +#endif diff --git a/rcl/src/rcl/rcl.c b/rcl/src/rcl/rcl.c index 789cf47308..36727a12d5 100644 --- a/rcl/src/rcl/rcl.c +++ b/rcl/src/rcl/rcl.c @@ -26,7 +26,7 @@ extern "C" #include "rcl/error_handling.h" #include "rcutils/logging_macros.h" #include "rcutils/stdatomic_helper.h" -#include "rcutils/logging.h" +#include "rcl/logging.h" #include "rmw/error_handling.h" static atomic_bool __rcl_is_initialized = ATOMIC_VAR_INIT(false); @@ -119,11 +119,7 @@ rcl_init(int argc, char const * const * argv, rcl_allocator_t allocator) goto fail; } - fail_ret = rcutils_logging_configure(global_args->impl->log_level, - global_args->impl->external_log_config_file, - !global_args->impl->log_stdout_disabled, - !global_args->impl->log_rosout_disabled, - !global_args->impl->log_ext_lib_disabled); + fail_ret = rcl_logging_configure(global_args); if (RCL_RET_OK != fail_ret) { RCUTILS_LOG_ERROR_NAMED(ROS_PACKAGE_NAME, "Failed to configure logging. %i", fail_ret); goto fail;