dokuwiki/inc/Logger.php

236 lines
6.9 KiB
PHP

<?php
namespace dokuwiki;
use dokuwiki\Extension\Event;
/**
* Log messages to a daily log file
*/
class Logger
{
public const LOG_ERROR = 'error';
public const LOG_DEPRECATED = 'deprecated';
public const LOG_DEBUG = 'debug';
/** @var Logger[] */
protected static $instances;
/** @var string what kind of log is this */
protected $facility;
protected $isLogging = true;
/**
* Logger constructor.
*
* @param string $facility The type of log
*/
protected function __construct($facility)
{
global $conf;
$this->facility = $facility;
// Should logging be disabled for this facility?
$dontlog = explode(',', $conf['dontlog']);
$dontlog = array_map('trim', $dontlog);
if (in_array($facility, $dontlog)) $this->isLogging = false;
}
/**
* Return a Logger instance for the given facility
*
* @param string $facility The type of log
* @return Logger
*/
public static function getInstance($facility = self::LOG_ERROR)
{
if (empty(self::$instances[$facility])) {
self::$instances[$facility] = new Logger($facility);
}
return self::$instances[$facility];
}
/**
* Convenience method to directly log to the error log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @return bool has a log been written?
*/
public static function error($message, $details = null, $file = '', $line = 0)
{
return self::getInstance(self::LOG_ERROR)->log(
$message,
$details,
$file,
$line
);
}
/**
* Convenience method to directly log to the debug log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @return bool has a log been written?
*/
public static function debug($message, $details = null, $file = '', $line = 0)
{
return self::getInstance(self::LOG_DEBUG)->log(
$message,
$details,
$file,
$line
);
}
/**
* Convenience method to directly log to the deprecation log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @return bool has a log been written?
*/
public static function deprecated($message, $details = null, $file = '', $line = 0)
{
return self::getInstance(self::LOG_DEPRECATED)->log(
$message,
$details,
$file,
$line
);
}
/**
* Log a message to the facility log
*
* @param string $message The log message
* @param mixed $details Any details that should be added to the log entry
* @param string $file A source filename if this is related to a source position
* @param int $line A line number for the above file
* @triggers LOGGER_DATA_FORMAT can be used to change the logged data or intercept it
* @return bool has a log been written?
*/
public function log($message, $details = null, $file = '', $line = 0)
{
global $EVENT_HANDLER;
if (!$this->isLogging) return false;
$datetime = time();
$data = [
'facility' => $this->facility,
'datetime' => $datetime,
'message' => $message,
'details' => $details,
'file' => $file,
'line' => $line,
'loglines' => [],
'logfile' => $this->getLogfile($datetime),
];
if ($EVENT_HANDLER !== null) {
$event = new Event('LOGGER_DATA_FORMAT', $data);
if ($event->advise_before()) {
$data['loglines'] = $this->formatLogLines($data);
}
$event->advise_after();
} else {
// The event system is not yet available, to ensure the log isn't lost even on
// fatal errors, the default action is executed
$data['loglines'] = $this->formatLogLines($data);
}
// only log when any data available
if (count($data['loglines'])) {
return $this->writeLogLines($data['loglines'], $data['logfile']);
} else {
return false;
}
}
/**
* Is this logging instace actually logging?
*
* @return bool
*/
public function isLogging()
{
return $this->isLogging;
}
/**
* Formats the given data as loglines
*
* @param array $data Event data from LOGGER_DATA_FORMAT
* @return string[] the lines to log
*/
protected function formatLogLines($data)
{
extract($data);
// details are logged indented
if ($details) {
if (!is_string($details)) {
$details = json_encode($details, JSON_PRETTY_PRINT);
}
$details = explode("\n", $details);
$loglines = array_map(static fn($line) => ' ' . $line, $details);
} elseif ($details) {
$loglines = [$details];
} else {
$loglines = [];
}
// datetime, fileline, message
$logline = gmdate('Y-m-d H:i:s', $datetime) . "\t";
if ($file) {
$logline .= $file;
if ($line) $logline .= "($line)";
}
$logline .= "\t" . $message;
array_unshift($loglines, $logline);
return $loglines;
}
/**
* Construct the log file for the given day
*
* @param false|string|int $date Date to access, false for today
* @return string
*/
public function getLogfile($date = false)
{
global $conf;
if ($date !== null && !is_numeric($date)) {
$date = strtotime($date);
}
if (!$date) $date = time();
return $conf['logdir'] . '/' . $this->facility . '/' . date('Y-m-d', $date) . '.log';
}
/**
* Write the given lines to today's facility log
*
* @param string[] $lines the raw lines to append to the log
* @param string $logfile where to write to
* @return bool true if the log was written
*/
protected function writeLogLines($lines, $logfile)
{
if (defined('DOKU_UNITTEST')) {
fwrite(STDERR, "\n[" . $this->facility . '] ' . implode("\n", $lines) . "\n");
}
return io_saveFile($logfile, implode("\n", $lines) . "\n", true);
}
}