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.
This commit is contained in:
Sadie Powell 2022-01-18 03:10:47 +00:00
parent 0df16f0144
commit c382faf9c9
18 changed files with 933 additions and 63 deletions

View File

@ -40,7 +40,7 @@ jobs:
- name: Run configure
run: |
./configure --enable-extras "argon2 geo_maxmind ldap mysql pgsql regex_pcre regex_posix regex_re2 sqlite3 ssl_gnutls ssl_mbedtls ssl_openssl sslrehashsignal"
./configure --enable-extras "argon2 geo_maxmind ldap log_syslog mysql pgsql regex_pcre regex_posix regex_re2 sqlite3 ssl_gnutls ssl_mbedtls ssl_openssl sslrehashsignal"
./configure --development --disable-auto-extras --disable-ownership --socketengine ${{ matrix.socketengine }}
- name: Build core

View File

@ -38,7 +38,7 @@ jobs:
- name: Run configure
run: |
./configure --enable-extras "argon2 geo_maxmind ldap mysql pgsql regex_pcre regex_posix regex_re2 sqlite3 ssl_gnutls ssl_mbedtls ssl_openssl sslrehashsignal"
./configure --enable-extras "argon2 geo_maxmind ldap log_syslog mysql pgsql regex_pcre regex_posix regex_re2 sqlite3 ssl_gnutls ssl_mbedtls ssl_openssl sslrehashsignal"
./configure --development --disable-auto-extras --socketengine ${{ matrix.socketengine }}
- name: Build core

View File

@ -37,7 +37,7 @@ jobs:
- name: Run configure
run: |
./configure --enable-extras "argon2 geo_maxmind ldap mysql pgsql regex_pcre regex_posix regex_re2 sqlite3 ssl_gnutls ssl_mbedtls ssl_openssl sslrehashsignal"
./configure --enable-extras "argon2 geo_maxmind ldap log_syslog mysql pgsql regex_pcre regex_posix regex_re2 sqlite3 ssl_gnutls ssl_mbedtls ssl_openssl sslrehashsignal"
./configure --development --disable-auto-extras --socketengine ${{ matrix.socketengine }}
- name: Build core

1
.gitignore vendored
View File

@ -17,6 +17,7 @@
/src/modules/m_argon2.cpp
/src/modules/m_geo_maxmind.cpp
/src/modules/m_ldap.cpp
/src/modules/m_log_syslog.cpp
/src/modules/m_mysql.cpp
/src/modules/m_pgsql.cpp
/src/modules/m_regex_pcre.cpp

3
configure vendored
View File

