diff --git a/iceoryx_hoofs/CMakeLists.txt b/iceoryx_hoofs/CMakeLists.txt index ab776ee873..7efc897d2e 100644 --- a/iceoryx_hoofs/CMakeLists.txt +++ b/iceoryx_hoofs/CMakeLists.txt @@ -114,6 +114,7 @@ iox_add_library( reporting/source/hoofs_error_reporting.cpp reporting/source/console_logger.cpp reporting/source/logger.cpp + reporting/source/logging.cpp time/source/duration.cpp utility/source/unique_id.cpp diff --git a/iceoryx_hoofs/primitives/include/iox/iceoryx_hoofs_types.hpp b/iceoryx_hoofs/primitives/include/iox/iceoryx_hoofs_types.hpp index 2f5171fd85..88ddbaa83f 100644 --- a/iceoryx_hoofs/primitives/include/iox/iceoryx_hoofs_types.hpp +++ b/iceoryx_hoofs/primitives/include/iox/iceoryx_hoofs_types.hpp @@ -20,6 +20,8 @@ /// @note since this file will be included by many other files, it should not include other header except /// iceoryx_platform or STL header +#include "iceoryx_platform/logging.hpp" + #include namespace iox @@ -31,13 +33,13 @@ namespace log /// @brief This enum defines the log levels used for logging. enum class LogLevel : uint8_t { - OFF = 0, - FATAL, - ERROR, - WARN, - INFO, - DEBUG, - TRACE + OFF = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_OFF, + FATAL = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_FATAL, + ERROR = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_ERROR, + WARN = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_WARN, + INFO = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_INFO, + DEBUG = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_DEBUG, + TRACE = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_TRACE, }; /// @brief converts LogLevel into a string literal diff --git a/iceoryx_hoofs/reporting/include/iox/detail/log/building_blocks/logger.inl b/iceoryx_hoofs/reporting/include/iox/detail/log/building_blocks/logger.inl index d6a9a55ca0..f5d6583fff 100644 --- a/iceoryx_hoofs/reporting/include/iox/detail/log/building_blocks/logger.inl +++ b/iceoryx_hoofs/reporting/include/iox/detail/log/building_blocks/logger.inl @@ -111,6 +111,7 @@ inline void Logger::initLoggerInternal(const LogLevel logLevel) noex { BaseLogger::setLogLevel(logLevel); BaseLogger::initLogger(logLevel); + iox_platform_set_log_backend(&platform_log_backend); m_isFinalized.store(true, std::memory_order_relaxed); } else diff --git a/iceoryx_hoofs/reporting/include/iox/log/building_blocks/logger.hpp b/iceoryx_hoofs/reporting/include/iox/log/building_blocks/logger.hpp index 6704fcf2b9..9a2c2a876e 100644 --- a/iceoryx_hoofs/reporting/include/iox/log/building_blocks/logger.hpp +++ b/iceoryx_hoofs/reporting/include/iox/log/building_blocks/logger.hpp @@ -18,6 +18,7 @@ #ifndef IOX_HOOFS_REPORTING_LOG_BUILDING_BLOCKS_LOGGER_HPP #define IOX_HOOFS_REPORTING_LOG_BUILDING_BLOCKS_LOGGER_HPP +#include "iceoryx_platform/logging.hpp" #include "iox/iceoryx_hoofs_types.hpp" #include @@ -52,6 +53,12 @@ LogLevel logLevelFromEnvOr(const LogLevel logLevel) noexcept; namespace internal { +/// @brief The backend for the platform logging frontend +/// @copydoc IceoryxPlatformLogBackend +/// @note Needs to be implemented in 'logging.cpp' in order to use the high level log API +void platform_log_backend( + const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg); + /// @brief This class acts as common interface for the Logger. It provides the common functionality and inherits from /// the BaseLogger which is provided as template parameter. Please have a look at the design document for more details. /// @tparam[in] BaseLogger is the actual implementation diff --git a/iceoryx_hoofs/reporting/source/logging.cpp b/iceoryx_hoofs/reporting/source/logging.cpp new file mode 100644 index 0000000000..01b3bebe09 --- /dev/null +++ b/iceoryx_hoofs/reporting/source/logging.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2024 by Mathias Kraus . All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "iox/logging.hpp" + +namespace iox::log::internal +{ +// NOLINTJUSTIFICATION Not used directly but as a function pointer to set the backend +// NOLINTNEXTLINE(readability-function-size) +void platform_log_backend( + const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg) +{ + auto level = LogLevel::TRACE; + switch (log_level) + { + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_OFF: + level = LogLevel::OFF; + break; + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_FATAL: + level = LogLevel::FATAL; + break; + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_ERROR: + level = LogLevel::ERROR; + break; + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_WARN: + level = LogLevel::WARN; + break; + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_INFO: + level = LogLevel::INFO; + break; + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_DEBUG: + level = LogLevel::DEBUG; + break; + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_TRACE: + level = LogLevel::TRACE; + break; + default: + level = LogLevel::TRACE; + } + IOX_LOG_INTERNAL(file, line, function, level, msg); +} +} // namespace iox::log::internal diff --git a/iceoryx_hoofs/test/moduletests/test_reporting_logging.cpp b/iceoryx_hoofs/test/moduletests/test_reporting_logging.cpp index 609ee666c2..05c11884cb 100644 --- a/iceoryx_hoofs/test/moduletests/test_reporting_logging.cpp +++ b/iceoryx_hoofs/test/moduletests/test_reporting_logging.cpp @@ -15,9 +15,9 @@ // // SPDX-License-Identifier: Apache-2.0 +#include "iceoryx_platform/logging.hpp" #include "iox/logging.hpp" -#include "iceoryx_hoofs/testing/mocks/logger_mock.hpp" #include "iceoryx_hoofs/testing/testing_logger.hpp" #include "test.hpp" @@ -25,7 +25,8 @@ namespace { using namespace ::testing; -void testLogLevelThreshold(const iox::log::LogLevel loggerLogLevel) +void testLogLevelThreshold(const iox::log::LogLevel loggerLogLevel, + const std::function& loggerCall) { iox::log::Logger::setLogLevel(loggerLogLevel); @@ -47,7 +48,6 @@ void testLogLevelThreshold(const iox::log::LogLevel loggerLogLevel) {iox::log::LogLevel::DEBUG, "Debug"}, {iox::log::LogLevel::TRACE, "Trace"}}; - iox::testing::Logger_Mock loggerMock; for (const auto& logEntryLogLevel : logEntryLogLevels) { if (!iox::testing::TestingLogger::doesLoggerSupportLogLevel(logEntryLogLevel.value)) @@ -56,7 +56,7 @@ void testLogLevelThreshold(const iox::log::LogLevel loggerLogLevel) } dynamic_cast(iox::log::Logger::get()).clearLogBuffer(); - IOX_LOG_INTERNAL("", 0, "", logEntryLogLevel.value, ""); + loggerCall(logEntryLogLevel.value); if (logEntryLogLevel.value <= loggerLogLevel) { @@ -87,7 +87,26 @@ TEST(LoggingLogLevelThreshold_test, LogLevel) { SCOPED_TRACE(std::string("Logger LogLevel: ") + iox::log::asStringLiteral(loggerLogLevel)); - testLogLevelThreshold(loggerLogLevel); + testLogLevelThreshold(loggerLogLevel, [](auto logLevel) { IOX_LOG_INTERNAL("", 0, "", logLevel, ""); }); + } +} + +TEST(LoggingLogLevelThreshold_test, LogLevelForPlatform) +{ + ::testing::Test::RecordProperty("TEST_ID", "574007ac-62ed-4cd1-95e8-e18a9f20e1e1"); + for (const auto loggerLogLevel : {iox::log::LogLevel::OFF, + iox::log::LogLevel::FATAL, + iox::log::LogLevel::ERROR, + iox::log::LogLevel::WARN, + iox::log::LogLevel::INFO, + iox::log::LogLevel::DEBUG, + iox::log::LogLevel::TRACE}) + { + SCOPED_TRACE(std::string("Logger LogLevel: ") + iox::log::asStringLiteral(loggerLogLevel)); + + testLogLevelThreshold(loggerLogLevel, [](auto logLevel) { + iox_platform_detail_log("", 0, "", static_cast(logLevel), ""); + }); } } diff --git a/iceoryx_platform/generic/include/iceoryx_platform/logging.hpp b/iceoryx_platform/generic/include/iceoryx_platform/logging.hpp new file mode 100644 index 0000000000..be2d7b986a --- /dev/null +++ b/iceoryx_platform/generic/include/iceoryx_platform/logging.hpp @@ -0,0 +1,63 @@ +// Copyright (c) 2024 by Mathias Kraus . All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#ifndef IOX_PLATFORM_LOGGING_HPP +#define IOX_PLATFORM_LOGGING_HPP + +enum IceoryxPlatformLogLevel +{ + IOX_PLATFORM_LOG_LEVEL_OFF = 0, + IOX_PLATFORM_LOG_LEVEL_FATAL, + IOX_PLATFORM_LOG_LEVEL_ERROR, + IOX_PLATFORM_LOG_LEVEL_WARN, + IOX_PLATFORM_LOG_LEVEL_INFO, + IOX_PLATFORM_LOG_LEVEL_DEBUG, + IOX_PLATFORM_LOG_LEVEL_TRACE +}; + +/// @brief Typedef to the platform log backend +/// @param[in] file should be the '__FILE__' compiler intrinsic +/// @param[in] line should be the '__LINE__' compiler intrinsic +/// @param[in] function should be the '__FUNCTION__' compiler intrinsic +/// @param[in] log_level is the log level to be used for the log message +/// @param[in] msg is the message to be logged +typedef void (*IceoryxPlatformLogBackend)( + const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg); + +/// @brief Sets the logging backend to the provided function +/// @param[in] log_backend to be used +/// @note The log_backend must have a static lifetime and must be thread-safe +void iox_platform_set_log_backend(IceoryxPlatformLogBackend log_backend); + +/// @note Implementation detail! Do not use directly +void iox_platform_detail_log( + const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg); + +/// @note Implementation detail! Do not use directly +void iox_platform_detail_default_log_backend( + const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg); + +// NOLINTJUSTIFICATION The functionality of the macro (obtaining the source location) cannot be achieved with C++17 +// NOLINTBEGIN(cppcoreguidelines-macro-usage) +/// @brief Frontend for logging in the iceoryx platform +/// @param[in] log_level is the log level to be used for the log message +/// @param[in] msg is the message to be logged +#define IOX_PLATFORM_LOG(log_level, msg) \ + iox_platform_detail_log( \ + __FILE__, __LINE__, static_cast(__FUNCTION__), IceoryxPlatformLogLevel::log_level, msg) +// NOLINTEND(cppcoreguidelines-macro-usage) + +#endif // IOX_PLATFORM_LOGGING_HPP diff --git a/iceoryx_platform/generic/source/logging.cpp b/iceoryx_platform/generic/source/logging.cpp new file mode 100644 index 0000000000..e85e5c94c2 --- /dev/null +++ b/iceoryx_platform/generic/source/logging.cpp @@ -0,0 +1,168 @@ +// Copyright (c) 2024 by Mathias Kraus . All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "iceoryx_platform/logging.hpp" + +#include +#include +#include +#include +#include + +// NOLINTJUSTIFICATION only used in this file; should be fine +// NOLINTNEXTLINE(readability-function-size) +void iox_platform_detail_default_log_backend( + const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg) +{ + if (log_level == IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_OFF) + { + return; + } + + std::stringstream stream; + + switch (log_level) + { + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_FATAL: + stream << "[Fatal]"; + break; + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_ERROR: + stream << "[Error]"; + break; + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_WARN: + stream << "[Warn ]"; + break; + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_INFO: + stream << "[Info ]"; + break; + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_DEBUG: + stream << "[Debug]"; + break; + case IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_TRACE: + stream << "[Trace]"; + break; + default: + stream << "[UNDEF]"; + break; + } + + stream << " " << file << ":" << line << " { " << function << " } " << msg; + + // NOLINTJUSTIFICATION We want to flush the line with each log output; if performance matters a custom log backend can be used, e.g. the logger in hoofs + // NOLINTNEXTLINE(performance-avoid-endl) + std::cout << stream.str() << std::endl; +} + +namespace +{ +enum class LoggerExchangeState : uint8_t +{ + DEFAULT, + PENDING, + CUSTOM, +}; + +struct IceoryxPlatformLogger +{ + std::atomic log_backend{&iox_platform_detail_default_log_backend}; + std::atomic logger_exchange_state{LoggerExchangeState::DEFAULT}; +}; + +IceoryxPlatformLogger& active_logger(IceoryxPlatformLogBackend new_log_backend) +{ + static IceoryxPlatformLogger logger; + + if (new_log_backend != nullptr) + { + auto exchange_state = LoggerExchangeState::DEFAULT; + auto successful = + logger.logger_exchange_state.compare_exchange_strong(exchange_state, LoggerExchangeState::PENDING); + if (successful) + { + logger.log_backend.store(new_log_backend); + logger.logger_exchange_state.store(LoggerExchangeState::CUSTOM); + } + else + { + constexpr uint64_t YIELDS_BEFORE_SLEEP{10000}; + uint64_t yield_counter = 0; + while (logger.logger_exchange_state.load() != LoggerExchangeState::CUSTOM) + { + if (yield_counter < YIELDS_BEFORE_SLEEP) + { + std::this_thread::yield(); + ++yield_counter; + } + else + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + + logger.log_backend.load(std::memory_order_relaxed)(__FILE__, + __LINE__, + static_cast(__FUNCTION__), + IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_ERROR, + "Trying to replace logger after already initialized"); + + new_log_backend(__FILE__, + __LINE__, + static_cast(__FUNCTION__), + IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_ERROR, + "Trying to replace logger after already initialized"); + } + } + + return logger; +} +} // namespace + +// NOLINTJUSTIFICATION only used from a macro which hides most of the parameter; should be fine +// NOLINTNEXTLINE(readability-function-size) +void iox_platform_detail_log( + const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg) +{ + // NOLINTJUSTIFICATION needed for the functionality + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + static IceoryxPlatformLogger* logger = &active_logger(nullptr); + thread_local static LoggerExchangeState current_logger_exchange_state = logger->logger_exchange_state.load(); + // NOTE if the logger got updated in between, the current_logger_exchange_state has an old value and we will enter + // the if statement + thread_local static IceoryxPlatformLogBackend log_backend = logger->log_backend.load(); + + if (current_logger_exchange_state == LoggerExchangeState::PENDING + || current_logger_exchange_state != logger->logger_exchange_state.load(std::memory_order_relaxed)) + { + // the logger can be changed only once and if we enter this if statement the 'active_logger' function will only + // return once 'logger_exchange_state' equals 'CUSTOM' + logger = &active_logger(nullptr); + log_backend = logger->log_backend.load(); + current_logger_exchange_state = logger->logger_exchange_state.load(); + } + + log_backend(file, line, function, log_level, msg); +} + +void iox_platform_set_log_backend(IceoryxPlatformLogBackend log_backend) +{ + if (log_backend == nullptr) + { + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'log_backend' must not be a nullptr!"); + return; + } + + active_logger(log_backend); +} diff --git a/iceoryx_platform/mac/source/semaphore.cpp b/iceoryx_platform/mac/source/semaphore.cpp index 7c6a31a3f1..90e2671852 100644 --- a/iceoryx_platform/mac/source/semaphore.cpp +++ b/iceoryx_platform/mac/source/semaphore.cpp @@ -16,12 +16,12 @@ // SPDX-License-Identifier: Apache-2.0 #include "iceoryx_platform/semaphore.hpp" +#include "iceoryx_platform/logging.hpp" #include "iceoryx_platform/time.hpp" #include #include #include -#include #include iox_sem_t::iox_sem_t() noexcept @@ -54,9 +54,9 @@ int iox_sem_getvalue(iox_sem_t* sem, int* sval) { if (sem->m_hasPosixHandle) { - std::cerr - << "\"sem_getvalue\" is not supported for named semaphores on MacOS and always returns 0, do not use it!" - << std::endl; + IOX_PLATFORM_LOG( + IOX_PLATFORM_LOG_LEVEL_ERROR, + "\"sem_getvalue\" is not supported for named semaphores on MacOS and always returns 0, do not use it!"); return 0; } *sval = static_cast(sem->m_value.load(std::memory_order_relaxed)); @@ -245,14 +245,14 @@ int iox_sem_init(iox_sem_t* sem, int, unsigned int value) pthread_mutexattr_t mutexAttr; if (pthread_mutexattr_init(&mutexAttr) != 0) { - printf("failed to initialize mutexattr\n"); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "failed to initialize mutexattr"); return -1; } if (pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_SHARED) != 0) { pthread_mutexattr_destroy(&mutexAttr); - printf("unable to set the shared process mutex attribute\n"); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "unable to set the shared process mutex attribute\n"); return -1; } @@ -261,7 +261,7 @@ int iox_sem_init(iox_sem_t* sem, int, unsigned int value) if (pthread_condattr_init(&condAttr) != 0) { pthread_mutexattr_destroy(&mutexAttr); - printf("failed to initialize condattr\n"); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "failed to initialize condattr\n"); return -1; } @@ -269,7 +269,8 @@ int iox_sem_init(iox_sem_t* sem, int, unsigned int value) { pthread_condattr_destroy(&condAttr); pthread_mutexattr_destroy(&mutexAttr); - printf("unable to set the shared process condition variable attribute\n"); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, + "unable to set the shared process condition variable attribute\n"); return -1; } @@ -277,7 +278,7 @@ int iox_sem_init(iox_sem_t* sem, int, unsigned int value) { pthread_condattr_destroy(&condAttr); pthread_mutexattr_destroy(&mutexAttr); - printf("failed to initialize inter process mutex\n"); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "failed to initialize inter process mutex\n"); return -1; } @@ -286,7 +287,7 @@ int iox_sem_init(iox_sem_t* sem, int, unsigned int value) pthread_mutex_destroy(&sem->m_handle.condition.mtx); pthread_condattr_destroy(&condAttr); pthread_mutexattr_destroy(&mutexAttr); - printf("failed to initialize inter process condition variable\n"); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "failed to initialize inter process condition variable\n"); return -1; } diff --git a/iceoryx_platform/test/moduletests/test_platform_logging.cpp b/iceoryx_platform/test/moduletests/test_platform_logging.cpp new file mode 100644 index 0000000000..db69a8410f --- /dev/null +++ b/iceoryx_platform/test/moduletests/test_platform_logging.cpp @@ -0,0 +1,155 @@ +// Copyright (c) 2024 by Mathias Kraus . All rights reserved. +// +// 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. +// +// SPDX-License-Identifier: Apache-2.0 + +#include "iceoryx_platform/logging.hpp" + +#include "test.hpp" + +#include +#include +#include +#include +#include +#include + +namespace +{ +using namespace ::testing; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) for testing only +std::atomic has_custom_backend{false}; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) for testing only +std::atomic dummy_backend_output_counter{0}; + +class LogOutput +{ + public: + void set(IceoryxPlatformLogLevel log_level, const char* msg) noexcept + { + std::lock_guard lock(m_mtx); + + m_log_level = log_level; + m_log_msg = msg; + } + + void clear() noexcept + { + std::lock_guard lock(m_mtx); + + m_log_level = IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_TRACE; + m_log_msg.clear(); + } + + std::pair get() const noexcept + { + std::lock_guard lock(m_mtx); + + return std::make_pair(m_log_level, m_log_msg); + } + + private: + mutable std::mutex m_mtx; + IceoryxPlatformLogLevel m_log_level{IceoryxPlatformLogLevel::IOX_PLATFORM_LOG_LEVEL_TRACE}; + std::string m_log_msg; +}; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) for testing only +LogOutput last_log_output; + +// NOLINTNEXTLINE(readability-function-size) not directly called; just for testing +void custom_log_backend( + const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg) +{ + has_custom_backend = true; + last_log_output.set(log_level, msg); + iox_platform_detail_default_log_backend(file, line, function, log_level, msg); +} + +// NOLINTNEXTLINE(readability-function-size) not directly called; just for testing +void dummy_log_backend( + const char* file, int line, const char* function, IceoryxPlatformLogLevel log_level, const char* msg) +{ + dummy_backend_output_counter.fetch_add(1); + last_log_output.set(log_level, msg); + iox_platform_detail_default_log_backend(file, line, function, log_level, msg); +} + +TEST(Logging_test, DefaultBackendLogsToCout) +{ + ::testing::Test::RecordProperty("TEST_ID", "a1d5fbb5-38e3-4829-bf1a-4ea312115f12"); + + ASSERT_FALSE(has_custom_backend); + + std::stringstream buffer; + std::streambuf* original_cout_buffer = std::cout.rdbuf(buffer.rdbuf()); + + constexpr const char* MESSAGE{"Hypnotoad will rock you"}; + + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_INFO, MESSAGE); + + std::string log = buffer.str(); + EXPECT_THAT(log, HasSubstr(MESSAGE)); + + std::cout.rdbuf(original_cout_buffer); +} + +TEST(Logging_test, SettingBackendWithNullptrFails) +{ + ::testing::Test::RecordProperty("TEST_ID", "85029c9d-3be2-463c-838a-9fce01e10640"); + + ASSERT_FALSE(has_custom_backend); + + iox_platform_set_log_backend(nullptr); + + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_INFO, "Hypnotoad"); + ASSERT_FALSE(has_custom_backend); +} + +TEST(Logging_test, SettingCustomBackendWorks) +{ + ::testing::Test::RecordProperty("TEST_ID", "9348e6ef-30a4-4cd7-a3d3-264efe378fba"); + + ASSERT_FALSE(has_custom_backend); + + iox_platform_set_log_backend(&custom_log_backend); + + last_log_output.clear(); + + constexpr const char* MESSAGE{"Who will rock you?"}; + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_INFO, MESSAGE); + + auto [log_level, log_msg] = last_log_output.get(); + EXPECT_THAT(log_level, Eq(IOX_PLATFORM_LOG_LEVEL_INFO)); + EXPECT_THAT(log_msg, StrEq(MESSAGE)); +} + +TEST(Logging_test, SettingCustomBackendTwiceFails) +{ + ::testing::Test::RecordProperty("TEST_ID", "cbc3446e-5c02-4f46-933f-c91627f669d3"); + + ASSERT_TRUE(has_custom_backend); + + constexpr uint64_t DUMMY_MESSAGE_COUNT_AFTER_FAILED_SETUP{1}; + ASSERT_THAT(dummy_backend_output_counter, Eq(0)); + iox_platform_set_log_backend(&dummy_log_backend); + ASSERT_THAT(dummy_backend_output_counter, Eq(DUMMY_MESSAGE_COUNT_AFTER_FAILED_SETUP)); + + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_INFO, "Hypnotoad"); + + ASSERT_THAT(dummy_backend_output_counter, Eq(DUMMY_MESSAGE_COUNT_AFTER_FAILED_SETUP)); +} + +} // namespace diff --git a/iceoryx_platform/win/source/fnctl.cpp b/iceoryx_platform/win/source/fnctl.cpp index 7326c5939e..0237127123 100644 --- a/iceoryx_platform/win/source/fnctl.cpp +++ b/iceoryx_platform/win/source/fnctl.cpp @@ -16,6 +16,7 @@ #include "iceoryx_platform/fcntl.hpp" #include "iceoryx_platform/handle_translator.hpp" +#include "iceoryx_platform/logging.hpp" #include "iceoryx_platform/win32_errorHandling.hpp" #include "iceoryx_platform/windows.hpp" @@ -23,6 +24,8 @@ #include #include +#include + int iox_open(const char* pathname, int flags, mode_t mode) { int fd; @@ -44,7 +47,10 @@ int iox_ext_open(const char* pathname, int flags, mode_t mode) if (handle == INVALID_HANDLE_VALUE) { - fprintf(stderr, "unable to create file \"%s\"\n", pathname); + std::stringstream stream; + stream << "unable to create file \"" << pathname << "\""; + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, stream.str().c_str()); + errno = EWOULDBLOCK; return -1; } @@ -54,14 +60,14 @@ int iox_ext_open(const char* pathname, int flags, mode_t mode) int iox_fcntl2(int, int) { - fprintf(stderr, "%s is not implemented in windows!\n", __PRETTY_FUNCTION__); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'iox_fcntl2' is not implemented in windows!"); errno = ENOSYS; return -1; } int iox_fcntl3(int, int, int) { - fprintf(stderr, "%s is not implemented in windows!\n", __PRETTY_FUNCTION__); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'iox_fcntl3' is not implemented in windows!"); errno = ENOSYS; return -1; } diff --git a/iceoryx_platform/win/source/getopt.cpp b/iceoryx_platform/win/source/getopt.cpp index 7600ce57b0..3b8cda250d 100644 --- a/iceoryx_platform/win/source/getopt.cpp +++ b/iceoryx_platform/win/source/getopt.cpp @@ -16,6 +16,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "iceoryx_platform/getopt.hpp" +#include "iceoryx_platform/logging.hpp" #include "iceoryx_platform/windows.hpp" #include @@ -28,8 +29,8 @@ int getopt_long(int argc, char* const[], const char*, const struct option*, int* { if (argc > 1) { - fprintf(stderr, "%s is not implemented in windows!\n", __PRETTY_FUNCTION__); - fprintf(stderr, "command line arguments are not supported in windows\n"); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'getopt_long' is not implemented in windows!"); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "command line arguments are not supported in windows!"); } return -1; } diff --git a/iceoryx_platform/win/source/handle_translator.cpp b/iceoryx_platform/win/source/handle_translator.cpp index 1f2c80bd3d..a6cffd51a9 100644 --- a/iceoryx_platform/win/source/handle_translator.cpp +++ b/iceoryx_platform/win/source/handle_translator.cpp @@ -15,8 +15,10 @@ // SPDX-License-Identifier: Apache-2.0 #include "iceoryx_platform/handle_translator.hpp" +#include "iceoryx_platform/logging.hpp" #include +#include constexpr int HandleTranslator::INVALID_LINUX_FD; @@ -81,8 +83,9 @@ void HandleTranslator::remove(const int linuxFd) noexcept auto iter = m_linuxToWindows.find(linuxFd); if (iter == m_linuxToWindows.end()) { - std::cerr << "Unable to release not registered file handle " << linuxFd << " since it was not acquired" - << std::endl; + std::stringstream stream; + stream << "Unable to release not registered file handle " << linuxFd << " since it was not acquired"; + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, stream.str().c_str()); return; } diff --git a/iceoryx_platform/win/source/mman.cpp b/iceoryx_platform/win/source/mman.cpp index 46b70c8aa7..537b25f23a 100644 --- a/iceoryx_platform/win/source/mman.cpp +++ b/iceoryx_platform/win/source/mman.cpp @@ -16,13 +16,14 @@ #include "iceoryx_platform/mman.hpp" #include "iceoryx_platform/handle_translator.hpp" +#include "iceoryx_platform/logging.hpp" #include "iceoryx_platform/platform_settings.hpp" #include "iceoryx_platform/win32_errorHandling.hpp" -#include #include #include #include +#include #include static std::map handle2segment; @@ -37,10 +38,12 @@ void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) DWORD numberOfBytesToMap = length; auto printErrorMessage = [&] { - std::cerr << "Failed to map file mapping into process space with mmap( addr = " << std::hex << addr << std::dec - << ", length = " << length << ", [always assume PROT_READ | PROT_WRITE] prot = " << prot - << ", [always assume MAP_SHARED] flags = " << flags << ", fd = " << fd - << ", [always assume 0] offset = " << offset << ")" << std::endl; + std::stringstream stream; + stream << "Failed to map file mapping into process space with mmap( addr = " << std::hex << addr << std::dec + << ", length = " << length << ", [always assume PROT_READ | PROT_WRITE] prot = " << prot + << ", [always assume MAP_SHARED] flags = " << flags << ", fd = " << fd + << ", [always assume 0] offset = " << offset << ")"; + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, stream.str().c_str()); }; void* mappedObject = Win32Call(MapViewOfFile, @@ -77,8 +80,10 @@ int munmap(void* addr, size_t length) } else { - std::cerr << "Failed to unmap memory region with munmap( addr = " << std::hex << addr << std::dec - << ", length = " << length << ")" << std::endl; + std::stringstream stream; + stream << "Failed to unmap memory region with munmap( addr = " << std::hex << addr << std::dec + << ", length = " << length << ")"; + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, stream.str().c_str()); } return -1; @@ -89,9 +94,11 @@ int iox_shm_open(const char* name, int oflag, mode_t mode) HANDLE sharedMemoryHandle{nullptr}; auto printErrorMessage = [&] { - std::cerr << "Failed to create shared memory with iox_shm_open( name = " << name - << ", [only consider O_CREAT and O_EXCL] oflag = " << oflag - << ", [always assume read, write, execute for everyone] mode = " << mode << ")" << std::endl; + std::stringstream stream; + stream << "Failed to create shared memory with iox_shm_open( name = " << name + << ", [only consider O_CREAT and O_EXCL] oflag = " << oflag + << ", [always assume read, write, execute for everyone] mode = " << mode << ")"; + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, stream.str().c_str()); }; bool hasCreatedShm = false; @@ -219,7 +226,9 @@ void internal_iox_shm_set_size(int fd, off_t length) auto iter = handle2segment.find(fd); if (iter == handle2segment.end()) { - std::cerr << "Unable to set shared memory size since the file descriptor is invalid." << std::endl; + std::stringstream stream; + stream << "Unable to set shared memory size since the file descriptor is invalid."; + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, stream.str().c_str()); return; } @@ -227,7 +236,9 @@ void internal_iox_shm_set_size(int fd, off_t length) FILE* shm_state = fopen(name.c_str(), "wb"); if (shm_state == NULL) { - std::cerr << "Unable create shared memory state file \"" << name << "\"" << std::endl; + std::stringstream stream; + stream << "Unable create shared memory state file \"" << name << "\""; + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, stream.str().c_str()); return; } uint64_t shm_size = length; diff --git a/iceoryx_platform/win/source/pthread.cpp b/iceoryx_platform/win/source/pthread.cpp index 549cbc76e0..c324f9d379 100644 --- a/iceoryx_platform/win/source/pthread.cpp +++ b/iceoryx_platform/win/source/pthread.cpp @@ -16,10 +16,12 @@ #include "iceoryx_platform/pthread.hpp" #include "iceoryx_platform/ipc_handle_manager.hpp" +#include "iceoryx_platform/logging.hpp" #include "iceoryx_platform/win32_errorHandling.hpp" #include "iceoryx_platform/windows.hpp" #include +#include #include HRESULT GetThreadDescription(HANDLE hThread, PWSTR* ppszThreadDescription); @@ -192,9 +194,10 @@ static HANDLE acquireMutexHandle(iox_pthread_mutex_t* mutex) newHandle = Win32Call(OpenMutexA, MUTEX_ALL_ACCESS, false, generateMutexName(mutex->uniqueId).c_str()).value; if (newHandle == nullptr) { - fprintf(stderr, - "interprocess mutex %s is corrupted - segmentation fault immenent\n", - generateMutexName(mutex->uniqueId).c_str()); + std::stringstream stream; + stream << "interprocess mutex '" << generateMutexName(mutex->uniqueId) + << "' is corrupted - segmentation fault immenent"; + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, stream.str().c_str()); return nullptr; } diff --git a/iceoryx_platform/win/source/semaphore.cpp b/iceoryx_platform/win/source/semaphore.cpp index 21f4210f67..9e0bf793f1 100644 --- a/iceoryx_platform/win/source/semaphore.cpp +++ b/iceoryx_platform/win/source/semaphore.cpp @@ -17,8 +17,10 @@ #include "iceoryx_platform/semaphore.hpp" #include "iceoryx_platform/ipc_handle_manager.hpp" +#include "iceoryx_platform/logging.hpp" #include +#include static std::string generateSemaphoreName(const UniqueSystemId& id) { @@ -42,9 +44,12 @@ static HANDLE acquireSemaphoreHandle(iox_sem_t* sem) Win32Call(OpenSemaphoreA, SEMAPHORE_ALL_ACCESS, false, generateSemaphoreName(sem->uniqueId).c_str()).value; if (newHandle == nullptr) { - fprintf(stderr, - "interprocess semaphore %s is corrupted - segmentation fault immenent\n", - generateSemaphoreName(sem->uniqueId).c_str()); + std::stringstream stream; + stream << "interprocess semaphore '" << generateSemaphoreName(sem->uniqueId) + << "' is corrupted - segmentation fault immenent"; + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, stream.str().c_str()); + + return nullptr; } @@ -155,7 +160,7 @@ int iox_sem_destroy(iox_sem_t* sem) return 0; } -static HANDLE sem_create_win32_semaphore(LONG value, LPCSTR name) +static Win32CallReturn sem_create_win32_semaphore(LONG value, LPCSTR name) { SECURITY_ATTRIBUTES securityAttribute; SECURITY_DESCRIPTOR securityDescriptor; @@ -175,8 +180,7 @@ static HANDLE sem_create_win32_semaphore(LONG value, LPCSTR name) securityAttribute.lpSecurityDescriptor = &securityDescriptor; securityAttribute.bInheritHandle = FALSE; - HANDLE returnValue = Win32Call(CreateSemaphoreA, &securityAttribute, value, IOX_SEM_VALUE_MAX, name).value; - return returnValue; + return Win32Call(CreateSemaphoreA, &securityAttribute, value, IOX_SEM_VALUE_MAX, name); } int iox_sem_init(iox_sem_t* sem, int pshared, unsigned int value) @@ -184,7 +188,7 @@ int iox_sem_init(iox_sem_t* sem, int pshared, unsigned int value) sem->isInterprocessSemaphore = (pshared == 1); if (sem->isInterprocessSemaphore) { - sem->handle = sem_create_win32_semaphore(value, generateSemaphoreName(sem->uniqueId).c_str()); + sem->handle = sem_create_win32_semaphore(value, generateSemaphoreName(sem->uniqueId).c_str()).value; if (sem->handle != nullptr) { IpcHandleManager::getInstance().addHandle(sem->uniqueId, OwnerShip::OWN, sem->handle); @@ -192,7 +196,7 @@ int iox_sem_init(iox_sem_t* sem, int pshared, unsigned int value) } else { - sem->handle = sem_create_win32_semaphore(value, nullptr); + sem->handle = sem_create_win32_semaphore(value, nullptr).value; } return (sem->handle != nullptr) ? 0 : -1; @@ -224,8 +228,9 @@ iox_sem_t* iox_sem_open_impl(const char* name, int oflag, ...) // mode_t mode, u unsigned int value = va_arg(va, unsigned int); va_end(va); - sem->handle = sem_create_win32_semaphore(value, name); - if (oflag & O_EXCL && GetLastError() == ERROR_ALREADY_EXISTS) + auto result = sem_create_win32_semaphore(value, name); + sem->handle = result.value; + if (oflag & O_EXCL && result.error == ERROR_ALREADY_EXISTS) { errno = EEXIST; iox_sem_close(sem); diff --git a/iceoryx_platform/win/source/socket.cpp b/iceoryx_platform/win/source/socket.cpp index e456066da1..c0e360d31e 100644 --- a/iceoryx_platform/win/source/socket.cpp +++ b/iceoryx_platform/win/source/socket.cpp @@ -15,47 +15,48 @@ // SPDX-License-Identifier: Apache-2.0 #include "iceoryx_platform/socket.hpp" +#include "iceoryx_platform/logging.hpp" #include int iox_bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen) { - fprintf(stderr, "%s is not implemented in windows!\n", __PRETTY_FUNCTION__); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'iox_bind' is not implemented in windows!"); return 0; } int iox_socket(int domain, int type, int protocol) { - fprintf(stderr, "%s is not implemented in windows!\n", __PRETTY_FUNCTION__); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'iox_socket' is not implemented in windows!"); return 0; } int iox_setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen) { - fprintf(stderr, "%s is not implemented in windows!\n", __PRETTY_FUNCTION__); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'iox_setsockopt' is not implemented in windows!"); return 0; } ssize_t iox_sendto(int sockfd, const void* buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen) { - fprintf(stderr, "%s is not implemented in windows!\n", __PRETTY_FUNCTION__); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'iox_sendto' is not implemented in windows!"); return 0; } ssize_t iox_recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen) { - fprintf(stderr, "%s is not implemented in windows!\n", __PRETTY_FUNCTION__); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'iox_recvfrom' is not implemented in windows!"); return 0; } int iox_connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen) { - fprintf(stderr, "%s is not implemented in windows!\n", __PRETTY_FUNCTION__); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'iox_connect' is not implemented in windows!"); return 0; } int iox_closesocket(int sockfd) { - fprintf(stderr, "%s is not implemented in windows!\n", __PRETTY_FUNCTION__); + IOX_PLATFORM_LOG(IOX_PLATFORM_LOG_LEVEL_ERROR, "'iox_closesocket' is not implemented in windows!"); return 0; } diff --git a/iceoryx_platform/win/source/win32_errorHandling.cpp b/iceoryx_platform/win/source/win32_errorHandling.cpp index 954699af6c..8409e335f4 100644 --- a/iceoryx_platform/win/source/win32_errorHandling.cpp +++ b/iceoryx_platform/win/source/win32_errorHandling.cpp @@ -16,29 +16,32 @@ // SPDX-License-Identifier: Apache-2.0 #include "iceoryx_platform/win32_errorHandling.hpp" +#include "iceoryx_platform/logging.hpp" -#include +#include #include int __PrintLastErrorToConsole(const char* functionName, const char* file, const int line) noexcept { - static std::mutex coutMutex; constexpr uint64_t BUFFER_SIZE{2048u}; int lastError = GetLastError(); if (lastError != 0) { char buffer[BUFFER_SIZE]; - FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + sprintf(buffer, "< Win32API Error > [%d] ::: ", lastError); + size_t used_buffer_size = strlen(buffer); + constexpr size_t NULL_TERMINATOR_SIZE{1}; + size_t remaining_buffer_size = BUFFER_SIZE - used_buffer_size - NULL_TERMINATOR_SIZE; + + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, NULL, lastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - buffer, - BUFFER_SIZE - 1, + &buffer[used_buffer_size], + remaining_buffer_size, NULL); - coutMutex.lock(); - fprintf(stderr, "< Win32API Error > %s:%d { %s } [ %d ] ::: %s", file, line, functionName, lastError, buffer); - coutMutex.unlock(); + iox_platform_detail_log(file, line, functionName, IOX_PLATFORM_LOG_LEVEL_ERROR, buffer); } return lastError; }