Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@
"geoip2/geoip2": "^2.13",
"jenssegers/agent": "^2.6",
"php-di/php-di": "^6.4",
"twig/twig": "^3.0"
"twig/twig": "^3.0",
"monolog/monolog": "^3.9"
},
"require-dev": {
"phpstan/phpstan": "1.6.9",
Expand Down
9 changes: 1 addition & 8 deletions core/classes/Core/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,21 +159,14 @@ public static function setMultiple(array $values): void
* Will log a warning if a legacy path (using `/` is used).
*
* @param string $path Path to parse.
* @return string|array Path split into sections or plain string if no section seperator was found.
* @return string|array Path split into sections or plain string if no section separator was found.
*/
private static function parsePath(string $path)
{
if (str_contains($path, '.')) {
return explode('.', $path);
}

// TODO: Remove for 2.1.0
if (str_contains($path, '/')) {
ErrorHandler::logWarning("Legacy config path: {$path}. Please use periods to seperate paths.");

return explode('/', $path);
}

return $path;
}

Expand Down
13 changes: 13 additions & 0 deletions core/classes/Core/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ abstract class Module
private string $_nameless_version;
private array $_load_before;
private array $_load_after;
protected Logger $_logger;

public function __construct(
Module $module,
Expand All @@ -44,6 +45,8 @@ public function __construct(

$this->_load_before = $load_before;
$this->_load_after = $load_after;

$this->_logger = new Logger($name);
}

/**
Expand Down Expand Up @@ -229,4 +232,14 @@ public static function getNameFromId(int $id): ?string

return null;
}

/**
* Get logger instance for module.
*
* @return Logger
*/
public function getLogger(): Logger
{
return $this->_logger;
}
}
10 changes: 8 additions & 2 deletions core/classes/Core/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,10 @@ public function addGroup(int $group_id, int $expire = 0): bool

$group = Group::find($group_id);
if (!$group) {
ErrorHandler::logWarning('Could not add invalid group ' . $group_id . ' to user ' . $this->data()->id);
Logger::getDefaultLogger()->warning(
'Could not add invalid group to user',
['group_id' => $group_id, 'user_id' => $this->data()->id]
);

return false;
}
Expand Down Expand Up @@ -751,7 +754,10 @@ public function setGroup(int $group_id, int $expire = 0)
{
$group = Group::find($group_id);
if (!$group) {
ErrorHandler::logWarning('Could not set invalid group ' . $group_id . ' to user ' . $this->data()->id);
Logger::getDefaultLogger()->warning(
'Could not set invalid group for user',
['group_id' => $group_id, 'user_id' => $this->data()->id]
);

return false;
}
Expand Down
114 changes: 114 additions & 0 deletions core/classes/Logging/Logger.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<?php

/**
* NamelessMC logger class.
*
* @package NamelessMC\Core
* @author Samerton
* @version 2.3.0
* @license MIT
*/

use Monolog\Handler\FilterHandler;
use Monolog\Handler\HandlerInterface;
use Monolog\Handler\StreamHandler;
use Monolog\Level;
use Monolog\Logger as MonologLogger;

class Logger
{
private string $_name;
private MonologLogger $_monolog;

private static ?Logger $_defaultLogger = null;

public function __construct(string $name) {
$this->_name = $name;

$this->_monolog = new MonologLogger($this->_name);

// All logger instances must log to file
// It is possible for other modules to register custom handlers, however the file handler will always be present
$baseDir = implode(DIRECTORY_SEPARATOR, [ROOT_PATH, 'cache', 'logs', $this->_name]);

// Debug log
if (defined('DEBUGGING') && DEBUGGING) {
$debugHandler = new MaxFileSizeLogHandler("$baseDir/debug.log", Level::Debug);
$debugFilter = new FilterHandler(
$debugHandler,
Level::Debug,
Level::Debug);

$this->registerLogHandler($debugFilter);
}

$infoHandler = new StreamHandler("$baseDir/info.log", Level::Info);
$infoFilter = new FilterHandler(
$infoHandler,
Level::Info,
Level::Warning);

$errorHandler = new StreamHandler("$baseDir/error.log", Level::Error);
$errorFilter = new FilterHandler(
$errorHandler,
Level::Error,
Level::Critical);

$this->registerLogHandler($infoFilter);
$this->registerLogHandler($errorFilter);

// Debug Bar integration
if (defined('PHPDEBUGBAR')) {
DebugBarHelper::getInstance()->addMonologCollector($this->_monolog);
}
}

public function registerLogHandler(HandlerInterface $handler): void
{
$this->_monolog->pushHandler($handler);
}

public function debug(string $message, array $meta = []): void
{
$this->_monolog->debug($message, $meta);
}

public function info(string $message, array $meta = []): void
{
$this->_monolog->info($message, $meta);
}

public function notice(string $message, array $meta = []): void
{
$this->_monolog->notice($message, $meta);
}

public function warning(string $message, array $meta = []): void
{
$this->_monolog->warning($message, $meta);
}

public function error(string $message, array $meta = []): void
{
$this->_monolog->error($message, $meta);
}

public function critical(string $message, array $meta = []): void
{
$this->_monolog->critical($message, $meta);
}

public static function getDefaultLogger(): ?Logger
{
return self::$_defaultLogger;
}

public static function setDefaultLogger(Logger $logger): void
{
if (isset(self::$_defaultLogger)) {
throw new Exception('Cannot change the default logger once it is set');
}

self::$_defaultLogger = $logger;
}
}
143 changes: 143 additions & 0 deletions core/classes/Logging/MaxFileSizeLogHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

/**
* NamelessMC max file size log handler class for Monolog.
*
* @package NamelessMC\Core
* @author Samerton
* @version 2.3.0
* @license MIT
*/

use Monolog\Level;
use Monolog\Utils;
use Monolog\LogRecord;
use Monolog\Handler\StreamHandler;

class MaxFileSizeLogHandler extends StreamHandler
{
protected string $fileName;
protected int $maxFiles;
protected int $maxFileSize;
protected bool|null $mustRotate = null;
protected string $filenameFormat;
protected string $dateFormat;

/**
* @param string $fileName Base path to file
* @param int|string|Level $level Level of logging this handler should handle - default debug
* @param int $maxFiles The number of files to keep, 0 is no limit - default 3
* @param bool $bubble Whether to "bubble" the log onto the next handler - default true
* @param int $maxFileSize Maximum file size before new file is created in bytes - default 500kb
*/
public function __construct(
string $fileName,
int|string|Level $level = Level::Debug,
int $maxFiles = 3,
bool $bubble = true,
int $maxFileSize = 500000,
) {
$this->fileName = Utils::canonicalizePath($fileName);
$this->maxFiles = $maxFiles;
$this->maxFileSize = $maxFileSize;

parent::__construct($this->getNewFileName(), $level, $bubble);
}

public function close(): void
{
parent::close();

if ($this->mustRotate === true) {
$this->rotate();
}
}

protected function write(LogRecord $record): void
{
// If the log is new then we need to rotate such that the file will exist
if ($this->mustRotate === null) {
$this->mustRotate = $this->url === null || !file_exists($this->url);
}

// Rotate now if the file size is too big
if (file_exists($this->url) && filesize($this->url) > $this->maxFileSize) {
$this->mustRotate = true;

$this->close();
}

parent::write($record);

if ($this->mustRotate === true) {
$this->close();
}
}

/**
* Rotates the files.
*/
protected function rotate(): void
{
$this->url = $this->getNewFileName();

$this->mustRotate = false;

if ($this->maxFiles === 0) {
return;
}

$logFiles = glob($this->getGlobPattern());

if ($logFiles === false) {
return;
}

// If we have reached the maximum number of allowed files, delete older files
if ($this->maxFiles < count($logFiles)) {
usort($logFiles, function ($a, $b) {
if (filemtime($a) < filemtime($b)) {
return 1;
}

return -1;
});

foreach (array_slice($logFiles, $this->maxFiles) as $file) {
if (is_writable($file)) {
unlink($file);
}
}
}

// Rename the log file to -old-<time> format so we can open up a new file
if (file_exists($this->url) && filesize($this->url) > $this->maxFileSize) {
$time = date('His');
$oldFile = $this->getNewFileName($time);
rename($this->url, $oldFile);
}
}

private function getNewFileName(string $time = ''): string
{
$fileInfo = pathinfo($this->fileName);
$dirName = $fileInfo['dirname'];
$fileExtension = $fileInfo['extension'];
$fileName = $fileInfo['filename'];

$date = date('Ymd');
$timeSuffix = $time ? "-old-$time" : '';

return implode(DIRECTORY_SEPARATOR, [$dirName, "$fileName-$date$timeSuffix.$fileExtension"]);
}

private function getGlobPattern(): string
{
$fileInfo = pathinfo($this->fileName);
$dirName = $fileInfo['dirname'];
$fileExtension = $fileInfo['extension'];
$fileName = $fileInfo['filename'];

return implode(DIRECTORY_SEPARATOR, [$dirName, "$fileName-*.$fileExtension"]);
}
}
13 changes: 13 additions & 0 deletions core/classes/Misc/DebugBarHelper.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use DebugBar\Bridge\MonologCollector;
use DebugBar\Bridge\NamespacedTwigProfileCollector;
use DebugBar\DataCollector\ConfigCollector;
use DebugBar\DataCollector\DataCollector;
Expand All @@ -10,6 +11,7 @@
use DebugBar\DataCollector\TimeDataCollector;
use DebugBar\DebugBar;
use Junker\DebugBar\Bridge\SmartyCollector;
use Monolog\Logger as MonologLogger;
use Twig\Environment;
use Twig\Extension\ProfilerExtension;
use Twig\Profiler\Profile;
Expand Down Expand Up @@ -64,6 +66,17 @@ public function addCollector(DataCollector $collector): void
$this->_debugBar->addCollector($collector);
}

public function addMonologCollector(MonologLogger $monolog): void
{
$collector = new MonologCollector($monolog);

if ($this->getDebugBar()->hasCollector($collector->getName())) {
return;
}

$this->getDebugBar()->addCollector($collector);
}

public function addSmartyCollector(Smarty $smarty): void
{
$smartyCollector = new SmartyCollector($smarty);
Expand Down
Loading
Loading