xref: /dokuwiki/inc/Logger.php (revision baa301e28478688cde18d4ec54025b65af1745cd)
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{
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
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    /**
150555e8b00SAndreas Gohr     * Formats the given data as loglines
151555e8b00SAndreas Gohr     *
152555e8b00SAndreas Gohr     * @param array $data Event data from LOGGER_DATA_FORMAT
153555e8b00SAndreas Gohr     * @return string[] the lines to log
154555e8b00SAndreas Gohr     */
155555e8b00SAndreas Gohr    protected function formatLogLines($data)
156555e8b00SAndreas Gohr    {
157555e8b00SAndreas Gohr        extract($data);
158555e8b00SAndreas Gohr
1590ecde6ceSAndreas Gohr        // details are logged indented
16070cc2cbfSAndreas Gohr        if ($details) {
16170cc2cbfSAndreas Gohr            if (!is_string($details)) {
1620ecde6ceSAndreas Gohr                $details = json_encode($details, JSON_PRETTY_PRINT);
16370cc2cbfSAndreas Gohr            }
1640ecde6ceSAndreas Gohr            $details = explode("\n", $details);
1650ecde6ceSAndreas Gohr            $loglines = array_map(function ($line) {
1660ecde6ceSAndreas Gohr                return '  ' . $line;
1670ecde6ceSAndreas Gohr            }, $details);
1680ecde6ceSAndreas Gohr        } elseif ($details) {
1690ecde6ceSAndreas Gohr            $loglines = [$details];
1700ecde6ceSAndreas Gohr        } else {
1710ecde6ceSAndreas Gohr            $loglines = [];
1720ecde6ceSAndreas Gohr        }
1730ecde6ceSAndreas Gohr
17470cc2cbfSAndreas Gohr        // datetime, fileline, message
175555e8b00SAndreas Gohr        $logline = gmdate('Y-m-d H:i:s', $datetime) . "\t";
1760ecde6ceSAndreas Gohr        if ($file) {
17770cc2cbfSAndreas Gohr            $logline .= $file;
1780ecde6ceSAndreas Gohr            if ($line) $logline .= "($line)";
1790ecde6ceSAndreas Gohr        }
18070cc2cbfSAndreas Gohr        $logline .= "\t" . $message;
1810ecde6ceSAndreas Gohr        array_unshift($loglines, $logline);
182555e8b00SAndreas Gohr
183555e8b00SAndreas Gohr        return $loglines;
1840ecde6ceSAndreas Gohr    }
1850ecde6ceSAndreas Gohr
1860ecde6ceSAndreas Gohr    /**
18770cc2cbfSAndreas Gohr     * Construct the log file for the given day
18870cc2cbfSAndreas Gohr     *
18970cc2cbfSAndreas Gohr     * @param false|string|int $date Date to access, false for today
19070cc2cbfSAndreas Gohr     * @return string
19170cc2cbfSAndreas Gohr     */
19270cc2cbfSAndreas Gohr    public function getLogfile($date = false)
19370cc2cbfSAndreas Gohr    {
19470cc2cbfSAndreas Gohr        global $conf;
19570cc2cbfSAndreas Gohr
196*baa301e2SAndreas Gohr        if($date !== null && !is_numeric($date)) {
197*baa301e2SAndreas Gohr            $date = strtotime($date);
198*baa301e2SAndreas Gohr        }
19970cc2cbfSAndreas Gohr        if (!$date) $date = time();
20070cc2cbfSAndreas Gohr
20170cc2cbfSAndreas Gohr        return $conf['logdir'] . '/' . $this->facility . '/' . date('Y-m-d', $date) . '.log';
20270cc2cbfSAndreas Gohr    }
20370cc2cbfSAndreas Gohr
20470cc2cbfSAndreas Gohr    /**
2050ecde6ceSAndreas Gohr     * Write the given lines to today's facility log
2060ecde6ceSAndreas Gohr     *
2070ecde6ceSAndreas Gohr     * @param string[] $lines the raw lines to append to the log
208555e8b00SAndreas Gohr     * @param string $logfile where to write to
2090ecde6ceSAndreas Gohr     * @return bool true if the log was written
2100ecde6ceSAndreas Gohr     */
211555e8b00SAndreas Gohr    protected function writeLogLines($lines, $logfile)
2120ecde6ceSAndreas Gohr    {
2130ecde6ceSAndreas Gohr        return io_saveFile($logfile, join("\n", $lines) . "\n", true);
2140ecde6ceSAndreas Gohr    }
2150ecde6ceSAndreas Gohr}
216