LSlog : add loggers, filters and format message by handlers

This commit is contained in:
Benjamin Renard 2020-05-08 12:32:21 +02:00
parent de73ffdd60
commit d069df4d6c
10 changed files with 443 additions and 80 deletions

View file

@ -195,6 +195,14 @@ $GLOBALS['LSlog']['handlers'] = array (
array (
'handler' => 'file',
'path' => 'tmp/LS.log',
//'level' => 'DEBUG',
// Filter on specific loggers
//'loggers' => array('LSurl', 'LSlang'),
// Default formats
//'format' => '%{requesturi} - %{remoteaddr} - %{ldapservername} - %{authuser} - %{level} - %{message}',
//'cli_format' => '%{clibinpath} - %{level} - %{message}',
//'datetime_prefix' => true, // Prefix message with datetime ?
//'datetime_format' => 'Y/m/d H:i:s', // Datetime prefix format (see php date() function)
),
array (
'handler' => 'email', // Email handler (each logged message generated an email)
@ -214,6 +222,25 @@ $GLOBALS['LSlog']['handlers'] = array (
),
*/
);
$GLOBALS['LSlog']['loggers'] = array (
/**
* Loggers permit to define different log parameters for specific components
* of LdapSaisie (a class, an addon, ...). You could :
* - Enabled/disabled logs for this component with 'enabled' parameter
* - Set a specific log level for this component with 'enabled' parameter
**/
/*
'LSurl' => array (
'level' => 'DEBUG',
),
'LSldap' => array (
'level' => 'DEBUG',
),
'LSlang' => array (
'enabled' => false,
),
*/
);
$GLOBALS['LSlog']['level'] = 'INFO'; // DEBUG, INFO, WARNING, ERROR, FATAL
$GLOBALS['LSlog']['enable'] = true;

View file

@ -53,6 +53,9 @@ class LSlog {
'FATAL' => 4,
);
// Current existing loggers
private static $loggers = array();
/**
* Start/initialize logging
*
@ -180,10 +183,11 @@ class LSlog {
*
* @param[in] $level string The message level
* @param[in] $message string The message
* @param[in] $logger string|null The logger name (optional, default: null)
*
* @retval void
**/
private static function logging($level, $message) {
public static function logging($level, $message, $logger=null) {
// Check LSlog is enabled
if (!self :: $enabled)
return;
@ -200,22 +204,16 @@ class LSlog {
$message = varDump($message);
}
// Add prefix
if (php_sapi_name() == "cli") {
global $argv;
$message = basename($argv[0])." - $level - $message";
}
else {
$message = $_SERVER['REQUEST_URI'].' - '.$_SERVER['REMOTE_ADDR'].' - '.self :: getLdapServerName().' - '.self :: getAuthenticatedUserDN()." - $level - $message";
}
foreach (self :: $handlers as $handler) {
// Check handler level
if (!$handler -> checkLevel($level))
continue;
// Check handler logger filters
if ($logger && !$handler -> checkLogger($logger))
continue;
// Logging on this handler
call_user_func(array($handler, 'logging'), $level, $message);
call_user_func(array($handler, 'logging'), $level, $message, $logger);
}
if ($level == 'FATAL') {
@ -247,33 +245,6 @@ class LSlog {
return (self :: $levels[$level] >= self :: $levels[$configured_level]);
}
/**
* Helper to retreive current LDAP server name
*
* @retval string Current LDAP server name
**/
private static function getLdapServerName() {
if (LSsession :: $ldapServer) {
if (isset(LSsession :: $ldapServer['name']))
return LSsession :: $ldapServer['name'];
else
return "#".LSsession :: $ldapServerId;
}
return "Not connected";
}
/**
* Helper to retreive current authenticated user DN
*
* @retval string Current authenticated user DN
**/
private static function getAuthenticatedUserDN() {
$auth_dn = LSsession :: getLSuserObjectDn();
if ($auth_dn)
return LSsession :: getLSuserObjectDn();
return "Anonymous";
}
/*
* PHP error logging helpers
*/
@ -304,17 +275,25 @@ class LSlog {
}
/**
* PHP set_exception_handler helper
* Log an exception
*
* @see https://www.php.net/set_exception_handler
* Could be used as PHP set_exception_handler callable
* @see https://www.php.net/set_exception_handler
*
* @param[in] $exception Exception The exception to log
* @param[in] $prefix string|null Custom message prefix (optional, default: "An exception occured :\n")
* @param[in] $fatal boolean Log exception as a fatal error (optional, default: true)
* @param[in] $logger string|null The logger name (optional, default: null)
*
* @retval void
**/
public static function exception($exception, $prefix=null, $fatal=true) {
public static function exception($exception, $prefix=null, $fatal=true, $logger=null) {
$message = ($prefix?"$prefix :\n":"An exception occured :\n"). self :: get_debug_backtrace_context(). "\n" .
"## ".$exception->getFile().":".$exception->getLine(). " : ". $exception->getMessage();
if ($fatal)
self :: fatal($message);
if (is_null($logger))
self :: logging(($fatal?'FATAL':'ERROR'), $message);
else
self :: error($message);
self :: logging(($fatal?'FATAL':'ERROR'), $message, $logger);
}
/**
@ -367,6 +346,25 @@ class LSlog {
return False;
}
/**
* Get logger for a specific name
*
* @param[in] $name The logger name
*
* @retval LSlog_logger The logger
**/
public static function &get_logger($name) {
if (!LSsession :: loadLSclass('LSlog_logger'))
self :: fatal('Fail to load LSlog_logger class.');
if (isset(self :: $loggers[$name]))
return self :: $loggers[$name];
self :: $loggers[$name] = new LSlog_logger(
$name,
self :: getConfig("loggers.$name", array())
);
return self :: $loggers[$name];
}
/*
* Public logging methods
*/

View file

@ -49,13 +49,14 @@ class LSlog_console extends LSlog_handler {
*
* @param[in] $level string The message level
* @param[in] $message string The message
* @param[in] $logger string|null The logger name (optional, default: null)
*
* @retval void
**/
public function logging($level, $message) {
public function logging($level, $message, $logger=null) {
return fwrite(
($level > 1?$this -> stderr:$this -> stdout),
date('Y/m/d H:i:s').' - '.$level.' - '.$message."\n"
$this -> format($level, $message, $logger)."\n"
);
}
}

View file

@ -30,6 +30,9 @@ class LSlog_email extends LSlog_handler {
// The configured email recipient
private $recipient = null;
// Default datetime prefix (enabled/disabled)
protected $default_datetime_prefix = false;
/**
* Constructor
*
@ -47,12 +50,17 @@ class LSlog_email extends LSlog_handler {
*
* @param[in] $level string The message level
* @param[in] $message string The message
* @param[in] $logger string|null The logger name (optional, default: null)
*
* @retval void
**/
public function logging($level, $message) {
public function logging($level, $message, $logger=null) {
if ($this -> recipient)
return error_log($message, 1, $this -> recipient);
return error_log(
$this -> format($level, $message, $logger),
1,
$this -> recipient
);
return false;
}
}

View file

@ -50,10 +50,15 @@ class LSlog_file extends LSlog_handler {
*
* @param[in] $level string The message level
* @param[in] $message string The message
* @param[in] $logger string|null The logger name (optional, default: null)
*
* @retval void
**/
public function logging($level, $message) {
return error_log(date('Y/m/d H:i:s').' - '.$message."\n", 3, $this -> path);
public function logging($level, $message, $logger=null) {
return error_log(
$this -> format($level, $message, $logger)."\n",
3,
$this -> path
);
}
}

View file

@ -28,7 +28,17 @@
class LSlog_handler {
// The handler configuration
private $config;
protected $config;
// Default log formats
protected $default_format = '%{requesturi} - %{remoteaddr} - %{ldapservername} - %{authuser} - %{logger} - %{level} - %{message}';
protected $default_cli_format = '%{clibinpath} - %{logger} - %{level} - %{message}';
// Default datetime prefix (enabled/disabled)
protected $default_datetime_prefix = true;
// Default datetime format
protected $default_datetime_format = 'Y/m/d H:i:s';
/**
* Constructor
@ -39,6 +49,9 @@ class LSlog_handler {
**/
public function __construct($config) {
$this -> config = $config;
$this -> loggers = $this -> getConfig('loggers', array());
if (!is_array($this -> loggers))
$this -> loggers = array($this -> loggers);
}
/**
@ -81,15 +94,89 @@ class LSlog_handler {
return LSlog :: checkLevel($level, $this -> getConfig('level'));
}
/**
* Check logger against configured loggers filters
*
* @param[in] $logger string The logger
*
* @retval bool True if message of this logger have to be logged, False otherwise
**/
public function checkLogger($logger) {
return (!$this -> loggers || in_array($logger, $this -> loggers));
}
/**
* Log a message
*
* @param[in] $level string The message level
* @param[in] $message string The message
* @param[in] $logger string|null The logger name (optional, default: null)
*
* @retval void
**/
public function logging($level, $message) {
return false;
public function logging($level, $message, $logger=null) {
return;
}
/**
* Format a message
*
* @param[in] $level string The message level
* @param[in] $message string The message
* @param[in] $logger string|null The logger name (optional, default: null)
*
* @retval string The formated message to log
**/
protected function format($level, $message, $logger=null) {
global $argv;
if (php_sapi_name() == "cli")
$format = $this -> getConfig('cli_format', $this -> default_cli_format, 'string');
else
$format = $this -> getConfig('format', $this -> default_format, 'string');
// Add datetime prefix (if enabled)
if ($this -> getConfig('datetime_prefix', $this -> default_datetime_prefix, 'boolean')) {
$format = date($this -> getConfig('datetime_format', $this -> default_datetime_format, 'string'))." - $format";
}
return getFData(
$format,
array(
'level' => $level,
'message' => $message,
'logger' => ($logger?$logger:'default'),
'datetime' => date('Y/m/d H:i:s'),
'clibinpath' => (isset($argv)?$argv[0]:'unknown bin path'),
'requesturi' => (isset($_SERVER)?$_SERVER['REQUEST_URI']:'unknown request URI'),
'remoteaddr' => (isset($_SERVER)?$_SERVER['REMOTE_ADDR']:'unknown remote address'),
'ldapservername' => self :: getLdapServerName(),
'authuser' => self :: getAuthenticatedUserDN(),
)
);
}
/**
* Helper to retreive current LDAP server name
*
* @retval string Current LDAP server name
**/
private static function getLdapServerName() {
if (LSsession :: $ldapServer) {
if (isset(LSsession :: $ldapServer['name']))
return LSsession :: $ldapServer['name'];
else
return "#".LSsession :: $ldapServerId;
}
return "Not connected";
}
/**
* Helper to retreive current authenticated user DN
*
* @retval string Current authenticated user DN
**/
private static function getAuthenticatedUserDN() {
$auth_dn = LSsession :: getLSuserObjectDn();
if ($auth_dn)
return LSsession :: getLSuserObjectDn();
return "Anonymous";
}
}

View file

@ -0,0 +1,195 @@
<?php
/*******************************************************************************
* Copyright (C) 2007 Easter-eggs
* http://ldapsaisie.labs.libre-entreprise.org
*
* Author: See AUTHORS file in top-level directory.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
******************************************************************************/
/**
* Logger class for LSlog
*
* @author Benjamin Renard <brenard@easter-eggs.com>
*/
class LSlog_logger {
// Name
private $name;
// The handler configuration
private $config;
// Enabled/disabled
private $enabled;
// Level
private $level;
/**
* Constructor
*
* @param[in] $name string The logger name
* @param[in] $config array The handler configuration (optional, default: array())
*
* @retval void
**/
public function __construct($name, $config=array()) {
$this -> name = $name;
$this -> config = $config;
$this -> enabled = $this -> getConfig('enabled', true, 'boolean');
$this -> level = $this -> getConfig('level');
if ($this -> enabled)
$this -> debug("Enabled $name logger with level=".$this -> level);
}
/**
* Get a configuration variable value
*
* @param[in] $var string The configuration variable name
* @param[in] $default mixed The default value to return if configuration variable
* is not set (Default : null)
* @param[in] $cast string The type of expected value. The configuration variable
* value will be cast as this type. Could be : bool, int,
* float or string. (Optional, default : raw value)
*
* @retval mixed The configuration variable value
**/
public function getConfig($var, $default=null, $cast=null) {
return LSconfig :: get($var, $default, $cast, $this -> config);
}
/**
* Get logger info
*
* @param[in] $key string The info name
*
* @retval mixed The info value
**/
public function __get($key) {
switch ($key) {
case 'name':
return $this -> name;
case 'enabled':
return $this -> enabled;
case 'level':
return $this -> level;
}
return;
}
/**
* Check level against configured level
*
* @param[in] $level string The level
*
* @retval bool True if a message with this level have to be logged, False otherwise
**/
public function checkLevel($level) {
// If no level configured, always log
if (!$this -> enabled || !$this -> level)
return True;
return LSlog :: checkLevel($level, $this -> level);
}
/**
* Log a message
*
* @param[in] $level string The message level
* @param[in] $message string The message
*
* @retval void
**/
public function logging($level, $message) {
if (!$this -> enabled || !$this -> checkLevel($level))
return;
LSlog :: logging($level, $message, $this -> name);
}
/*
* Public logging methods
*/
/**
* Log a message with level DEBUG
*
* @param[in] $message The message to log
*
* @retval void
**/
public function debug($message) {
$this -> logging('DEBUG', $message);
}
/**
* Log a message with level INFO
*
* @param[in] $message The message to log
*
* @retval void
**/
public function info($message) {
$this -> logging('INFO', $message);
}
/**
* Log a message with level WARNING
*
* @param[in] $message The message to log
*
* @retval void
**/
public function warning($message) {
$this -> logging('WARNING', $message);
}
/**
* Log a message with level ERROR
*
* @param[in] $message The message to log
*
* @retval void
**/
public function error($message) {
$this -> logging('ERROR', $message);
}
/**
* Log a message with level FATAL
*
* @param[in] $message The message to log
*
* @retval void
**/
public function fatal($message) {
$this -> logging('FATAL', $message);
}
/**
* Log an exception
*
* @param[in] $exception Exception The exception to log
* @param[in] $prefix string|null Custom message prefix (optional, see LSlog :: exception())
* @param[in] $fatal boolean Log exception as a fatal error (optional, default: true)
*
* @retval void
**/
public function exception($exception, $prefix=null, $fatal=true) {
if (!$this -> enabled)
return;
LSlog :: exception($exception, $prefix, $fatal, $this -> name);
}
}

View file

@ -45,6 +45,9 @@ class LSlog_syslog extends LSlog_handler {
// Default syslog priority (used if level is not provided or invalid)
private static $default_priority = LOG_WARNING;
// Default datetime prefix (enabled/disabled)
protected $default_datetime_prefix = false;
/**
* Constructor
*
@ -71,13 +74,14 @@ class LSlog_syslog extends LSlog_handler {
*
* @param[in] $level string The message level
* @param[in] $message string The message
* @param[in] $logger string|null The logger name (optional, default: null)
*
* @retval void
**/
public function logging($level, $message) {
public function logging($level, $message, $logger=null) {
return syslog(
$this -> level2priority($level),
$message
$this -> format($level, $message, $logger)
);
}

View file

@ -27,15 +27,21 @@
*/
class LSlog_system extends LSlog_handler {
// Default datetime prefix (enabled/disabled)
protected $default_datetime_prefix = false;
/**
* Log a message
*
* @param[in] $level string The message level
* @param[in] $message string The message
* @param[in] $logger string|null The logger name (optional, default: null)
*
* @retval void
**/
public function logging($level, $message) {
error_log($message);
public function logging($level, $message, $logger=null) {
error_log(
$this -> format($level, $message, $logger)
);
}
}

View file

@ -56,6 +56,38 @@ class LSurl {
// Rewrited request param
const REWRITED_REQUEST_PARAM = 'REQUESTED_URL';
// Logger
private static $logger = null;
/*
* Log a message via class logger
*
* @param[in] $level string The log level (see LSlog)
* @param[in] $message string The message to log
*
* @retval void
**/
private static function log($level, $message) {
if (is_null(self :: $logger))
self :: $logger = LSlog :: get_logger('LSurl');
self :: $logger -> logging($level, $message);
}
/**
* Log an exception via class logger
*
* @param[in] $exception Exception The exception to log
* @param[in] $prefix string|null Custom message prefix (optional, see self :: log_exception())
* @param[in] $fatal boolean Log exception as a fatal error (optional, default: true)
*
* @retval void
**/
public static function log_exception($exception, $prefix=null, $fatal=true) {
if (is_null(self :: $logger))
self :: $logger = LSlog :: get_logger('LSurl');
self :: $logger -> exception($exception, $prefix, $fatal);
}
/**
* Add an URL pattern
*
@ -81,14 +113,14 @@ class LSurl {
);
}
elseif ($override) {
LSlog :: debug("URL : override pattern '$pattern' with handler '$handler' (old handler = '".self :: $patterns[$pattern]."')");
self :: log("\Udebug", "URL : override pattern '$pattern' with handler '$handler' (old handler = '".self :: $patterns[$pattern]."')");
self :: $patterns[$pattern] = array(
'handler' => $handler,
'authenticated' => $authenticated,
);
}
else {
LSlog :: debug("URL : pattern '$pattern' already defined : do not override.");
self :: log("DEBUG", "URL : pattern '$pattern' already defined : do not override.");
}
}
}
@ -104,16 +136,16 @@ class LSurl {
private static function get_request($default_url=null) {
$current_url = self :: get_current_url();
if (is_null($current_url)) {
LSlog :: fatal(_("Fail to determine the requested URL."));
self :: log("FATAL", _("Fail to determine the requested URL."));
exit();
}
if (!is_array(self :: $patterns)) {
LSlog :: fatal('No URL patterns configured !');
self :: log("FATAL", 'No URL patterns configured !');
exit();
}
LSlog :: debug("URL : current url = '$current_url'");
LSlog :: debug("URL : check current url with the following URL patterns :\n - ".implode("\n - ", array_keys(self :: $patterns)));
self :: log("DEBUG", "URL : current url = '$current_url'");
self :: log("DEBUG", "URL : check current url with the following URL patterns :\n - ".implode("\n - ", array_keys(self :: $patterns)));
foreach (self :: $patterns as $pattern => $handler_infos) {
$m = self :: url_match($pattern, $current_url);
if (is_array($m)) {
@ -121,16 +153,16 @@ class LSurl {
// Reset last redirect
if (isset($_SESSION['last_redirect']))
unset($_SESSION['last_redirect']);
LSlog :: debug("URL : result :\n".varDump($request, 1));
self :: log("DEBUG", "URL : result :\n".varDump($request, 1));
return $request;
}
}
if (!is_null($default_url)) {
LSlog :: debug("Current URL match with no pattern. Redirect to default URL ('$default_url')");
self :: log("DEBUG", "Current URL match with no pattern. Redirect to default URL ('$default_url')");
self :: redirect($default_url);
exit();
}
LSlog :: debug("Current URL match with no pattern. Use error 404 handler.");
self :: log("DEBUG", "Current URL match with no pattern. Use error 404 handler.");
return new LSurlRequest(
$current_url,
array(
@ -154,7 +186,7 @@ class LSurl {
if (!$current_url) return False;
}
if (preg_match($pattern, $current_url, $m)) {
LSlog :: debug("URL : Match found with pattern '$pattern' :\n\t".str_replace("\n", "\n\t", print_r($m, 1)));
self :: log("DEBUG", "URL : Match found with pattern '$pattern' :\n\t".str_replace("\n", "\n\t", print_r($m, 1)));
return $m;
}
return False;
@ -168,7 +200,7 @@ class LSurl {
public static function get_current_url() {
if (array_key_exists(self :: REWRITED_REQUEST_PARAM, $_REQUEST))
return $_REQUEST[self :: REWRITED_REQUEST_PARAM];
LSlog :: warning('LSurl : Rewrite request param not present, try to detect current URL.');
self :: log("WARNING", 'LSurl : Rewrite request param not present, try to detect current URL.');
return self :: detect_current_url();
}
@ -186,13 +218,13 @@ class LSurl {
$public_root_url = LSconfig :: get('public_root_url', '/', 'string');
if ($public_root_url[0] == '/') {
LSlog :: debug("LSurl :: get_public_absolute_url($relative_url): configured public root URL is relative ($public_root_url) => try to detect it from current request infos.");
self :: log("DEBUG", "LSurl :: get_public_absolute_url($relative_url): configured public root URL is relative ($public_root_url) => try to detect it from current request infos.");
$public_root_url = 'http'.(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'?'s':'').'://'.$_SERVER['HTTP_HOST'].$public_root_url;
LSlog :: debug("LSurl :: get_public_absolute_url($relative_url): detected public_root_url: $public_root_url");
self :: log("DEBUG", "LSurl :: get_public_absolute_url($relative_url): detected public_root_url: $public_root_url");
}
$url = self :: remove_trailing_slash($public_root_url)."/$relative_url";
LSlog :: debug("LSurl :: get_public_absolute_url($relative_url): result = $url");
self :: log("DEBUG", "LSurl :: get_public_absolute_url($relative_url): result = $url");
return $url;
}
@ -221,11 +253,11 @@ class LSurl {
// Prevent loop
if (isset($_SESSION['last_redirect']) && $_SESSION['last_redirect'] == $url)
LSlog :: fatal(_("Fail to determine the requested URL (loop detected)."));
self :: log("FATAL", _("Fail to determine the requested URL (loop detected)."));
else
$_SESSION['last_redirect'] = $url;
LSlog :: debug("redirect($go) => Redirect to : <$url>");
self :: log("DEBUG", "redirect($go) => Redirect to : <$url>");
header("Location: $url");
// Set & display template
@ -259,8 +291,8 @@ class LSurl {
$request = self :: get_request($default_url);
if (!is_callable($request -> handler)) {
LSlog :: error("URL handler function ".$request -> handler."() does not exists !");
LSlog :: fatal("This request could not be handled.");
self :: log("ERROR", "URL handler function ".$request -> handler."() does not exists !");
self :: log("FATAL", "This request could not be handled.");
}
if (class_exists('LStemplate'))
@ -276,8 +308,8 @@ class LSurl {
return call_user_func($request -> handler, $request);
}
catch (Exception $e) {
LSlog :: exception($e, "An exception occured running URL handler function ".$request -> handler."()");
LSlog :: fatal("This request could not be processed correctly.");
self :: log_exception($e, "An exception occured running URL handler function ".$request -> handler."()");
self :: log("FATAL", "This request could not be processed correctly.");
}
}
@ -291,16 +323,16 @@ class LSurl {
* @retval string|false The current request URL or false if detection fail
**/
private static function detect_current_url() {
LSlog :: debug("URL : request URI = '".$_SERVER['REQUEST_URI']."'");
self :: log("DEBUG", "URL : request URI = '".$_SERVER['REQUEST_URI']."'");
$base = self :: get_rewrite_base();
LSlog :: debug("URL : rewrite base = '$base'");
self :: log("DEBUG", "URL : rewrite base = '$base'");
if ($_SERVER['REQUEST_URI'] == $base)
return '';
if (substr($_SERVER['REQUEST_URI'], 0, strlen($base)) != $base) {
LSlog :: error("URL : request URI (".$_SERVER['REQUEST_URI'].") does not start with rewrite base ($base)");
self :: log("ERROR", "URL : request URI (".$_SERVER['REQUEST_URI'].") does not start with rewrite base ($base)");
return False;
}