187
lib/utils/mu-logger.cc
Normal file
187
lib/utils/mu-logger.cc
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
/*
|
||||||
|
** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
|
**
|
||||||
|
** 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 <glib.h>
|
||||||
|
#include <glib/gstdio.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
72
lib/utils/mu-logger.hh
Normal file
72
lib/utils/mu-logger.hh
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
** Copyright (C) 2020 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||||
|
**
|
||||||
|
** 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 <string>
|
||||||
|
#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__ */
|
||||||
Reference in New Issue
Block a user