10ecde6ceSAndreas Gohr<?php 20ecde6ceSAndreas Gohr 30ecde6ceSAndreas Gohrnamespace dokuwiki; 40ecde6ceSAndreas Gohr 5*555e8b00SAndreas Gohruse dokuwiki\Extension\Event; 6*555e8b00SAndreas Gohr 7*555e8b00SAndreas Gohr/** 8*555e8b00SAndreas Gohr * Log messages to a daily log file 9*555e8b00SAndreas Gohr */ 100ecde6ceSAndreas Gohrclass Logger 110ecde6ceSAndreas Gohr{ 120ecde6ceSAndreas Gohr const LOG_ERROR = 'error'; 130ecde6ceSAndreas Gohr const LOG_DEPRECATED = 'deprecated'; 140ecde6ceSAndreas Gohr 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 109*555e8b00SAndreas 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 { 114cad4fbf6SAndreas Gohr if (!$this->isLogging) return false; 115cad4fbf6SAndreas Gohr 116*555e8b00SAndreas Gohr $datetime = time(); 117*555e8b00SAndreas Gohr $data = [ 118*555e8b00SAndreas Gohr 'facility' => $this->facility, 119*555e8b00SAndreas Gohr 'datetime' => $datetime, 120*555e8b00SAndreas Gohr 'message' => $message, 121*555e8b00SAndreas Gohr 'details' => $details, 122*555e8b00SAndreas Gohr 'file' => $file, 123*555e8b00SAndreas Gohr 'line' => $line, 124*555e8b00SAndreas Gohr 'loglines' => [], 125*555e8b00SAndreas Gohr 'logfile' => $this->getLogfile($datetime), 126*555e8b00SAndreas Gohr ]; 127*555e8b00SAndreas Gohr $event = new Event('LOGGER_DATA_FORMAT', $data); 128*555e8b00SAndreas Gohr 129*555e8b00SAndreas Gohr if ($event->advise_before()) { 130*555e8b00SAndreas Gohr $data['loglines'] = $this->formatLogLines($data); 131*555e8b00SAndreas Gohr } 132*555e8b00SAndreas Gohr $event->advise_after(); 133*555e8b00SAndreas Gohr 134*555e8b00SAndreas Gohr // only log when any data available 135*555e8b00SAndreas Gohr if (count($data['loglines'])) { 136*555e8b00SAndreas Gohr return $this->writeLogLines($data['loglines'], $data['logfile']); 137*555e8b00SAndreas Gohr } else { 138*555e8b00SAndreas Gohr return false; 139*555e8b00SAndreas Gohr } 140*555e8b00SAndreas Gohr } 141*555e8b00SAndreas Gohr 142*555e8b00SAndreas Gohr /** 143*555e8b00SAndreas Gohr * Formats the given data as loglines 144*555e8b00SAndreas Gohr * 145*555e8b00SAndreas Gohr * @param array $data Event data from LOGGER_DATA_FORMAT 146*555e8b00SAndreas Gohr * @return string[] the lines to log 147*555e8b00SAndreas Gohr */ 148*555e8b00SAndreas Gohr protected function formatLogLines($data) 149*555e8b00SAndreas Gohr { 150*555e8b00SAndreas Gohr extract($data); 151*555e8b00SAndreas Gohr 1520ecde6ceSAndreas Gohr // details are logged indented 15370cc2cbfSAndreas Gohr if ($details) { 15470cc2cbfSAndreas Gohr if (!is_string($details)) { 1550ecde6ceSAndreas Gohr $details = json_encode($details, JSON_PRETTY_PRINT); 15670cc2cbfSAndreas Gohr } 1570ecde6ceSAndreas Gohr $details = explode("\n", $details); 1580ecde6ceSAndreas Gohr $loglines = array_map(function ($line) { 1590ecde6ceSAndreas Gohr return ' ' . $line; 1600ecde6ceSAndreas Gohr }, $details); 1610ecde6ceSAndreas Gohr } elseif ($details) { 1620ecde6ceSAndreas Gohr $loglines = [$details]; 1630ecde6ceSAndreas Gohr } else { 1640ecde6ceSAndreas Gohr $loglines = []; 1650ecde6ceSAndreas Gohr } 1660ecde6ceSAndreas Gohr 16770cc2cbfSAndreas Gohr // datetime, fileline, message 168*555e8b00SAndreas Gohr $logline = gmdate('Y-m-d H:i:s', $datetime) . "\t"; 1690ecde6ceSAndreas Gohr if ($file) { 17070cc2cbfSAndreas Gohr $logline .= $file; 1710ecde6ceSAndreas Gohr if ($line) $logline .= "($line)"; 1720ecde6ceSAndreas Gohr } 17370cc2cbfSAndreas Gohr $logline .= "\t" . $message; 1740ecde6ceSAndreas Gohr array_unshift($loglines, $logline); 175*555e8b00SAndreas Gohr 176*555e8b00SAndreas Gohr return $loglines; 1770ecde6ceSAndreas Gohr } 1780ecde6ceSAndreas Gohr 1790ecde6ceSAndreas Gohr /** 18070cc2cbfSAndreas Gohr * Construct the log file for the given day 18170cc2cbfSAndreas Gohr * 18270cc2cbfSAndreas Gohr * @param false|string|int $date Date to access, false for today 18370cc2cbfSAndreas Gohr * @return string 18470cc2cbfSAndreas Gohr */ 18570cc2cbfSAndreas Gohr public function getLogfile($date = false) 18670cc2cbfSAndreas Gohr { 18770cc2cbfSAndreas Gohr global $conf; 18870cc2cbfSAndreas Gohr 18970cc2cbfSAndreas Gohr if ($date !== null) $date = strtotime($date); 19070cc2cbfSAndreas Gohr if (!$date) $date = time(); 19170cc2cbfSAndreas Gohr 19270cc2cbfSAndreas Gohr return $conf['logdir'] . '/' . $this->facility . '/' . date('Y-m-d', $date) . '.log'; 19370cc2cbfSAndreas Gohr } 19470cc2cbfSAndreas Gohr 19570cc2cbfSAndreas Gohr /** 1960ecde6ceSAndreas Gohr * Write the given lines to today's facility log 1970ecde6ceSAndreas Gohr * 1980ecde6ceSAndreas Gohr * @param string[] $lines the raw lines to append to the log 199*555e8b00SAndreas Gohr * @param string $logfile where to write to 2000ecde6ceSAndreas Gohr * @return bool true if the log was written 2010ecde6ceSAndreas Gohr */ 202*555e8b00SAndreas Gohr protected function writeLogLines($lines, $logfile) 2030ecde6ceSAndreas Gohr { 2040ecde6ceSAndreas Gohr return io_saveFile($logfile, join("\n", $lines) . "\n", true); 2050ecde6ceSAndreas Gohr } 2060ecde6ceSAndreas Gohr} 207