xref: /plugin/combo/ComboStrap/LogUtility.php (revision 39c00e7ee80f440398e043c1b028639b65574701)
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*39c00e7eSNicouse dokuwiki\Logger;
1604fd306cSNickeauuse Throwable;
1704fd306cSNickeau
1837748cd8SNickeaurequire_once(__DIR__ . '/PluginUtility.php');
1937748cd8SNickeau
2037748cd8SNickeauclass LogUtility
2137748cd8SNickeau{
2237748cd8SNickeau
2337748cd8SNickeau    /**
2437748cd8SNickeau     * Constant for the function {@link msg()}
2537748cd8SNickeau     * -1 = error, 0 = info, 1 = success, 2 = notify
2637748cd8SNickeau     * (Not even in order of importance)
2737748cd8SNickeau     */
2804fd306cSNickeau    const LVL_MSG_ABOVE_ERROR = 5; // a level to disable the error to thrown in test
2937748cd8SNickeau    const LVL_MSG_ERROR = 4; //-1;
3037748cd8SNickeau    const LVL_MSG_WARNING = 3; //2;
3137748cd8SNickeau    const LVL_MSG_SUCCESS = 2; //1;
3237748cd8SNickeau    const LVL_MSG_INFO = 1; //0;
3337748cd8SNickeau    const LVL_MSG_DEBUG = 0; //3;
3437748cd8SNickeau
3537748cd8SNickeau
3637748cd8SNickeau    /**
3737748cd8SNickeau     * Id level to name
3837748cd8SNickeau     */
3937748cd8SNickeau    const LVL_NAME = array(
4037748cd8SNickeau        0 => "debug",
4137748cd8SNickeau        1 => "info",
4237748cd8SNickeau        3 => "warning",
4337748cd8SNickeau        2 => "success",
4437748cd8SNickeau        4 => "error"
4537748cd8SNickeau    );
4637748cd8SNickeau
4737748cd8SNickeau    /**
4837748cd8SNickeau     * Id level to name
4937748cd8SNickeau     * {@link msg()} constant
5037748cd8SNickeau     */
5137748cd8SNickeau    const LVL_TO_MSG_LEVEL = array(
5237748cd8SNickeau        0 => 3,
5337748cd8SNickeau        1 => 0,
5437748cd8SNickeau        2 => 1,
5537748cd8SNickeau        3 => 2,
5637748cd8SNickeau        4 => -1
5737748cd8SNickeau    );
5837748cd8SNickeau
5937748cd8SNickeau
6037748cd8SNickeau    const LOGLEVEL_URI_QUERY_PROPERTY = "loglevel";
6104fd306cSNickeau    const SUPPORT_CANONICAL = "support";
6204fd306cSNickeau
630e43c1dbSgerardnico    /**
640e43c1dbSgerardnico     *
650e43c1dbSgerardnico     * @var bool
660e43c1dbSgerardnico     */
6704fd306cSNickeau    private static bool $throwExceptionOnDevTest = true;
6804fd306cSNickeau    /**
6904fd306cSNickeau     * @var int
7004fd306cSNickeau     */
7104fd306cSNickeau    const DEFAULT_THROW_LEVEL = self::LVL_MSG_WARNING;
7237748cd8SNickeau
7337748cd8SNickeau    /**
7437748cd8SNickeau     * Send a message to a manager and log it
7537748cd8SNickeau     * Fail if in test
7637748cd8SNickeau     * @param string $message
7737748cd8SNickeau     * @param int $level - the level see LVL constant
7837748cd8SNickeau     * @param string $canonical - the canonical
79*39c00e7eSNico     * @param \Exception|null $e
8037748cd8SNickeau     */
81*39c00e7eSNico    public static function msg(string $message, int $level = self::LVL_MSG_ERROR, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null)
8237748cd8SNickeau    {
8337748cd8SNickeau
84d61dea15Sgerardnico        try {
85d61dea15Sgerardnico            self::messageNotEmpty($message);
8604fd306cSNickeau        } catch (ExceptionCompile $e) {
87d61dea15Sgerardnico            self::log2file($e->getMessage(), LogUtility::LVL_MSG_ERROR, $canonical);
88e7bbc4b8Sgerardnico        }
89e7bbc4b8Sgerardnico
9037748cd8SNickeau        /**
9137748cd8SNickeau         * Log to frontend
9237748cd8SNickeau         */
93c3437056SNickeau        self::log2FrontEnd($message, $level, $canonical);
9437748cd8SNickeau
9537748cd8SNickeau        /**
9637748cd8SNickeau         * Log level passed for a page (only for file used)
9737748cd8SNickeau         * to not allow an attacker to see all errors in frontend
9837748cd8SNickeau         */
9937748cd8SNickeau        global $INPUT;
10037748cd8SNickeau        $loglevelProp = $INPUT->str(self::LOGLEVEL_URI_QUERY_PROPERTY, null);
10137748cd8SNickeau        if (!empty($loglevelProp)) {
10237748cd8SNickeau            $level = $loglevelProp;
10337748cd8SNickeau        }
1042f4da794Sgerardnico        /**
1052f4da794Sgerardnico         * TODO: Make it a configuration ?
1062f4da794Sgerardnico         */
1072f4da794Sgerardnico        if ($level >= self::LVL_MSG_WARNING) {
10804fd306cSNickeau            self::log2file($message, $level, $canonical, $e);
1092f4da794Sgerardnico        }
11037748cd8SNickeau
11137748cd8SNickeau        /**
11237748cd8SNickeau         * If test, we throw an error
11337748cd8SNickeau         */
11404fd306cSNickeau        self::throwErrorIfTest($level, $message, $e);
11537748cd8SNickeau    }
11637748cd8SNickeau
11737748cd8SNickeau    /**
11837748cd8SNickeau     * Print log to a  file
11937748cd8SNickeau     *
12037748cd8SNickeau     * Adapted from {@link dbglog}
12137748cd8SNickeau     * Note: {@link dbg()} dbg print to the web page
12237748cd8SNickeau     *
123e96d9362Sgerardnico     * @param null|string $msg - may be null always this is the default if a variable is not initialized.
12437748cd8SNickeau     * @param int $logLevel
125*39c00e7eSNico     * @param string|null $canonical
126*39c00e7eSNico     * @param \Exception|null $e
12737748cd8SNickeau     */
128*39c00e7eSNico    static function log2file(?string $msg, int $logLevel = self::LVL_MSG_ERROR, ?string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null)
12937748cd8SNickeau    {
13037748cd8SNickeau
131d61dea15Sgerardnico        try {
132d61dea15Sgerardnico            self::messageNotEmpty($msg);
13304fd306cSNickeau        } catch (ExceptionCompile $e) {
134d61dea15Sgerardnico            $msg = $e->getMessage();
135d61dea15Sgerardnico            $logLevel = self::LVL_MSG_ERROR;
136d61dea15Sgerardnico        }
137d61dea15Sgerardnico
138c3437056SNickeau        if (PluginUtility::isTest() || $logLevel >= self::LVL_MSG_WARNING) {
13937748cd8SNickeau
14037748cd8SNickeau            $prefix = PluginUtility::$PLUGIN_NAME;
14137748cd8SNickeau            if (!empty($canonical)) {
14237748cd8SNickeau                $prefix .= ' - ' . $canonical;
14337748cd8SNickeau            }
14437748cd8SNickeau            $msg = $prefix . ' - ' . $msg;
14537748cd8SNickeau
14637748cd8SNickeau            global $INPUT;
14737748cd8SNickeau
148c3437056SNickeau            /**
149c3437056SNickeau             * Adding page - context information
15004fd306cSNickeau             * We are not using {@link MarkupPath::createFromRequestedPage()}
151c3437056SNickeau             * because it throws an error message when the environment
152c3437056SNickeau             * is not good, creating a recursive call.
153c3437056SNickeau             */
15404fd306cSNickeau            $id = $INPUT->str("id");
15537748cd8SNickeau            $sep = " - ";
156*39c00e7eSNico            $messageWritten = date('c') . $sep . self::LVL_NAME[$logLevel] . $sep . $msg . $sep . $INPUT->server->str('REMOTE_ADDR') . $sep . $id . "\n";
157*39c00e7eSNico            // dokuwiki does not have the warning level
158*39c00e7eSNico            Logger::error($messageWritten);
15904fd306cSNickeau            self::throwErrorIfTest($logLevel, $msg, $e);
160c3437056SNickeau
161c3437056SNickeau
16237748cd8SNickeau        }
16337748cd8SNickeau
16437748cd8SNickeau    }
16537748cd8SNickeau
16637748cd8SNickeau    /**
16737748cd8SNickeau     * @param $message
16837748cd8SNickeau     * @param $level
169d61dea15Sgerardnico     * @param string $canonical
170*39c00e7eSNico     * @param bool $publicMessage
17137748cd8SNickeau     */
172*39c00e7eSNico    public static function log2FrontEnd($message, $level, string $canonical = self::SUPPORT_CANONICAL, bool $publicMessage = false)
17337748cd8SNickeau    {
174d61dea15Sgerardnico
175d61dea15Sgerardnico        try {
176d61dea15Sgerardnico            self::messageNotEmpty($message);
17704fd306cSNickeau        } catch (ExceptionCompile $e) {
178d61dea15Sgerardnico            $message = $e->getMessage();
179d61dea15Sgerardnico            $level = self::LVL_MSG_ERROR;
180d61dea15Sgerardnico        }
181d61dea15Sgerardnico
18237748cd8SNickeau        /**
18337748cd8SNickeau         * If we are not in the console
18437748cd8SNickeau         * and not in test
18537748cd8SNickeau         * we test that the message comes in the front end
18637748cd8SNickeau         * (example {@link \plugin_combo_frontmatter_test}
18737748cd8SNickeau         */
188c3437056SNickeau        $isTerminal = Console::isConsoleRun();
189c3437056SNickeau        if ($isTerminal) {
19037748cd8SNickeau            if (!defined('DOKU_UNITTEST')) {
191c3437056SNickeau                /**
192c3437056SNickeau                 * such as {@link cli_plugin_combo}
193c3437056SNickeau                 */
194c3437056SNickeau                $userAgent = "cli";
195c3437056SNickeau            } else {
196c3437056SNickeau                $userAgent = "phpunit";
19737748cd8SNickeau            }
198c3437056SNickeau        } else {
199c3437056SNickeau            $userAgent = "browser";
20037748cd8SNickeau        }
201c3437056SNickeau
202c3437056SNickeau        switch ($userAgent) {
203c3437056SNickeau            case "cli":
204c3437056SNickeau                echo "$message\n";
205c3437056SNickeau                break;
206c3437056SNickeau            case "phpunit":
207c3437056SNickeau            case "browser":
208c3437056SNickeau            default:
20904fd306cSNickeau                if ($canonical !== null) {
21004fd306cSNickeau                    $label = ucfirst(str_replace(":", " ", $canonical));
21104fd306cSNickeau                    $htmlMsg = PluginUtility::getDocumentationHyperLink($canonical, $label, false);
21204fd306cSNickeau                } else {
21304fd306cSNickeau                    $htmlMsg = PluginUtility::getDocumentationHyperLink("", PluginUtility::$PLUGIN_NAME, false);
21437748cd8SNickeau                }
21537748cd8SNickeau
21604fd306cSNickeau
21737748cd8SNickeau                /**
21837748cd8SNickeau                 * Adding page - context information
21937748cd8SNickeau                 * We are not creating the page
22004fd306cSNickeau                 * direction from {@link MarkupPath::createFromRequestedPage()}
22137748cd8SNickeau                 * because it throws an error message when the environment
22237748cd8SNickeau                 * is not good, creating a recursive call.
22337748cd8SNickeau                 */
22404fd306cSNickeau                global $INPUT;
22504fd306cSNickeau                $id = $INPUT->str("id");
22637748cd8SNickeau                if ($id != null) {
227c3437056SNickeau
228c3437056SNickeau                    /**
229c3437056SNickeau                     * We don't use any Page object to not
230c3437056SNickeau                     * create a cycle while building it
231c3437056SNickeau                     */
232c3437056SNickeau                    $url = wl($id, [], true);
233c3437056SNickeau                    $htmlMsg .= " - <a href=\"$url\">$id</a>";
234c3437056SNickeau
23537748cd8SNickeau                }
23637748cd8SNickeau
23737748cd8SNickeau                /**
23837748cd8SNickeau                 *
23937748cd8SNickeau                 */
24037748cd8SNickeau                $htmlMsg .= " - " . $message;
24137748cd8SNickeau                if ($level > self::LVL_MSG_DEBUG) {
24237748cd8SNickeau                    $dokuWikiLevel = self::LVL_TO_MSG_LEVEL[$level];
24304fd306cSNickeau                    if ($publicMessage) {
24404fd306cSNickeau                        $allow = MSG_PUBLIC;
24504fd306cSNickeau                    } else {
24604fd306cSNickeau                        $allow = MSG_USERS_ONLY;
24704fd306cSNickeau                    }
24804fd306cSNickeau                    msg($htmlMsg, $dokuWikiLevel, '', '', $allow);
24937748cd8SNickeau                }
25037748cd8SNickeau        }
25137748cd8SNickeau    }
25237748cd8SNickeau
25337748cd8SNickeau    /**
25437748cd8SNickeau     * Log a message to the browser console
25537748cd8SNickeau     * @param $message
25637748cd8SNickeau     */
25737748cd8SNickeau    public static function log2BrowserConsole($message)
25837748cd8SNickeau    {
25937748cd8SNickeau        // TODO
26037748cd8SNickeau    }
261c3437056SNickeau
26204fd306cSNickeau
26304fd306cSNickeau    /**
26404fd306cSNickeau     * @param $level
26504fd306cSNickeau     * @param $message
26604fd306cSNickeau     * @param $e - the original exception for chaining
26704fd306cSNickeau     * @return void
26804fd306cSNickeau     */
26904fd306cSNickeau    private static function throwErrorIfTest($level, $message, \Exception $e = null)
270c3437056SNickeau    {
271a535cfe0Sgerardnico        if (PluginUtility::isTest() && self::$throwExceptionOnDevTest) {
272a535cfe0Sgerardnico            try {
273a535cfe0Sgerardnico                $actualLevel = ExecutionContext::getExecutionContext()->getConfig()->getLogExceptionLevel();
274a535cfe0Sgerardnico            } catch (ExceptionNotFound $e) {
275a535cfe0Sgerardnico                // In context creation
276a535cfe0Sgerardnico                return;
277a535cfe0Sgerardnico            }
278a535cfe0Sgerardnico            if ($level >= $actualLevel) {
27904fd306cSNickeau                throw new LogException($message, $level, $e);
280c3437056SNickeau            }
281c3437056SNickeau        }
282a535cfe0Sgerardnico    }
283d61dea15Sgerardnico
284d61dea15Sgerardnico    /**
2854e9ad3c2Sgerardnico     * @param string|null $message
28604fd306cSNickeau     * @throws ExceptionCompile
287d61dea15Sgerardnico     */
2884e9ad3c2Sgerardnico    private static function messageNotEmpty(?string $message)
289d61dea15Sgerardnico    {
290d61dea15Sgerardnico        $message = trim($message);
2912fea41feSgerardnico        if ($message === null || $message === "") {
292d61dea15Sgerardnico            $newMessage = "The passed message to the log was empty or null. BackTrace: \n";
29333a4f219Sgerardnico            $newMessage .= LogUtility::getCallStack();
29404fd306cSNickeau            throw new ExceptionCompile($newMessage);
295d61dea15Sgerardnico        }
296d61dea15Sgerardnico    }
2970e43c1dbSgerardnico
2980e43c1dbSgerardnico    public static function disableThrowExceptionOnDevTest()
2990e43c1dbSgerardnico    {
3000e43c1dbSgerardnico        self::$throwExceptionOnDevTest = false;
3010e43c1dbSgerardnico    }
3020e43c1dbSgerardnico
3030e43c1dbSgerardnico    public static function enableThrowExceptionOnDevTest()
3040e43c1dbSgerardnico    {
3050e43c1dbSgerardnico        self::$throwExceptionOnDevTest = true;
3060e43c1dbSgerardnico    }
3074cadd4f8SNickeau
3084cadd4f8SNickeau    public static function wrapInRedForHtml(string $message): string
3094cadd4f8SNickeau    {
31004fd306cSNickeau        return "<span class=\"text-danger\">$message</span>";
3114cadd4f8SNickeau    }
31233a4f219Sgerardnico
31333a4f219Sgerardnico    /**
31433a4f219Sgerardnico     * @return false|string - the actual php call stack (known as backtrace)
31533a4f219Sgerardnico     */
31633a4f219Sgerardnico    public static function getCallStack()
31733a4f219Sgerardnico    {
31833a4f219Sgerardnico        ob_start();
31933a4f219Sgerardnico        $limit = 10;
32033a4f219Sgerardnico        /**
32133a4f219Sgerardnico         * DEBUG_BACKTRACE_IGNORE_ARGS options to avoid
32233a4f219Sgerardnico         * PHP Fatal error:  Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1876967424 bytes)
32333a4f219Sgerardnico         */
32433a4f219Sgerardnico        debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit); // It prints also the data passed :)
32533a4f219Sgerardnico        $trace = ob_get_contents();
32633a4f219Sgerardnico        ob_end_clean();
32733a4f219Sgerardnico        return $trace;
32833a4f219Sgerardnico    }
32904fd306cSNickeau
33004fd306cSNickeau    /**
33104fd306cSNickeau     * @param string $message the message
33204fd306cSNickeau     * @param string $canonical the page
33304fd306cSNickeau     * @param \Exception|null $e the original exception for trace chaining
33404fd306cSNickeau     * @return void
33504fd306cSNickeau     */
33604fd306cSNickeau    public static function error(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null)
33704fd306cSNickeau    {
33804fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_ERROR, $canonical, $e);
33904fd306cSNickeau    }
34004fd306cSNickeau
341*39c00e7eSNico    public static function warning(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null)
34204fd306cSNickeau    {
34304fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_WARNING, $canonical, $e);
34404fd306cSNickeau    }
34504fd306cSNickeau
346*39c00e7eSNico    public static function info(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null)
34704fd306cSNickeau    {
34804fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_INFO, $canonical, $e);
34904fd306cSNickeau    }
35004fd306cSNickeau
35104fd306cSNickeau    /**
35204fd306cSNickeau     * @param int $level
35304fd306cSNickeau     * @return void
35404fd306cSNickeau     * @deprecated use {@link SiteConfig::setLogExceptionLevel()}
35504fd306cSNickeau     */
35604fd306cSNickeau    public static function setTestExceptionLevel(int $level)
35704fd306cSNickeau    {
35804fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel($level);
35904fd306cSNickeau    }
36004fd306cSNickeau
36104fd306cSNickeau    public static function setTestExceptionLevelToDefault()
36204fd306cSNickeau    {
36304fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel(self::LVL_MSG_WARNING);
36404fd306cSNickeau    }
36504fd306cSNickeau
36604fd306cSNickeau    public static function errorIfDevOrTest($message, $canonical = "support")
36704fd306cSNickeau    {
36804fd306cSNickeau        if (PluginUtility::isDevOrTest()) {
36904fd306cSNickeau            LogUtility::error($message, $canonical);
37004fd306cSNickeau        }
37104fd306cSNickeau    }
37204fd306cSNickeau
37304fd306cSNickeau    /**
37404fd306cSNickeau     * @return void
37504fd306cSNickeau     * @deprecated use the config object instead
37604fd306cSNickeau     */
37704fd306cSNickeau    public static function setTestExceptionLevelToError()
37804fd306cSNickeau    {
37904fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionToError();
38004fd306cSNickeau    }
38104fd306cSNickeau
38204fd306cSNickeau    /**
38304fd306cSNickeau     * Advertise an error that should not take place if the code was
38404fd306cSNickeau     * written properly
38504fd306cSNickeau     * @param string $message
38604fd306cSNickeau     * @param string $canonical
38704fd306cSNickeau     * @param Throwable|null $previous
38804fd306cSNickeau     * @return void
38904fd306cSNickeau     */
39004fd306cSNickeau    public static function internalError(string $message, string $canonical = "support", Throwable $previous = null)
39104fd306cSNickeau    {
39204fd306cSNickeau        $internalErrorMessage = "Sorry. An internal error has occurred";
39304fd306cSNickeau        if (PluginUtility::isDevOrTest()) {
39404fd306cSNickeau            throw new ExceptionRuntimeInternal("$internalErrorMessage - $message", $canonical, 1, $previous);
39504fd306cSNickeau        } else {
39604fd306cSNickeau            $errorPreviousMessage = "";
39704fd306cSNickeau            if ($previous !== null) {
39804fd306cSNickeau                $errorPreviousMessage = " Error: {$previous->getMessage()}";
39904fd306cSNickeau            }
40004fd306cSNickeau            self::error("{$internalErrorMessage}: $message.$errorPreviousMessage", $canonical);
40104fd306cSNickeau        }
40204fd306cSNickeau    }
40304fd306cSNickeau
40404fd306cSNickeau    /**
40504fd306cSNickeau     * @param string $message
40604fd306cSNickeau     * @param string $canonical
40704fd306cSNickeau     * @param $e
40804fd306cSNickeau     * @return void
40904fd306cSNickeau     * Debug, trace
41004fd306cSNickeau     */
41104fd306cSNickeau    public static function debug(string $message, string $canonical = self::SUPPORT_CANONICAL, $e = null)
41204fd306cSNickeau    {
41304fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_DEBUG, $canonical, $e);
41404fd306cSNickeau    }
41504fd306cSNickeau
41604fd306cSNickeau    public static function infoToPublic(string $html, string $canonical)
41704fd306cSNickeau    {
41804fd306cSNickeau        self::log2FrontEnd($html, LogUtility::LVL_MSG_INFO, $canonical, true);
41904fd306cSNickeau    }
42004fd306cSNickeau
42137748cd8SNickeau}
422