mu/lib/utils/mu-logger.cc

183 lines
4.5 KiB
C++

/*
** 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 <cstring>
#include "mu-logger.hh"
using namespace 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:" << g_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 (g_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() << ": "
<< g_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;
}
if (g_getenv("MU_LOG_STDOUTERR"))
opts |= LogOptions::StdOutErr;
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)
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;
}