diff --git a/lib/utils/meson.build b/lib/utils/meson.build index da8bca22..63e8dd2b 100644 --- a/lib/utils/meson.build +++ b/lib/utils/meson.build @@ -65,4 +65,10 @@ test('test-utils-file', cpp_args: ['-DBUILD_TESTS'], dependencies: [glib_dep, config_h_dep, lib_mu_utils_dep])) +test('test-logger', + executable('test-logger', 'mu-logger.cc', + install: false, + cpp_args: ['-DBUILD_TESTS'], + dependencies: [glib_dep, lib_mu_utils_dep])) + subdir('tests') diff --git a/lib/utils/mu-logger.cc b/lib/utils/mu-logger.cc index 4a661b59..882f3ba5 100644 --- a/lib/utils/mu-logger.cc +++ b/lib/utils/mu-logger.cc @@ -28,6 +28,9 @@ #include #include +#include +#include + #include "mu-logger.hh" using namespace Mu; @@ -36,6 +39,7 @@ static bool MuLogInitialized = false; static Mu::Logger::Options MuLogOptions; static std::ofstream MuStream; static auto MaxLogFileSize = 1000 * 1024; +static std::mutex logger_mtx; static std::string MuLogPath; @@ -84,6 +88,8 @@ maybe_rotate_logfile() static GLogWriterOutput log_file(GLogLevelFlags level, const GLogField* fields, gsize n_fields, gpointer user_data) { + std::lock_guard lock{logger_mtx}; + if (!maybe_open_logfile()) return G_LOG_WRITER_UNHANDLED; @@ -124,7 +130,6 @@ Mu::Logger::make(const std::string& path, Mu::Logger::Options opts) return Ok(Logger(path, opts)); } - Mu::Logger::Logger(const std::string& path, Mu::Logger::Options opts) { if (g_getenv("MU_LOG_STDOUTERR")) @@ -147,7 +152,8 @@ Mu::Logger::Logger(const std::string& path, Mu::Logger::Options opts) } // log to the journal, or, if not available to a file. - if (log_journal(level, fields, n_fields, user_data) != G_LOG_WRITER_HANDLED) + if (any_of(MuLogOptions & Options::File) || + log_journal(level, fields, n_fields, user_data) != G_LOG_WRITER_HANDLED) return log_file(level, fields, n_fields, user_data); else return G_LOG_WRITER_HANDLED; @@ -172,3 +178,56 @@ Logger::~Logger() MuLogInitialized = false; } + + +#ifdef BUILD_TESTS +#include +#include + +#include "mu-test-utils.hh" + +static void +test_logger_threads(void) +{ + const auto testpath{test_random_tmpdir() + "/test.log"}; + g_message("log-file: %s", testpath.c_str()); + + auto logger = Logger::make(testpath.c_str(), Logger::Options::File | Logger::Options::Debug); + assert_valid_result(logger); + + const auto thread_num = 16; + std::atomic running = true; + + std::vector threads; + + /* log to the logger file from many threass */ + for (auto n = 0; n != thread_num; ++n) + threads.emplace_back( + std::thread([n,&running]{ + while (running) { + g_debug("log message from thread <%d>", n); + std::this_thread::yield(); + } + })); + + using namespace std::chrono_literals; + std::this_thread::sleep_for(1s); + running = false; + + for (auto n = 0; n != 16; ++n) + if (threads[n].joinable()) + threads[n].join(); +} + + +int +main(int argc, char* argv[]) +{ + mu_test_init(&argc, &argv); + + g_test_add_func("/utils/logger", test_logger_threads); + + return g_test_run(); +} + +#endif /*BUILD_TESTS*/ diff --git a/lib/utils/mu-logger.hh b/lib/utils/mu-logger.hh index 54290dbe..6024e286 100644 --- a/lib/utils/mu-logger.hh +++ b/lib/utils/mu-logger.hh @@ -1,5 +1,5 @@ /* -** Copyright (C) 2020-2022 Dirk-Jan C. Binnema +** Copyright (C) 2020-2023 Dirk-Jan C. Binnema ** ** This program is free software; you can redistribute it and/or modify it ** under the terms of the GNU General Public License as published by the @@ -39,12 +39,12 @@ struct Logger { enum struct Options { None = 0, /**< Nothing specific */ StdOutErr = 1 << 1, /**< Log to stdout/stderr */ - Debug = 1 << 2, /**< Include debug-level logs */ + File = 1 << 2, /**< Force logging to file, even if journal available */ + Debug = 1 << 3, /**< Include debug-level logs */ }; - /** - * Initialize the logging system. + * Initialize the logging sub-system. * * Note that the path is only used if structured logging fails -- * practically, it goes to the file if there's no systemd/journald.