xref: /plugin/combo/ComboStrap/LogUtility.php (revision 462bbef8cb078e2d73162fa3eba1ed36f0ef281c)
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
1539c00e7eSNicouse 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
7939c00e7eSNico     * @param \Exception|null $e
8037748cd8SNickeau     */
8139c00e7eSNico    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
12539c00e7eSNico     * @param string|null $canonical
12639c00e7eSNico     * @param \Exception|null $e
12737748cd8SNickeau     */
12839c00e7eSNico    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");
155*462bbef8SNico            $messageWritten = self::LVL_NAME[$logLevel] . " - $msg - (Page: $id, IP: {$INPUT->server->str('REMOTE_ADDR')})\n";
15639c00e7eSNico            // dokuwiki does not have the warning level
15739c00e7eSNico            Logger::error($messageWritten);
15804fd306cSNickeau            self::throwErrorIfTest($logLevel, $msg, $e);
159c3437056SNickeau
16037748cd8SNickeau        }
16137748cd8SNickeau
16237748cd8SNickeau    }
16337748cd8SNickeau
16437748cd8SNickeau    /**
16537748cd8SNickeau     * @param $message
16637748cd8SNickeau     * @param $level
167d61dea15Sgerardnico     * @param string $canonical
16839c00e7eSNico     * @param bool $publicMessage
16937748cd8SNickeau     */
17039c00e7eSNico    public static function log2FrontEnd($message, $level, string $canonical = self::SUPPORT_CANONICAL, bool $publicMessage = false)
17137748cd8SNickeau    {
172d61dea15Sgerardnico
173d61dea15Sgerardnico        try {
174d61dea15Sgerardnico            self::messageNotEmpty($message);
17504fd306cSNickeau        } catch (ExceptionCompile $e) {
176d61dea15Sgerardnico            $message = $e->getMessage();
177d61dea15Sgerardnico            $level = self::LVL_MSG_ERROR;
178d61dea15Sgerardnico        }
179d61dea15Sgerardnico
18037748cd8SNickeau        /**
18137748cd8SNickeau         * If we are not in the console
18237748cd8SNickeau         * and not in test
18337748cd8SNickeau         * we test that the message comes in the front end
18437748cd8SNickeau         * (example {@link \plugin_combo_frontmatter_test}
18537748cd8SNickeau         */
186c3437056SNickeau        $isTerminal = Console::isConsoleRun();
187c3437056SNickeau        if ($isTerminal) {
18837748cd8SNickeau            if (!defined('DOKU_UNITTEST')) {
189c3437056SNickeau                /**
190c3437056SNickeau                 * such as {@link cli_plugin_combo}
191c3437056SNickeau                 */
192c3437056SNickeau                $userAgent = "cli";
193c3437056SNickeau            } else {
194c3437056SNickeau                $userAgent = "phpunit";
19537748cd8SNickeau            }
196c3437056SNickeau        } else {
197c3437056SNickeau            $userAgent = "browser";
19837748cd8SNickeau        }
199c3437056SNickeau
200c3437056SNickeau        switch ($userAgent) {
201c3437056SNickeau            case "cli":
202c3437056SNickeau                echo "$message\n";
203c3437056SNickeau                break;
204c3437056SNickeau            case "phpunit":
205c3437056SNickeau            case "browser":
206c3437056SNickeau            default:
20704fd306cSNickeau                if ($canonical !== null) {
20804fd306cSNickeau                    $label = ucfirst(str_replace(":", " ", $canonical));
20904fd306cSNickeau                    $htmlMsg = PluginUtility::getDocumentationHyperLink($canonical, $label, false);
21004fd306cSNickeau                } else {
21104fd306cSNickeau                    $htmlMsg = PluginUtility::getDocumentationHyperLink("", PluginUtility::$PLUGIN_NAME, false);
21237748cd8SNickeau                }
21337748cd8SNickeau
21404fd306cSNickeau
21537748cd8SNickeau                /**
21637748cd8SNickeau                 * Adding page - context information
21737748cd8SNickeau                 * We are not creating the page
21804fd306cSNickeau                 * direction from {@link MarkupPath::createFromRequestedPage()}
21937748cd8SNickeau                 * because it throws an error message when the environment
22037748cd8SNickeau                 * is not good, creating a recursive call.
22137748cd8SNickeau                 */
22204fd306cSNickeau                global $INPUT;
22304fd306cSNickeau                $id = $INPUT->str("id");
22437748cd8SNickeau                if ($id != null) {
225c3437056SNickeau
226c3437056SNickeau                    /**
227c3437056SNickeau                     * We don't use any Page object to not
228c3437056SNickeau                     * create a cycle while building it
229c3437056SNickeau                     */
230c3437056SNickeau                    $url = wl($id, [], true);
231c3437056SNickeau                    $htmlMsg .= " - <a href=\"$url\">$id</a>";
232c3437056SNickeau
23337748cd8SNickeau                }
23437748cd8SNickeau
23537748cd8SNickeau                /**
23637748cd8SNickeau                 *
23737748cd8SNickeau                 */
23837748cd8SNickeau                $htmlMsg .= " - " . $message;
23937748cd8SNickeau                if ($level > self::LVL_MSG_DEBUG) {
24037748cd8SNickeau                    $dokuWikiLevel = self::LVL_TO_MSG_LEVEL[$level];
24104fd306cSNickeau                    if ($publicMessage) {
24204fd306cSNickeau                        $allow = MSG_PUBLIC;
24304fd306cSNickeau                    } else {
24404fd306cSNickeau                        $allow = MSG_USERS_ONLY;
24504fd306cSNickeau                    }
24604fd306cSNickeau                    msg($htmlMsg, $dokuWikiLevel, '', '', $allow);
24737748cd8SNickeau                }
24837748cd8SNickeau        }
24937748cd8SNickeau    }
25037748cd8SNickeau
25137748cd8SNickeau    /**
25237748cd8SNickeau     * Log a message to the browser console
25337748cd8SNickeau     * @param $message
25437748cd8SNickeau     */
25537748cd8SNickeau    public static function log2BrowserConsole($message)
25637748cd8SNickeau    {
25737748cd8SNickeau        // TODO
25837748cd8SNickeau    }
259c3437056SNickeau
26004fd306cSNickeau
26104fd306cSNickeau    /**
26204fd306cSNickeau     * @param $level
26304fd306cSNickeau     * @param $message
26404fd306cSNickeau     * @param $e - the original exception for chaining
26504fd306cSNickeau     * @return void
26604fd306cSNickeau     */
26704fd306cSNickeau    private static function throwErrorIfTest($level, $message, \Exception $e = null)
268c3437056SNickeau    {
269a535cfe0Sgerardnico        if (PluginUtility::isTest() && self::$throwExceptionOnDevTest) {
270a535cfe0Sgerardnico            try {
271a535cfe0Sgerardnico                $actualLevel = ExecutionContext::getExecutionContext()->getConfig()->getLogExceptionLevel();
272a535cfe0Sgerardnico            } catch (ExceptionNotFound $e) {
273a535cfe0Sgerardnico                // In context creation
274a535cfe0Sgerardnico                return;
275a535cfe0Sgerardnico            }
276a535cfe0Sgerardnico            if ($level >= $actualLevel) {
27704fd306cSNickeau                throw new LogException($message, $level, $e);
278c3437056SNickeau            }
279c3437056SNickeau        }
280a535cfe0Sgerardnico    }
281d61dea15Sgerardnico
282d61dea15Sgerardnico    /**
2834e9ad3c2Sgerardnico     * @param string|null $message
28404fd306cSNickeau     * @throws ExceptionCompile
285d61dea15Sgerardnico     */
2864e9ad3c2Sgerardnico    private static function messageNotEmpty(?string $message)
287d61dea15Sgerardnico    {
288d61dea15Sgerardnico        $message = trim($message);
2892fea41feSgerardnico        if ($message === null || $message === "") {
290d61dea15Sgerardnico            $newMessage = "The passed message to the log was empty or null. BackTrace: \n";
29133a4f219Sgerardnico            $newMessage .= LogUtility::getCallStack();
29204fd306cSNickeau            throw new ExceptionCompile($newMessage);
293d61dea15Sgerardnico        }
294d61dea15Sgerardnico    }
2950e43c1dbSgerardnico
2960e43c1dbSgerardnico    public static function disableThrowExceptionOnDevTest()
2970e43c1dbSgerardnico    {
2980e43c1dbSgerardnico        self::$throwExceptionOnDevTest = false;
2990e43c1dbSgerardnico    }
3000e43c1dbSgerardnico
3010e43c1dbSgerardnico    public static function enableThrowExceptionOnDevTest()
3020e43c1dbSgerardnico    {
3030e43c1dbSgerardnico        self::$throwExceptionOnDevTest = true;
3040e43c1dbSgerardnico    }
3054cadd4f8SNickeau
3064cadd4f8SNickeau    public static function wrapInRedForHtml(string $message): string
3074cadd4f8SNickeau    {
30804fd306cSNickeau        return "<span class=\"text-danger\">$message</span>";
3094cadd4f8SNickeau    }
31033a4f219Sgerardnico
31133a4f219Sgerardnico    /**
31233a4f219Sgerardnico     * @return false|string - the actual php call stack (known as backtrace)
31333a4f219Sgerardnico     */
31433a4f219Sgerardnico    public static function getCallStack()
31533a4f219Sgerardnico    {
31633a4f219Sgerardnico        ob_start();
31733a4f219Sgerardnico        $limit = 10;
31833a4f219Sgerardnico        /**
31933a4f219Sgerardnico         * DEBUG_BACKTRACE_IGNORE_ARGS options to avoid
32033a4f219Sgerardnico         * PHP Fatal error:  Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1876967424 bytes)
32133a4f219Sgerardnico         */
32233a4f219Sgerardnico        debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit); // It prints also the data passed :)
32333a4f219Sgerardnico        $trace = ob_get_contents();
32433a4f219Sgerardnico        ob_end_clean();
32533a4f219Sgerardnico        return $trace;
32633a4f219Sgerardnico    }
32704fd306cSNickeau
32804fd306cSNickeau    /**
32904fd306cSNickeau     * @param string $message the message
33004fd306cSNickeau     * @param string $canonical the page
33104fd306cSNickeau     * @param \Exception|null $e the original exception for trace chaining
33204fd306cSNickeau     * @return void
33304fd306cSNickeau     */
33404fd306cSNickeau    public static function error(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null)
33504fd306cSNickeau    {
33604fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_ERROR, $canonical, $e);
33704fd306cSNickeau    }
33804fd306cSNickeau
33939c00e7eSNico    public static function warning(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null)
34004fd306cSNickeau    {
34104fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_WARNING, $canonical, $e);
34204fd306cSNickeau    }
34304fd306cSNickeau
34439c00e7eSNico    public static function info(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null)
34504fd306cSNickeau    {
34604fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_INFO, $canonical, $e);
34704fd306cSNickeau    }
34804fd306cSNickeau
34904fd306cSNickeau    /**
35004fd306cSNickeau     * @param int $level
35104fd306cSNickeau     * @return void
35204fd306cSNickeau     * @deprecated use {@link SiteConfig::setLogExceptionLevel()}
35304fd306cSNickeau     */
35404fd306cSNickeau    public static function setTestExceptionLevel(int $level)
35504fd306cSNickeau    {
35604fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel($level);
35704fd306cSNickeau    }
35804fd306cSNickeau
35904fd306cSNickeau    public static function setTestExceptionLevelToDefault()
36004fd306cSNickeau    {
36104fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel(self::LVL_MSG_WARNING);
36204fd306cSNickeau    }
36304fd306cSNickeau
36404fd306cSNickeau    public static function errorIfDevOrTest($message, $canonical = "support")
36504fd306cSNickeau    {
36604fd306cSNickeau        if (PluginUtility::isDevOrTest()) {
36704fd306cSNickeau            LogUtility::error($message, $canonical);
36804fd306cSNickeau        }
36904fd306cSNickeau    }
37004fd306cSNickeau
37104fd306cSNickeau    /**
37204fd306cSNickeau     * @return void
37304fd306cSNickeau     * @deprecated use the config object instead
37404fd306cSNickeau     */
37504fd306cSNickeau    public static function setTestExceptionLevelToError()
37604fd306cSNickeau    {
37704fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionToError();
37804fd306cSNickeau    }
37904fd306cSNickeau
38004fd306cSNickeau    /**
38104fd306cSNickeau     * Advertise an error that should not take place if the code was
38204fd306cSNickeau     * written properly
38304fd306cSNickeau     * @param string $message
38404fd306cSNickeau     * @param string $canonical
38504fd306cSNickeau     * @param Throwable|null $previous
38604fd306cSNickeau     * @return void
38704fd306cSNickeau     */
38804fd306cSNickeau    public static function internalError(string $message, string $canonical = "support", Throwable $previous = null)
38904fd306cSNickeau    {
39004fd306cSNickeau        $internalErrorMessage = "Sorry. An internal error has occurred";
39104fd306cSNickeau        if (PluginUtility::isDevOrTest()) {
39204fd306cSNickeau            throw new ExceptionRuntimeInternal("$internalErrorMessage - $message", $canonical, 1, $previous);
39304fd306cSNickeau        } else {
39404fd306cSNickeau            $errorPreviousMessage = "";
39504fd306cSNickeau            if ($previous !== null) {
39604fd306cSNickeau                $errorPreviousMessage = " Error: {$previous->getMessage()}";
39704fd306cSNickeau            }
39804fd306cSNickeau            self::error("{$internalErrorMessage}: $message.$errorPreviousMessage", $canonical);
39904fd306cSNickeau        }
40004fd306cSNickeau    }
40104fd306cSNickeau
40204fd306cSNickeau    /**
40304fd306cSNickeau     * @param string $message
40404fd306cSNickeau     * @param string $canonical
40504fd306cSNickeau     * @param $e
40604fd306cSNickeau     * @return void
40704fd306cSNickeau     * Debug, trace
40804fd306cSNickeau     */
40904fd306cSNickeau    public static function debug(string $message, string $canonical = self::SUPPORT_CANONICAL, $e = null)
41004fd306cSNickeau    {
41104fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_DEBUG, $canonical, $e);
41204fd306cSNickeau    }
41304fd306cSNickeau
41404fd306cSNickeau    public static function infoToPublic(string $html, string $canonical)
41504fd306cSNickeau    {
41604fd306cSNickeau        self::log2FrontEnd($html, LogUtility::LVL_MSG_INFO, $canonical, true);
41704fd306cSNickeau    }
41804fd306cSNickeau
41937748cd8SNickeau}
420