aboutsummaryrefslogtreecommitdiff
path: root/src/logging.cpp
diff options
context:
space:
mode:
authorGravatar Sadie Powell2022-01-18 03:10:47 +0000
committerGravatar Sadie Powell2022-05-01 22:07:04 +0100
commitc382faf9c941ad2ab93f806b6477933d41566719 (patch)
tree9244cf84190cf74d4e16085b2ae9bc9b5520457f /src/logging.cpp
parentDelete the old logging system. (diff)
Rewrite the entire logging system.
- Much cleaner API for writing to the log. - Adds support for stderr and stdout logging to the core. - Adds support for sql and syslog logging in modules.
Diffstat (limited to 'src/logging.cpp')
-rw-r--r--src/logging.cpp247
1 files changed, 247 insertions, 0 deletions
diff --git a/src/logging.cpp b/src/logging.cpp
new file mode 100644
index 000000000..8aaa81104
--- /dev/null
+++ b/src/logging.cpp
@@ -0,0 +1,247 @@
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2022 Sadie Powell <sadie@witchery.services>
+ *
+ * This file is part of InspIRCd. InspIRCd 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, version 2.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "inspircd.h"
+
+Log::FileMethod::FileMethod(const std::string& n, FILE* fh, unsigned long fl, bool ac)
+ : autoclose(ac)
+ , file(fh)
+ , flush(fl)
+ , name(n)
+{
+}
+Log::FileMethod::~FileMethod()
+{
+ if (autoclose)
+ fclose(file);
+}
+
+void Log::FileMethod::OnLog(Level level, const std::string& type, const std::string& message)
+{
+ static time_t prevtime = 0;
+ static std::string timestr;
+ if (prevtime != ServerInstance->Time())
+ {
+ prevtime = ServerInstance->Time();
+ timestr = InspIRCd::TimeString(prevtime);
+ }
+
+ fputs(timestr.c_str(), file);
+ fputs(" ", file);
+ fputs(type.c_str(), file);
+ fputs(": ", file);
+ fputs(message.c_str(), file);
+#if defined _WIN32
+ fputs("\r\n", file);
+#else
+ fputs("\n", file);
+#endif
+
+ if (!(++lines % flush))
+ fflush(file);
+
+ if (ferror(file))
+ throw CoreException(InspIRCd::Format("Unable to write to %s: %s", name.c_str(), strerror(errno)));
+}
+
+Log::Engine::Engine(Module* Creator, const std::string& Name)
+ : DataProvider(Creator, "log/" + Name)
+{
+}
+
+Log::Engine::~Engine()
+{
+ if (creator)
+ ServerInstance->Logs.UnloadEngine(this);
+}
+
+Log::FileEngine::FileEngine(Module* Creator)
+ : Engine(Creator, "file")
+{
+}
+
+Log::MethodPtr Log::FileEngine::Create(std::shared_ptr<ConfigTag> tag)
+{
+ const std::string target = tag->getString("target");
+ if (target.empty())
+ throw CoreException("<log:target> must be specified for file logger at " + tag->source.str());
+
+ const std::string fulltarget = ServerInstance->Config->Paths.PrependLog(InspIRCd::TimeString(ServerInstance->Time(), target.c_str()));
+ FILE* fh = fopen(fulltarget.c_str(), "a");
+ if (!fh)
+ {
+ throw CoreException(InspIRCd::Format("Unable to open %s for file logger at %s: %s",
+ fulltarget.c_str(), tag->source.str().c_str(), strerror(errno)));
+ }
+
+ const unsigned long flush = tag->getUInt("flush", 20, 1);
+ return std::make_shared<FileMethod>(fulltarget, fh, flush, true);
+}
+
+Log::StreamEngine::StreamEngine(Module* Creator, const std::string& Name, FILE* fh)
+ : Engine(Creator, Name)
+ , file(fh)
+{
+}
+
+Log::MethodPtr Log::StreamEngine::Create(std::shared_ptr<ConfigTag> tag)
+{
+ return std::make_shared<FileMethod>(name, file, 1, false);
+}
+
+Log::Manager::CachedMessage::CachedMessage(Level l, const std::string& t, const std::string& m)
+ : level(l)
+ , type(t)
+ , message(m)
+{
+}
+
+Log::Manager::Info::Info(Level l, TokenList t, MethodPtr m, bool c, const Engine* e)
+ : config(c)
+ , level(l)
+ , types(std::move(t))
+ , method(std::move(m))
+ , engine(e)
+{
+}
+
+Log::Manager::Manager()
+ : filelog(nullptr)
+ , stderrlog(nullptr, "stderr", stderr)
+ , stdoutlog(nullptr, "stdout", stdout)
+{
+}
+
+void Log::Manager::CloseLogs()
+{
+ size_t logger_count = loggers.size();
+ loggers.erase(std::remove_if(loggers.begin(), loggers.end(), [](const Info& info) { return info.config; }), loggers.end());
+ Normal("LOG", "Closing the logs; removed %zu/%zu loggers.", logger_count - loggers.size(), logger_count);
+
+}
+
+void Log::Manager::EnableDebugMode()
+{
+ TokenList types = std::string("*");
+ MethodPtr method = stdoutlog.Create(ServerInstance->Config->EmptyTag);
+ loggers.emplace_back(Level::RAWIO, std::move(types), std::move(method), false, &stdoutlog);
+}
+
+void Log::Manager::OpenLogs(bool requiremethods)
+{
+ // If the server is started in debug mode we don't write logs.
+ if (ServerInstance->Config->cmdline.forcedebug)
+ {
+ Normal("LOG", "Not opening loggers because we were started with --debug");
+ ServerInstance->Config->RawLog = true;
+ return;
+ }
+
+ // If the server is started with logging disabled we don't write logs.
+ if (!ServerInstance->Config->cmdline.writelog)
+ {
+ Normal("LOG", "Not opening loggers because we were started with --nolog");
+ return;
+ }
+
+ for (const auto& [_, tag] : ServerInstance->Config->ConfTags("log"))
+ {
+ const std::string methodstr = tag->getString("method", "file", 1);
+ Log::Engine* engine = ServerInstance->Modules.FindDataService<Log::Engine>("log/" + methodstr);
+ if (!engine)
+ {
+ if (!requiremethods)
+ continue; // We will open this later.
+
+ throw CoreException(methodstr + " is not a valid logging method at " + tag->source.str());
+ }
+
+ const Level level = tag->getEnum("level", Level::NORMAL, {
+ { "error", Level::ERROR },
+ { "warning", Level::WARNING },
+ { "normal", Level::NORMAL },
+ { "debug", Level::DEBUG },
+ { "rawio", Level::RAWIO },
+
+ // Deprecated v3 names.
+ { "sparse", Level::ERROR },
+ { "verbose", Level::WARNING },
+ { "default", Level::NORMAL },
+
+ });
+ TokenList types = tag->getString("type", "*", 1);
+ MethodPtr method = engine->Create(tag);
+ loggers.emplace_back(level, std::move(types), method, true, engine);
+ }
+
+ if (requiremethods && caching)
+ {
+ // The server has finished starting up so we can write out any cached log messages.
+ for (const auto& logger : loggers)
+ {
+ if (!logger.method->AcceptsCachedMessages())
+ continue; // Does not support logging.
+
+ for (const auto& message : cache)
+ {
+ if (logger.level >= message.level && logger.types.Contains(message.type))
+ logger.method->OnLog(message.level, message.type, message.message);
+ }
+ }
+
+ cache.clear();
+ cache.shrink_to_fit();
+ caching = false;
+ }
+}
+
+void Log::Manager::RegisterServices()
+{
+ ServiceProvider* coreloggers[] = { &filelog, &stderrlog, &stdoutlog };
+ ServerInstance->Modules.AddServices(coreloggers, sizeof(coreloggers)/sizeof(ServiceProvider*));
+}
+
+void Log::Manager::UnloadEngine(const Engine* engine)
+{
+ size_t logger_count = loggers.size();
+ loggers.erase(std::remove_if(loggers.begin(), loggers.end(), [&engine](const Info& info) { return info.engine == engine; }), loggers.end());
+ Normal("LOG", "The %s log engine is unloading; removed %zu/%zu loggers.", engine->name.c_str(), logger_count - loggers.size(), logger_count);
+}
+
+void Log::Manager::Write(Level level, const std::string& type, const std::string& message)
+{
+ if (logging)
+ return; // Avoid log loops.
+
+ logging = true;
+ for (const auto& logger : loggers)
+ {
+ if (logger.level >= level && logger.types.Contains(type))
+ logger.method->OnLog(level, type, message);
+ }
+
+ if (caching)
+ cache.emplace_back(level, type, message);
+ logging = false;
+}
+
+void Log::Manager::Write(Level level, const std::string& type, const char* format, va_list& args)
+{
+ Write(level, type, InspIRCd::Format(args, format));
+}