* @license BSD */ namespace Domframework; /** * The logger class permit to log the information from the soft * It allow to debug too */ class Logger { /* The logger class can be used with : $d=new logger; $d->timezone = "Europe/Paris"; $d->logtype="display|file"; $d->logfile="/tmp/toto.log"; $d->logwrite ("Super log default"); $d->logwrite ("Super log DEBUG", $d::DEBUG); $d->logwrite ("Super log NOTICE", $d::NOTICE); */ // TODO : Add SQL support /** * The method to log. * Can be display, file, syslog, stderr, session * Can be merged with a pipe : "display|syslog|file" or put in array */ public $logtype = "stderr"; /** * For logtype=file, the filename to use */ public $logfile = false; /** * Timezone use to save the logs */ public $timezone = "UTC"; /** * Minimum log level in the logs */ public $loglevelmin = LOG_NOTICE; /** * In Syslog mode, the facility to use * See http://fr2.php.net/manual/en/function.openlog.php for $syslogFacility */ public $syslogFacility = LOG_USER; /** * In Syslog, prefix the log by the text */ public $syslogPrefix = false; /** * Remove X entries of backtrace to see the right file generating the error */ public $backTraceSkip = 1; /** * Display the backtrace in all the messages */ public $backtraceDisplay = false; /** * The priorities which can be used in the priorities * LOG_EMERG system is unusable * LOG_ALERT action must be taken immediately * LOG_CRIT critical conditions * LOG_ERR error conditions * LOG_WARNING warning conditions * LOG_NOTICE normal, but significant, condition * LOG_INFO informational message * LOG_DEBUG debug-level message */ private $priorities = [ LOG_EMERG => "EMERG", LOG_ALERT => "ALERT", LOG_CRIT => "CRITICAL", LOG_ERR => "ERROR", LOG_WARNING => "WARNING", LOG_NOTICE => "NOTICE", LOG_INFO => "INFO", LOG_DEBUG => "DEBUG", ]; /** * Catch all the messages raised by the PHP, in trigger_error * Use the properties of the logger to save the logs */ public function catchAll() { set_error_handler([&$this, "errorHandler"]); } /** * The error handler to log * @param integer $errno The error number to use * @param string $errstr The error message to save * @param string $errfile The file which generate the error * @param integer $errline The line where the error is generated */ public function errorHandler($errno, $errstr, $errfile, $errline) { if (!(error_reporting() & $errno)) { // This error code is not included in error_reporting return; } $priority = LOG_ERR; switch ($errno) { case E_ERROR: $priority = LOG_ERR; break; case E_WARNING: $priority = LOG_WARNING; break; case E_PARSE: $priority = LOG_ERR; break; case E_NOTICE: $priority = LOG_NOTICE; break; case E_CORE_ERROR: $priority = LOG_ERR; break; case E_CORE_WARNING: $priority = LOG_WARNING; break; case E_COMPILE_ERROR: $priority = LOG_ERR; break; case E_COMPILE_WARNING: $priority = LOG_WARNING; break; case E_USER_ERROR: $priority = LOG_ERR; break; case E_USER_WARNING: $priority = LOG_WARNING; break; case E_USER_NOTICE: $priority = LOG_NOTICE; break; case E_STRICT: $priority = LOG_WARNING; break; case E_RECOVERABLE_ERROR: $priority = LOG_ERR; break; case E_DEPRECATED: $priority = LOG_WARNING; break; case E_USER_DEPRECATED: $priority = LOG_WARNING; break; } $this->log($priority, $errstr); } /** * Store a new message log in the log manager defined by $logtype * The message can be multiple types. An array will be stored in textual form * but it will accept only one depth. * The $message is not fixed, you can pass the number of parameters you want * @param integer|null $priority Priority to use * @param mixed ...$message Message to log */ public function log($priority, $message) { if ($priority === null) { $priority = LOG_NOTICE; } if (! is_int($priority)) { switch ($priority) { case "LOG_EMERG": $priority = LOG_EMERG; break; case "LOG_ALERT": $priority = LOG_ALERT; break; case "LOG_CRIT": $priority = LOG_CRIT; break; case "LOG_ERR": $priority = LOG_ERR; break; case "LOG_WARNING": $priority = LOG_WARNING; break; case "LOG_NOTICE": $priority = LOG_NOTICE; break; case "LOG_INFO": $priority = LOG_INFO; break; case "LOG_DEBUG": $priority = LOG_DEBUG; break; } } if (! is_int($this->loglevelmin)) { switch ($this->loglevelmin) { case "LOG_EMERG": $this->loglevelmin = LOG_EMERG; break; case "LOG_ALERT": $this->loglevelmin = LOG_ALERT; break; case "LOG_CRIT": $this->loglevelmin = LOG_CRIT; break; case "LOG_ERR": $this->loglevelmin = LOG_ERR; break; case "LOG_WARNING": $this->loglevelmin = LOG_WARNING; break; case "LOG_NOTICE": $this->loglevelmin = LOG_NOTICE; break; case "LOG_INFO": $this->loglevelmin = LOG_INFO; break; case "LOG_DEBUG": $this->loglevelmin = LOG_DEBUG; break; } } if ($this->loglevelmin < $priority) { return; } $back = []; $backtrace = debug_backtrace(); for ($i = 0; $i <= $this->backTraceSkip; $i++) { $back = array_shift($backtrace); } while (! array_key_exists("file", $back) && count($backtrace) > 0) { $back = array_shift($backtrace); } $msg = ""; if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) { $msg .= "[" . $_SERVER["HTTP_X_FORWARDED_FOR"] . "] "; } elseif (isset($_SERVER["REMOTE_ADDR"])) { $msg .= "[" . $_SERVER["REMOTE_ADDR"] . "] "; } // The messages are all the parameters of the function except the first, // which is the priority $messages = func_get_args(); array_shift($messages); // Convert each part of message to text foreach ($messages as $m) { if (is_array($m)) { $msg .= "["; foreach ($m as $key => $val) { if (is_array($val)) { foreach ($val as $key2 => $val2) { $msg .= "$key2=>$val2,"; } } else { $msg .= "$key=>$val,"; } } $msg .= "]"; } else { $msg .= $m; } } // Add the filename which generate the error $msg .= " [" . basename($back["file"]) . ":" . $back["line"] . "]"; // Display the backtrace if it is needed if ($this->backtraceDisplay) { $e = new \Exception(); $msg .= "\n" . ($e->getTraceAsString()); } $logsType = []; if (is_string($this->logtype)) { $logsType = explode("|", $this->logtype); } elseif (is_array($this->logtype)) { $logsType = $this->logtype; } if (in_array("display", $logsType, true)) { $this->logdisplay($msg, $priority); } if (in_array("stderr", $logsType, true)) { $this->logstderr($msg, $priority); } if (in_array("file", $logsType, true)) { $this->logfile($msg, $priority); } if (in_array("syslog", $logsType, true)) { $this->logsyslog($msg, $priority); } if (in_array("session", $logsType, true)) { $this->logsession($msg, $priority); } } /** * Log $message on file * @param string $message Message to log * @param integer|null $priority Priority to use */ private function logfile($message, $priority) { if ($this->logfile === false) { throw new \Exception("Undefined file where logging"); } if (!file_exists($this->logfile)) { if (! is_dir(dirname($this->logfile))) { throw new \Exception("You must create the " . dirname($this->logfile) . "directory"); } if (! is_writable(dirname($this->logfile))) { throw new \Exception("The directory " . dirname($this->logfile) . " must" . " be writable to create log file"); } } elseif (!is_writable($this->logfile)) { if (function_exists("posix_geteuid")) { $user = posix_geteuid(); } elseif (getenv('USERNAME') !== false) { $user = getenv('USERNAME'); } else { $user = ""; } throw new \Exception("Logfile $this->logfile is not writable for user " . $user); } ini_set("date.timezone", $this->timezone); $message = date("Y/m/d H:i:s") . " [" . $this->priorities[$priority] . "] " . $message . "\n"; file_put_contents($this->logfile, $message, FILE_APPEND | LOCK_EX); } /** * Log $message on screen with adding date. Add the HTML flags if not in CLI. * @param string $message Message to log * @param integer|null $priority Priority to use */ private function logdisplay($message, $priority) { if (php_sapi_name() !== "cli") { echo ""; } ini_set("date.timezone", $this->timezone); $message = date("Y/m/d H:i:s") . " [" . $this->priorities[$priority] . "] " . $message; if (php_sapi_name() !== "cli") { $message = nl2br($message); } echo "$message"; if (php_sapi_name() !== "cli") { echo "
"; } echo "\n"; } /** * Log $message on stderr with adding date. Add the HTML flags if not in CLI. * @param string $message Message to log * @param integer|null $priority Priority to use */ private function logstderr($message, $priority) { ini_set("date.timezone", $this->timezone); $message = date("Y/m/d H:i:s") . " [" . $this->priorities[$priority] . "] " . $message; file_put_contents("php://stderr", "$message"); file_put_contents("php://stderr", "\n"); } /** * Log $message on syslog * @param string $message Message to log * @param integer|null $priority Priority to use */ private function logsyslog($message, $priority) { if (is_string($this->syslogFacility)) { switch ($this->syslogFacility) { case "LOG_AUTH": $this->syslogFacility = LOG_AUTH; break; case "LOG_AUTHPRIV": $this->syslogFacility = LOG_AUTHPRIV; break; case "LOG_DAEMON": $this->syslogFacility = LOG_DAEMON; break; case "LOG_KERN": $this->syslogFacility = LOG_KERN; break; case "LOG_LOCAL0": $this->syslogFacility = LOG_LOCAL0; break; case "LOG_LOCAL1": $this->syslogFacility = LOG_LOCAL1; break; case "LOG_LOCAL2": $this->syslogFacility = LOG_LOCAL2; break; case "LOG_LOCAL3": $this->syslogFacility = LOG_LOCAL3; break; case "LOG_LOCAL4": $this->syslogFacility = LOG_LOCAL4; break; case "LOG_LOCAL5": $this->syslogFacility = LOG_LOCAL5; break; case "LOG_LOCAL6": $this->syslogFacility = LOG_LOCAL6; break; case "LOG_LOCAL7": $this->syslogFacility = LOG_LOCAL7; break; case "LOG_LPR": $this->syslogFacility = LOG_LPR; break; case "LOG_MAIL": $this->syslogFacility = LOG_MAIL; break; case "LOG_NEWS": $this->syslogFacility = LOG_NEWS; break; case "LOG_SYSLOG": $this->syslogFacility = LOG_SYSLOG; break; case "LOG_USER": $this->syslogFacility = LOG_USER; break; case "LOG_UUCP": $this->syslogFacility = LOG_UUCP; break; } } openlog($this->syslogPrefix, null, $this->syslogFacility); // Syslog display a #012 when there is a \n : remove it $message = str_replace("\n", "", $message); syslog($priority, $message); } /** * Log $message on session (for debug) * @param string $message Message to log * @param integer|null $priority Priority to use */ private function logsession($message, $priority) { if (! isset($_SESSION)) { throw new \Exception("No session available to store the log", 500); } ini_set("date.timezone", $this->timezone); $message = date("Y/m/d H:i:s") . " [" . $this->priorities[$priority] . "] " . $message; $_SESSION["domframework"]["logger"][] = $message; } }