From 4dd764d9e8d80bd98eca29cbdbda0f7b0e56cbdc Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sat, 30 May 2020 18:33:27 +0300 Subject: [PATCH] lib/utils: add mu-logger.{cc,hh} They were missing. Fixes #1713. --- lib/utils/mu-logger.cc | 187 +++++++++++++++++++++++++++++++++++++++++ lib/utils/mu-logger.hh | 72 ++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 lib/utils/mu-logger.cc create mode 100644 lib/utils/mu-logger.hh diff --git a/lib/utils/mu-logger.cc b/lib/utils/mu-logger.cc new file mode 100644 index 00000000..12466d60 --- /dev/null +++ b/lib/utils/mu-logger.cc @@ -0,0 +1,187 @@ +/* +** Copyright (C) 2020 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 +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#define G_LOG_USE_STRUCTURED +#include +#include +#include +#include +#include + +#include +#include + +#include "mu-logger.hh" + +using namespace Mu; + +constexpr auto MuLogDomain = "mu"; +static bool MuLogInitialized = false; +static Mu::LogOptions MuLogOptions; +static std::ofstream MuStream; +static auto MaxLogFileSize = 1000 * 1024; + +static std::string MuLogPath; + +static bool +maybe_open_logfile () +{ + if (MuStream.is_open()) + return true; + + MuStream.open (MuLogPath, std::ios::out | std::ios::app ); + if (!MuStream.is_open()) { + std::cerr << "opening " << MuLogPath << " failed:" + << strerror(errno) << std::endl; + return false; + } + + MuStream.sync_with_stdio(false); + return true; +} + +static bool +maybe_rotate_logfile () +{ + static unsigned n = 0; + + if (n++ % 1000 != 0) + return true; + + GStatBuf statbuf; + if (::stat (MuLogPath.c_str(), &statbuf) == -1 || + statbuf.st_size <= MaxLogFileSize) + return true; + + const auto old = MuLogPath + ".old"; + g_unlink(old.c_str()); // opportunistic + + if (MuStream.is_open()) + MuStream.close(); + + if (g_rename(MuLogPath.c_str(), old.c_str()) != 0) + std::cerr << "failed to rename " + << MuLogPath << " -> " << old.c_str() + << ": " << ::strerror(errno) << std::endl; + + return maybe_open_logfile(); +} + +static GLogWriterOutput +log_file (GLogLevelFlags level, const GLogField *fields, gsize n_fields, + gpointer user_data) +{ + if (!maybe_open_logfile()) + return G_LOG_WRITER_UNHANDLED; + + char timebuf[22]; + time_t now{::time(NULL)}; + ::strftime (timebuf, sizeof(timebuf), "%F %T", ::localtime(&now)); + + char *msg = g_log_writer_format_fields (level, fields, n_fields, FALSE); + if (msg && msg[0] == '\n') // hmm... seems lines start with '\n'r + msg[0] = ' '; + + MuStream << timebuf << ' ' << msg << std::endl; + + g_free (msg); + + return maybe_rotate_logfile() ? G_LOG_WRITER_HANDLED : G_LOG_WRITER_UNHANDLED; +} + +static GLogWriterOutput +log_stdouterr (GLogLevelFlags level, const GLogField *fields, gsize n_fields, + gpointer user_data) +{ + return g_log_writer_standard_streams (level, fields, n_fields, user_data); +} + +static GLogWriterOutput +log_journal (GLogLevelFlags level, const GLogField *fields, gsize n_fields, + gpointer user_data) +{ + return g_log_writer_journald (level, fields, n_fields, user_data); +} + +void +Mu::log_init (const std::string& path, Mu::LogOptions opts) +{ + if (MuLogInitialized) { + g_error ("logging is already initialized"); + return; + } + + MuLogOptions = opts; + MuLogPath = path; + + g_log_set_writer_func ( + [](GLogLevelFlags level, const GLogField *fields, gsize n_fields, + gpointer user_data) { + + // filter out debug-level messages? + if (level == G_LOG_LEVEL_DEBUG && + (none_of (MuLogOptions & Mu::LogOptions::Debug))) + return G_LOG_WRITER_HANDLED; + + // log criticals to stdout / err or if asked + if (level == G_LOG_LEVEL_CRITICAL || + any_of(MuLogOptions & Mu::LogOptions::StdOutErr)){ + log_stdouterr (level, fields, n_fields, user_data); + } + + // log to the journal, or, if not available to a file. + if (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; + }, NULL, NULL); + + g_message ("logging initialized; debug: %s, stdout/stderr: %s", + any_of(log_get_options() & LogOptions::Debug) ? "yes" : "no", + any_of(log_get_options() & LogOptions::StdOutErr) ? "yes" : "no"); + + MuLogInitialized = true; +} + +void +Mu::log_uninit () +{ + if (!MuLogInitialized) { + g_warning ("logging was not initialized"); + return; + } + + if (MuStream.is_open()) + MuStream.close(); + + MuLogInitialized = false; +} + +void +Mu::log_set_options (Mu::LogOptions opts) +{ + MuLogOptions = opts; +} + +Mu::LogOptions +Mu::log_get_options () +{ + return MuLogOptions; +} diff --git a/lib/utils/mu-logger.hh b/lib/utils/mu-logger.hh new file mode 100644 index 00000000..6c17ab0f --- /dev/null +++ b/lib/utils/mu-logger.hh @@ -0,0 +1,72 @@ +/* +** Copyright (C) 2020 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 +** Free Software Foundation; either version 3, or (at your option) any +** later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, +** Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +** +*/ + +#ifndef MU_LOGGER_HH__ +#define MU_LOGGER_HH__ + +#include +#include "utils/mu-utils.hh" + +namespace Mu { + +/** + * Logging options + * + */ +enum struct LogOptions { + None = 0, /**< Nothing specific */ + StdOutErr = 1 << 1, /**< Log to stdout/stderr */ + Debug = 1 << 2, /**< Include debug-level logs */ +}; + +/** + * Initialize the logging system. Note that the path is only used if structured + * logging fails -- practically, it goes to the file if there's + * systemd/journald. + * + * @param path path to the log file + * @param opts logging options + */ +void log_init (const std::string& path, LogOptions opts); + +/** + * Uninitialize the logging system + * + */ +void log_uninit(); + +/** + * Change the logging options. + * + * @param opts options + */ +void log_set_options (LogOptions opts); + +/** + * Get the current log options + * + * @return the log options + */ +LogOptions log_get_options (); + + +} // namepace Mu +MU_ENABLE_BITOPS(Mu::LogOptions); + +#endif /* MU_LOGGER_HH__ */