1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
|
<?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);
}
}
|