From 28395d55e5d3e7a1c7b302a3a5d34459803db12d Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 4 Feb 2024 12:42:21 +0100 Subject: [PATCH] Refactor the logging system --- inc/config.php | 17 +++- inc/driver/log-driver.php | 189 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+), 2 deletions(-) create mode 100644 inc/driver/log-driver.php diff --git a/inc/config.php b/inc/config.php index ffd1b544..c34eb034 100644 --- a/inc/config.php +++ b/inc/config.php @@ -65,8 +65,21 @@ // been generated. This keeps the script from querying the database and causing strain when not needed. $config['has_installed'] = '.installed'; - // Use syslog() for logging all error messages and unauthorized login attempts. - $config['syslog'] = false; + // Deprecated, use 'log_system'. + // $config['syslog'] = false; + + $config['log_system'] = []; + // Log all error messages and unauthorized login attempts. + // Can be "syslog", "error_log" (default), "file", "stderr" or "none". + $config['log_system']['type'] = 'error_log'; + // The application name used by the logging system. Defaults to "tinyboard" for backwards compatibility. + $config['log_system']['name'] = 'tinyboard'; + // Only relevant if 'log_system' is set to "syslog". If true, double print the logs also in stderr. + // Defaults to false. + $config['log_system']['syslog_stderr'] = false; + // Only relevant if "log_system" is set to `file`. Sets the file that vichan will log to. + // Defaults to '/var/log/vichan.log'. + $config['log_system']['file_path'] = '/var/log/vichan.log'; // Use `host` via shell_exec() to lookup hostnames, avoiding query timeouts. May not work on your system. // Requires safe_mode to be disabled. diff --git a/inc/driver/log-driver.php b/inc/driver/log-driver.php new file mode 100644 index 00000000..0026f009 --- /dev/null +++ b/inc/driver/log-driver.php @@ -0,0 +1,189 @@ +level = $level; + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + if (isset($_SERVER['REMOTE_ADDR'], $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'])) { + // CGI + syslog($level, "$message - client: {$_SERVER['REMOTE_ADDR']}, request: \"{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']}\""); + } else { + syslog($level, $message); + } + } + } + }; + } + + /** + * Log via the php function error_log. + */ + public static function error_log(string $name, int $level): Log { + return new class($name, $level) implements Log { + private string $name; + private int $level; + + public function __construct(string $name, int $level) { + $this->name = $name; + $this->level = $level; + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + $lv = LogDrivers::levelToString($level); + $line = "{$this->name} $lv: $message"; + error_log($line, 0, null, null); + } + } + }; + } + + /** + * Log to a file. + */ + public static function file(string $name, int $level, string $file_path): Log { + /* + * error_log is slow as hell in it's 3rd mode, so use fopen + file locking instead. + * https://grobmeier.solutions/performance-ofnonblocking-write-to-files-via-php-21082009.html + * + * Whatever file appending is atomic is contentious: + * - There are no POSIX guarantees: https://stackoverflow.com/a/7237901 + * - But linus suggested they are on linux, on some filesystems: https://web.archive.org/web/20151201111541/http://article.gmane.org/gmane.linux.kernel/43445 + * - But it doesn't seem to be always the case: https://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/ + * + * So we just use file locking to be sure. + */ + + $fd = fopen($file_path, 'a'); + if ($fd === false) { + throw new RuntimeException("Unable to open log file at $file_path"); + } + + $logger = new class($name, $level, $fd) implements Log { + private string $name; + private int $level; + private mixed $fd; + + public function __construct(string $name, int $level, mixed $fd) { + $this->name = $name; + $this->level = $level; + $this->fd = $fd; + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + $lv = LogDrivers::levelToString($level); + $line = "{$this->name} $lv: $message\n"; + flock($this->fd, LOCK_EX); + fwrite($this->fd, $line); + flock($this->fd, LOCK_UN); + } + } + + public function close() { + fclose($this->fd); + } + }; + + // Close the file on shutdown. + register_shutdown_function([$logger, 'close']); + + return $logger; + } + + /** + * Log to php's standard error file stream. + */ + public static function stderr(string $name, int $level): Log { + return new class($name, $level) implements Log { + private $name; + private $level; + + public function __construct(string $name, int $level) { + $this->name = $name; + $this->level = $level; + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + $lv = LogDrivers::levelToString($level); + fwrite(STDERR, "{$this->name} $lv: $message\n"); + } + } + }; + } + + /** + * No-op logging system. + */ + public static function none(): Log { + return new class() implements Log { + public function log($level, $message): void { + // No-op. + } + }; + } +} + +interface Log { + public const EMERG = LOG_EMERG; + public const ERROR = LOG_ERR; + public const WARNING = LOG_WARNING; + public const NOTICE = LOG_NOTICE; + public const INFO = LOG_INFO; + public const DEBUG = LOG_DEBUG; + + + /** + * Log a message if the level of relevancy is at least the minimum. + * + * @param int $level Message level. Use Log interface constants. + * @param string $message The message to log. + */ + public function log(int $level, string $message): void; +}