Files
DomFramework/src/Logger.php

462 lines
15 KiB
PHP

<?php
/**
* DomFramework
* @package domframework
* @author Dominique Fournier <dominique@fournier38.fr>
* @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 "<tt>";
}
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 "</tt><br/>";
}
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;
}
}