Newer
Older
namespace SimpleSAML;
use SimpleSAML\Logger\ErrorLogLoggingHandler;
use SimpleSAML\Logger\FileLoggingHandler;
use SimpleSAML\Logger\LoggingHandlerInterface;
use SimpleSAML\Logger\StandardErrorLoggingHandler;
use SimpleSAML\Logger\SyslogLoggingHandler;
use function array_key_exists;
use function array_pop;
use function array_shift;
use function array_unshift;
use function bin2hex;
use function class_exists;
use function class_implements;
use function defined;
use function error_reporting;
use function explode;
use function gmdate;
use function in_array;
use function intval;
use function is_null;
use function openssl_random_pseudo_bytes;
use function php_sapi_name;
use function register_shutdown_function;
use function str_replace;
use function strtolower;
use function substr;
Jaime Perez
committed
* The main logger class for SimpleSAMLphp.
Andreas Åkre Solberg
committed
*
Jaime Perez
committed
{
* @var \SimpleSAML\Logger\LoggingHandlerInterface|null
private static ?LoggingHandlerInterface $loggingHandler = null;
private static bool $initializing = false;
/**
* @var integer|null
*/
/**
* @var boolean
*/
Jaime Perez
committed
/**
* Array with messages logged before the logging handler was initialized.
*
* @var array
*/
/**
* List of log levels.
*
* This list is used to restore the log levels after some log levels have been disabled.
*
* @var array
*/
/**
* The current mask of log levels disabled.
*
* Note: this mask is not directly related to the PHP error reporting level.
*
* @var int
*/
Jaime Perez
committed
/**
* This constant defines the string we set the track ID to while we are fetching the track ID from the session
* class. This is used to prevent infinite recursion.
Jaime Perez
committed
*/
public const NO_TRACKID = '_NOTRACKIDYET_';
Jaime Perez
committed
/**
* This variable holds the track ID we have retrieved from the session class. It can also be NULL, in which case
Jaime Perez Crespo
committed
* we haven't fetched the track ID yet, or self::NO_TRACKID, which means that we are fetching the track ID now.
Jaime Perez
committed
*/
private static string $trackid = self::NO_TRACKID;
/**
* This variable holds the format used to log any message. Its use varies depending on the log handler used (for
* instance, you cannot control here how dates are displayed when using syslog or errorlog handlers), but in
* general the options are:
*
* - %date{<format>}: the date and time, with its format specified inside the brackets. See the PHP documentation
* of the date() function for more information on the format. If the brackets are omitted, the standard
* format is applied. This can be useful if you just want to control the placement of the date, but don't care
* about the format.
*
* - %process: the name of the SimpleSAMLphp process. Remember you can configure this in the 'logging.processname'
Jaime Perez Crespo
committed
* option. The SyslogLoggingHandler will just remove this.
*
Jaime Perez Crespo
committed
* - %level: the log level (name or number depending on the handler used). Please note different logging handlers
* will print the log level differently.
*
* - %stat: if the log entry is intended for statistical purposes, it will print the string 'STAT ' (bear in mind
* the trailing space).
*
* - %trackid: the track ID, an identifier that allows you to track a single session.
*
* - %srcip: the IP address of the client. If you are behind a proxy, make sure to modify the
* $_SERVER['REMOTE_ADDR'] variable on your code accordingly to the X-Forwarded-For header.
*
* - %msg: the message to be logged.
*
* @var string The format of the log line.
*/
private static string $format = '%date{M j H:i:s} %process %level %stat[%trackid] %msg';
Jaime Perez Crespo
committed
/**
* This variable tells if we have a shutdown function registered or not.
*
* @var bool
*/
private static bool $shutdownRegistered = false;
Jaime Perez Crespo
committed
/**
* This variable tells if we are shutting down.
*
* @var bool
*/
private static bool $shuttingDown = false;
Jaime Perez Crespo
committed
Jaime Perez
committed
/**
* Log an emergency message.
*
* @param string $string The message to log.
Jaime Perez
committed
*/
public static function emergency(string $string): void
Jaime Perez
committed
{
self::log(self::EMERG, $string);
}
/**
* Log a critical message.
*
* @param string $string The message to log.
Jaime Perez
committed
*/
public static function critical(string $string): void
Jaime Perez
committed
{
self::log(self::CRIT, $string);
}
/**
* Log an alert.
*
* @param string $string The message to log.
Jaime Perez
committed
*/
public static function alert(string $string): void
Jaime Perez
committed
{
self::log(self::ALERT, $string);
}
/**
* Log an error.
*
* @param string $string The message to log.
Jaime Perez
committed
*/
public static function error(string $string): void
Jaime Perez
committed
{
self::log(self::ERR, $string);
}
/**
* Log a warning.
*
* @param string $string The message to log.
Jaime Perez
committed
*/
public static function warning(string $string): void
Jaime Perez
committed
{
self::log(self::WARNING, $string);
}
Jaime Perez
committed
/**
* We reserve the notice level for statistics, so do not use this level for other kind of log messages.
*
* @param string $string The message to log.
Jaime Perez
committed
*/
public static function notice(string $string): void
Jaime Perez
committed
{
self::log(self::NOTICE, $string);
}
/**
* Info messages are a bit less verbose than debug messages. This is useful to trace a session.
*
* @param string $string The message to log.
Jaime Perez
committed
*/
public static function info(string $string): void
Jaime Perez
committed
{
self::log(self::INFO, $string);
}
/**
* Debug messages are very verbose, and will contain more information than what is necessary for a production
* system.
*
* @param string $string The message to log.
Jaime Perez
committed
*/
public static function debug(string $string): void
Jaime Perez
committed
{
self::log(self::DEBUG, $string);
}
/**
* Statistics.
*
* @param string $string The message to log.
Jaime Perez
committed
*/
public static function stats(string $string): void
Jaime Perez
committed
{
self::log(self::NOTICE, $string, self::$logLevel >= self::NOTICE);
Jaime Perez
committed
}
/**
* Set the logger to capture logs.
*
* @param boolean $val Whether to capture logs or not. Defaults to TRUE.
Jaime Perez
committed
*/
public static function setCaptureLog(bool $val = true): void
Jaime Perez
committed
{
self::$captureLog = $val;
}
/**
* Get the captured log.
Jaime Perez
committed
*/
Jaime Perez
committed
{
return self::$capturedLog;
}
/**
* Clears the captured log.
*/
public static function clearCapturedLog(): void
{
self::$capturedLog = [];
}
Jaime Perez Crespo
committed
/**
* Set the track identifier to use in all logs.
*
* @param string $trackId The track identifier to use during this session.
Jaime Perez Crespo
committed
*/
public static function setTrackId(string $trackId): void
Jaime Perez Crespo
committed
{
self::$trackid = $trackId;
Jaime Perez Crespo
committed
}
/**
* Flush any pending log messages to the logging handler.
*
{
foreach (self::$earlyLog as $msg) {
self::log($msg['level'], $msg['string'], $msg['statsLog']);
}
self::$earlyLog = [];
}
/**
* Flush any pending deferred logs during shutdown.
*
Jaime Perez Crespo
committed
* This method is intended to be registered as a shutdown handler, so that any pending messages that weren't sent
* to the logging handler at that point, can still make it. It is therefore not intended to be called manually.
*
*/
Jaime Perez Crespo
committed
{
if (self::$trackid === self::NO_TRACKID) {
try {
$s = Session::getSessionFromRequest();
// loading session failed. We don't care why, at this point we have a transient session, so we use that
$s = Session::getSessionFromRequest();
}
self::$trackid = $s->getTrackID();
Jaime Pérez
committed
}
Jaime Perez Crespo
committed
self::$shuttingDown = true;
Jaime Perez Crespo
committed
}
/**
* Evaluate whether errors of a certain error level are masked or not.
*
* @param int $errno The level of the error to check.
Jaime Perez Crespo
committed
*
* @return bool True if the error is masked, false otherwise.
*/
public static function isErrorMasked(int $errno): bool
{
return ($errno & self::$logMask) || !($errno & error_reporting());
}
/**
* Disable error reporting for the given log levels.
*
* Every call to this function must be followed by a call to popErrorMask().
*
* @param int $mask The log levels that should be masked.
*/
public static function maskErrors(int $mask): void
{
$currentEnabled = error_reporting();
self::$logLevelStack[] = [$currentEnabled, self::$logMask];
$currentEnabled &= ~$mask;
error_reporting($currentEnabled);
self::$logMask |= $mask;
}
/**
* Pop an error mask.
*
* This function restores the previous error mask.
*/
{
$lastMask = array_pop(self::$logLevelStack);
error_reporting($lastMask[0]);
self::$logMask = $lastMask[1];
}
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
/**
* Returns the current logging handler
*
* @return LoggingHandlerInterface
*/
public static function getLoggingHandler(): ?LoggingHandlerInterface
{
return self::$loggingHandler;
}
/**
* Sets the current logging handler
*
* @param LoggingHandlerInterface|null $loggingHandler The logging handler to set
*/
public static function setLoggingHandler(?LoggingHandlerInterface $loggingHandler): void
{
self::$initializing = false;
self::$loggingHandler = $loggingHandler;
}
/**
* Sets the log level.
*
* @param int $level One of the Logger class constants.
*/
public static function setLogLevel(int $level): void
{
self::$logLevel = $level;
}
Jaime Perez Crespo
committed
/**
* Defer a message for later logging.
*
Jaime Perez Crespo
committed
* @param int $level The log level corresponding to this message.
* @param string $message The message itself to log.
Jaime Perez Crespo
committed
* @param boolean $stats Whether this is a stats message or a regular one.
*/
private static function defer(int $level, string $message, bool $stats): void
Jaime Perez Crespo
committed
{
// save the message for later
self::$earlyLog[] = ['level' => $level, 'string' => $message, 'statsLog' => $stats];
Jaime Perez Crespo
committed
// register a shutdown handler if needed
if (!self::$shutdownRegistered) {
register_shutdown_function([self::class, 'shutdown']);
Jaime Perez Crespo
committed
self::$shutdownRegistered = true;
}
}
Jaime Perez Crespo
committed
private static function createLoggingHandler(?string $handler = null): void
Jaime Perez
committed
{
self::$initializing = true;
Jaime Perez Crespo
committed
// a set of known logging handlers
'syslog' => SyslogLoggingHandler::class,
'file' => FileLoggingHandler::class,
'errorlog' => ErrorLogLoggingHandler::class,
'stderr' => StandardErrorLoggingHandler::class,
Jaime Perez Crespo
committed
Jaime Perez
committed
// get the configuration
$config = Configuration::getInstance();
Jaime Perez
committed
Jaime Perez Crespo
committed
// setting minimum log_level
self::$logLevel = $config->getOptionalInteger('logging.level', self::INFO);
Jaime Perez Crespo
committed
Jaime Perez
committed
// get the metadata handler option from the configuration
if (is_null($handler)) {
$handler = $config->getOptionalString(
'logging.handler',
php_sapi_name() === 'cli' || defined('STDIN') ? 'stderr' : 'syslog'
);
Jaime Perez
committed
if (!array_key_exists($handler, $known_handlers) && class_exists($handler)) {
if (!in_array(LoggingHandlerInterface::class, class_implements($handler), true)) {
throw new Exception("The logging handler '$handler' is invalid.");
Jaime Perez Crespo
committed
}
Jaime Perez
committed
} else {
Jaime Perez Crespo
committed
$handler = strtolower($handler);
if (!array_key_exists($handler, $known_handlers)) {
"Invalid value for the 'logging.handler' configuration option. Unknown handler '" . $handler . "'."
Jaime Perez Crespo
committed
);
}
$handler = $known_handlers[$handler];
Jaime Perez
committed
}
self::$format = $config->getOptionalString('logging.format', self::$format);
try {
/** @var \SimpleSAML\Logger\LoggingHandlerInterface */
self::$loggingHandler = new $handler($config);
self::$loggingHandler->setLogFormat(self::$format);
self::$initializing = false;
self::$loggingHandler = new ErrorLogLoggingHandler($config);
self::$initializing = false;
self::log(self::CRIT, $e->getMessage(), false);
}
Jaime Perez
committed
}
/**
* @param int $level
* @param string $string
* @param bool $statsLog
*/
private static function log(int $level, string $string, bool $statsLog = false): void
Jaime Perez
committed
{
if (self::$initializing) {
Jaime Perez Crespo
committed
// some error occurred while initializing logging
self::defer($level, $string, $statsLog);
Jaime Perez Crespo
committed
return;
Jaime Perez Crespo
committed
} elseif (php_sapi_name() === 'cli' || defined('STDIN')) {
$_SERVER['REMOTE_ADDR'] = "CLI";
self::$trackid = 'CL' . bin2hex(openssl_random_pseudo_bytes(4));
Jaime Perez Crespo
committed
}
}
if (!isset(self::$loggingHandler)) {
Jaime Perez
committed
self::createLoggingHandler();
}
if (self::$captureLog) {
$sample = microtime(false);
list($msecs, $mtime) = explode(' ', $sample);
$time = intval($mtime);
$usec = substr($msecs, 2, 3);
$ts = gmdate('H:i:s', $time) . '.' . $usec . 'Z';
Jaime Perez
committed
}
if (self::$logLevel >= $level || $statsLog) {
$formats = ['%trackid', '%msg', '%srcip', '%stat'];
$replacements = [self::$trackid, $string, $_SERVER['REMOTE_ADDR']];
$stat = '';
if ($statsLog) {
$stat = 'STAT ';
}
$replacements[] = $stat;
Jaime Perez Crespo
committed
if (self::$trackid === self::NO_TRACKID && !self::$shuttingDown) {
// we have a log without track ID and we are not still shutting down, so defer logging
self::defer($level, $string, $statsLog);
return;
} elseif (self::$trackid === self::NO_TRACKID) {
// shutting down without a track ID, prettify it
array_shift($replacements);
array_unshift($replacements, 'N/A');
}
// we either have a track ID or we are shutting down, so just log the message
$string = str_replace($formats, $replacements, self::$format);
Jaime Perez
committed
self::$loggingHandler->log($level, $string);
}
}