xref: /plugin/combo/ComboStrap/LogUtility.php (revision a535cfe0d323d4c195de9a327242253b83aec138)
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
1504fd306cSNickeauuse Throwable;
1604fd306cSNickeau
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     */
2704fd306cSNickeau    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";
6004fd306cSNickeau    const SUPPORT_CANONICAL = "support";
6104fd306cSNickeau
620e43c1dbSgerardnico    /**
630e43c1dbSgerardnico     *
640e43c1dbSgerardnico     * @var bool
650e43c1dbSgerardnico     */
6604fd306cSNickeau    private static bool $throwExceptionOnDevTest = true;
6704fd306cSNickeau    /**
6804fd306cSNickeau     * @var int
6904fd306cSNickeau     */
7004fd306cSNickeau    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     */
7904fd306cSNickeau    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);
8404fd306cSNickeau        } 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) {
10604fd306cSNickeau            self::log2file($message, $level, $canonical, $e);
1072f4da794Sgerardnico        }
10837748cd8SNickeau
10937748cd8SNickeau        /**
11037748cd8SNickeau         * If test, we throw an error
11137748cd8SNickeau         */
11204fd306cSNickeau        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
12404fd306cSNickeau     * @param \Exception $e
12537748cd8SNickeau     */
12604fd306cSNickeau    static function log2file(?string $msg, int $logLevel = self::LVL_MSG_ERROR, $canonical = null, \Exception $e = null)
12737748cd8SNickeau    {
12837748cd8SNickeau
129d61dea15Sgerardnico        try {
130d61dea15Sgerardnico            self::messageNotEmpty($msg);
13104fd306cSNickeau        } 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
14904fd306cSNickeau             * 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             */
15304fd306cSNickeau            $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
16404fd306cSNickeau            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     */
17704fd306cSNickeau    public static function log2FrontEnd($message, $level, $canonical = "support", bool $publicMessage = false)
17837748cd8SNickeau    {
179d61dea15Sgerardnico
180d61dea15Sgerardnico        try {
181d61dea15Sgerardnico            self::messageNotEmpty($message);
18204fd306cSNickeau        } 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:
21404fd306cSNickeau                if ($canonical !== null) {
21504fd306cSNickeau                    $label = ucfirst(str_replace(":", " ", $canonical));
21604fd306cSNickeau                    $htmlMsg = PluginUtility::getDocumentationHyperLink($canonical, $label, false);
21704fd306cSNickeau                } else {
21804fd306cSNickeau                    $htmlMsg = PluginUtility::getDocumentationHyperLink("", PluginUtility::$PLUGIN_NAME, false);
21937748cd8SNickeau                }
22037748cd8SNickeau
22104fd306cSNickeau
22237748cd8SNickeau                /**
22337748cd8SNickeau                 * Adding page - context information
22437748cd8SNickeau                 * We are not creating the page
22504fd306cSNickeau                 * direction from {@link MarkupPath::createFromRequestedPage()}
22637748cd8SNickeau                 * because it throws an error message when the environment
22737748cd8SNickeau                 * is not good, creating a recursive call.
22837748cd8SNickeau                 */
22904fd306cSNickeau                global $INPUT;
23004fd306cSNickeau                $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];
24804fd306cSNickeau                    if ($publicMessage) {
24904fd306cSNickeau                        $allow = MSG_PUBLIC;
25004fd306cSNickeau                    } else {
25104fd306cSNickeau                        $allow = MSG_USERS_ONLY;
25204fd306cSNickeau                    }
25304fd306cSNickeau                    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
26704fd306cSNickeau
26804fd306cSNickeau    /**
26904fd306cSNickeau     * @param $level
27004fd306cSNickeau     * @param $message
27104fd306cSNickeau     * @param $e - the original exception for chaining
27204fd306cSNickeau     * @return void
27304fd306cSNickeau     */
27404fd306cSNickeau    private static function throwErrorIfTest($level, $message, \Exception $e = null)
275c3437056SNickeau    {
276*a535cfe0Sgerardnico        if (PluginUtility::isTest() && self::$throwExceptionOnDevTest) {
277*a535cfe0Sgerardnico            try {
278*a535cfe0Sgerardnico                $actualLevel = ExecutionContext::getExecutionContext()->getConfig()->getLogExceptionLevel();
279*a535cfe0Sgerardnico            } catch (ExceptionNotFound $e) {
280*a535cfe0Sgerardnico                // In context creation
281*a535cfe0Sgerardnico                return;
282*a535cfe0Sgerardnico            }
283*a535cfe0Sgerardnico            if ($level >= $actualLevel) {
28404fd306cSNickeau                throw new LogException($message, $level, $e);
285c3437056SNickeau            }
286c3437056SNickeau        }
287*a535cfe0Sgerardnico    }
288d61dea15Sgerardnico
289d61dea15Sgerardnico    /**
2904e9ad3c2Sgerardnico     * @param string|null $message
29104fd306cSNickeau     * @throws ExceptionCompile
292d61dea15Sgerardnico     */
2934e9ad3c2Sgerardnico    private static function messageNotEmpty(?string $message)
294d61dea15Sgerardnico    {
295d61dea15Sgerardnico        $message = trim($message);
2962fea41feSgerardnico        if ($message === null || $message === "") {
297d61dea15Sgerardnico            $newMessage = "The passed message to the log was empty or null. BackTrace: \n";
29833a4f219Sgerardnico            $newMessage .= LogUtility::getCallStack();
29904fd306cSNickeau            throw new ExceptionCompile($newMessage);
300d61dea15Sgerardnico        }
301d61dea15Sgerardnico    }
3020e43c1dbSgerardnico
3030e43c1dbSgerardnico    public static function disableThrowExceptionOnDevTest()
3040e43c1dbSgerardnico    {
3050e43c1dbSgerardnico        self::$throwExceptionOnDevTest = false;
3060e43c1dbSgerardnico    }
3070e43c1dbSgerardnico
3080e43c1dbSgerardnico    public static function enableThrowExceptionOnDevTest()
3090e43c1dbSgerardnico    {
3100e43c1dbSgerardnico        self::$throwExceptionOnDevTest = true;
3110e43c1dbSgerardnico    }
3124cadd4f8SNickeau
3134cadd4f8SNickeau    public static function wrapInRedForHtml(string $message): string
3144cadd4f8SNickeau    {
31504fd306cSNickeau        return "<span class=\"text-danger\">$message</span>";
3164cadd4f8SNickeau    }
31733a4f219Sgerardnico
31833a4f219Sgerardnico    /**
31933a4f219Sgerardnico     * @return false|string - the actual php call stack (known as backtrace)
32033a4f219Sgerardnico     */
32133a4f219Sgerardnico    public static function getCallStack()
32233a4f219Sgerardnico    {
32333a4f219Sgerardnico        ob_start();
32433a4f219Sgerardnico        $limit = 10;
32533a4f219Sgerardnico        /**
32633a4f219Sgerardnico         * DEBUG_BACKTRACE_IGNORE_ARGS options to avoid
32733a4f219Sgerardnico         * PHP Fatal error:  Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1876967424 bytes)
32833a4f219Sgerardnico         */
32933a4f219Sgerardnico        debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit); // It prints also the data passed :)
33033a4f219Sgerardnico        $trace = ob_get_contents();
33133a4f219Sgerardnico        ob_end_clean();
33233a4f219Sgerardnico        return $trace;
33333a4f219Sgerardnico    }
33404fd306cSNickeau
33504fd306cSNickeau    /**
33604fd306cSNickeau     * @param string $message the message
33704fd306cSNickeau     * @param string $canonical the page
33804fd306cSNickeau     * @param \Exception|null $e the original exception for trace chaining
33904fd306cSNickeau     * @return void
34004fd306cSNickeau     */
34104fd306cSNickeau    public static function error(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null)
34204fd306cSNickeau    {
34304fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_ERROR, $canonical, $e);
34404fd306cSNickeau    }
34504fd306cSNickeau
34604fd306cSNickeau    public static function warning(string $message, string $canonical = "support", \Exception $e = null)
34704fd306cSNickeau    {
34804fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_WARNING, $canonical, $e);
34904fd306cSNickeau    }
35004fd306cSNickeau
35104fd306cSNickeau    public static function info(string $message, string $canonical = "support", \Exception $e = null)
35204fd306cSNickeau    {
35304fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_INFO, $canonical, $e);
35404fd306cSNickeau    }
35504fd306cSNickeau
35604fd306cSNickeau    /**
35704fd306cSNickeau     * @param int $level
35804fd306cSNickeau     * @return void
35904fd306cSNickeau     * @deprecated use {@link SiteConfig::setLogExceptionLevel()}
36004fd306cSNickeau     */
36104fd306cSNickeau    public static function setTestExceptionLevel(int $level)
36204fd306cSNickeau    {
36304fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel($level);
36404fd306cSNickeau    }
36504fd306cSNickeau
36604fd306cSNickeau    public static function setTestExceptionLevelToDefault()
36704fd306cSNickeau    {
36804fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel(self::LVL_MSG_WARNING);
36904fd306cSNickeau    }
37004fd306cSNickeau
37104fd306cSNickeau    public static function errorIfDevOrTest($message, $canonical = "support")
37204fd306cSNickeau    {
37304fd306cSNickeau        if (PluginUtility::isDevOrTest()) {
37404fd306cSNickeau            LogUtility::error($message, $canonical);
37504fd306cSNickeau        }
37604fd306cSNickeau    }
37704fd306cSNickeau
37804fd306cSNickeau    /**
37904fd306cSNickeau     * @return void
38004fd306cSNickeau     * @deprecated use the config object instead
38104fd306cSNickeau     */
38204fd306cSNickeau    public static function setTestExceptionLevelToError()
38304fd306cSNickeau    {
38404fd306cSNickeau        ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionToError();
38504fd306cSNickeau    }
38604fd306cSNickeau
38704fd306cSNickeau    /**
38804fd306cSNickeau     * Advertise an error that should not take place if the code was
38904fd306cSNickeau     * written properly
39004fd306cSNickeau     * @param string $message
39104fd306cSNickeau     * @param string $canonical
39204fd306cSNickeau     * @param Throwable|null $previous
39304fd306cSNickeau     * @return void
39404fd306cSNickeau     */
39504fd306cSNickeau    public static function internalError(string $message, string $canonical = "support", Throwable $previous = null)
39604fd306cSNickeau    {
39704fd306cSNickeau        $internalErrorMessage = "Sorry. An internal error has occurred";
39804fd306cSNickeau        if (PluginUtility::isDevOrTest()) {
39904fd306cSNickeau            throw new ExceptionRuntimeInternal("$internalErrorMessage - $message", $canonical, 1, $previous);
40004fd306cSNickeau        } else {
40104fd306cSNickeau            $errorPreviousMessage = "";
40204fd306cSNickeau            if ($previous !== null) {
40304fd306cSNickeau                $errorPreviousMessage = " Error: {$previous->getMessage()}";
40404fd306cSNickeau            }
40504fd306cSNickeau            self::error("{$internalErrorMessage}: $message.$errorPreviousMessage", $canonical);
40604fd306cSNickeau        }
40704fd306cSNickeau    }
40804fd306cSNickeau
40904fd306cSNickeau    /**
41004fd306cSNickeau     * @param string $message
41104fd306cSNickeau     * @param string $canonical
41204fd306cSNickeau     * @param $e
41304fd306cSNickeau     * @return void
41404fd306cSNickeau     * Debug, trace
41504fd306cSNickeau     */
41604fd306cSNickeau    public static function debug(string $message, string $canonical = self::SUPPORT_CANONICAL, $e = null)
41704fd306cSNickeau    {
41804fd306cSNickeau        self::msg($message, LogUtility::LVL_MSG_DEBUG, $canonical, $e);
41904fd306cSNickeau    }
42004fd306cSNickeau
42104fd306cSNickeau    public static function infoToPublic(string $html, string $canonical)
42204fd306cSNickeau    {
42304fd306cSNickeau        self::log2FrontEnd($html, LogUtility::LVL_MSG_INFO, $canonical, true);
42404fd306cSNickeau    }
42504fd306cSNickeau
42637748cd8SNickeau}
427