You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

236 lines
6.9 KiB

  1. <?php
  2. namespace dokuwiki;
  3. use dokuwiki\Extension\Event;
  4. /**
  5. * Log messages to a daily log file
  6. */
  7. class Logger
  8. {
  9. public const LOG_ERROR = 'error';
  10. public const LOG_DEPRECATED = 'deprecated';
  11. public const LOG_DEBUG = 'debug';
  12. /** @var Logger[] */
  13. protected static $instances;
  14. /** @var string what kind of log is this */
  15. protected $facility;
  16. protected $isLogging = true;
  17. /**
  18. * Logger constructor.
  19. *
  20. * @param string $facility The type of log
  21. */
  22. protected function __construct($facility)
  23. {
  24. global $conf;
  25. $this->facility = $facility;
  26. // Should logging be disabled for this facility?
  27. $dontlog = explode(',', $conf['dontlog']);
  28. $dontlog = array_map('trim', $dontlog);
  29. if (in_array($facility, $dontlog)) $this->isLogging = false;
  30. }
  31. /**
  32. * Return a Logger instance for the given facility
  33. *
  34. * @param string $facility The type of log
  35. * @return Logger
  36. */
  37. public static function getInstance($facility = self::LOG_ERROR)
  38. {
  39. if (empty(self::$instances[$facility])) {
  40. self::$instances[$facility] = new Logger($facility);
  41. }
  42. return self::$instances[$facility];
  43. }
  44. /**
  45. * Convenience method to directly log to the error log
  46. *
  47. * @param string $message The log message
  48. * @param mixed $details Any details that should be added to the log entry
  49. * @param string $file A source filename if this is related to a source position
  50. * @param int $line A line number for the above file
  51. * @return bool has a log been written?
  52. */
  53. public static function error($message, $details = null, $file = '', $line = 0)
  54. {
  55. return self::getInstance(self::LOG_ERROR)->log(
  56. $message,
  57. $details,
  58. $file,
  59. $line
  60. );
  61. }
  62. /**
  63. * Convenience method to directly log to the debug log
  64. *
  65. * @param string $message The log message
  66. * @param mixed $details Any details that should be added to the log entry
  67. * @param string $file A source filename if this is related to a source position
  68. * @param int $line A line number for the above file
  69. * @return bool has a log been written?
  70. */
  71. public static function debug($message, $details = null, $file = '', $line = 0)
  72. {
  73. return self::getInstance(self::LOG_DEBUG)->log(
  74. $message,
  75. $details,
  76. $file,
  77. $line
  78. );
  79. }
  80. /**
  81. * Convenience method to directly log to the deprecation log
  82. *
  83. * @param string $message The log message
  84. * @param mixed $details Any details that should be added to the log entry
  85. * @param string $file A source filename if this is related to a source position
  86. * @param int $line A line number for the above file
  87. * @return bool has a log been written?
  88. */
  89. public static function deprecated($message, $details = null, $file = '', $line = 0)
  90. {
  91. return self::getInstance(self::LOG_DEPRECATED)->log(
  92. $message,
  93. $details,
  94. $file,
  95. $line
  96. );
  97. }
  98. /**
  99. * Log a message to the facility log
  100. *
  101. * @param string $message The log message
  102. * @param mixed $details Any details that should be added to the log entry
  103. * @param string $file A source filename if this is related to a source position
  104. * @param int $line A line number for the above file
  105. * @triggers LOGGER_DATA_FORMAT can be used to change the logged data or intercept it
  106. * @return bool has a log been written?
  107. */
  108. public function log($message, $details = null, $file = '', $line = 0)
  109. {
  110. global $EVENT_HANDLER;
  111. if (!$this->isLogging) return false;
  112. $datetime = time();
  113. $data = [
  114. 'facility' => $this->facility,
  115. 'datetime' => $datetime,
  116. 'message' => $message,
  117. 'details' => $details,
  118. 'file' => $file,
  119. 'line' => $line,
  120. 'loglines' => [],
  121. 'logfile' => $this->getLogfile($datetime),
  122. ];
  123. if ($EVENT_HANDLER !== null) {
  124. $event = new Event('LOGGER_DATA_FORMAT', $data);
  125. if ($event->advise_before()) {
  126. $data['loglines'] = $this->formatLogLines($data);
  127. }
  128. $event->advise_after();
  129. } else {
  130. // The event system is not yet available, to ensure the log isn't lost even on
  131. // fatal errors, the default action is executed
  132. $data['loglines'] = $this->formatLogLines($data);
  133. }
  134. // only log when any data available
  135. if (count($data['loglines'])) {
  136. return $this->writeLogLines($data['loglines'], $data['logfile']);
  137. } else {
  138. return false;
  139. }
  140. }
  141. /**
  142. * Is this logging instace actually logging?
  143. *
  144. * @return bool
  145. */
  146. public function isLogging()
  147. {
  148. return $this->isLogging;
  149. }
  150. /**
  151. * Formats the given data as loglines
  152. *
  153. * @param array $data Event data from LOGGER_DATA_FORMAT
  154. * @return string[] the lines to log
  155. */
  156. protected function formatLogLines($data)
  157. {
  158. extract($data);
  159. // details are logged indented
  160. if ($details) {
  161. if (!is_string($details)) {
  162. $details = json_encode($details, JSON_PRETTY_PRINT);
  163. }
  164. $details = explode("\n", $details);
  165. $loglines = array_map(static fn($line) => ' ' . $line, $details);
  166. } elseif ($details) {
  167. $loglines = [$details];
  168. } else {
  169. $loglines = [];
  170. }
  171. // datetime, fileline, message
  172. $logline = gmdate('Y-m-d H:i:s', $datetime) . "\t";
  173. if ($file) {
  174. $logline .= $file;
  175. if ($line) $logline .= "($line)";
  176. }
  177. $logline .= "\t" . $message;
  178. array_unshift($loglines, $logline);
  179. return $loglines;
  180. }
  181. /**
  182. * Construct the log file for the given day
  183. *
  184. * @param false|string|int $date Date to access, false for today
  185. * @return string
  186. */
  187. public function getLogfile($date = false)
  188. {
  189. global $conf;
  190. if ($date !== null && !is_numeric($date)) {
  191. $date = strtotime($date);
  192. }
  193. if (!$date) $date = time();
  194. return $conf['logdir'] . '/' . $this->facility . '/' . date('Y-m-d', $date) . '.log';
  195. }
  196. /**
  197. * Write the given lines to today's facility log
  198. *
  199. * @param string[] $lines the raw lines to append to the log
  200. * @param string $logfile where to write to
  201. * @return bool true if the log was written
  202. */
  203. protected function writeLogLines($lines, $logfile)
  204. {
  205. if (defined('DOKU_UNITTEST')) {
  206. fwrite(STDERR, "\n[" . $this->facility . '] ' . implode("\n", $lines) . "\n");
  207. }
  208. return io_saveFile($logfile, implode("\n", $lines) . "\n", true);
  209. }
  210. }