@ -395,7 +395,8 @@ if (prompt_bool $interactive, $question, 0) {
'm_mysql.cpp' => 'mysql_config --version',
'm_pgsql.cpp' => 'pg_config --version',
'm_ldap.cpp' => "echo '#include <ldap.h>' | $config{CXX} -E -",
'm_regex_pcre.cpp' => 'pkg-config --exists libpcre2-8',
'm_log_syslog.cpp' => undef,
'm_regex_pcre.cpp' => 'pkg-config --exists libpcre2-8',
'm_regex_posix.cpp' => undef,
'm_regex_re2.cpp' => 'pkg-config --exists re2',
'm_sqlite3.cpp' => 'pkg-config --exists sqlite3',

View File

@ -865,56 +865,39 @@
# where you do not have the ability to set build time configuration. #
#<path configdir="conf" datadir="data" logdir="logs" moduledir="modules">
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
# Logging
# -------
#
# Logging is covered with the <log> tag, which you may use to change
# the behaviour of the logging of the IRCd.
#
# An example log tag would be:
# <log method="file" type="OPER" level="default" target="opers.log">
# which would log all information on /OPER (failed and successful) to
# a file called opers.log.
#
# There are many different types which may be used, and modules may
# generate their own. A list of useful types:
# - USERS - information relating to user connection and disconnection
# - OPER - successful and failed oper attempts
# - KILL - kill related messages
# - FILTER - messages related to filter matches (filter module)
# - CONFIG - configuration related messages
# - COMMAND - die and restart messages, and messages related to unknown user types
# - SOCKET - socket engine informational/error messages
# - MODULE - module related messages
# - STARTUP - messages related to starting up the server
#
# You may also log *everything* by using a type of *, and subtract things out
# of that by using -TYPE - for example "* -USERINPUT -USEROUTPUT".
#
# Useful levels are:
# - default (general messages, including errors)
# - sparse (misc error messages)
# - debug (debug messages)
#
# Some types only produce output in the debug level, those are:
# - BANCACHE - ban cache debug messages
# - CHANNELS - information relating to joining/creating channels
# - CULLLIST - debug messages related to issues with removing users
# - RESOLVER - DNS related debug messages
# - CONNECTCLASS - Connection class debug messages
# - USERINPUT
# - USEROUTPUT
#
# If your server is producing a high levels of log messages you can also set the
# flush="[positive number]" attribute to specify how many log messages should be
# buffered before flushing to disk. You should probably not specify this unless
# you are having problems.
#
# The following log tag is highly default and uncustomised. It is recommended you
# sort out your own log tags. This is just here so you get some output.
#-#-#-#-#-#-#-#-#-#-#-# LOGGING CONFIGURATION #-#-#-#-#-#-#-#-#-#-#-#-#
# #
# The <log> tag allows you to define a list of targets to write log #
# messages to. #
# #
# method - The method to use when logging. This can be set to "file" #
# to log to a file, "stderr" to log to the standard error #
# stream, or "stdout" to log to the standard output stream. #
# You can also set it to a log method provided by a module. #
# #
# level - The level of messages to write to this logger. Can be set #
# to "error", "warning", "normal", or "debug". #
# #
# type - A space-delimited list of log types to write to this logger. #
# See https://docs.inspircd.org/4/configuration/#log for a #
# full list of log types. You can also use * to include every #
# log type and then -TYPE to exclude specific unwanted types. #
# #
# target - If the method is set to "file" then the name of the file #
# to write log messages to. #
<log method="file" type="* -USERINPUT -USEROUTPUT" level="default" target="ircd.log">
<log method="file"
level="normal"
type="* -USERINPUT -USEROUTPUT"
target="inspircd.log">
#<log method="stderr"
# level="normal"
# type="* -USERINPUT -USEROUTPUT">
#<log method="stdout"
# level="normal"
# type="* -USERINPUT -USEROUTPUT">
#-#-#-#-#-#-#-#-#-#-#-#-#- WHOWAS OPTIONS -#-#-#-#-#-#-#-#-#-#-#-#-#
# #

View File

@ -1366,6 +1366,32 @@
# opers are to be authenticated via LDAP, so in case this module is #
# not loaded the oper accounts are still protected by a password. #
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
# SQL logging module: Allows writing messages to an SQL database.. #
#<module name="log_sql">
#
# This module adds the following fields to the <log> tag:
#
# dbid - The id for the <database> tag that defines your database
# connection details.
# query - A custom query to use when inserting logs into the database.
#
#<log method="sql"
# level="normal"
# type="* -USERINPUT -USEROUTPUT"
# dbid="sql-log"
# query="INSERT INTO ircd_log (time, type, message) VALUES (FROM_UNIXTIME($time), '$type', '$message');">
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
# Syslog logging module: Allows writing messages to the system log. #
# This module is in extras. Re-run configure with: #
# ./configure --enable-extras log_syslog
#<module name="log_syslog">
#
#<log method="syslog"
# level="normal"
# type="* -USERINPUT -USEROUTPUT">
#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#-#
# Map hiding module: replaces /MAP and /LINKS output to users with a #
# message to see a website, set by maphide="https://test.org/map" in #

View File

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS `ircd_log` (
`time` datetime,
`type` varchar(50),
`message` text
);

View File

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS "ircd_log" (
"time" datetime,
"type" varchar(50),
"message" text
);

View File

@ -0,0 +1,5 @@
Table ircd_log {
time datetime
type varchar(50)
message text
}

View File

@ -0,0 +1,6 @@
CREATE TABLE "ircd_log" (
"time" datetime,
"type" varchar(50),
"message" text
);

View File

@ -103,6 +103,7 @@ CoreExport extern InspIRCd* ServerInstance;
#include "inspstring.h"
#include "protocol.h"
#include "bancache.h"
#include "logging.h"
/** This class contains various STATS counters
* It is used by the InspIRCd class, which internally
@ -226,7 +227,7 @@ public:
/** LogManager handles logging.
*/
LogManager Logs;
Log::Manager Logs;
/** ModuleManager contains everything related to loading/unloading
* modules.

350
include/logging.h Normal file
View File

@ -0,0 +1,350 @@
/*
* 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/>.
*/
#pragma once
namespace Log
{
class Method;
class FileMethod;
class Engine;
class FileEngine;
class StreamEngine;
class Manager;
typedef std::shared_ptr<Method> MethodPtr;
enum class Level
: uint8_t
{
/** A critical message which must be investigated. */
ERROR = 0,
/** An important message which should be investigated. */
WARNING = 1,
/** A general message which is useful to have on record. */
NORMAL = 2,
/** A debug message that we might want to store when testing. */
DEBUG = 3,
/** A sensitive message that we should not store lightly. */
RAWIO = 4,
};
}
/** Base class for logging methods. */
class CoreExport Log::Method
: public Cullable
{
public:
virtual ~Method() = default;
/** Determines whether this logging method accepts cached messages. */
virtual bool AcceptsCachedMessages() const { return true; }
/** Writes a message to the logger.
* @param level The level at which the log message was written.
* @param type The component which wrote the log message.
* @param message The message which was written to the log.
*/
virtual void OnLog(Level level, const std::string& type, const std::string& message) = 0;
};
/** A logger that writes to a file stream. */
class CoreExport Log::FileMethod final
: public Method
{
private:
/** Whether to autoclose the file on exit. */
bool autoclose;
/** The file to which the log is written. */
FILE* file;
/** How often the file stream should be flushed. */
const unsigned long flush;
/** The number of lines which have been written since the file stream was created. */
unsigned long lines = 0;
/** The name the underlying file. */
const std::string name;
public:
FileMethod(const std::string& n, FILE* fh, unsigned long fl, bool ac);
~FileMethod() override;
/** @copydoc Log::Method::AcceptsCachedMessages */
bool AcceptsCachedMessages() const override { return false; }
/** @copydoc Log::Method::OnLog */
void OnLog(Level level, const std::string& type, const std::string& message) override;
};
/** Base class for logging engines. */
class CoreExport Log::Engine
: public DataProvider
{
protected:
Engine(Module* Creator, const std::string& Name);
public:
virtual ~Engine();
/** Creates a new logger from the specified config.
* @param tag The config tag to configure the logger with.
*/
virtual MethodPtr Create(std::shared_ptr<ConfigTag> tag) = 0;
};
/** A logger which writes to a file. */
class CoreExport Log::FileEngine final
: public Engine
{
public:
FileEngine(Module* Creator);
/** @copydoc Log::Engine::Create */
MethodPtr Create(std::shared_ptr<ConfigTag> tag) override;
};
/** A logger which writes to a stream. */
class CoreExport Log::StreamEngine final
: public Engine
{
private:
FILE* file;
public:
StreamEngine(Module* Creator, const std::string& Name, FILE* fh);
/** @copydoc Log::Engine::Create */
MethodPtr Create(std::shared_ptr<ConfigTag> tag) override;
};
/** Manager for the logging system. */
class CoreExport Log::Manager final
{
private:
/** A log message which has been cached until modules load. */
struct CachedMessage final
{
/** The level the message was logged at. */
Level level;
/** The type of the message that was logged. */
std::string type;
/** The message that was logged. */
std::string message;
CachedMessage(Level l, const std::string& t, const std::string& m);
};
/** Encapsulates information about a logger. */
struct Info final
{
/** Whether the logger was read from the server config. */
bool config;
/** The minimum log level that this logger accepts. */
Level level;
/** The types of log message that this logger accepts. */
TokenList types;
/** The handler for this logger type. */
MethodPtr method;
/** The engine which created this logger. */
const Engine* engine;
Info(Level l, TokenList t, MethodPtr m, bool c, const Engine* e);
};
/** The log messages we have cached for modules. */
std::vector<CachedMessage> cache;
/** Whether we have just started up and need to cache messages until modules are loaded. */
bool caching = true;
/** The logger engine that writes to a file. */
Log::FileEngine filelog;
/** A logger that writes to stderr. */
Log::StreamEngine stderrlog;
/** A logger that writes to stdout. */
Log::StreamEngine stdoutlog;
/** The currently registered loggers. */
std::vector<Info> loggers;
/** Whether we are currently logging to a file. */
bool logging = false;
/** Writes a message to the server log.
* @param level The level to log at.
* @param type The type of message that is being logged.
* @param message The message to log.
* */
void Write(Level level, const std::string& type, const std::string& message);
/** Writes a message to the server logs.
* @param level The level to log at.
* @param type The type of message that is being logged.
* @param format The message to format and then log.
* @param args The arguments to use when formatting the log message
* */
void Write(Level level, const std::string& type, const char* format, va_list& args) ATTR_NOT_NULL(4) ATTR_PRINTF(4, 0);
public:
Manager();
/** Closes all loggers which were opened from the config. */
void CloseLogs();
/** Enables writing rawio logs to the standard output stream. */
void EnableDebugMode();
/** Opens loggers that are specified in the config. */
void OpenLogs(bool requiremethods);
/** Registers the core logging services with the event system. */
void RegisterServices();
/** Unloads all loggers that are provided by the specified engine.
* @param engine The engine to unload the loggers of.
*/
void UnloadEngine(const Engine* engine);
/** Writes an error message to the server log.
* @param type The type of message that is being logged.
* @param format A format string to format and then log.
* */
inline void Error(const std::string& type, const std::string& message)
{
Write(Level::ERROR, type, message);
}
/** Writes an error message to the server log.
* @param level The level to log at.
* @param type The type of message that is being logged.
* @param format A format string to format and then log.
* */
inline void Error(const std::string& type, const char* format, ...) ATTR_NOT_NULL(3) ATTR_PRINTF(3, 4)
{
va_list args;
va_start(args, format);
Write(Level::ERROR, type, format, args);
va_end(args);
}
/** Writes a warning message to the server log.
* @param type The type of message that is being logged.
* @param format A format string to format and then log.
* */
inline void Warning(const std::string& type, const std::string& message)
{
Write(Level::WARNING, type, message);
}
/** Writes a warning message to the server log.
* @param level The level to log at.
* @param type The type of message that is being logged.
* @param format A format string to format and then log.
* */
inline void Warning(const std::string& type, const char *format, ...) ATTR_NOT_NULL(3) ATTR_PRINTF(3, 4)
{
va_list args;
va_start(args, format);
Write(Level::WARNING, type, format, args);
va_end(args);
}
/** Writes a normal message to the server log.
* @param type The type of message that is being logged.
* @param format A format string to format and then log.
* */
inline void Normal(const std::string& type, const std::string& message)
{
Write(Level::NORMAL, type, message);
}
/** Writes a normal message to the server log.
* @param level The level to log at.
* @param type The type of message that is being logged.
* @param format A format string to format and then log.
* */
inline void Normal(const std::string& type, const char *format, ...) ATTR_NOT_NULL(3) ATTR_PRINTF(3, 4)
{
va_list args;
va_start(args, format);
Write(Level::NORMAL, type, format, args);
va_end(args);
}
/** Writes a debug message to the server log.
* @param type The type of message that is being logged.
* @param format A format string to format and then log.
* */
inline void Debug(const std::string& type, const std::string& message)
{
Write(Level::DEBUG, type, message);
}
/** Writes a debug message to the server log.
* @param level The level to log at.
* @param type The type of message that is being logged.
* @param format A format string to format and then log.
* */
inline void Debug(const std::string& type, const char *format, ...) ATTR_NOT_NULL(3) ATTR_PRINTF(3, 4)
{
va_list args;
va_start(args, format);
Write(Level::DEBUG, type, format, args);
va_end(args);
}
/** Writes a raw I/O message to the server log.
* @param type The type of message that is being logged.
* @param format A format string to format and then log.
* */
inline void RawIO(const std::string& type, const std::string& message)
{
Write(Level::RAWIO, type, message);
}
/** Writes a raw I/O message to the server log.
* @param level The level to log at.
* @param type The type of message that is being logged.
* @param format A format string to format and then log.
* */
inline void RawIO(const std::string& type, const char *format, ...) ATTR_NOT_NULL(3) ATTR_PRINTF(3, 4)
{
va_list args;
va_start(args, format);
Write(Level::RAWIO, type, format, args);
va_end(args);
}
};

View File

@ -670,8 +670,17 @@ void ConfigReaderThread::OnStop()
// The description of this server may have changed - update it for WHOIS etc.
ServerInstance->FakeClient->server->description = Config->ServerDesc;
ServerInstance->Logs.CloseLogs();
ServerInstance->Logs.OpenFileLogs();
try
{
ServerInstance->Logs.CloseLogs();
ServerInstance->Logs.OpenLogs(true);
}
catch (CoreException& ex)
{
ServerInstance->Logs.Normal("LOG", "Cannot open log files: " + ex.GetReason());
if (user)
user->WriteNotice("Cannot open log files: " + ex.GetReason());
}
if (Config->RawLog && !old->RawLog)
ServerInstance->Users.ServerNoticeAll("*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded.");

View File

@ -500,13 +500,9 @@ InspIRCd::InspIRCd(int argc, char** argv)
<< "See " << rang::style::bold << rang::fg::green << "/INFO" << rang::style::reset << " for contributors & authors" << std::endl
<< std::endl;
Logs.RegisterServices();
if (Config->cmdline.forcedebug)
{
FILE* newstdout = fdopen(dup(STDOUT_FILENO), "w");
FileWriter* fw = new FileWriter(newstdout, 1);
FileLogStream* fls = new FileLogStream(LOG_RAWIO, fw);
Logs.AddLogTypes("*", fls, true);
}
Logs.EnableDebugMode();
if (!FindConfigFile(ConfigFileName))
{
@ -528,7 +524,17 @@ InspIRCd::InspIRCd(int argc, char** argv)
*/
this->Config->Read();
this->Config->Apply(NULL, "");
Logs.OpenFileLogs();
try
{
Logs.CloseLogs();
Logs.OpenLogs(false);
}
catch (const CoreException& ex)
{
std::cout << "ERROR: Cannot open log files: " << ex.GetReason() << std::endl << "Exiting..." << std::endl;
Exit(EXIT_STATUS_LOG);
}
// If we don't have a SID, generate one based on the server name and the server description
if (Config->sid.empty())
@ -548,6 +554,17 @@ InspIRCd::InspIRCd(int argc, char** argv)
TryBindPorts();
this->Modules.LoadAll();
try
{
// We reopen logs again after modules to allow module loggers to have a chance to register.
Logs.CloseLogs();
Logs.OpenLogs(true);
}
catch (const CoreException& ex)
{
std::cout << "ERROR: Cannot open log files: " << ex.GetReason() << std::endl << "Exiting..." << std::endl;
Exit(EXIT_STATUS_LOG);
}
std::cout << "InspIRCd is now running as '" << Config->ServerName << "'[" << Config->GetSID() << "] with " << SocketEngine::GetMaxFds() << " max open sockets" << std::endl;

247
src/logging.cpp Normal file
View File

@ -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));
}

