xref: /plugin/combo/ComboStrap/LogUtility.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
137748cd8SNickeau<?php
237748cd8SNickeau/**
337748cd8SNickeau * Copyright (c) 2020. ComboStrap, Inc. and its affiliates. All Rights Reserved.
437748cd8SNickeau *
537748cd8SNickeau * This source code is licensed under the GPL license found in the
637748cd8SNickeau * COPYING  file in the root directory of this source tree.
737748cd8SNickeau *
837748cd8SNickeau * @license  GPL 3 (https://www.gnu.org/licenses/gpl-3.0.en.html)
937748cd8SNickeau * @author   ComboStrap <support@combostrap.com>
1037748cd8SNickeau *
1137748cd8SNickeau */
1237748cd8SNickeau
1337748cd8SNickeaunamespace ComboStrap;
1437748cd8SNickeau
15*04fd306cSNickeauuse Throwable;
16*04fd306cSNickeau
1737748cd8SNickeaurequire_once(__DIR__ . '/PluginUtility.php');
1837748cd8SNickeau
1937748cd8SNickeauclass LogUtility
2037748cd8SNickeau{
2137748cd8SNickeau
2237748cd8SNickeau    /**
2337748cd8SNickeau     * Constant for the function {@link msg()}
2437748cd8SNickeau     * -1 = error, 0 = info, 1 = success, 2 = notify
2537748cd8SNickeau     * (Not even in order of importance)
2637748cd8SNickeau     */
27*04fd306cSNickeau    const LVL_MSG_ABOVE_ERROR = 5; // a level to disable the error to thrown in test
2837748cd8SNickeau    const LVL_MSG_ERROR = 4; //-1;
2937748cd8SNickeau    const LVL_MSG_WARNING = 3; //2;
3037748cd8SNickeau    const LVL_MSG_SUCCESS = 2; //1;
3137748cd8SNickeau    const LVL_MSG_INFO = 1; //0;
3237748cd8SNickeau    const LVL_MSG_DEBUG = 0; //3;
3337748cd8SNickeau
3437748cd8SNickeau
3537748cd8SNickeau    /**
3637748cd8SNickeau     * Id level to name
3737748cd8SNickeau     */
3837748cd8SNickeau    const LVL_NAME = array(
3937748cd8SNickeau        0 => "debug",
4037748cd8SNickeau        1 => "info",
4137748cd8SNickeau        3 => "warning",
4237748cd8SNickeau        2 => "success",
4337748cd8SNickeau        4 => "error"
4437748cd8SNickeau    );
4537748cd8SNickeau
4637748cd8SNickeau    /**
4737748cd8SNickeau     * Id level to name
4837748cd8SNickeau     * {@link msg()} constant
4937748cd8SNickeau     */
5037748cd8SNickeau    const LVL_TO_MSG_LEVEL = array(
5137748cd8SNickeau        0 => 3,
5237748cd8SNickeau        1 => 0,
5337748cd8SNickeau        2 => 1,
5437748cd8SNickeau        3 => 2,
5537748cd8SNickeau        4 => -1
5637748cd8SNickeau    );
5737748cd8SNickeau
5837748cd8SNickeau
5937748cd8SNickeau    const LOGLEVEL_URI_QUERY_PROPERTY = "loglevel";
60*04fd306cSNickeau    const SUPPORT_CANONICAL = "support";
61*04fd306cSNickeau
620e43c1dbSgerardnico    /**
630e43c1dbSgerardnico     *
640e43c1dbSgerardnico     * @var bool
650e43c1dbSgerardnico     */
66*04fd306cSNickeau    private static bool $throwExceptionOnDevTest = true;
67*04fd306cSNickeau    /**
68*04fd306cSNickeau     * @var int
69*04fd306cSNickeau     */
70*04fd306cSNickeau    const DEFAULT_THROW_LEVEL = self::LVL_MSG_WARNING;
7137748cd8SNickeau
7237748cd8SNickeau    /**
7337748cd8SNickeau     * Send a message to a manager and log it
7437748cd8SNickeau     * Fail if in test
7537748cd8SNickeau     * @param string $message
7637748cd8SNickeau     * @param int $level - the level see LVL constant
7737748cd8SNickeau     * @param string $canonical - the canonical
7837748cd8SNickeau     */
79*04fd306cSNickeau    public static function msg(string $message, int $level = self::LVL_MSG_ERROR, string $canonical = "support", \Exception $e = null)
8037748cd8SNickeau    {
8137748cd8SNickeau
82d61dea15Sgerardnico        try {
83d61dea15Sgerardnico            self::messageNotEmpty($message);
84*04fd306cSNickeau        } catch (ExceptionCompile $e) {
85d61dea15Sgerardnico            self::log2file($e->getMessage(), LogUtility::LVL_MSG_ERROR, $canonical);
86e7bbc4b8Sgerardnico        }
87e7bbc4b8Sgerardnico
8837748cd8SNickeau        /**
8937748cd8SNickeau         * Log to frontend
9037748cd8SNickeau         */
91c3437056SNickeau        self::log2FrontEnd($message, $level, $canonical);
9237748cd8SNickeau
9337748cd8SNickeau        /**
9437748cd8SNickeau         * Log level passed for a page (only for file used)
9537748cd8SNickeau         * to not allow an attacker to see all errors in frontend
9637748cd8SNickeau         */
9737748cd8SNickeau        global $INPUT;
9837748cd8SNickeau        $loglevelProp = $INPUT->str(self::LOGLEVEL_URI_QUERY_PROPERTY, null);
9937748cd8SNickeau        if (!empty($loglevelProp)) {
10037748cd8SNickeau            $level = $loglevelProp;
10137748cd8SNickeau        }
1022f4da794Sgerardnico        /**
1032f4da794Sgerardnico         * TODO: Make it a configuration ?
1042f4da794Sgerardnico         */
1052f4da794Sgerardnico        if ($level >= self::LVL_MSG_WARNING) {
106*04fd306cSNickeau            self::log2file($message, $level, $canonical, $e);
1072f4da794Sgerardnico        }
10837748cd8SNickeau
10937748cd8SNickeau        /**
11037748cd8SNickeau         * If test, we throw an error
11137748cd8SNickeau         */
112*04fd306cSNickeau        self::throwErrorIfTest($level, $message, $e);
11337748cd8SNickeau    }
11437748cd8SNickeau
11537748cd8SNickeau    /**
11637748cd8SNickeau     * Print log to a  file
11737748cd8SNickeau     *
11837748cd8SNickeau     * Adapted from {@link dbglog}
11937748cd8SNickeau     * Note: {@link dbg()} dbg print to the web page
12037748cd8SNickeau     *
121e96d9362Sgerardnico     * @param null|string $msg - may be null always this is the default if a variable is not initialized.
12237748cd8SNickeau     * @param int $logLevel
12337748cd8SNickeau     * @param null $canonical
124*04fd306cSNickeau     * @param \Exception $e
12537748cd8SNickeau     */
126*04fd306cSNickeau    static function log2file(?string $msg, int $logLevel = self::LVL_MSG_ERROR, $canonical = null, \Exception $e = null)
12737748cd8SNickeau    {
12837748cd8SNickeau
129d61dea15Sgerardnico        try {
130d61dea15Sgerardnico            self::messageNotEmpty($msg);
131*04fd306cSNickeau        } catch (ExceptionCompile $e) {
132d61dea15Sgerardnico            $msg = $e->getMessage();
133d61dea15Sgerardnico            $logLevel = self::LVL_MSG_ERROR;
134d61dea15Sgerardnico        }
135d61dea15Sgerardnico
136c3437056SNickeau        if (PluginUtility::isTest() || $logLevel >= self::LVL_MSG_WARNING) {
13737748cd8SNickeau
13837748cd8SNickeau            $prefix = PluginUtility::$PLUGIN_NAME;
13937748cd8SNickeau            if (!empty($canonical)) {
14037748cd8SNickeau                $prefix .= ' - ' . $canonical;
14137748cd8SNickeau            }
14237748cd8SNickeau            $msg = $prefix . ' - ' . $msg;
14337748cd8SNickeau
14437748cd8SNickeau            global $INPUT;
14537748cd8SNickeau            global $conf;
14637748cd8SNickeau
147c3437056SNickeau            /**
148c3437056SNickeau             * Adding page - context information
149*04fd306cSNickeau             * We are not using {@link MarkupPath::createFromRequestedPage()}
150c3437056SNickeau             * because it throws an error message when the environment
151c3437056SNickeau             * is not good, creating a recursive call.
152c3437056SNickeau             */
153*04fd306cSNickeau            $id = $INPUT->str("id");
15437748cd8SNickeau
15537748cd8SNickeau            $file = $conf['cachedir'] . '/debug.log';
15637748cd8SNickeau            $fh = fopen($file, 'a');
15737748cd8SNickeau            if ($fh) {
15837748cd8SNickeau                $sep = " - ";
15937748cd8SNickeau                fwrite($fh, date('c') . $sep . self::LVL_NAME[$logLevel] . $sep . $msg . $sep . $INPUT->server->str('REMOTE_ADDR') . $sep . $id . "\n");
16037748cd8SNickeau                fclose($fh);
16137748cd8SNickeau            }
162c3437056SNickeau
163c3437056SNickeau
164*04fd306cSNickeau            self::throwErrorIfTest($logLevel, $msg, $e);
165c3437056SNickeau
166c3437056SNickeau
16737748cd8SNickeau        }
16837748cd8SNickeau
16937748cd8SNickeau    }
17037748cd8SNickeau
17137748cd8SNickeau    /**
17237748cd8SNickeau     * @param $message
17337748cd8SNickeau     * @param $level
174d61dea15Sgerardnico     * @param string $canonical
17537748cd8SNickeau     * @param bool $withIconURL
17637748cd8SNickeau     */
177*04fd306cSNickeau    public static function log2FrontEnd($message, $level, $canonical = "support", bool $publicMessage = false)
17837748cd8SNickeau    {
179d61dea15Sgerardnico
180d61dea15Sgerardnico        try {
181d61dea15Sgerardnico            self::messageNotEmpty($message);
182*04fd306cSNickeau        } catch (ExceptionCompile $e) {
183d61dea15Sgerardnico            $message = $e->getMessage();
184d61dea15Sgerardnico            $level = self::LVL_MSG_ERROR;
185d61dea15Sgerardnico        }
186d61dea15Sgerardnico
18737748cd8SNickeau        /**
18837748cd8SNickeau         * If we are not in the console
18937748cd8SNickeau         * and not in test
19037748cd8SNickeau         * we test that the message comes in the front end
19137748cd8SNickeau         * (example {@link \plugin_combo_frontmatter_test}
19237748cd8SNickeau         */
193c3437056SNickeau        $isTerminal = Console::isConsoleRun();
194c3437056SNickeau        if ($isTerminal) {
19537748cd8SNickeau            if (!defined('DOKU_UNITTEST')) {
196c3437056SNickeau                /**
197c3437056SNickeau                 * such as {@link cli_plugin_combo}
198c3437056SNickeau                 */
199c3437056SNickeau                $userAgent = "cli";
200c3437056SNickeau            } else {
201c3437056SNickeau                $userAgent = "phpunit";
20237748cd8SNickeau            }
203c3437056SNickeau        } else {
204c3437056SNickeau            $userAgent = "browser";
20537748cd8SNickeau        }
206c3437056SNickeau
207c3437056SNickeau        switch ($userAgent) {
208c3437056SNickeau            case "cli":
209c3437056SNickeau                echo "$message\n";
210c3437056SNickeau                break;
211c3437056SNickeau            case "phpunit":
212c3437056SNickeau            case "browser":
213c3437056SNickeau            default:
214*04fd306cSNickeau                if ($canonical !== null) {
215*04fd306cSNickeau                    $label = ucfirst(str_replace(":", " ", $canonical));
216*04fd306cSNickeau                    $htmlMsg = PluginUtility::getDocumentationHyperLink($canonical, $label, false);
217*04fd306cSNickeau                } else {
218*04fd306cSNickeau                    $htmlMsg = PluginUtility::getDocumentationHyperLink("", PluginUtility::$PLUGIN_NAME, false);
21937748cd8SNickeau                }
22037748cd8SNickeau
221*04fd306cSNickeau
22237748cd8SNickeau                /**
22337748cd8SNickeau                 * Adding page - context information
22437748cd8SNickeau                 * We are not creating the page
225*04fd306cSNickeau                 * direction from {@link MarkupPath::createFromRequestedPage()}
22637748cd8SNickeau                 * because it throws an error message when the environment
22737748cd8SNickeau                 * is not good, creating a recursive call.
22837748cd8SNickeau                 */
229*04fd306cSNickeau                global $INPUT;
230*04fd306cSNickeau                $id = $INPUT->str("id");
23137748cd8SNickeau                if ($id != null) {
232c3437056SNickeau
233c3437056SNickeau                    /**
234c3437056SNickeau                     * We don't use any Page object to not
235c3437056SNickeau                     * create a cycle while building it
236c3437056SNickeau                     */
237c3437056SNickeau                    $url = wl($id, [], true);
238c3437056SNickeau                    $htmlMsg .= " - <a href=\"$url\">$id</a>";
239c3437056SNickeau
24037748cd8SNickeau                }
24137748cd8SNickeau
24237748cd8SNickeau                /**
24337748cd8SNickeau                 *
24437748cd8SNickeau                 */
24537748cd8SNickeau                $htmlMsg .= " - " . $message;
24637748cd8SNickeau                if ($level > self::LVL_MSG_DEBUG) {
24737748cd8SNickeau                    $dokuWikiLevel = self::LVL_TO_MSG_LEVEL[$level];
248*04fd306cSNickeau                    if ($publicMessage) {
249*04fd306cSNickeau                        $allow = MSG_PUBLIC;
250*04fd306cSNickeau                    } else {
251*04fd306cSNickeau                        $allow = MSG_USERS_ONLY;
252*04fd306cSNickeau                    }
253*04fd306cSNickeau                    msg($htmlMsg, $dokuWikiLevel, '', '',$allow);
25437748cd8SNickeau                }
25537748cd8SNickeau        }
25637748cd8SNickeau    }
25737748cd8SNickeau
25837748cd8SNickeau    /**
25937748cd8SNickeau     * Log a message to the browser console
26037748cd8SNickeau     * @param $message
26137748cd8SNickeau     */
26237748cd8SNickeau    public static function log2BrowserConsole($message)
26337748cd8SNickeau    {
26437748cd8SNickeau        // TODO
26537748cd8SNickeau    }
266c3437056SNickeau
267*04fd306cSNickeau
268*04fd306cSNickeau    /**
269*04fd306cSNickeau     * @param $level
270*04fd306cSNickeau     * @param $message
271*04fd306cSNickeau     * @param $e - the original exception for chaining
272*04fd306cSNickeau     * @return void
273*04fd306cSNickeau     */
274*04fd306cSNickeau    private static function throwErrorIfTest($level, $message, \Exception $e = null)
275c3437056SNickeau    {
276*04fd306cSNickeau        $actualLevel = ExecutionContext::getActualOrCreateFromEnv()->getConfig()
277*04fd306cSNickeau            ->getLogExceptionLevel();
278c3437056SNickeau        if (PluginUtility::isTest()
279*04fd306cSNickeau            && ($level >= $actualLevel)
2800e43c1dbSgerardnico            && self::$throwExceptionOnDevTest
281c3437056SNickeau        ) {
282*04fd306cSNickeau            throw new LogException($message, $level, $e);
283c3437056SNickeau        }
284c3437056SNickeau    }
285d61dea15Sgerardnico
286d61dea15Sgerardnico    /**
2874e9ad3c2Sgerardnico     * @param string|null $message
288*04fd306cSNickeau     * @throws ExceptionCompile
289d61dea15Sgerardnico     */
2904e9ad3c2Sgerardnico    private static function messageNotEmpty(?string $message)
291d61dea15Sgerardnico    {
292d61dea15Sgerardnico        $message = trim($message);
2932fea41feSgerardnico        if ($message === null || $message === "") {
294d61dea15Sgerardnico            $newMessage = "The passed message to the log was empty or null. BackTrace: \n";
29533a4f219Sgerardnico            $newMessage .= LogUtility::getCallStack();
296*04fd306cSNickeau            throw new ExceptionCompile($newMessage);
297d61dea15Sgerardnico        }
298d61dea15Sgerardnico    }
2990e43c1dbSgerardnico
3000e43c1dbSgerardnico    public static function disableThrowExceptionOnDevTest()
3010e43c1dbSgerardnico    {
3020e43c1dbSgerardnico        self::$throwExceptionOnDevTest = false;
3030e43c1dbSgerardnico    }
3040e43c1dbSgerardnico
3050e43c1dbSgerardnico    public static function enableThrowExceptionOnDevTest()
3060e43c1dbSgerardnico    {
3070e43c1dbSgerardnico        self::$throwExceptionOnDevTest = true;
3080e43c1dbSgerardnico    }
3094cadd4f8SNickeau
3104cadd4f8SNickeau    public static function wrapInRedForHtml(string $message): string
3114cadd4f8SNickeau    {
312*04fd306cSNickeau        return "<span class=\"text-danger\">$message</span>";
3134cadd4f8SNickeau    }
31433a4f219Sgerardnico
31533a4f219Sgerardnico    /**
31633a4f219Sgerardnico     * @return false|string - the actual php call stack (known as backtrace)
31733a4f219Sgerardnico     */
31833a4f219Sgerardnico    public static function getCallStack()
31933a4f219Sgerardnico    {
32033a4f219Sgerardnico        ob_start();
32133a4f219Sgerardnico        $limit = 10;
32233a4f219Sgerardnico        /**
32333a4f219Sgerardnico         * DEBUG_BACKTRACE_IGNORE_ARGS options to avoid
32433a4f219Sgerardnico         * PHP Fatal error:  Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1876967424 bytes)
32533a4f219Sgerardnico         */
32633a4f219Sgerardnico        debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit); // It prints also the data passed :)
32733a4f219Sgerardnico        $trace = ob_get_contents();
32833a4f219Sgerardnico        ob_end_clean();
32933a4f219Sgerardnico        return $trace;
33033a4f219Sgerardnico    }
331*04fd306cSNickeau
332*04fd306cSNickeau    /**
333*04fd306cSNickeau     * @param string $message the message
334*04fd306cSNickeau     * @param string $canonical the page
335*04fd306cSNickeau     * @param \Exception|null $e the original exception for trace chaining
336*04fd306cSNickeau     * @return void
337*04fd306cSNickeau     */
338*04fd306cSNickeau    public static function error(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null)
339*04fd306cSNickeau    {
340*04fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_ERROR, $canonical, $e);
341*04fd306cSNickeau    }
342*04fd306cSNickeau
343*04fd306cSNickeau    public static function warning(string $message, string $canonical = "support", \Exception $e = null)
344*04fd306cSNickeau    {
345*04fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_WARNING, $canonical, $e);
346*04fd306cSNickeau    }
347*04fd306cSNickeau
348*04fd306cSNickeau    public static function info(string $message, string $canonical = "support", \Exception $e = null)
349*04fd306cSNickeau    {
350*04fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_INFO, $canonical, $e);
351*04fd306cSNickeau    }
352*04fd306cSNickeau
353*04fd306cSNickeau    /**
354*04fd306cSNickeau     * @param int $level
355*04fd306cSNickeau     * @return void
356*04fd306cSNickeau     * @deprecated use {@link SiteConfig::setLogExceptionLevel()}
357*04fd306cSNickeau     */
358*04fd306cSNickeau    public static function setTestExceptionLevel(int $level)
359*04fd306cSNickeau    {
360*04fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel($level);
361*04fd306cSNickeau    }
362*04fd306cSNickeau
363*04fd306cSNickeau    public static function setTestExceptionLevelToDefault()
364*04fd306cSNickeau    {
365*04fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel(self::LVL_MSG_WARNING);
366*04fd306cSNickeau    }
367*04fd306cSNickeau
368*04fd306cSNickeau    public static function errorIfDevOrTest($message, $canonical = "support")
369*04fd306cSNickeau    {
370*04fd306cSNickeau        if (PluginUtility::isDevOrTest()) {
371*04fd306cSNickeau            LogUtility::error($message, $canonical);
372*04fd306cSNickeau        }
373*04fd306cSNickeau    }
374*04fd306cSNickeau
375*04fd306cSNickeau    /**
376*04fd306cSNickeau     * @return void
377*04fd306cSNickeau     * @deprecated use the config object instead
378*04fd306cSNickeau     */
379*04fd306cSNickeau    public static function setTestExceptionLevelToError()
380*04fd306cSNickeau    {
381*04fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionToError();
382*04fd306cSNickeau    }
383*04fd306cSNickeau
384*04fd306cSNickeau    /**
385*04fd306cSNickeau     * Advertise an error that should not take place if the code was
386*04fd306cSNickeau     * written properly
387*04fd306cSNickeau     * @param string $message
388*04fd306cSNickeau     * @param string $canonical
389*04fd306cSNickeau     * @param Throwable|null $previous
390*04fd306cSNickeau     * @return void
391*04fd306cSNickeau     */
392*04fd306cSNickeau    public static function internalError(string $message, string $canonical = "support", Throwable $previous = null)
393*04fd306cSNickeau    {
394*04fd306cSNickeau        $internalErrorMessage = "Sorry. An internal error has occurred";
395*04fd306cSNickeau        if (PluginUtility::isDevOrTest()) {
396*04fd306cSNickeau            throw new ExceptionRuntimeInternal("$internalErrorMessage - $message", $canonical, 1, $previous);
397*04fd306cSNickeau        } else {
398*04fd306cSNickeau            $errorPreviousMessage = "";
399*04fd306cSNickeau            if ($previous !== null) {
400*04fd306cSNickeau                $errorPreviousMessage = " Error: {$previous->getMessage()}";
401*04fd306cSNickeau            }
402*04fd306cSNickeau            self::error("{$internalErrorMessage}: $message.$errorPreviousMessage", $canonical);
403*04fd306cSNickeau        }
404*04fd306cSNickeau    }
405*04fd306cSNickeau
406*04fd306cSNickeau    /**
407*04fd306cSNickeau     * @param string $message
408*04fd306cSNickeau     * @param string $canonical
409*04fd306cSNickeau     * @param $e
410*04fd306cSNickeau     * @return void
411*04fd306cSNickeau     * Debug, trace
412*04fd306cSNickeau     */
413*04fd306cSNickeau    public static function debug(string $message, string $canonical = self::SUPPORT_CANONICAL, $e = null)
414*04fd306cSNickeau    {
415*04fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_DEBUG, $canonical, $e);
416*04fd306cSNickeau    }
417*04fd306cSNickeau
418*04fd306cSNickeau    public static function infoToPublic(string $html, string $canonical)
419*04fd306cSNickeau    {
420*04fd306cSNickeau        self::log2FrontEnd($html, LogUtility::LVL_MSG_INFO, $canonical, true);
421*04fd306cSNickeau    }
422*04fd306cSNickeau
42337748cd8SNickeau}
424