diff --git a/src/common/file_settings_persistence.cpp b/src/common/file_settings_persistence.cpp index d28fd13..04ecdf8 100644 --- a/src/common/file_settings_persistence.cpp +++ b/src/common/file_settings_persistence.cpp @@ -26,7 +26,7 @@ namespace display_device { return false; } - std::ranges::copy(data, std::ostreambuf_iterator { stream }); + std::copy(std::begin(data), std::end(data), std::ostreambuf_iterator { stream }); return true; } catch (const std::exception &error) { diff --git a/src/common/include/display_device/logging.h b/src/common/include/display_device/logging.h index 9f925e7..f8b35e9 100644 --- a/src/common/include/display_device/logging.h +++ b/src/common/include/display_device/logging.h @@ -26,7 +26,8 @@ namespace display_device { debug, info, warning, - error + error, + fatal }; /** diff --git a/src/common/logging.cpp b/src/common/logging.cpp index 6c5f8fd..2912efb 100644 --- a/src/common/logging.cpp +++ b/src/common/logging.cpp @@ -1,3 +1,7 @@ +#if !defined(_MSC_VER) && !defined(_POSIX_THREAD_SAFE_FUNCTIONS) + #define _POSIX_THREAD_SAFE_FUNCTIONS // For localtime_r +#endif + // class header include #include "display_device/logging.h" @@ -8,6 +12,22 @@ #include namespace display_device { + namespace { + std::tm + threadSafeLocaltime(const std::time_t &time) { +#if defined(_MSC_VER) // MSVCRT (2005+): std::localtime is threadsafe + const auto tm_ptr { std::localtime(&time) }; +#else // POSIX + std::tm buffer; + const auto tm_ptr { localtime_r(&time, &buffer) }; +#endif // _MSC_VER + if (tm_ptr) { + return *tm_ptr; + } + return {}; + } + } // namespace + Logger & Logger::get() { static Logger instance; // GCOVR_EXCL_BR_LINE for some reason... @@ -15,7 +35,7 @@ namespace display_device { } void - Logger::setLogLevel(LogLevel log_level) { + Logger::setLogLevel(const LogLevel log_level) { m_enabled_log_level = log_level; } @@ -44,45 +64,18 @@ namespace display_device { std::stringstream stream; { - // Time (limited by GCC 11, so it's not pretty and no timezones are supported...) + // Time (limited by GCC 10, so it's not pretty...) { - static const auto get_time { []() { - static const auto to_year_month_day { [](const auto &now) { - return std::chrono::year_month_day { std::chrono::time_point_cast(now) }; - } }; - static const auto to_hour_minute_second { [](const auto &now) { - const auto start_of_day { std::chrono::floor(now) }; - const auto time_since_start_of_day { std::chrono::round(now - start_of_day) }; - return std::chrono::hh_mm_ss { time_since_start_of_day }; - } }; - static const auto to_milliseconds { [](const auto &now) { - const auto now_ms { std::chrono::duration_cast(now.time_since_epoch()) }; - const auto time_s { std::chrono::duration_cast(now.time_since_epoch()) }; - return now_ms - time_s; - } }; - - const auto now { std::chrono::system_clock::now() }; - return std::make_tuple(to_year_month_day(now), to_hour_minute_second(now), to_milliseconds(now)); - } }; - - const auto [year_month_day, hh_mm_ss, ms] { get_time() }; - const auto old_flags { stream.flags() }; // Save formatting flags so that they can be restored... + const auto now { std::chrono::system_clock::now() }; + const auto now_ms { std::chrono::duration_cast(now.time_since_epoch()) }; + const auto now_s { std::chrono::duration_cast(now_ms) }; + + const std::time_t time { std::chrono::system_clock::to_time_t(now) }; + const auto localtime { threadSafeLocaltime(time) }; + const auto now_decimal_part { now_ms - now_s }; - stream << "["; - stream << std::setfill('0') << std::setw(2) << static_cast(year_month_day.year()); - stream << "-"; - stream << std::setfill('0') << std::setw(2) << static_cast(year_month_day.month()); - stream << "-"; - stream << std::setfill('0') << std::setw(2) << static_cast(year_month_day.day()); - stream << " "; - stream << std::setfill('0') << std::setw(2) << hh_mm_ss.hours().count(); - stream << ":"; - stream << std::setfill('0') << std::setw(2) << hh_mm_ss.minutes().count(); - stream << ":"; - stream << std::setfill('0') << std::setw(2) << hh_mm_ss.seconds().count(); - stream << "."; - stream << std::setfill('0') << std::setw(3) << ms.count(); - stream << "] "; + const auto old_flags { stream.flags() }; // Save formatting flags so that they can be restored... + stream << std::put_time(&localtime, "[%Y-%m-%d %H:%M:%S.") << std::setfill('0') << std::setw(3) << now_decimal_part.count() << "] "; stream.flags(old_flags); } @@ -103,6 +96,9 @@ namespace display_device { case LogLevel::error: stream << "ERROR: "; break; + case LogLevel::fatal: + stream << "FATAL: "; + break; } // Value diff --git a/tests/unit/general/test_file_settings_persistence.cpp b/tests/unit/general/test_file_settings_persistence.cpp index cb9589e..9780e29 100644 --- a/tests/unit/general/test_file_settings_persistence.cpp +++ b/tests/unit/general/test_file_settings_persistence.cpp @@ -61,7 +61,7 @@ TEST_F_S(Store, FileOverwritten) { { std::ofstream file { filepath, std::ios_base::binary }; - std::ranges::copy(data1, std::ostreambuf_iterator { file }); + std::copy(std::begin(data1), std::end(data1), std::ostreambuf_iterator { file }); } EXPECT_TRUE(std::filesystem::exists(filepath)); @@ -92,7 +92,7 @@ TEST_F_S(Load, FileRead) { { std::ofstream file { filepath, std::ios_base::binary }; - std::ranges::copy(data, std::ostreambuf_iterator { file }); + std::copy(std::begin(data), std::end(data), std::ostreambuf_iterator { file }); } EXPECT_EQ(getImpl(filepath).load(), data); diff --git a/tests/unit/general/test_logging.cpp b/tests/unit/general/test_logging.cpp index 9da596f..57c04f0 100644 --- a/tests/unit/general/test_logging.cpp +++ b/tests/unit/general/test_logging.cpp @@ -18,6 +18,7 @@ TEST_S(LogLevelVerbose) { EXPECT_EQ(logger.isLogLevelEnabled(level::info), true); EXPECT_EQ(logger.isLogLevelEnabled(level::warning), true); EXPECT_EQ(logger.isLogLevelEnabled(level::error), true); + EXPECT_EQ(logger.isLogLevelEnabled(level::fatal), true); } TEST_S(LogLevelDebug) { @@ -31,6 +32,7 @@ TEST_S(LogLevelDebug) { EXPECT_EQ(logger.isLogLevelEnabled(level::info), true); EXPECT_EQ(logger.isLogLevelEnabled(level::warning), true); EXPECT_EQ(logger.isLogLevelEnabled(level::error), true); + EXPECT_EQ(logger.isLogLevelEnabled(level::fatal), true); } TEST_S(LogLevelInfo) { @@ -44,6 +46,7 @@ TEST_S(LogLevelInfo) { EXPECT_EQ(logger.isLogLevelEnabled(level::info), true); EXPECT_EQ(logger.isLogLevelEnabled(level::warning), true); EXPECT_EQ(logger.isLogLevelEnabled(level::error), true); + EXPECT_EQ(logger.isLogLevelEnabled(level::fatal), true); } TEST_S(LogLevelWarning) { @@ -57,6 +60,7 @@ TEST_S(LogLevelWarning) { EXPECT_EQ(logger.isLogLevelEnabled(level::info), false); EXPECT_EQ(logger.isLogLevelEnabled(level::warning), true); EXPECT_EQ(logger.isLogLevelEnabled(level::error), true); + EXPECT_EQ(logger.isLogLevelEnabled(level::fatal), true); } TEST_S(LogLevelError) { @@ -70,6 +74,21 @@ TEST_S(LogLevelError) { EXPECT_EQ(logger.isLogLevelEnabled(level::info), false); EXPECT_EQ(logger.isLogLevelEnabled(level::warning), false); EXPECT_EQ(logger.isLogLevelEnabled(level::error), true); + EXPECT_EQ(logger.isLogLevelEnabled(level::fatal), true); +} + +TEST_S(LogLevelFatal) { + using level = display_device::Logger::LogLevel; + auto &logger { display_device::Logger::get() }; + + logger.setLogLevel(level::fatal); + + EXPECT_EQ(logger.isLogLevelEnabled(level::verbose), false); + EXPECT_EQ(logger.isLogLevelEnabled(level::debug), false); + EXPECT_EQ(logger.isLogLevelEnabled(level::info), false); + EXPECT_EQ(logger.isLogLevelEnabled(level::warning), false); + EXPECT_EQ(logger.isLogLevelEnabled(level::error), false); + EXPECT_EQ(logger.isLogLevelEnabled(level::fatal), true); } TEST_S(DefaultLogger) { @@ -89,6 +108,7 @@ TEST_S(DefaultLogger) { EXPECT_TRUE(testRegex(write_and_get_cout(level::info, "Hello World!"), R"(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}\] INFO: Hello World!\n)")); EXPECT_TRUE(testRegex(write_and_get_cout(level::warning, "Hello World!"), R"(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}\] WARNING: Hello World!\n)")); EXPECT_TRUE(testRegex(write_and_get_cout(level::error, "Hello World!"), R"(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}\] ERROR: Hello World!\n)")); + EXPECT_TRUE(testRegex(write_and_get_cout(level::fatal, "Hello World!"), R"(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}.\d{3}\] FATAL: Hello World!\n)")); // clang-format on } @@ -122,6 +142,10 @@ TEST_S(CustomCallback) { logger.write(level::error, "Hello World!"); EXPECT_EQ(output, "4 Hello World!"); EXPECT_TRUE(m_cout_buffer.str().empty()); + + logger.write(level::fatal, "Hello World!"); + EXPECT_EQ(output, "5 Hello World!"); + EXPECT_TRUE(m_cout_buffer.str().empty()); } TEST_S(WriteMethodRespectsLogLevel, DefaultLogger) {