Merge pull request #4038 from dokuwiki/create-pull-request/patch
[dokuwiki.git] / inc / Logger.php
blob632f37d046d452350556a11013143b7aaa9796d4
1 <?php
3 namespace dokuwiki;
5 use dokuwiki\Extension\Event;
7 /**
8 * Log messages to a daily log file
9 */
10 class Logger
12 const LOG_ERROR = 'error';
13 const LOG_DEPRECATED = 'deprecated';
14 const LOG_DEBUG = 'debug';
16 /** @var Logger[] */
17 static protected $instances;
19 /** @var string what kind of log is this */
20 protected $facility;
22 protected $isLogging = true;
24 /**
25 * Logger constructor.
27 * @param string $facility The type of log
29 protected function __construct($facility)
31 global $conf;
32 $this->facility = $facility;
34 // Should logging be disabled for this facility?
35 $dontlog = explode(',', $conf['dontlog']);
36 $dontlog = array_map('trim', $dontlog);
37 if (in_array($facility, $dontlog)) $this->isLogging = false;
40 /**
41 * Return a Logger instance for the given facility
43 * @param string $facility The type of log
44 * @return Logger
46 static public function getInstance($facility = self::LOG_ERROR)
48 if (empty(self::$instances[$facility])) {
49 self::$instances[$facility] = new Logger($facility);
51 return self::$instances[$facility];
54 /**
55 * Convenience method to directly log to the error log
57 * @param string $message The log message
58 * @param mixed $details Any details that should be added to the log entry
59 * @param string $file A source filename if this is related to a source position
60 * @param int $line A line number for the above file
61 * @return bool has a log been written?
63 static public function error($message, $details = null, $file = '', $line = 0)
65 return self::getInstance(self::LOG_ERROR)->log(
66 $message, $details, $file, $line
70 /**
71 * Convenience method to directly log to the debug log
73 * @param string $message The log message
74 * @param mixed $details Any details that should be added to the log entry
75 * @param string $file A source filename if this is related to a source position
76 * @param int $line A line number for the above file
77 * @return bool has a log been written?
79 static public function debug($message, $details = null, $file = '', $line = 0)
81 return self::getInstance(self::LOG_DEBUG)->log(
82 $message, $details, $file, $line
86 /**
87 * Convenience method to directly log to the deprecation log
89 * @param string $message The log message
90 * @param mixed $details Any details that should be added to the log entry
91 * @param string $file A source filename if this is related to a source position
92 * @param int $line A line number for the above file
93 * @return bool has a log been written?
95 static public function deprecated($message, $details = null, $file = '', $line = 0)
97 return self::getInstance(self::LOG_DEPRECATED)->log(
98 $message, $details, $file, $line
103 * Log a message to the facility log
105 * @param string $message The log message
106 * @param mixed $details Any details that should be added to the log entry
107 * @param string $file A source filename if this is related to a source position
108 * @param int $line A line number for the above file
109 * @triggers LOGGER_DATA_FORMAT can be used to change the logged data or intercept it
110 * @return bool has a log been written?
112 public function log($message, $details = null, $file = '', $line = 0)
114 global $EVENT_HANDLER;
115 if (!$this->isLogging) return false;
117 $datetime = time();
118 $data = [
119 'facility' => $this->facility,
120 'datetime' => $datetime,
121 'message' => $message,
122 'details' => $details,
123 'file' => $file,
124 'line' => $line,
125 'loglines' => [],
126 'logfile' => $this->getLogfile($datetime),
129 if ($EVENT_HANDLER !== null) {
130 $event = new Event('LOGGER_DATA_FORMAT', $data);
131 if ($event->advise_before()) {
132 $data['loglines'] = $this->formatLogLines($data);
134 $event->advise_after();
135 } else {
136 // The event system is not yet available, to ensure the log isn't lost even on
137 // fatal errors, the default action is executed
138 $data['loglines'] = $this->formatLogLines($data);
141 // only log when any data available
142 if (count($data['loglines'])) {
143 return $this->writeLogLines($data['loglines'], $data['logfile']);
144 } else {
145 return false;
150 * Is this logging instace actually logging?
152 * @return bool
154 public function isLogging()
156 return $this->isLogging;
160 * Formats the given data as loglines
162 * @param array $data Event data from LOGGER_DATA_FORMAT
163 * @return string[] the lines to log
165 protected function formatLogLines($data)
167 extract($data);
169 // details are logged indented
170 if ($details) {
171 if (!is_string($details)) {
172 $details = json_encode($details, JSON_PRETTY_PRINT);
174 $details = explode("\n", $details);
175 $loglines = array_map(function ($line) {
176 return ' ' . $line;
177 }, $details);
178 } elseif ($details) {
179 $loglines = [$details];
180 } else {
181 $loglines = [];
184 // datetime, fileline, message
185 $logline = gmdate('Y-m-d H:i:s', $datetime) . "\t";
186 if ($file) {
187 $logline .= $file;
188 if ($line) $logline .= "($line)";
190 $logline .= "\t" . $message;
191 array_unshift($loglines, $logline);
193 return $loglines;
197 * Construct the log file for the given day
199 * @param false|string|int $date Date to access, false for today
200 * @return string
202 public function getLogfile($date = false)
204 global $conf;
206 if ($date !== null && !is_numeric($date)) {
207 $date = strtotime($date);
209 if (!$date) $date = time();
211 return $conf['logdir'] . '/' . $this->facility . '/' . date('Y-m-d', $date) . '.log';
215 * Write the given lines to today's facility log
217 * @param string[] $lines the raw lines to append to the log
218 * @param string $logfile where to write to
219 * @return bool true if the log was written
221 protected function writeLogLines($lines, $logfile)
223 if (defined('DOKU_UNITTEST')) {
224 fwrite(STDERR, "\n[" . $this->facility . '] ' . join("\n", $lines) . "\n");
226 return io_saveFile($logfile, join("\n", $lines) . "\n", true);