Files
newznab-tmux-NNTmux/newznab/Logger.php
T

760 lines
17 KiB
PHP

<?php
namespace newznab;
/**
* Show log message to CLI/Web and log it to a file.
* Turn these on in automated.config.php
*
* @example usage:
*
* (in method, LOG_INFO would be the severity of your error, see below)
* $this->Logger->start("MyClassName", "MyMethodName", "My debug message.", LOG_INFO);
*/
class Logger
{
// You can use these constants when using the start method.
const LOG_FATAL = 1; // Fatal error, the program exited.
const LOG_ERROR = 2; // Recoverable error.
const LOG_WARNING = 3; // Warnings.
const LOG_NOTICE = 4; // Notices.
const LOG_INFO = 5; // Info message, not important.
const LOG_SQL = 6; // Full SQL query when it fails.
/**
* Name of class we are currently logging.
* @var string
* @access private
*/
private $class;
/**
* Name of method we are currently logging.
* @var string
* @access private
*/
private $method;
/**
* The log message.
* @var string
* @access private
*/
private $logMessage = '';
/**
* Severity level.
* @var string
* @access private
*/
private $severity = '';
/**
* Class instance of colorCLI
* @var object
* @access private
*/
private $colorCLI;
/**
* Should we echo to CLI or web?
* @var bool
* @access private
*/
private $outputCLI = true;
/**
* Cache of the date.
* @var string
* @access private
*/
private $dateCache = '';
/**
* Cache of unix time.
* @var int
* @access private
*/
private $timeCache;
/**
* Is this the windows O/S?
* @var bool
* @access private
*/
private $isWindows;
/**
* Unix time instance was created.
* @var int
* @access private
*/
private $timeStart;
/**
* @var resource|null Resource for log file.
* @access private
*/
private $resource = null;
/**
* How many old logs can we have max in the logs folder.
* (per log type, ex.: debug can have x logs, not_yEnc can have x logs, etc)
* @var int
* @access private
*/
private $maxLogs;
/**
* Max log size in MegaBytes.
* @var int
* @access private
*/
private $maxLogSize;
/**
* Full path to the log file.
* @var string
* @access private
*/
private $logPath;
/**
* Current name of the log file.
* @var string
* @access private
*/
private $currentLogName;
/**
* Current folder to store log files.
* @var string
* @access private
*/
private $currentLogFolder;
/**
* Show memory usage in log/cli out?
* @var bool
* @access private
*/
private $showMemoryUsage;
/**
* Show CPU load in log/cli out?
* @var bool
* @access private
*/
private $showCPULoad;
/**
* Show running time of script on log/cli out?
* @var bool
* @access private
*/
private $showRunningTime;
/**
* Show resource usages on log/cli out?.
* @var bool
* @access private
*/
private $showResourceUsage;
/**
* Constructor.
*
* @param array $options (Optional) Class instances.
* (Optional) Folder to store log files in.
* (Optional) Filename of log, must be alphanumeric (a-z 0-9) and contain no file extensions.
*
* @access public
* @throws LoggerException
*/
public function __construct(array $options = [])
{
if (!NN_LOGGING && !NN_DEBUG) {
return;
}
$defaults = [
'ColorCLI' => null,
'LogFolder' => '',
'LogFileName' => ''
];
$options += $defaults;
$this->colorCLI = ($options['ColorCLI'] instanceof ColorCLI ? $options['ColorCLI'] : new ColorCLI());
$this->getSettings();
$this->currentLogFolder = (
!empty($options['LogFolder'])
? $options['LogFolder']
: $this->currentLogFolder
);
$this->currentLogName = (
!empty($options['LogFileName'])
? $options['LogFileName']
: $this->currentLogName
) . '.log';
$this->setLogFile();
$this->outputCLI = (strtolower(PHP_SAPI) === 'cli');
$this->isWindows = (strtolower(substr(PHP_OS, 0, 3)) === 'win');
$this->timeStart = time();
}
/**
* Close the log file resource.
*
* @access public
*/
public function __destruct()
{
$this->closeFile();
}
/**
* Public method for logging and/or echoing log messages.
*
* @param string $class The name of the class.
* @param string $method The method this is coming from.
* @param string $message The message to log/echo.
* @param int $severity How severe is this message?
* 1 Fatal - The program had to stop (exit).
* 2 Error - Something went very wrong but we recovered.
* 3 Warning - Not an error, but something we can probably fix.
* 4 Notice - User errors - the user did not enable any groups for example.
* 5 Info - General info, like we logged in to usenet for example.
* 6 Query - Failed SQL queries. (the full query).
*
* @access public
*/
public function log($class, $method, $message, $severity)
{
// Check if echo debugging or logging is on.
if (!NN_DEBUG && !NN_LOGGING) {
return;
}
$this->severity = $severity;
// Check the severity of the message, if disabled return, if enabled create part of the log message.
if (!$this->checkSeverity()) {
return;
}
$this->class = $class;
$this->method = $method;
$this->logMessage = $message;
$this->formLogMessage();
$this->echoMessage();
$this->logMessage();
}
/**
* Return current/peak memory usage or difference between current/peak memory usage and previous usage.
*
* @param int $oldUsage Output from a previous memory_get_usage().
* @param bool $realUsage Use (true)system memory usage or (false)emalloc() usage, use emalloc() for debugging.
* @param bool $peak Get peak memory usage.
*
* @return string
*
* @access public
*/
public function showMemUsage($oldUsage = 0, $realUsage = false, $peak = false)
{
$currentUsage = ($peak ? memory_get_peak_usage($realUsage) : memory_get_usage($realUsage));
$actualUsage = ($oldUsage > 0 ? $currentUsage - $oldUsage : $currentUsage);
$units = [
'B ',
'KB',
'MB',
'GB',
'TB',
'PB'
];
return
str_pad(
number_format(
round(
$actualUsage
/
pow(
1024,
($i =
floor(
log(
$actualUsage,
1024
)
)
)
), 2
)
), 4, '~~~', STR_PAD_LEFT
) .
$units[(int)$i];
}
/**
* Get resource usage string.
*
* @return bool|string
*
* @access public
*/
public function getResUsage()
{
if (!$this->isWindows) {
$usage = getrusage();
return
'USR: ' . $this->formatTimeString($usage['ru_utime.tv_sec']) .
' SYS: ' . $this->formatTimeString($usage['ru_stime.tv_sec']) .
' FAULTS: ' . $usage['ru_majflt'] .
' SWAPS: ' . $usage['ru_nswap'];
}
return false;
}
/**
* Get system load.
*
* @return string|bool
*
* @access public
*/
public function getSystemLoad()
{
if (!$this->isWindows) {
$string = '';
// Fix for single digits (2) or single float (2.1).
foreach(sys_getloadavg() as $load) {
$strLen = strlen($load);
if ($strLen === 1) {
$string .= $load . '.00,';
} elseif ($strLen === 3) {
$string .= str_pad($load, 4, '0', STR_PAD_RIGHT) . ',';
} else {
$string .= $load . ',';
}
}
return substr($string, 0, -1);
}
return false;
}
/**
* Changes the location of the log file.
*
* @param string $folder Folder where the log should be stored.
* @param string $fileName Name of the file (must be alphanumeric and contain no file extensions).
* @access public
*/
public function changeLogFileLocation($folder, $fileName)
{
$this->currentLogFolder = $folder;
$this->currentLogName = $fileName;
$this->setLogFile();
}
/**
* Get the log folder, log name and full path to the default log.
*
* @return array
* @access public
* @static
*/
static public function getDefaultLogPaths()
{
$defaultLogName = (defined('NN_LOGGING_LOG_NAME') ? NN_LOGGING_LOG_NAME : 'newznab');
$defaultLogName = (ctype_alnum($defaultLogName) ? $defaultLogName : 'newznab');
$defaultLogFolder = (defined('NN_LOGGING_LOG_FOLDER') && is_dir(NN_LOGGING_LOG_FOLDER) ? NN_LOGGING_LOG_FOLDER : NN_LOGS);
$defaultLogFolder = (in_array(substr($defaultLogFolder, -1), ['/', '\\']) ? $defaultLogFolder : $defaultLogFolder . DS);
return [
'LogFolder' => $defaultLogFolder,
'LogName' => $defaultLogName,
'LogPath' => $defaultLogFolder . $defaultLogName . '.log'
];
}
/**
* Sets the path and name for the log file.
*
* @throws LoggerException
* @access private
*/
private function setLogFile()
{
// Only run this if NN_LOGGING is on.
if (!NN_LOGGING) {
return;
}
$this->closeFile();
$this->logPath = $this->currentLogFolder . $this->currentLogName;
if (!is_dir($this->currentLogFolder)) {
$this->createFolder();
}
$this->initiateLog();
// Check if we need to rotate the log if it exceeds max size..
$this->rotateLog();
$this->openFile();
}
/**
* Get/set all settings.
* @access private
*/
private function getSettings()
{
$this->maxLogs = (defined('NN_LOGGING_MAX_LOGS') ? NN_LOGGING_MAX_LOGS : 20);
$this->maxLogs = ($this->maxLogs < 1 ? 20 : $this->maxLogs);
$this->maxLogSize = (defined('NN_LOGGING_MAX_SIZE') ? NN_LOGGING_MAX_SIZE : 30);
$this->maxLogSize = ($this->maxLogSize < 1 ? 30 : $this->maxLogSize);
$this->showMemoryUsage = (bool)(defined('NN_LOGGING_LOG_MEMORY_USAGE') ? NN_LOGGING_LOG_MEMORY_USAGE : true);
$this->showCPULoad = (bool)(defined('NN_LOGGING_LOG_CPU_LOAD') ? NN_LOGGING_LOG_CPU_LOAD : true);
$this->showRunningTime = (bool)(defined('NN_LOGGING_LOG_RUNNING_TIME') ? NN_LOGGING_LOG_RUNNING_TIME : true);
$this->showResourceUsage = (bool)(defined('NN_LOGGING_LOG_RESOURCE_USAGE') ? NN_LOGGING_LOG_RESOURCE_USAGE : false);
$paths = self::getDefaultLogPaths();
$this->currentLogName = $paths['LogName'];
$this->currentLogFolder = $paths['LogFolder'];
}
/**
* Close the file resource.
*
* @access private
*/
private function closeFile()
{
if (is_resource($this->resource)) {
@fclose($this->resource);
}
$this->resource = null;
}
/**
* Opens the log file.
*
* @throws LoggerException
*/
private function openFile()
{
if (!is_resource($this->resource)) {
$this->resource = @fopen($this->logPath, 'ab');
if (!$this->resource) {
throw new LoggerException('Unable to open log file ' . $this->logPath);
}
}
}
/**
* Log message to file.
*
* @access private
*/
private function logMessage()
{
// Check if debug logging is on.
if (!NN_LOGGING) {
return;
}
clearstatcache(true, $this->logPath);
// Check if we should rotate the logs.
$this->rotateLog();
// If another process deleted the file, try to re-open it.
if (!is_file($this->logPath)) {
$this->setLogFile();
}
@fwrite($this->resource, $this->logMessage . PHP_EOL);
}
/**
* Get the date and cache it.
*
* @return string
*
* @access private
*/
private function getDate()
{
// Cache the date, update it every 1 minute, since date() is extremely slow and time() is extremely fast.
if ($this->dateCache === '' || $this->timeCache < (time() - 60)) {
$this->dateCache = date('d/M/Y H:i');
$this->timeCache = time();
}
return $this->dateCache;
}
/**
* Check if the log folder exists, if not create it.
*
* @access private
* @throws LoggerException
*/
private function createFolder()
{
// Check if the log folder exists, create it if not.
if (!is_dir($this->currentLogFolder)) {
$old = umask(0777);
if (!mkdir($this->currentLogFolder)) {
throw new LoggerException('Unable to create log file folder ' . $this->currentLogFolder);
}
chmod($this->currentLogFolder, 0777);
umask($old);
}
}
/**
* Initiate a log file.
*
* @access private
* @throws LoggerException
*/
private function initiateLog()
{
if (!is_file($this->logPath)) {
if (!file_put_contents(
$this->logPath,
'[' . $this->getDate() . '] [INIT] [Initiating new log file.]' . PHP_EOL)
) {
throw new LoggerException('Unable to create new log file ' . $this->logPath);
}
chmod($this->logPath, 0664);
}
}
/**
* Rotate log file if it exceeds a certain size.
*
* @access private
*/
private function rotateLog()
{
// Check if we need to rotate the log if it exceeds max size..
$logSize = filesize($this->logPath);
if ($logSize === false) {
return;
} else if ($logSize >= ($this->maxLogSize * 1024 * 1024)) {
$this->closeFile();
$this->compressLog();
$this->initiateLog();
$this->pruneLogs();
$this->openFile();
}
}
/**
* Compress the old log using GZip.
*
* @access private
*/
private function compressLog()
{
$handle = @fopen($this->logPath, 'rb');
$zipHandle = @gzopen(str_replace('.log', '', $this->logPath) . '.' . time() . '.gz', 'w6');
if (!$handle || !$zipHandle) {
return;
}
while (!feof($handle)) {
$data = @fread($handle, 32768);
@gzwrite($zipHandle, $data);
}
@fclose($handle);
@gzclose($zipHandle);
// Delete the original uncompressed log file.
unlink($this->logPath);
}
/**
* Delete old logs (if we have more than $this->maxLogs).
*
* @access private
*/
private function pruneLogs()
{
// Get all the logs with the name.
$logs = glob(str_replace('.log', '', $this->logPath) . '.[0-9]*.gz');
// If there are no old logs or less than maxLogs return false.
if (!$logs || (count($logs) < $this->maxLogs)) {
return;
}
// Sort the logs alphabetically, so the oldest ones are at the top, the new at the bottom.
asort($logs);
// Remove all new logs from array (all elements under the last 51 elements of the array).
array_splice($logs, -$this->maxLogs+1);
// Delete all the logs left in the array.
array_map('unlink', $logs);
}
/**
* Echo log message to CLI or web.
*
* @access private
*/
private function echoMessage()
{
if (!NN_DEBUG) {
return;
}
// Check if this is CLI or web.
if ($this->outputCLI) {
echo $this->colorCLI->debug($this->logMessage);
} else {
echo '<pre>' . $this->logMessage . '</pre><br />';
}
}
/**
* Creates the message object for the log message.
*
* @access private
*/
private function formLogMessage()
{
$pid = getmypid();
$this->logMessage =
// Current date/time ; [02/Mar/2014 14:50 EST
'[' . $this->getDate() . '] ' .
// The severity.
$this->severity .
// Average system load.
(($this->showCPULoad && !$this->isWindows) ? ' [' . $this->getSystemLoad() . ']' : '') .
// Script running time.
($this->showRunningTime ? ' [' . $this->formatTimeString(time() - $this->timeStart) . ']' : '') .
// PHP memory usage.
($this->showMemoryUsage ? ' [MEM:' . $this->showMemUsage(0, true) . ']' : '') .
// Resource usage (user time, system time, major page faults, memory swaps).
(($this->showResourceUsage && !$this->isWindows) ? ' [' . $this->getResUsage() . ']' : '') .
// Running process id.
($pid ? ' [PID:' . $pid . ']' : '') .
// The class/function.
' [' . $this->class . '.' . $this->method . ']' .
' [' .
// Now reformat the log message, first stripping leading spaces.
trim(
// Removing 2 or more spaces.
preg_replace('/\s{2,}/', ' ',
// Removing new lines and carriage returns.
str_replace(["\n", '\n', "\r", '\r'], ' ', $this->logMessage)
)
) .
']';
}
/**
* Convert seconds to hours minutes seconds string.
*
* @param int $seconds
*
* @return string
*
* @access private
*/
private function formatTimeString($seconds)
{
$time = '';
if ($seconds > 3600) {
$time .= str_pad(round((($seconds % 86400) / 3600)), 2, '0', STR_PAD_LEFT) . 'H:';
} else {
$time .= '00H:';
}
if ($seconds > 60) {
$time .= str_pad(round((($seconds % 3600) / 60)), 2 , '0', STR_PAD_LEFT) . 'M:';
} else {
$time .= '00M:';
}
$time .= str_pad($seconds % 60, 2 , '0', STR_PAD_LEFT) . 'S';
return $time;
}
/**
* Check if the user wants to echo or log this message, form part of the log message at the same time.
*
* @return bool
*
* @access private
*/
private function checkSeverity()
{
switch ($this->severity) {
case self::LOG_FATAL:
if (NN_LOGFATAL) {
$this->severity = '[FATAL] ';
return true;
}
return false;
case self::LOG_ERROR:
if (NN_LOGERROR) {
$this->severity = '[ERROR] ';
return true;
}
return false;
case self::LOG_WARNING:
if (NN_LOGWARNING) {
$this->severity = '[WARN] ';
return true;
}
return false;
case self::LOG_NOTICE:
if (NN_LOGNOTICE) {
$this->severity = '[NOTICE]';
return true;
}
return false;
case self::LOG_INFO:
if (NN_LOGINFO) {
$this->severity = '[INFO] ';
return true;
}
return false;
case self::LOG_SQL:
if (NN_LOGQUERIES) {
$this->severity = '[SQL] ';
return true;
}
return false;
default:
return false;
}
}
}