first commit
This commit is contained in:
57
plugins/system/logrotation/logrotation.xml
Normal file
57
plugins/system/logrotation/logrotation.xml
Normal file
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="plugin" group="system" method="upgrade">
|
||||
<name>plg_system_logrotation</name>
|
||||
<author>Joomla! Project</author>
|
||||
<creationDate>2018-05</creationDate>
|
||||
<copyright>(C) 2018 Open Source Matters, Inc.</copyright>
|
||||
<license>GNU General Public License version 2 or later; see LICENSE.txt</license>
|
||||
<authorEmail>admin@joomla.org</authorEmail>
|
||||
<authorUrl>www.joomla.org</authorUrl>
|
||||
<version>3.9.0</version>
|
||||
<description>PLG_SYSTEM_LOGROTATION_XML_DESCRIPTION</description>
|
||||
<namespace path="src">Joomla\Plugin\System\LogRotation</namespace>
|
||||
<files>
|
||||
<folder plugin="logrotation">services</folder>
|
||||
<folder>src</folder>
|
||||
</files>
|
||||
<languages folder="language">
|
||||
<language tag="en-GB">language/en-GB/plg_system_logrotation.ini</language>
|
||||
<language tag="en-GB">language/en-GB/plg_system_logrotation.sys.ini</language>
|
||||
</languages>
|
||||
<config>
|
||||
<fields name="params">
|
||||
<fieldset name="basic">
|
||||
<field
|
||||
name="cachetimeout"
|
||||
type="integer"
|
||||
label="PLG_SYSTEM_LOGROTATION_CACHETIMEOUT_LABEL"
|
||||
first="0"
|
||||
last="120"
|
||||
step="1"
|
||||
default="30"
|
||||
filter="int"
|
||||
validate="number"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="logstokeep"
|
||||
type="integer"
|
||||
label="PLG_SYSTEM_LOGROTATION_LOGSTOKEEP_LABEL"
|
||||
first="1"
|
||||
last="10"
|
||||
step="1"
|
||||
default="1"
|
||||
filter="int"
|
||||
validate="number"
|
||||
/>
|
||||
|
||||
<field
|
||||
name="lastrun"
|
||||
type="hidden"
|
||||
default="0"
|
||||
filter="integer"
|
||||
/>
|
||||
</fieldset>
|
||||
</fields>
|
||||
</config>
|
||||
</extension>
|
||||
49
plugins/system/logrotation/services/provider.php
Normal file
49
plugins/system/logrotation/services/provider.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.logrotation
|
||||
*
|
||||
* @copyright (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Extension\PluginInterface;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Plugin\PluginHelper;
|
||||
use Joomla\Database\DatabaseInterface;
|
||||
use Joomla\DI\Container;
|
||||
use Joomla\DI\ServiceProviderInterface;
|
||||
use Joomla\Event\DispatcherInterface;
|
||||
use Joomla\Plugin\System\LogRotation\Extension\LogRotation;
|
||||
|
||||
return new class () implements ServiceProviderInterface {
|
||||
/**
|
||||
* Registers the service provider with a DI container.
|
||||
*
|
||||
* @param Container $container The DI container.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 4.4.0
|
||||
*/
|
||||
public function register(Container $container): void
|
||||
{
|
||||
$container->set(
|
||||
PluginInterface::class,
|
||||
function (Container $container) {
|
||||
$dispatcher = $container->get(DispatcherInterface::class);
|
||||
$plugin = new LogRotation(
|
||||
$dispatcher,
|
||||
(array) PluginHelper::getPlugin('system', 'logrotation')
|
||||
);
|
||||
$plugin->setApplication(Factory::getApplication());
|
||||
$plugin->setDatabase($container->get(DatabaseInterface::class));
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
238
plugins/system/logrotation/src/Extension/LogRotation.php
Normal file
238
plugins/system/logrotation/src/Extension/LogRotation.php
Normal file
@@ -0,0 +1,238 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Plugin
|
||||
* @subpackage System.logrotation
|
||||
*
|
||||
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*/
|
||||
|
||||
namespace Joomla\Plugin\System\LogRotation\Extension;
|
||||
|
||||
use Joomla\CMS\Cache\Cache;
|
||||
use Joomla\CMS\Filesystem\Folder;
|
||||
use Joomla\CMS\Plugin\CMSPlugin;
|
||||
use Joomla\Database\DatabaseAwareTrait;
|
||||
use Joomla\Filesystem\File;
|
||||
use Joomla\Filesystem\Path;
|
||||
|
||||
// phpcs:disable PSR1.Files.SideEffects
|
||||
\defined('_JEXEC') or die;
|
||||
// phpcs:enable PSR1.Files.SideEffects
|
||||
|
||||
/**
|
||||
* Joomla! Log Rotation plugin
|
||||
*
|
||||
* Rotate the log files created by Joomla core
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
final class LogRotation extends CMSPlugin
|
||||
{
|
||||
use DatabaseAwareTrait;
|
||||
|
||||
/**
|
||||
* Load the language file on instantiation.
|
||||
*
|
||||
* @var boolean
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
protected $autoloadLanguage = true;
|
||||
|
||||
/**
|
||||
* The log check and rotation code is triggered after the page has fully rendered.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
public function onAfterRender()
|
||||
{
|
||||
// Get the timeout as configured in plugin parameters
|
||||
|
||||
/** @var \Joomla\Registry\Registry $params */
|
||||
$cache_timeout = (int) $this->params->get('cachetimeout', 30);
|
||||
$cache_timeout = 24 * 3600 * $cache_timeout;
|
||||
$logsToKeep = (int) $this->params->get('logstokeep', 1);
|
||||
|
||||
// Do we need to run? Compare the last run timestamp stored in the plugin's options with the current
|
||||
// timestamp. If the difference is greater than the cache timeout we shall not execute again.
|
||||
$now = time();
|
||||
$last = (int) $this->params->get('lastrun', 0);
|
||||
|
||||
if ((abs($now - $last) < $cache_timeout)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update last run status
|
||||
$this->params->set('lastrun', $now);
|
||||
|
||||
$paramsJson = $this->params->toString('JSON');
|
||||
$db = $this->getDatabase();
|
||||
$query = $db->getQuery(true)
|
||||
->update($db->quoteName('#__extensions'))
|
||||
->set($db->quoteName('params') . ' = :params')
|
||||
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
|
||||
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
|
||||
->where($db->quoteName('element') . ' = ' . $db->quote('logrotation'))
|
||||
->bind(':params', $paramsJson);
|
||||
|
||||
try {
|
||||
// Lock the tables to prevent multiple plugin executions causing a race condition
|
||||
$db->lockTable('#__extensions');
|
||||
} catch (\Exception $e) {
|
||||
// If we can't lock the tables it's too risky to continue execution
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Update the plugin parameters
|
||||
$result = $db->setQuery($query)->execute();
|
||||
|
||||
$this->clearCacheGroups(['com_plugins'], [0, 1]);
|
||||
} catch (\Exception $exc) {
|
||||
// If we failed to execute
|
||||
$db->unlockTables();
|
||||
$result = false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Unlock the tables after writing
|
||||
$db->unlockTables();
|
||||
} catch (\Exception $e) {
|
||||
// If we can't lock the tables assume we have somehow failed
|
||||
$result = false;
|
||||
}
|
||||
|
||||
// Stop on failure
|
||||
if (!$result) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the log path
|
||||
$logPath = Path::clean($this->getApplication()->get('log_path'));
|
||||
|
||||
// Invalid path, stop processing further
|
||||
if (!is_dir($logPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$logFiles = $this->getLogFiles($logPath);
|
||||
|
||||
// Sort log files by version number in reserve order
|
||||
krsort($logFiles, SORT_NUMERIC);
|
||||
|
||||
foreach ($logFiles as $version => $files) {
|
||||
if ($version >= $logsToKeep) {
|
||||
// Delete files which has version greater than or equals $logsToKeep
|
||||
foreach ($files as $file) {
|
||||
File::delete($logPath . '/' . $file);
|
||||
}
|
||||
} else {
|
||||
// For files which has version smaller than $logsToKeep, rotate (increase version number)
|
||||
foreach ($files as $file) {
|
||||
$this->rotate($logPath, $file, $version);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get log files from log folder
|
||||
*
|
||||
* @param string $path The folder to get log files
|
||||
*
|
||||
* @return array The log files in the given path grouped by version number (not rotated files has number 0)
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
private function getLogFiles($path)
|
||||
{
|
||||
$logFiles = [];
|
||||
$files = Folder::files($path, '\.php$');
|
||||
|
||||
foreach ($files as $file) {
|
||||
$parts = explode('.', $file);
|
||||
|
||||
/*
|
||||
* Rotated log file has this filename format [VERSION].[FILENAME].php. So if $parts has at least 3 elements
|
||||
* and the first element is a number, we know that it's a rotated file and can get it's current version
|
||||
*/
|
||||
if (count($parts) >= 3 && is_numeric($parts[0])) {
|
||||
$version = (int) $parts[0];
|
||||
} else {
|
||||
$version = 0;
|
||||
}
|
||||
|
||||
if (!isset($logFiles[$version])) {
|
||||
$logFiles[$version] = [];
|
||||
}
|
||||
|
||||
$logFiles[$version][] = $file;
|
||||
}
|
||||
|
||||
return $logFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to rotate (increase version) of a log file
|
||||
*
|
||||
* @param string $path Path to file to rotate
|
||||
* @param string $filename Name of file to rotate
|
||||
* @param int $currentVersion The current version number
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
private function rotate($path, $filename, $currentVersion)
|
||||
{
|
||||
if ($currentVersion === 0) {
|
||||
$rotatedFile = $path . '/1.' . $filename;
|
||||
} else {
|
||||
/*
|
||||
* Rotated log file has this filename format [VERSION].[FILENAME].php. To rotate it, we just need to explode
|
||||
* the filename into an array, increase value of first element (keep version) and implode it back to get the
|
||||
* rotated file name
|
||||
*/
|
||||
$parts = explode('.', $filename);
|
||||
$parts[0] = $currentVersion + 1;
|
||||
|
||||
$rotatedFile = $path . '/' . implode('.', $parts);
|
||||
}
|
||||
|
||||
File::move($path . '/' . $filename, $rotatedFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears cache groups. We use it to clear the plugins cache after we update the last run timestamp.
|
||||
*
|
||||
* @param array $clearGroups The cache groups to clean
|
||||
* @param array $cacheClients The cache clients (site, admin) to clean
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @since 3.9.0
|
||||
*/
|
||||
private function clearCacheGroups(array $clearGroups, array $cacheClients = [0, 1])
|
||||
{
|
||||
foreach ($clearGroups as $group) {
|
||||
foreach ($cacheClients as $client_id) {
|
||||
try {
|
||||
$options = [
|
||||
'defaultgroup' => $group,
|
||||
'cachebase' => $client_id ? JPATH_ADMINISTRATOR . '/cache' :
|
||||
$this->getApplication()->get('cache_path', JPATH_SITE . '/cache'),
|
||||
];
|
||||
|
||||
$cache = Cache::getInstance('callback', $options);
|
||||
$cache->clean();
|
||||
} catch (\Exception $e) {
|
||||
// Ignore it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user