View File

@ -0,0 +1,94 @@
/*
* 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"
#include <syslog.h>
class SyslogMethod final
: public Log::Method
{
private:
// Converts an InspIRCd log level to syslog priority.
int LevelToPriority(Log::Level level)
{
switch (level)
{
case Log::Level::ERROR:
return LOG_ERR;
case Log::Level::WARNING:
return LOG_WARNING;
case Log::Level::NORMAL:
return LOG_NOTICE;
case Log::Level::DEBUG:
case Log::Level::RAWIO:
return LOG_DEBUG;
}
// Should never happen.
return LOG_NOTICE;
}
public:
void OnLog(Log::Level level, const std::string& type, const std::string& message) override
{
syslog(LevelToPriority(level), "%s: %s", type.c_str(), message.c_str());
}
};
class SyslogEngine final
: public Log::Engine
{
public:
SyslogEngine(Module* Creator)
: Log::Engine(Creator, "syslog")
{
}
public:
Log::MethodPtr Create(std::shared_ptr<ConfigTag> tag) override
{
return std::make_shared<SyslogMethod>();
}
};
class ModuleLogSyslog final
: public Module
{
private:
SyslogEngine engine;
public:
ModuleLogSyslog()
: Module(VF_VENDOR, "Provides the ability to write logs to syslog.")
, engine(this)
{
openlog("inspircd", LOG_NDELAY|LOG_PID, LOG_USER);
}
~ModuleLogSyslog()
{
closelog();
}
};
MODULE_INIT(ModuleLogSyslog)

118
src/modules/m_log_sql.cpp Normal file
View File

@ -0,0 +1,118 @@
/*
* 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"
#include "modules/sql.h"
namespace
{
Module* thismod;
}
class SQLQuery final
: public SQL::Query
{
public:
SQLQuery(Module* mod)
: SQL::Query(mod)
{
}
void OnResult(SQL::Result& res) override
{
// Nothing to do here.
}
void OnError(SQL::Error& error) override
{
ServerInstance->SNO.WriteGlobalSno('a', "Unable to write to SQL log (query error: %s).", error.ToString());
}
};
class SQLMethod final
: public Log::Method
{
private:
std::string query;
dynamic_reference<SQL::Provider> sql;
public:
SQLMethod(const dynamic_reference<SQL::Provider>& s, const std::string& q)
: query(q)
, sql(s)
{
}
void OnLog(Log::Level level, const std::string& type, const std::string& message) override
{
if (!sql)
{
ServerInstance->SNO.WriteGlobalSno('a', "Unable to write to SQL log (database %s not available).", sql->GetId().c_str());
return;
}
SQL::ParamMap params = {
{ "level", ConvToStr(static_cast<uint8_t>(level)) },
{ "message", message },
{ "time", ConvToStr(ServerInstance->Time()) },
{ "type", type },
};
sql->Submit(new SQLQuery(thismod), query, params);
}
};
class SQLEngine final
: public Log::Engine
{
public:
SQLEngine(Module* Creator)
: Log::Engine(Creator, "sql")
{
}
public:
Log::MethodPtr Create(std::shared_ptr<ConfigTag> tag) override
{
dynamic_reference<SQL::Provider> sql(creator, "SQL");
const std::string dbid = tag->getString("dbid");
if (!dbid.empty())
sql.SetProvider("SQL/" + dbid);
const std::string query = tag->getString("query", "INSERT INTO ircd_log (time, type, message) VALUES (FROM_UNIXTIME($time), '$type', '$message');", 1);
return std::make_shared<SQLMethod>(sql, query);
}
};
class ModuleLogSQL final
: public Module
{
private:
SQLEngine engine;
public:
ModuleLogSQL()
: Module(VF_VENDOR, "Provides the ability to write logs to an SQL database.")
, engine(this)
{
thismod = this;
}
};
MODULE_INIT(ModuleLogSQL)