*
*/
namespace ComboStrap;
use Throwable;
require_once(__DIR__ . '/PluginUtility.php');
class LogUtility
{
/**
* Constant for the function {@link msg()}
* -1 = error, 0 = info, 1 = success, 2 = notify
* (Not even in order of importance)
*/
const LVL_MSG_ABOVE_ERROR = 5; // a level to disable the error to thrown in test
const LVL_MSG_ERROR = 4; //-1;
const LVL_MSG_WARNING = 3; //2;
const LVL_MSG_SUCCESS = 2; //1;
const LVL_MSG_INFO = 1; //0;
const LVL_MSG_DEBUG = 0; //3;
/**
* Id level to name
*/
const LVL_NAME = array(
0 => "debug",
1 => "info",
3 => "warning",
2 => "success",
4 => "error"
);
/**
* Id level to name
* {@link msg()} constant
*/
const LVL_TO_MSG_LEVEL = array(
0 => 3,
1 => 0,
2 => 1,
3 => 2,
4 => -1
);
const LOGLEVEL_URI_QUERY_PROPERTY = "loglevel";
const SUPPORT_CANONICAL = "support";
/**
*
* @var bool
*/
private static bool $throwExceptionOnDevTest = true;
/**
* @var int
*/
const DEFAULT_THROW_LEVEL = self::LVL_MSG_WARNING;
/**
* Send a message to a manager and log it
* Fail if in test
* @param string $message
* @param int $level - the level see LVL constant
* @param string $canonical - the canonical
*/
public static function msg(string $message, int $level = self::LVL_MSG_ERROR, string $canonical = "support", \Exception $e = null)
{
try {
self::messageNotEmpty($message);
} catch (ExceptionCompile $e) {
self::log2file($e->getMessage(), LogUtility::LVL_MSG_ERROR, $canonical);
}
/**
* Log to frontend
*/
self::log2FrontEnd($message, $level, $canonical);
/**
* Log level passed for a page (only for file used)
* to not allow an attacker to see all errors in frontend
*/
global $INPUT;
$loglevelProp = $INPUT->str(self::LOGLEVEL_URI_QUERY_PROPERTY, null);
if (!empty($loglevelProp)) {
$level = $loglevelProp;
}
/**
* TODO: Make it a configuration ?
*/
if ($level >= self::LVL_MSG_WARNING) {
self::log2file($message, $level, $canonical, $e);
}
/**
* If test, we throw an error
*/
self::throwErrorIfTest($level, $message, $e);
}
/**
* Print log to a file
*
* Adapted from {@link dbglog}
* Note: {@link dbg()} dbg print to the web page
*
* @param null|string $msg - may be null always this is the default if a variable is not initialized.
* @param int $logLevel
* @param null $canonical
* @param \Exception $e
*/
static function log2file(?string $msg, int $logLevel = self::LVL_MSG_ERROR, $canonical = null, \Exception $e = null)
{
try {
self::messageNotEmpty($msg);
} catch (ExceptionCompile $e) {
$msg = $e->getMessage();
$logLevel = self::LVL_MSG_ERROR;
}
if (PluginUtility::isTest() || $logLevel >= self::LVL_MSG_WARNING) {
$prefix = PluginUtility::$PLUGIN_NAME;
if (!empty($canonical)) {
$prefix .= ' - ' . $canonical;
}
$msg = $prefix . ' - ' . $msg;
global $INPUT;
global $conf;
/**
* Adding page - context information
* We are not using {@link MarkupPath::createFromRequestedPage()}
* because it throws an error message when the environment
* is not good, creating a recursive call.
*/
$id = $INPUT->str("id");
$file = $conf['cachedir'] . '/debug.log';
$fh = fopen($file, 'a');
if ($fh) {
$sep = " - ";
fwrite($fh, date('c') . $sep . self::LVL_NAME[$logLevel] . $sep . $msg . $sep . $INPUT->server->str('REMOTE_ADDR') . $sep . $id . "\n");
fclose($fh);
}
self::throwErrorIfTest($logLevel, $msg, $e);
}
}
/**
* @param $message
* @param $level
* @param string $canonical
* @param bool $withIconURL
*/
public static function log2FrontEnd($message, $level, $canonical = "support", bool $publicMessage = false)
{
try {
self::messageNotEmpty($message);
} catch (ExceptionCompile $e) {
$message = $e->getMessage();
$level = self::LVL_MSG_ERROR;
}
/**
* If we are not in the console
* and not in test
* we test that the message comes in the front end
* (example {@link \plugin_combo_frontmatter_test}
*/
$isTerminal = Console::isConsoleRun();
if ($isTerminal) {
if (!defined('DOKU_UNITTEST')) {
/**
* such as {@link cli_plugin_combo}
*/
$userAgent = "cli";
} else {
$userAgent = "phpunit";
}
} else {
$userAgent = "browser";
}
switch ($userAgent) {
case "cli":
echo "$message\n";
break;
case "phpunit":
case "browser":
default:
if ($canonical !== null) {
$label = ucfirst(str_replace(":", " ", $canonical));
$htmlMsg = PluginUtility::getDocumentationHyperLink($canonical, $label, false);
} else {
$htmlMsg = PluginUtility::getDocumentationHyperLink("", PluginUtility::$PLUGIN_NAME, false);
}
/**
* Adding page - context information
* We are not creating the page
* direction from {@link MarkupPath::createFromRequestedPage()}
* because it throws an error message when the environment
* is not good, creating a recursive call.
*/
global $INPUT;
$id = $INPUT->str("id");
if ($id != null) {
/**
* We don't use any Page object to not
* create a cycle while building it
*/
$url = wl($id, [], true);
$htmlMsg .= " - $id";
}
/**
*
*/
$htmlMsg .= " - " . $message;
if ($level > self::LVL_MSG_DEBUG) {
$dokuWikiLevel = self::LVL_TO_MSG_LEVEL[$level];
if ($publicMessage) {
$allow = MSG_PUBLIC;
} else {
$allow = MSG_USERS_ONLY;
}
msg($htmlMsg, $dokuWikiLevel, '', '',$allow);
}
}
}
/**
* Log a message to the browser console
* @param $message
*/
public static function log2BrowserConsole($message)
{
// TODO
}
/**
* @param $level
* @param $message
* @param $e - the original exception for chaining
* @return void
*/
private static function throwErrorIfTest($level, $message, \Exception $e = null)
{
$actualLevel = ExecutionContext::getActualOrCreateFromEnv()->getConfig()
->getLogExceptionLevel();
if (PluginUtility::isTest()
&& ($level >= $actualLevel)
&& self::$throwExceptionOnDevTest
) {
throw new LogException($message, $level, $e);
}
}
/**
* @param string|null $message
* @throws ExceptionCompile
*/
private static function messageNotEmpty(?string $message)
{
$message = trim($message);
if ($message === null || $message === "") {
$newMessage = "The passed message to the log was empty or null. BackTrace: \n";
$newMessage .= LogUtility::getCallStack();
throw new ExceptionCompile($newMessage);
}
}
public static function disableThrowExceptionOnDevTest()
{
self::$throwExceptionOnDevTest = false;
}
public static function enableThrowExceptionOnDevTest()
{
self::$throwExceptionOnDevTest = true;
}
public static function wrapInRedForHtml(string $message): string
{
return "$message";
}
/**
* @return false|string - the actual php call stack (known as backtrace)
*/
public static function getCallStack()
{
ob_start();
$limit = 10;
/**
* DEBUG_BACKTRACE_IGNORE_ARGS options to avoid
* PHP Fatal error: Allowed memory size of 2147483648 bytes exhausted (tried to allocate 1876967424 bytes)
*/
debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, $limit); // It prints also the data passed :)
$trace = ob_get_contents();
ob_end_clean();
return $trace;
}
/**
* @param string $message the message
* @param string $canonical the page
* @param \Exception|null $e the original exception for trace chaining
* @return void
*/
public static function error(string $message, string $canonical = self::SUPPORT_CANONICAL, \Exception $e = null)
{
self::msg($message, LogUtility::LVL_MSG_ERROR, $canonical, $e);
}
public static function warning(string $message, string $canonical = "support", \Exception $e = null)
{
self::msg($message, LogUtility::LVL_MSG_WARNING, $canonical, $e);
}
public static function info(string $message, string $canonical = "support", \Exception $e = null)
{
self::msg($message, LogUtility::LVL_MSG_INFO, $canonical, $e);
}
/**
* @param int $level
* @return void
* @deprecated use {@link SiteConfig::setLogExceptionLevel()}
*/
public static function setTestExceptionLevel(int $level)
{
ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel($level);
}
public static function setTestExceptionLevelToDefault()
{
ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionLevel(self::LVL_MSG_WARNING);
}
public static function errorIfDevOrTest($message, $canonical = "support")
{
if (PluginUtility::isDevOrTest()) {
LogUtility::error($message, $canonical);
}
}
/**
* @return void
* @deprecated use the config object instead
*/
public static function setTestExceptionLevelToError()
{
ExecutionContext::getActualOrCreateFromEnv()->getConfig()->setLogExceptionToError();
}
/**
* Advertise an error that should not take place if the code was
* written properly
* @param string $message
* @param string $canonical
* @param Throwable|null $previous
* @return void
*/
public static function internalError(string $message, string $canonical = "support", Throwable $previous = null)
{
$internalErrorMessage = "Sorry. An internal error has occurred";
if (PluginUtility::isDevOrTest()) {
throw new ExceptionRuntimeInternal("$internalErrorMessage - $message", $canonical, 1, $previous);
} else {
$errorPreviousMessage = "";
if ($previous !== null) {
$errorPreviousMessage = " Error: {$previous->getMessage()}";
}
self::error("{$internalErrorMessage}: $message.$errorPreviousMessage", $canonical);
}
}
/**
* @param string $message
* @param string $canonical
* @param $e
* @return void
* Debug, trace
*/
public static function debug(string $message, string $canonical = self::SUPPORT_CANONICAL, $e = null)
{
self::msg($message, LogUtility::LVL_MSG_DEBUG, $canonical, $e);
}
public static function infoToPublic(string $html, string $canonical)
{
self::log2FrontEnd($html, LogUtility::LVL_MSG_INFO, $canonical, true);
}
}