iceoryx2
C++ Language Bindings
Loading...
Searching...
No Matches
testing_logger.hpp
Go to the documentation of this file.
1// Copyright (c) 2022 by Apex.AI Inc. All rights reserved.
2// Copyright (c) 2025 Contributors to the Eclipse Foundation
3//
4// See the NOTICE file(s) distributed with this work for additional
5// information regarding copyright ownership.
6//
7// This program and the accompanying materials are made available under the
8// terms of the Apache Software License 2.0 which is available at
9// https://www.apache.org/licenses/LICENSE-2.0, or the MIT license
10// which is available at https://opensource.org/licenses/MIT.
11//
12// SPDX-License-Identifier: Apache-2.0 OR MIT
13
14#ifndef IOX2_BB_TESTING_TESTING_LOGGER_HPP
15#define IOX2_BB_TESTING_TESTING_LOGGER_HPP
16
20
21#include <gmock/gmock.h>
22#include <gtest/gtest.h>
23
24#include <functional>
25#include <iostream>
26#include <mutex>
27
28#include <csignal>
29#include <cstdio>
30#include <cstring>
31
32// NOLINTNEXTLINE(hicpp-deprecated-headers,modernize-deprecated-headers) required to work on some platforms
33#include <setjmp.h>
34
35namespace iox2 {
36namespace bb {
37namespace testing {
40class LogPrinter : public ::testing::EmptyTestEventListener {
41 public:
42 auto OnTestStart(const ::testing::TestInfo& info) -> void override;
43 auto OnTestPartResult(const ::testing::TestPartResult& result) -> void override;
44
45#ifndef _WIN32
46 private:
47 struct sigaction m_sigsegv_old_action = {};
48 struct sigaction m_sigfpe_old_action = {};
49 struct sigaction m_sigabrt_old_action = {};
50#endif
51};
52
67
68 public:
69 ~TestingLogger() override = default;
70
71 TestingLogger(const TestingLogger&) = delete;
73
74 auto operator=(const TestingLogger&) -> TestingLogger& = delete;
76
91 static auto init() noexcept -> void {
92 using namespace iox2::legacy;
93
94 static TestingLogger LOGGER;
95 log::Logger::setActiveLogger(LOGGER);
96 log::Logger::init(log::logLevelFromEnvOr(log::LogLevel::Trace));
97
98 const std::lock_guard<std::mutex> lock(LOGGER.m_loggerDataLock);
99
100 // disable logger output only after initializing the logger to get error messages from initialization
101 // JUSTIFICATION getenv is required for the functionality of the testing logger and will be called only once in
102 // main NOLINTNEXTLINE(concurrency-mt-unsafe)
103 if (const auto* allow_log_string = std::getenv("IOX2_TESTING_ALLOW_LOG")) {
104 if (log::equalStrings(allow_log_string, "on") || log::equalStrings(allow_log_string, "ON")) {
105 LOGGER.m_loggerData.allowLog = true;
106 } else {
107 LOGGER.m_loggerData.allowLog = false;
108 std::cout << "" << std::endl;
109 std::cout << "Invalid value for 'IOX2_TESTING_ALLOW_LOG' environment variable!'" << std::endl;
110 std::cout << "Found: " << allow_log_string << std::endl;
111 std::cout << "Allowed is one of: on, ON" << std::endl;
112 }
113 } else {
114 LOGGER.m_loggerData.allowLog = false;
115 }
116
117 auto& listeners = ::testing::UnitTest::GetInstance()->listeners();
118 // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) required by the callee
119 listeners.Append(new (std::nothrow) LogPrinter);
120 }
121
123 auto clear_log_buffer() noexcept -> void {
124 const std::lock_guard<std::mutex> lock(m_loggerDataLock);
125 m_loggerData.buffer.clear();
126 }
127
129 auto print_log_buffer() noexcept -> void {
130 const std::lock_guard<std::mutex> lock(m_loggerDataLock);
131 if (m_loggerData.buffer.empty()) {
132 return;
133 }
134 puts("#### Log start ####");
135 for (const auto& log : m_loggerData.buffer) {
136 puts(log.c_str());
137 }
138 puts("#### Log end ####");
139 m_loggerData.buffer.clear();
140 }
141
145 static auto get_number_of_log_messages() noexcept -> uint64_t {
146 auto& logger = dynamic_cast<TestingLogger&>(legacy::log::Logger::get());
147 const std::lock_guard<std::mutex> lock(logger.m_loggerDataLock);
148 return logger.m_loggerData.buffer.size();
149 }
150
154 legacy::log::LogLevel log_level, const std::function<void(const std::vector<std::string>&)>& check) -> void {
155 if (does_logger_support_log_level(log_level)) {
156 check(get_log_messages());
157 }
158 }
159
164 static constexpr auto does_logger_support_log_level(const legacy::log::LogLevel log_level) noexcept -> bool {
165 return legacy::log::MINIMAL_LOG_LEVEL >= log_level;
166 }
167
168 protected:
169 void flush() noexcept override {
170 const std::lock_guard<std::mutex> lock(m_loggerDataLock);
171 const auto log_buffer = Base::getLogBuffer();
172 m_loggerData.buffer.emplace_back(log_buffer.buffer, log_buffer.writeIndex);
173
174 if (m_loggerData.allowLog) {
175 Base::flush();
176 }
177
178 Base::assumeFlushed();
179 }
180
181 private:
182 TestingLogger() noexcept = default;
183
184 static auto get_log_messages() noexcept -> std::vector<std::string> {
185 auto& logger = dynamic_cast<TestingLogger&>(legacy::log::Logger::get());
186 const std::lock_guard<std::mutex> lock(logger.m_loggerDataLock);
187 return logger.m_loggerData.buffer;
188 }
189
190 struct LoggerData {
191 std::vector<std::string> buffer;
192 bool allowLog { true };
193 };
194
195 std::mutex m_loggerDataLock;
196 LoggerData m_loggerData;
197};
198
199#ifndef _WIN32
200namespace detail {
201// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) global variable is required as jmp target
202static jmp_buf exit_jmp_buffer;
203
204static void sig_handler_flush_logger(int sig, siginfo_t* info IOX2_MAYBE_UNUSED, void* ucontext IOX2_MAYBE_UNUSED) {
205 using namespace iox2::legacy;
206
207 constexpr const char* COLOR_RESET { "\033[m" };
208
209 std::cout << log::logLevelDisplayColor(log::LogLevel::Warn)
210 << "Catched signal: " << log::logLevelDisplayColor(log::LogLevel::Fatal);
211 switch (sig) {
212 case SIGSEGV:
213 std::cout << "SIGSEGV" << std::flush;
214 break;
215 case SIGFPE:
216 std::cout << "SIGFPE" << std::flush;
217 break;
218 case SIGABRT:
219 std::cout << "SIGABRT" << std::flush;
220 break;
221 default:
222 std::cout << sig;
223 break;
224 }
225
226 std::cout << COLOR_RESET << "\n\n" << std::flush;
227
228 dynamic_cast<TestingLogger&>(log::Logger::get()).print_log_buffer();
229
230 std::cout << "\n"
231 << log::logLevelDisplayColor(log::LogLevel::Warn)
232 << "Aborting execution by causing a SIGSEV with 'longjmp' to prevent triggering the signal handler again!"
233 << COLOR_RESET << "\n"
234 << std::flush;
235
236 constexpr int JMP_VALUE { 1 };
237 // NOLINTNEXTLINE(cert-err52-cpp,modernize-avoid-setjmp-longjmp) exception cannot be used and longjmp/setjmp is a working fallback
238 longjmp(&exit_jmp_buffer[0], JMP_VALUE);
239}
240} // namespace detail
241#endif
242
243inline auto LogPrinter::OnTestStart(const ::testing::TestInfo& info IOX2_MAYBE_UNUSED) -> void {
244 dynamic_cast<TestingLogger&>(legacy::log::Logger::get()).clear_log_buffer();
245 TestingLogger::setLogLevel(legacy::log::LogLevel::Trace);
246
247 std::set_terminate([]() -> auto {
248 std::cout << "Terminate called\n" << std::flush;
249 dynamic_cast<TestingLogger&>(legacy::log::Logger::get()).print_log_buffer();
250 std::abort();
251 });
252
253#ifndef _WIN32
254 struct sigaction action = {};
255 memset(&action, 0, sizeof(struct sigaction));
256 sigemptyset(&action.sa_mask);
257
258 action.sa_flags = SA_NODEFER;
259 action.sa_sigaction = detail::sig_handler_flush_logger;
260
261 sigaction(SIGSEGV, &action, &m_sigsegv_old_action);
262 sigaction(SIGFPE, &action, &m_sigfpe_old_action);
263 sigaction(SIGABRT, &action, &m_sigabrt_old_action);
264#endif
265}
266
267inline auto LogPrinter::OnTestPartResult(const ::testing::TestPartResult& result) -> void {
268 if (result.failed()) {
269 dynamic_cast<TestingLogger&>(legacy::log::Logger::get()).print_log_buffer();
270 }
271
272#ifndef _WIN32
273 sigaction(SIGSEGV, &m_sigsegv_old_action, nullptr);
274 m_sigsegv_old_action = {};
275 sigaction(SIGFPE, &m_sigfpe_old_action, nullptr);
276 m_sigfpe_old_action = {};
277 sigaction(SIGABRT, &m_sigabrt_old_action, nullptr);
278 m_sigabrt_old_action = {};
279#endif
280}
281
282} // namespace testing
283} // namespace bb
284} // namespace iox2
285
286#endif // IOX2_BB_TESTING_TESTING_LOGGER_HPP
#define IOX2_MAYBE_UNUSED
IOX2_MAYBE_UNUSED adds the [[gnu::unused]] attribute when it is available for the current compiler or...
This class hooks into gTest to automatically clear the log messages on the start of a test an print t...
auto OnTestStart(const ::testing::TestInfo &info) -> void override
auto OnTestPartResult(const ::testing::TestPartResult &result) -> void override
This logger is used for tests. It caches all the log messages and prints them to the console when a t...
static constexpr auto does_logger_support_log_level(const legacy::log::LogLevel log_level) noexcept -> bool
Checks if the the LogLevel is above the minimal supported LogLevel compiled into the binary.
~TestingLogger() override=default
static auto check_log_message_if_log_level_is_supported(legacy::log::LogLevel log_level, const std::function< void(const std::vector< std::string > &)> &check) -> void
Runs the provided checker function for the collected log messages.
auto print_log_buffer() noexcept -> void
Prints all log messages from the internal cache. This is automatically done at the end of a failed te...
static auto get_number_of_log_messages() noexcept -> uint64_t
Number of caches log messages.
void flush() noexcept override
TestingLogger(TestingLogger &&)=delete
auto operator=(TestingLogger &&) -> TestingLogger &=delete
auto operator=(const TestingLogger &) -> TestingLogger &=delete
static auto init() noexcept -> void
Initialized the logger. This should be called in the main function of the test binary.
auto clear_log_buffer() noexcept -> void
Removes all log messages from the internal cache. This is automatically done at the start of each tes...
TestingLogger(const TestingLogger &)=delete
This class acts as common interface for the Logger. It provides the common functionality and inherits...
Definition logger.hpp:71
static Logger & get() noexcept
Access to the logger singleton instance.
static void sig_handler_flush_logger(int sig, siginfo_t *info IOX2_MAYBE_UNUSED, void *ucontext IOX2_MAYBE_UNUSED)
LogLevel
This enum defines the log levels used for logging.
Definition logger.hpp:30
static constexpr LogLevel MINIMAL_LOG_LEVEL
The minimal log level which will be compiled into the application. All log levels below this will be ...
Definition logger.hpp:47
internal::Logger< ConsoleLogger > TestingLoggerBase
Definition logger.hpp:35
void log(LogLevel log_level, const char *origin, const char *message)
Adds a log message to the logger.