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{ 12*74981a4eSAndreas Gohr public const LOG_ERROR = 'error'; 13*74981a4eSAndreas Gohr public const LOG_DEPRECATED = 'deprecated'; 14*74981a4eSAndreas Gohr public const LOG_DEBUG = 'debug'; 150ecde6ceSAndreas Gohr 160ecde6ceSAndreas Gohr /** @var Logger[] */ 170ecde6ceSAndreas Gohr static protected $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 */ 460ecde6ceSAndreas Gohr static public 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 */ 63c2050393SAndreas Gohr static public function error($message, $details = null, $file = '', $line = 0) 64c2050393SAndreas Gohr { 65c2050393SAndreas Gohr return self::getInstance(self::LOG_ERROR)->log( 66c2050393SAndreas Gohr $message, $details, $file, $line 67c2050393SAndreas Gohr ); 68c2050393SAndreas Gohr } 69c2050393SAndreas Gohr 70c2050393SAndreas Gohr /** 71c2050393SAndreas Gohr * Convenience method to directly log to the debug log 72c2050393SAndreas Gohr * 73c2050393SAndreas Gohr * @param string $message The log message 74c2050393SAndreas Gohr * @param mixed $details Any details that should be added to the log entry 75c2050393SAndreas Gohr * @param string $file A source filename if this is related to a source position 76c2050393SAndreas Gohr * @param int $line A line number for the above file 77cad4fbf6SAndreas Gohr * @return bool has a log been written? 78c2050393SAndreas Gohr */ 79c2050393SAndreas Gohr static public function debug($message, $details = null, $file = '', $line = 0) 80c2050393SAndreas Gohr { 81c2050393SAndreas Gohr return self::getInstance(self::LOG_DEBUG)->log( 82c2050393SAndreas Gohr $message, $details, $file, $line 83c2050393SAndreas Gohr ); 84c2050393SAndreas Gohr } 85c2050393SAndreas Gohr 86c2050393SAndreas Gohr /** 87c2050393SAndreas Gohr * Convenience method to directly log to the deprecation log 88c2050393SAndreas Gohr * 89c2050393SAndreas Gohr * @param string $message The log message 90c2050393SAndreas Gohr * @param mixed $details Any details that should be added to the log entry 91c2050393SAndreas Gohr * @param string $file A source filename if this is related to a source position 92c2050393SAndreas Gohr * @param int $line A line number for the above file 93cad4fbf6SAndreas Gohr * @return bool has a log been written? 94c2050393SAndreas Gohr */ 95c2050393SAndreas Gohr static public function deprecated($message, $details = null, $file = '', $line = 0) 96c2050393SAndreas Gohr { 97c2050393SAndreas Gohr return self::getInstance(self::LOG_DEPRECATED)->log( 98c2050393SAndreas Gohr $message, $details, $file, $line 99c2050393SAndreas Gohr ); 100c2050393SAndreas Gohr } 101c2050393SAndreas Gohr 102c2050393SAndreas Gohr /** 1030ecde6ceSAndreas Gohr * Log a message to the facility log 1040ecde6ceSAndreas Gohr * 1050ecde6ceSAndreas Gohr * @param string $message The log message 1060ecde6ceSAndreas Gohr * @param mixed $details Any details that should be added to the log entry 1070ecde6ceSAndreas Gohr * @param string $file A source filename if this is related to a source position 1080ecde6ceSAndreas Gohr * @param int $line A line number for the above file 109555e8b00SAndreas Gohr * @triggers LOGGER_DATA_FORMAT can be used to change the logged data or intercept it 110cad4fbf6SAndreas Gohr * @return bool has a log been written? 1110ecde6ceSAndreas Gohr */ 1120ecde6ceSAndreas Gohr public function log($message, $details = null, $file = '', $line = 0) 1130ecde6ceSAndreas Gohr { 1144b647920SAndreas Gohr global $EVENT_HANDLER; 115cad4fbf6SAndreas Gohr if (!$this->isLogging) return false; 116cad4fbf6SAndreas Gohr 117555e8b00SAndreas Gohr $datetime = time(); 118555e8b00SAndreas Gohr $data = [ 119555e8b00SAndreas Gohr 'facility' => $this->facility, 120555e8b00SAndreas Gohr 'datetime' => $datetime, 121555e8b00SAndreas Gohr 'message' => $message, 122555e8b00SAndreas Gohr 'details' => $details, 123555e8b00SAndreas Gohr 'file' => $file, 124555e8b00SAndreas Gohr 'line' => $line, 125555e8b00SAndreas Gohr 'loglines' => [], 126555e8b00SAndreas Gohr 'logfile' => $this->getLogfile($datetime), 127555e8b00SAndreas Gohr ]; 128555e8b00SAndreas Gohr 1294b647920SAndreas Gohr if ($EVENT_HANDLER !== null) { 1304b647920SAndreas Gohr $event = new Event('LOGGER_DATA_FORMAT', $data); 131555e8b00SAndreas Gohr if ($event->advise_before()) { 132555e8b00SAndreas Gohr $data['loglines'] = $this->formatLogLines($data); 133555e8b00SAndreas Gohr } 134555e8b00SAndreas Gohr $event->advise_after(); 1354b647920SAndreas Gohr } else { 1364b647920SAndreas Gohr // The event system is not yet available, to ensure the log isn't lost even on 1374b647920SAndreas Gohr // fatal errors, the default action is executed 1384b647920SAndreas Gohr $data['loglines'] = $this->formatLogLines($data); 1394b647920SAndreas Gohr } 140555e8b00SAndreas Gohr 141555e8b00SAndreas Gohr // only log when any data available 142555e8b00SAndreas Gohr if (count($data['loglines'])) { 143555e8b00SAndreas Gohr return $this->writeLogLines($data['loglines'], $data['logfile']); 144555e8b00SAndreas Gohr } else { 145555e8b00SAndreas Gohr return false; 146555e8b00SAndreas Gohr } 147555e8b00SAndreas Gohr } 148555e8b00SAndreas Gohr 149555e8b00SAndreas Gohr /** 15025edeecaSAndreas Gohr * Is this logging instace actually logging? 15125edeecaSAndreas Gohr * 15225edeecaSAndreas Gohr * @return bool 15325edeecaSAndreas Gohr */ 15425edeecaSAndreas Gohr public function isLogging() 15525edeecaSAndreas Gohr { 15625edeecaSAndreas Gohr return $this->isLogging; 15725edeecaSAndreas Gohr } 15825edeecaSAndreas Gohr 15925edeecaSAndreas Gohr /** 160555e8b00SAndreas Gohr * Formats the given data as loglines 161555e8b00SAndreas Gohr * 162555e8b00SAndreas Gohr * @param array $data Event data from LOGGER_DATA_FORMAT 163555e8b00SAndreas Gohr * @return string[] the lines to log 164555e8b00SAndreas Gohr */ 165555e8b00SAndreas Gohr protected function formatLogLines($data) 166555e8b00SAndreas Gohr { 167555e8b00SAndreas Gohr extract($data); 168555e8b00SAndreas Gohr 1690ecde6ceSAndreas Gohr // details are logged indented 17070cc2cbfSAndreas Gohr if ($details) { 17170cc2cbfSAndreas Gohr if (!is_string($details)) { 1720ecde6ceSAndreas Gohr $details = json_encode($details, JSON_PRETTY_PRINT); 17370cc2cbfSAndreas Gohr } 1740ecde6ceSAndreas Gohr $details = explode("\n", $details); 17524870174SAndreas Gohr $loglines = array_map(static fn($line) => ' ' . $line, $details); 1760ecde6ceSAndreas Gohr } elseif ($details) { 1770ecde6ceSAndreas Gohr $loglines = [$details]; 1780ecde6ceSAndreas Gohr } else { 1790ecde6ceSAndreas Gohr $loglines = []; 1800ecde6ceSAndreas Gohr } 1810ecde6ceSAndreas Gohr 18270cc2cbfSAndreas Gohr // datetime, fileline, message 183555e8b00SAndreas Gohr $logline = gmdate('Y-m-d H:i:s', $datetime) . "\t"; 1840ecde6ceSAndreas Gohr if ($file) { 18570cc2cbfSAndreas Gohr $logline .= $file; 1860ecde6ceSAndreas Gohr if ($line) $logline .= "($line)"; 1870ecde6ceSAndreas Gohr } 18870cc2cbfSAndreas Gohr $logline .= "\t" . $message; 1890ecde6ceSAndreas Gohr array_unshift($loglines, $logline); 190555e8b00SAndreas Gohr 191555e8b00SAndreas Gohr return $loglines; 1920ecde6ceSAndreas Gohr } 1930ecde6ceSAndreas Gohr 1940ecde6ceSAndreas Gohr /** 19570cc2cbfSAndreas Gohr * Construct the log file for the given day 19670cc2cbfSAndreas Gohr * 19770cc2cbfSAndreas Gohr * @param false|string|int $date Date to access, false for today 19870cc2cbfSAndreas Gohr * @return string 19970cc2cbfSAndreas Gohr */ 20070cc2cbfSAndreas Gohr public function getLogfile($date = false) 20170cc2cbfSAndreas Gohr { 20270cc2cbfSAndreas Gohr global $conf; 20370cc2cbfSAndreas Gohr 204baa301e2SAndreas Gohr if ($date !== null && !is_numeric($date)) { 205baa301e2SAndreas Gohr $date = strtotime($date); 206baa301e2SAndreas Gohr } 20770cc2cbfSAndreas Gohr if (!$date) $date = time(); 20870cc2cbfSAndreas Gohr 20970cc2cbfSAndreas Gohr return $conf['logdir'] . '/' . $this->facility . '/' . date('Y-m-d', $date) . '.log'; 21070cc2cbfSAndreas Gohr } 21170cc2cbfSAndreas Gohr 21270cc2cbfSAndreas Gohr /** 2130ecde6ceSAndreas Gohr * Write the given lines to today's facility log 2140ecde6ceSAndreas Gohr * 2150ecde6ceSAndreas Gohr * @param string[] $lines the raw lines to append to the log 216555e8b00SAndreas Gohr * @param string $logfile where to write to 2170ecde6ceSAndreas Gohr * @return bool true if the log was written 2180ecde6ceSAndreas Gohr */ 219555e8b00SAndreas Gohr protected function writeLogLines($lines, $logfile) 2200ecde6ceSAndreas Gohr { 2216c6732d6SAndreas Gohr if (defined('DOKU_UNITTEST')) { 22224870174SAndreas Gohr fwrite(STDERR, "\n[" . $this->facility . '] ' . implode("\n", $lines) . "\n"); 2236c6732d6SAndreas Gohr } 22424870174SAndreas Gohr return io_saveFile($logfile, implode("\n", $lines) . "\n", true); 2250ecde6ceSAndreas Gohr } 2260ecde6ceSAndreas Gohr} 227