mirror of
https://github.com/pmmp/PocketMine-MP.git
synced 2025-06-10 21:45:35 +00:00
Log rotate (#4032)
`server.log` is not rotated by default and grows unmanageably large, to the point where it's so huge that it's not possible to read it with any standard text editor anymore. This PR implements automatic log rotation. - When the `server.log` reaches 32MB in size, it's moved to the `log_archive/` folder of the server's data folder. - The archive's file name will look something like this: `server.2024-03-15T15.26.24.0.log` - The file's name contains the date and time when the file was archived. This may be useful if you're trying to find logs from a particular time frame. This has several benefits: - Much more easily find logs from a particular time frame without scrolling through GBs of logs - Free up space without stopping the server - Archived log files in `log_archive/` can be safely deleted and/or modified while the server is running If you want to automatically compress or clean up the log files, I suggest an external cron job or disk watcher. Closes #4029.
This commit is contained in:
parent
e31fd122d9
commit
7148c7a222
@ -326,7 +326,8 @@ JIT_WARNING
|
||||
Terminal::init();
|
||||
}
|
||||
|
||||
$logger = new MainLogger(Path::join($dataPath, "server.log"), Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()));
|
||||
$logger = new MainLogger(Path::join($dataPath, "server.log"), Path::join($dataPath, "log_archive"), Terminal::hasFormattingCodes(), "Server", new \DateTimeZone(Timezone::get()));
|
||||
|
||||
\GlobalLogger::set($logger);
|
||||
|
||||
emit_performance_warnings($logger);
|
||||
|
@ -44,7 +44,7 @@ class MainLogger extends AttachableThreadSafeLogger implements \BufferedLogger{
|
||||
/**
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
public function __construct(string $logFile, bool $useFormattingCodes, string $mainThreadName, \DateTimeZone $timezone, bool $logDebug = false){
|
||||
public function __construct(string $logFile, string $logArchiveDir, bool $useFormattingCodes, string $mainThreadName, \DateTimeZone $timezone, bool $logDebug = false){
|
||||
parent::__construct();
|
||||
$this->logDebug = $logDebug;
|
||||
|
||||
@ -52,7 +52,7 @@ class MainLogger extends AttachableThreadSafeLogger implements \BufferedLogger{
|
||||
$this->mainThreadName = $mainThreadName;
|
||||
$this->timezone = $timezone->getName();
|
||||
|
||||
$this->logWriterThread = new MainLoggerThread($logFile);
|
||||
$this->logWriterThread = new MainLoggerThread($logFile, $logArchiveDir);
|
||||
$this->logWriterThread->start(NativeThread::INHERIT_NONE);
|
||||
}
|
||||
|
||||
|
@ -25,23 +25,42 @@ namespace pocketmine\utils;
|
||||
|
||||
use pmmp\thread\Thread;
|
||||
use pmmp\thread\ThreadSafeArray;
|
||||
use function clearstatcache;
|
||||
use function date;
|
||||
use function fclose;
|
||||
use function file_exists;
|
||||
use function fopen;
|
||||
use function fstat;
|
||||
use function fwrite;
|
||||
use function is_dir;
|
||||
use function is_file;
|
||||
use function is_resource;
|
||||
use function mkdir;
|
||||
use function pathinfo;
|
||||
use function rename;
|
||||
use function strlen;
|
||||
use function touch;
|
||||
use const PATHINFO_EXTENSION;
|
||||
use const PATHINFO_FILENAME;
|
||||
|
||||
final class MainLoggerThread extends Thread{
|
||||
|
||||
/** @phpstan-var ThreadSafeArray<int, string> */
|
||||
private ThreadSafeArray $buffer;
|
||||
private bool $syncFlush = false;
|
||||
private bool $shutdown = false;
|
||||
|
||||
public function __construct(
|
||||
private string $logFile
|
||||
private string $logFile,
|
||||
private string $archiveDir,
|
||||
private readonly int $maxFileSize = 32 * 1024 * 1024 //32 MB
|
||||
){
|
||||
$this->buffer = new ThreadSafeArray();
|
||||
touch($this->logFile);
|
||||
if(!@mkdir($this->archiveDir) && !is_dir($this->archiveDir)){
|
||||
throw new \RuntimeException("Unable to create archive directory: " . (
|
||||
is_file($this->archiveDir) ? "it already exists and is not a directory" : "permission denied"));
|
||||
}
|
||||
}
|
||||
|
||||
public function write(string $line) : void{
|
||||
@ -71,12 +90,64 @@ final class MainLoggerThread extends Thread{
|
||||
$this->join();
|
||||
}
|
||||
|
||||
/** @return resource */
|
||||
private function openLogFile(string $file, int &$size){
|
||||
$logResource = fopen($file, "ab");
|
||||
if(!is_resource($logResource)){
|
||||
throw new \RuntimeException("Couldn't open log file");
|
||||
}
|
||||
$stat = fstat($logResource);
|
||||
if($stat === false){
|
||||
throw new AssumptionFailedError("fstat() should not fail here");
|
||||
}
|
||||
$size = $stat['size'];
|
||||
return $logResource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $logResource
|
||||
* @return resource
|
||||
*/
|
||||
private function archiveLogFile($logResource, int &$size){
|
||||
fclose($logResource);
|
||||
|
||||
clearstatcache();
|
||||
|
||||
$i = 0;
|
||||
$date = date("Y-m-d\TH.i.s");
|
||||
$baseName = pathinfo($this->logFile, PATHINFO_FILENAME);
|
||||
$extension = pathinfo($this->logFile, PATHINFO_EXTENSION);
|
||||
do{
|
||||
//this shouldn't be necessary, but in case the user messes with the system time for some reason ...
|
||||
$fileName = "$baseName.$date.$i.$extension";
|
||||
$out = $this->archiveDir . "/" . $fileName;
|
||||
$i++;
|
||||
}while(file_exists($out));
|
||||
|
||||
//the user may have externally deleted the whole directory - make sure it exists before we do anything
|
||||
@mkdir($this->archiveDir);
|
||||
rename($this->logFile, $out);
|
||||
|
||||
$logResource = $this->openLogFile($this->logFile, $size);
|
||||
fwrite($logResource, "--- Starting new log file - old log file archived as $fileName ---\n");
|
||||
|
||||
return $logResource;
|
||||
}
|
||||
|
||||
private function logFileReadyToArchive(int $size) : bool{
|
||||
return $size >= $this->maxFileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param resource $logResource
|
||||
*/
|
||||
private function writeLogStream($logResource) : void{
|
||||
private function writeLogStream(&$logResource, int &$size) : void{
|
||||
while(($chunk = $this->buffer->shift()) !== null){
|
||||
fwrite($logResource, $chunk);
|
||||
$size += strlen($chunk);
|
||||
if($this->logFileReadyToArchive($size)){
|
||||
$logResource = $this->archiveLogFile($logResource, $size);
|
||||
}
|
||||
}
|
||||
|
||||
$this->synchronized(function() : void{
|
||||
@ -88,13 +159,14 @@ final class MainLoggerThread extends Thread{
|
||||
}
|
||||
|
||||
public function run() : void{
|
||||
$logResource = fopen($this->logFile, "ab");
|
||||
if(!is_resource($logResource)){
|
||||
throw new \RuntimeException("Couldn't open log file");
|
||||
$size = 0;
|
||||
$logResource = $this->openLogFile($this->logFile, $size);
|
||||
if($this->logFileReadyToArchive($size)){
|
||||
$logResource = $this->archiveLogFile($logResource, $size);
|
||||
}
|
||||
|
||||
while(!$this->shutdown){
|
||||
$this->writeLogStream($logResource);
|
||||
$this->writeLogStream($logResource, $size);
|
||||
$this->synchronized(function() : void{
|
||||
if(!$this->shutdown && !$this->syncFlush){
|
||||
$this->wait();
|
||||
@ -102,7 +174,7 @@ final class MainLoggerThread extends Thread{
|
||||
});
|
||||
}
|
||||
|
||||
$this->writeLogStream($logResource);
|
||||
$this->writeLogStream($logResource, $size);
|
||||
|
||||
fclose($logResource);
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ class AsyncPoolTest extends TestCase{
|
||||
|
||||
public function setUp() : void{
|
||||
@define('pocketmine\\COMPOSER_AUTOLOADER_PATH', dirname(__DIR__, 3) . '/vendor/autoload.php');
|
||||
$this->mainLogger = new MainLogger(tempnam(sys_get_temp_dir(), "pmlog"), false, "Main", new \DateTimeZone('UTC'));
|
||||
$this->mainLogger = new MainLogger(tempnam(sys_get_temp_dir(), "pmlog"), sys_get_temp_dir(), false, "Main", new \DateTimeZone('UTC'));
|
||||
$this->pool = new AsyncPool(2, 1024, new ThreadSafeClassLoader(), $this->mainLogger, new SleeperHandler());
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user