10ecde6ceSAndreas Gohr<?php 20ecde6ceSAndreas Gohr 30ecde6ceSAndreas Gohrnamespace dokuwiki; 40ecde6ceSAndreas Gohr 5555e8b00SAndreas Gohruse dokuwiki\Extension\Event; 6555e8b00SAndreas Gohr 7555e8b00SAndreas Gohr/** 8555e8b00SAndreas Gohr * Log messages to a daily log file 9555e8b00SAndreas Gohr */ 100ecde6ceSAndreas Gohrclass Logger 110ecde6ceSAndreas Gohr{ 1274981a4eSAndreas Gohr public const LOG_ERROR = 'error'; 1374981a4eSAndreas Gohr public const LOG_DEPRECATED = 'deprecated'; 1474981a4eSAndreas Gohr public const LOG_DEBUG = 'debug'; 150ecde6ceSAndreas Gohr 160ecde6ceSAndreas Gohr /** @var Logger[] */ 17fe15e2c0SAndreas Gohr protected static $instances; 180ecde6ceSAndreas Gohr 190ecde6ceSAndreas Gohr /** @var string what kind of log is this */ 200ecde6ceSAndreas Gohr protected $facility; 210ecde6ceSAndreas Gohr 22cad4fbf6SAndreas Gohr protected $isLogging = true; 23cad4fbf6SAndreas Gohr 240ecde6ceSAndreas Gohr /** 250ecde6ceSAndreas Gohr * Logger constructor. 260ecde6ceSAndreas Gohr * 270ecde6ceSAndreas Gohr * @param string $facility The type of log 280ecde6ceSAndreas Gohr */ 290ecde6ceSAndreas Gohr protected function __construct($facility) 300ecde6ceSAndreas Gohr { 31cad4fbf6SAndreas Gohr global $conf; 320ecde6ceSAndreas Gohr $this->facility = $facility; 33cad4fbf6SAndreas Gohr 34cad4fbf6SAndreas Gohr // Should logging be disabled for this facility? 35cad4fbf6SAndreas Gohr $dontlog = explode(',', $conf['dontlog']); 36cad4fbf6SAndreas Gohr $dontlog = array_map('trim', $dontlog); 37cad4fbf6SAndreas Gohr if (in_array($facility, $dontlog)) $this->isLogging = false; 380ecde6ceSAndreas Gohr } 390ecde6ceSAndreas Gohr 400ecde6ceSAndreas Gohr /** 410ecde6ceSAndreas Gohr * Return a Logger instance for the given facility 420ecde6ceSAndreas Gohr * 430ecde6ceSAndreas Gohr * @param string $facility The type of log 440ecde6ceSAndreas Gohr * @return Logger 450ecde6ceSAndreas Gohr */ 46fe15e2c0SAndreas Gohr public static function getInstance($facility = self::LOG_ERROR) 470ecde6ceSAndreas Gohr { 48a8f9f939SDamien Regad if (empty(self::$instances[$facility])) { 490ecde6ceSAndreas Gohr self::$instances[$facility] = new Logger($facility); 500ecde6ceSAndreas Gohr } 510ecde6ceSAndreas Gohr return self::$instances[$facility]; 520ecde6ceSAndreas Gohr } 530ecde6ceSAndreas Gohr 540ecde6ceSAndreas Gohr /** 55c2050393SAndreas Gohr * Convenience method to directly log to the error log 56c2050393SAndreas Gohr * 57c2050393SAndreas Gohr * @param string $message The log message 58c2050393SAndreas Gohr * @param mixed $details Any details that should be added to the log entry 59c2050393SAndreas Gohr * @param string $file A source filename if this is related to a source position 60c2050393SAndreas Gohr * @param int $line A line number for the above file 61cad4fbf6SAndreas Gohr * @return bool has a log been written? 62c2050393SAndreas Gohr */ 63fe15e2c0SAndreas Gohr public static function error($message, $details = null, $file = '', $line = 0) 64c2050393SAndreas Gohr { 65c2050393SAndreas Gohr return self::getInstance(self::LOG_ERROR)->log( 66dccd6b2bSAndreas Gohr $message, 67dccd6b2bSAndreas Gohr $details, 68dccd6b2bSAndreas Gohr $file, 69dccd6b2bSAndreas Gohr $line 70c2050393SAndreas Gohr ); 71c2050393SAndreas Gohr } 72c2050393SAndreas Gohr 73c2050393SAndreas Gohr /** 74c2050393SAndreas Gohr * Convenience method to directly log to the debug log 75c2050393SAndreas Gohr * 76c2050393SAndreas Gohr * @param string $message The log message 77c2050393SAndreas Gohr * @param mixed $details Any details that should be added to the log entry 78c2050393SAndreas Gohr * @param string $file A source filename if this is related to a source position 79c2050393SAndreas Gohr * @param int $line A line number for the above file 80cad4fbf6SAndreas Gohr * @return bool has a log been written? 81c2050393SAndreas Gohr */ 82fe15e2c0SAndreas Gohr public static function debug($message, $details = null, $file = '', $line = 0) 83c2050393SAndreas Gohr { 84c2050393SAndreas Gohr return self::getInstance(self::LOG_DEBUG)->log( 85dccd6b2bSAndreas Gohr $message, 86dccd6b2bSAndreas Gohr $details, 87dccd6b2bSAndreas Gohr $file, 88dccd6b2bSAndreas Gohr $line 89c2050393SAndreas Gohr ); 90c2050393SAndreas Gohr } 91c2050393SAndreas Gohr 92c2050393SAndreas Gohr /** 93c2050393SAndreas Gohr * Convenience method to directly log to the deprecation log 94c2050393SAndreas Gohr * 95c2050393SAndreas Gohr * @param string $message The log message 96c2050393SAndreas Gohr * @param mixed $details Any details that should be added to the log entry 97c2050393SAndreas Gohr * @param string $file A source filename if this is related to a source position 98c2050393SAndreas Gohr * @param int $line A line number for the above file 99cad4fbf6SAndreas Gohr * @return bool has a log been written? 100c2050393SAndreas Gohr */ 101fe15e2c0SAndreas Gohr public static function deprecated($message, $details = null, $file = '', $line = 0) 102c2050393SAndreas Gohr { 103c2050393SAndreas Gohr return self::getInstance(self::LOG_DEPRECATED)->log( 104dccd6b2bSAndreas Gohr $message, 105dccd6b2bSAndreas Gohr $details, 106dccd6b2bSAndreas Gohr $file, 107dccd6b2bSAndreas Gohr $line 108c2050393SAndreas Gohr ); 109c2050393SAndreas Gohr } 110c2050393SAndreas Gohr 111c2050393SAndreas Gohr /** 1120ecde6ceSAndreas Gohr * Log a message to the facility log 1130ecde6ceSAndreas Gohr * 1140ecde6ceSAndreas Gohr * @param string $message The log message 1150ecde6ceSAndreas Gohr * @param mixed $details Any details that should be added to the log entry 1160ecde6ceSAndreas Gohr * @param string $file A source filename if this is related to a source position 1170ecde6ceSAndreas Gohr * @param int $line A line number for the above file 118555e8b00SAndreas Gohr * @triggers LOGGER_DATA_FORMAT can be used to change the logged data or intercept it 119cad4fbf6SAndreas Gohr * @return bool has a log been written? 1200ecde6ceSAndreas Gohr */ 1210ecde6ceSAndreas Gohr public function log($message, $details = null, $file = '', $line = 0) 1220ecde6ceSAndreas Gohr { 1234b647920SAndreas Gohr global $EVENT_HANDLER; 124cad4fbf6SAndreas Gohr if (!$this->isLogging) return false; 125cad4fbf6SAndreas Gohr 126555e8b00SAndreas Gohr $datetime = time(); 127555e8b00SAndreas Gohr $data = [ 128555e8b00SAndreas Gohr 'facility' => $this->facility, 129555e8b00SAndreas Gohr 'datetime' => $datetime, 130555e8b00SAndreas Gohr 'message' => $message, 131555e8b00SAndreas Gohr 'details' => $details, 132555e8b00SAndreas Gohr 'file' => $file, 133555e8b00SAndreas Gohr 'line' => $line, 134555e8b00SAndreas Gohr 'loglines' => [], 135555e8b00SAndreas Gohr 'logfile' => $this->getLogfile($datetime), 136555e8b00SAndreas Gohr ]; 137555e8b00SAndreas Gohr 1384b647920SAndreas Gohr if ($EVENT_HANDLER !== null) { 1394b647920SAndreas Gohr $event = new Event('LOGGER_DATA_FORMAT', $data); 140555e8b00SAndreas Gohr if ($event->advise_before()) { 141555e8b00SAndreas Gohr $data['loglines'] = $this->formatLogLines($data); 142555e8b00SAndreas Gohr } 143555e8b00SAndreas Gohr $event->advise_after(); 1444b647920SAndreas Gohr } else { 1454b647920SAndreas Gohr // The event system is not yet available, to ensure the log isn't lost even on 1464b647920SAndreas Gohr // fatal errors, the default action is executed 1474b647920SAndreas Gohr $data['loglines'] = $this->formatLogLines($data); 1484b647920SAndreas Gohr } 149555e8b00SAndreas Gohr 150555e8b00SAndreas Gohr // only log when any data available 151555e8b00SAndreas Gohr if (count($data['loglines'])) { 152555e8b00SAndreas Gohr return $this->writeLogLines($data['loglines'], $data['logfile']); 153555e8b00SAndreas Gohr } else { 154555e8b00SAndreas Gohr return false; 155555e8b00SAndreas Gohr } 156555e8b00SAndreas Gohr } 157555e8b00SAndreas Gohr 158555e8b00SAndreas Gohr /** 15925edeecaSAndreas Gohr * Is this logging instace actually logging? 16025edeecaSAndreas Gohr * 16125edeecaSAndreas Gohr * @return bool 16225edeecaSAndreas Gohr */ 16325edeecaSAndreas Gohr public function isLogging() 16425edeecaSAndreas Gohr { 16525edeecaSAndreas Gohr return $this->isLogging; 16625edeecaSAndreas Gohr } 16725edeecaSAndreas Gohr 16825edeecaSAndreas Gohr /** 169555e8b00SAndreas Gohr * Formats the given data as loglines 170555e8b00SAndreas Gohr * 171555e8b00SAndreas Gohr * @param array $data Event data from LOGGER_DATA_FORMAT 172555e8b00SAndreas Gohr * @return string[] the lines to log 173555e8b00SAndreas Gohr */ 174555e8b00SAndreas Gohr protected function formatLogLines($data) 175555e8b00SAndreas Gohr { 176555e8b00SAndreas Gohr extract($data); 177555e8b00SAndreas Gohr 1780ecde6ceSAndreas Gohr // details are logged indented 17970cc2cbfSAndreas Gohr if ($details) { 18070cc2cbfSAndreas Gohr if (!is_string($details)) { 1810ecde6ceSAndreas Gohr $details = json_encode($details, JSON_PRETTY_PRINT); 18270cc2cbfSAndreas Gohr } 1830ecde6ceSAndreas Gohr $details = explode("\n", $details); 18424870174SAndreas Gohr $loglines = array_map(static fn($line) => ' ' . $line, $details); 1850ecde6ceSAndreas Gohr } elseif ($details) { 1860ecde6ceSAndreas Gohr $loglines = [$details]; 1870ecde6ceSAndreas Gohr } else { 1880ecde6ceSAndreas Gohr $loglines = []; 1890ecde6ceSAndreas Gohr } 1900ecde6ceSAndreas Gohr 19170cc2cbfSAndreas Gohr // datetime, fileline, message 192*d4059ee7Shauk92 $logline = date('Y-m-d H:i:s', $datetime) . "\t"; 1930ecde6ceSAndreas Gohr if ($file) { 19470cc2cbfSAndreas Gohr $logline .= $file; 1950ecde6ceSAndreas Gohr if ($line) $logline .= "($line)"; 1960ecde6ceSAndreas Gohr } 19770cc2cbfSAndreas Gohr $logline .= "\t" . $message; 1980ecde6ceSAndreas Gohr array_unshift($loglines, $logline); 199555e8b00SAndreas Gohr 200555e8b00SAndreas Gohr return $loglines; 2010ecde6ceSAndreas Gohr } 2020ecde6ceSAndreas Gohr 2030ecde6ceSAndreas Gohr /** 20470cc2cbfSAndreas Gohr * Construct the log file for the given day 20570cc2cbfSAndreas Gohr * 20670cc2cbfSAndreas Gohr * @param false|string|int $date Date to access, false for today 20770cc2cbfSAndreas Gohr * @return string 20870cc2cbfSAndreas Gohr */ 20970cc2cbfSAndreas Gohr public function getLogfile($date = false) 21070cc2cbfSAndreas Gohr { 21170cc2cbfSAndreas Gohr global $conf; 21270cc2cbfSAndreas Gohr 213baa301e2SAndreas Gohr if ($date !== null && !is_numeric($date)) { 214baa301e2SAndreas Gohr $date = strtotime($date); 215baa301e2SAndreas Gohr } 21670cc2cbfSAndreas Gohr if (!$date) $date = time(); 21770cc2cbfSAndreas Gohr 21870cc2cbfSAndreas Gohr return $conf['logdir'] . '/' . $this->facility . '/' . date('Y-m-d', $date) . '.log'; 21970cc2cbfSAndreas Gohr } 22070cc2cbfSAndreas Gohr 22170cc2cbfSAndreas Gohr /** 2220ecde6ceSAndreas Gohr * Write the given lines to today's facility log 2230ecde6ceSAndreas Gohr * 2240ecde6ceSAndreas Gohr * @param string[] $lines the raw lines to append to the log 225555e8b00SAndreas Gohr * @param string $logfile where to write to 2260ecde6ceSAndreas Gohr * @return bool true if the log was written 2270ecde6ceSAndreas Gohr */ 228555e8b00SAndreas Gohr protected function writeLogLines($lines, $logfile) 2290ecde6ceSAndreas Gohr { 2306c6732d6SAndreas Gohr if (defined('DOKU_UNITTEST')) { 23124870174SAndreas Gohr fwrite(STDERR, "\n[" . $this->facility . '] ' . implode("\n", $lines) . "\n"); 2326c6732d6SAndreas Gohr } 23324870174SAndreas Gohr return io_saveFile($logfile, implode("\n", $lines) . "\n", true); 2340ecde6ceSAndreas Gohr } 2350ecde6ceSAndreas Gohr} 236