Skip to content

Commit

Permalink
Psr\Log\LogLevel constants accepted as $level (#2)
Browse files Browse the repository at this point in the history
### Added
- Class configuration is managed by an array where field names are defined as constants to enable IDE hints.

### Fixed
- argument `$level` of method `log` accepts also strings defined in Psr\Log\LogLevel as required by [PSR-3](https://www.php-fig.org/psr/psr-3/)
  • Loading branch information
WorkOfStan authored Jul 23, 2024
1 parent 5b66757 commit b1683d7
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 59 deletions.
14 changes: 11 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### `Security` in case of vulnerabilities

## [0.2] - 2024-07-23
### Added
- Class configuration is managed by an array where field names are defined as constants to enable IDE hints.

### Fixed
- argument `$level` of method `log` accepts also strings defined in Psr\Log\LogLevel as required by [PSR-3](https://www.php-fig.org/psr/psr-3/)

## [0.1] - 2024-07-12
- A PSR-3 compliant logger with adjustable verbosity based on Backyard\BackyardError
- A [PSR-3](https://www.php-fig.org/psr/psr-3/) compliant logger with adjustable verbosity (based on Backyard\BackyardError)

[Unreleased]: https://github.com/WorkOfStan/seablast-dist/compare/v0.1...HEAD
[0.1]: https://github.com/WorkOfStan/seablast-dist/releases/tag/v0.1
[Unreleased]: https://github.com/WorkOfStan/seablast-logger/compare/v0.2...HEAD
[0.2]: https://github.com/WorkOfStan/seablast-logger/compare/v0.1...v0.2
[0.1]: https://github.com/WorkOfStan/seablast-logger/releases/tag/v0.1
28 changes: 15 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,33 @@
# seablast-logger
A [PSR-3](http://www.php-fig.org/psr/psr-3/) compliant logger with adjustable verbosity.

The logging levels can be tailored to suit different environments.
The logging level verbosity can be tailored to suit different environments.
For instance, in a development environment, the logger can be configured to log more detailed information compared to a production environment, all without changing your code.
Simply adjust the verbosity.

The `logging_level` is the most important setting. These parameters can be configured when instantiating the logger:
```php
use Seablast\Logger\Logger;
$conf = array(
// THESE ARE THE DEFAULT SETTINGS
// 0 = send message to PHP's system logger; recommended is however 3, i.e. append to the file destination set in the field 'logging_file'
'error_log_message_type' => 0,
Logger::CONF_ERROR_LOG_MESSAGE_TYPE => 0,
// if error_log_message_type equals 3, the message is appended to this file destination (path and name)
'logging_file' => '',
// log up to the level set here, default=5 = debug
'logging_level' => 5,
Logger::CONF_LOGGING_FILE => '',
// verbosity: log up to the level set here, default=5 = debug
Logger::CONF_LOGGING_LEVEL => 5,
// rename or renumber, if needed
'logging_level_name' => array(0 => 'unknown', 1 => 'fatal', 'error', 'warning', 'info', 'debug', 'speed'),
Logger::CONF_LOGGING_LEVEL_NAME => array(0 => 'unknown', 1 => 'fatal', 'error', 'warning', 'info', 'debug', 'speed'),
// the logging level to which the page generation speed (i.e. error_number 6) is to be logged
'logging_level_page_speed' => 5,
Logger::CONF_LOGGING_LEVEL_PAGE_SPEED => 5,
// false => use logging_file with log extension as destination; true => adds .Y-m.log to the logging file
'log_monthly_rotation' => true,
Logger::CONF_LOG_MONTHLY_ROTATION => true,
// prefix message that took longer than profiling step (float) sec from the previous one by SLOWSTEP
'log_profiling_step' => false,
Logger::CONF_LOG_PROFILING_STEP => false,
// fatal error may just be written in log, on production, it is however recommended to set an e-mail, where to announce fatal errors
'mail_for_admin_enabled' => false,
Logger::CONF_MAIL_FOR_ADMIN_ENABLED => false,
);
$logger = new Seablast\Logger\Logger($conf);
$logger = new Logger($conf);
```
See [test.php](test.php) for usage.

Expand All @@ -47,8 +49,8 @@ And ignores
Since Nette\Tracy::v2.6.0, i.e. `"php": ">=7.1"` it is possible to use a PSR-3 adapter, allowing for integration of [seablast/logger](https://github.com/WorkOfStan/seablast-logger).

```php
$logger = new Seablast\Logger\Logger();
$tracyLogger = new Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($logger);
$logger = new \Seablast\Logger\Logger();
$tracyLogger = new \Tracy\Bridges\Psr\PsrToTracyLoggerAdapter($logger);
Debugger::setLogger($tracyLogger);
Debugger::enable();
```
105 changes: 70 additions & 35 deletions src/Logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,30 @@

use Psr\Log\AbstractLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Seablast\Logger\LoggerTime;

/**
* TODO wrapper in backyard
* - add $RUNNING_TIME = $this->getLastRunningTime();
* - KEEP dieGraciously
* - only log calls Seablast\Logger\Logger and catches ErrorLogFailureException('error_log() => return false
* - when adding PHP/8 support, add :void to the inherited methods and remove PHP/5 support
* TODO when adding PHP/8 support, add :void to the inherited methods and remove PHP/5 support
*/
class Logger extends AbstractLogger implements LoggerInterface
{
// phpcs:disable Generic.Files.LineLength
// Define constants for configuration keys
// phpcs:disable PSR12.Properties.ConstantVisibility.NotFound
// todo remove phpcs exception when PHP/5 support removed
const CONF_ERROR_LOG_MESSAGE_TYPE = 'error_log_message_type';
const CONF_LOGGING_FILE = 'logging_file';
const CONF_LOGGING_LEVEL = 'logging_level';
const CONF_LOGGING_LEVEL_NAME = 'logging_level_name';
const CONF_LOGGING_LEVEL_PAGE_SPEED = 'logging_level_page_speed';
const CONF_LOG_MONTHLY_ROTATION = 'log_monthly_rotation';
const CONF_LOG_PROFILING_STEP = 'log_profiling_step';
const CONF_MAIL_FOR_ADMIN_ENABLED = 'mail_for_admin_enabled';
// phpcs:enable

/** @var array<mixed> int,string,bool,array */
protected $conf = array();
Expand All @@ -40,13 +52,13 @@ public function __construct(array $conf = array(), LoggerTime $time = null)
array( // default values
// 0 = send message to PHP's system logger;
// recommended is however 3, i.e. append to the file destination set in the field 'logging_file'
'error_log_message_type' => 0,
self::CONF_ERROR_LOG_MESSAGE_TYPE => 0,
// if error_log_message_type equals 3, the message is appended to this file destination (path and name)
'logging_file' => '',
// log up to the level set here, default=5 = debug
'logging_level' => 5,
self::CONF_LOGGING_FILE => '',
// verbosity: log up to the level set here, default=5 = debug
self::CONF_LOGGING_LEVEL => 5,
// rename or renumber, if needed
'logging_level_name' => array(
self::CONF_LOGGING_LEVEL_NAME => array(
0 => 'unknown',
1 => 'fatal',
'error',
Expand All @@ -56,29 +68,30 @@ public function __construct(array $conf = array(), LoggerTime $time = null)
'speed'
),
// the logging level to which the page generation speed (i.e. error_number 6) is to be logged
'logging_level_page_speed' => 5,
self::CONF_LOGGING_LEVEL_PAGE_SPEED => 5,
// false => use logging_file with log extension as destination
// true => adds .Y-m.log to the logging file
'log_monthly_rotation' => true,
self::CONF_LOG_MONTHLY_ROTATION => true,
// prefix message that took longer than profiling step (float) sec from the previous one by SLOWSTEP
'log_profiling_step' => false,
// UNCOMMENT only if needed //'log_standard_output' => false, //true, pokud má zároveň vypisovat na obrazovku; false, pokud má vypisovat jen do logu
self::CONF_LOG_PROFILING_STEP => false,
// UNCOMMENT only if needed //'log_standard_output' => false,
// //true, pokud má zároveň vypisovat na obrazovku; false, pokud má vypisovat jen do logu
// fatal error may just be written in log,
// on production, it is however recommended to set an e-mail, where to announce fatal errors
'mail_for_admin_enabled' => false,
self::CONF_MAIL_FOR_ADMIN_ENABLED => false,
),
$conf
);
if (!is_int($this->conf['logging_level'])) {
throw new \Psr\Log\InvalidArgumentException('The logging_level is not an integer.');
if (!is_int($this->conf[self::CONF_LOGGING_LEVEL])) {
throw new \Psr\Log\InvalidArgumentException('The logging_level MUST be an integer.');
}
$this->overrideLoggingLevel = $this->conf['logging_level'];
//@todo do not use $this->conf but set the class properties right here accordingly; and also provide means to set the values otherwise later
//240709 set later is probably not necessary
$this->overrideLoggingLevel = $this->conf[self::CONF_LOGGING_LEVEL];
//@todo replace $this->conf by the class properties
}

/**
* Class doesn't automatically use any GET parameter to override the set logging level, as it could be used to flood the error log.
* Class doesn't automatically use any GET parameter to override the set logging level,
* as it could be used to flood the error log.
* It is however possible to programmatically raise the logging level set in configuration.
*
* @param int $newLevel
Expand Down Expand Up @@ -218,12 +231,13 @@ public function debug($message, array $context = array())
$this->log(5, $message, $context);
}

// phpcs:disable Generic.Files.LineLength
/**
* Error_log() modified to log necessary debug information by application to its own log.
* Logs with an arbitrary level, i.e. may not log debug info on production.
* Logs with an arbitrary verbosity level, e.g. debug info on production may be omitted.
* Compliant with PSR-3 http://www.php-fig.org/psr/psr-3/
*
* @param int $level Error level
* @param mixed $level int|string Error level
* @param string $message Message to be logged
* @param array<int> $context OPTIONAL To enable error log filtering 'error_number' field expected or the first element element expected containing number of error category
*
Expand Down Expand Up @@ -257,9 +271,31 @@ public function log($level, $message, array $context = array())
//TODO: přidat proměnnou $line - mělo by být vždy voláno jako basename(__FILE__)."#".__LINE__ , takže bude jasné, ze které řádky source souboru to bylo voláno
// Ve výsledku do logu zapíše:
//[Timestamp: d-M-Y H:i:s] [Logging level] [$error_number] [$_SERVER['SCRIPT_FILENAME']] [username@gethostbyaddr($_SERVER['REMOTE_ADDR'])] [sec od startu stránky] $message

if (!is_string($message)) {
error_log("wrong message: Logger->log({$level}," . print_r($message, true) . ")");
$message = "wrong message type " . gettype($message) . ": Logger->log(" . print_r($level, true) . "," . print_r($message, true) . ")";
$this->error($message);
}
// psr log levels to numbered severity
$psr2int = array(
LogLevel::EMERGENCY => 0,
LogLevel::ALERT => 1,
LogLevel::CRITICAL => 1,
LogLevel::ERROR => 2,
LogLevel::WARNING => 3,
LogLevel::NOTICE => 4,
LogLevel::INFO => 4,
LogLevel::DEBUG => 5,
);
if (is_string($level)) {
if (array_key_exists($level, $psr2int)) {
$level = $psr2int[$level];
} else {
$this->error('level has unexpected string value ' . $level . ' message: ' . $message);
$level = 0;
}
} elseif (!is_int($level)) {
$this->error('level has unexpected type ' . gettype($level) . ' message: ' . $message);
$level = 0;
}

// if context array is set then get the value of the 'error_number' field or the first element
Expand All @@ -274,19 +310,17 @@ public function log($level, $message, array $context = array())
(
$level <= max(
array(
$this->conf['logging_level'],
$this->conf[self::CONF_LOGGING_LEVEL],
$this->overrideLoggingLevel,
// $this->conf['error_hack_from_get'], //set potentially as GET parameter
// $ERROR_HACK, //set as variable in the application script
)
)
)
// or log page_speed everytime error_number equals 6 and
// logging_level_page_speed has at least the severity of logging_level
|| (($error_number === 6) && ($this->conf['logging_level_page_speed'] <= $this->conf['logging_level']))
|| (($error_number === 6) && ($this->conf[self::CONF_LOGGING_LEVEL_PAGE_SPEED] <= $this->conf[self::CONF_LOGGING_LEVEL]))
) {
$RUNNING_TIME_PREVIOUS = $this->runningTime;
if (((($this->runningTime = round($this->time->getmicrotime() - $this->time->getPageTimestamp(), 4)) - $RUNNING_TIME_PREVIOUS) > $this->conf['log_profiling_step']) && $this->conf['log_profiling_step']) {
if (((($this->runningTime = round($this->time->getmicrotime() - $this->time->getPageTimestamp(), 4)) - $RUNNING_TIME_PREVIOUS) > $this->conf[self::CONF_LOG_PROFILING_STEP]) && $this->conf[self::CONF_LOG_PROFILING_STEP]) {
$message = "SLOWSTEP " . $message; //110812, PROFILING
}

Expand All @@ -295,30 +329,32 @@ public function log($level, $message, array $context = array())
// echo((($level <= 2) ? "<b>" : "") . "{$message} [{$this->runningTime}]" . (($level <= 2) ? "</b>" : "") . "<hr/>" . PHP_EOL); //110811, if fatal or error then bold//111119, RUNNING_TIME
//}

$message_prefix = "[" . date("d-M-Y H:i:s") . "] [" . $this->conf['logging_level_name'][$level] . "] [" . $error_number . "] [" . $_SERVER['SCRIPT_FILENAME'] . "] ["
$message_prefix = "[" . date("d-M-Y H:i:s") . "] [" . $this->conf[self::CONF_LOGGING_LEVEL_NAME][$level] . "] [" . $error_number . "] [" . $_SERVER['SCRIPT_FILENAME'] . "] ["
. $this->user . "@"
. (isset($_SERVER['REMOTE_ADDR']) ? gethostbyaddr($_SERVER['REMOTE_ADDR']) : '-')//PHPUnit test (CLI) does not set REMOTE_ADDR
. "] [" . $this->runningTime . "] ["
. (isset($_SERVER["REQUEST_URI"]) ? $_SERVER["REQUEST_URI"] : '-')//PHPUnit test (CLI) does not set REQUEST_URI
. "] ";
//gethostbyaddr($_SERVER['REMOTE_ADDR'])// co udělá s IP, která nelze přeložit? nebylo by lepší logovat přímo IP?
if (($this->conf['error_log_message_type'] == 3) && !$this->conf['logging_file']) {// $logging_file not set and it should be
if (($this->conf[self::CONF_ERROR_LOG_MESSAGE_TYPE] == 3) && !$this->conf[self::CONF_LOGGING_FILE]) {// $logging_file not set and it should be
$result = error_log($message_prefix . "(error: logging_file should be set!) $message"); // so write into the default destination
//zaroven by mohlo poslat mail nebo tak neco .. vypis na obrazovku je asi az krajni reseni
} else {
$messageType = ($this->conf['error_log_message_type'] == 0) ? $this->conf['error_log_message_type'] : 3;
$result = ($this->conf['log_monthly_rotation']) ? error_log($message_prefix . $message . (($messageType != 0) ? (PHP_EOL) : ('')), $messageType, "{$this->conf['logging_file']}." . date("Y-m") . ".log") //writes into a monthly rotating file
: error_log($message_prefix . $message . PHP_EOL, $messageType, "{$this->conf['logging_file']}.log"); //writes into one file
$messageType = ($this->conf[self::CONF_ERROR_LOG_MESSAGE_TYPE] == 0) ? $this->conf[self::CONF_ERROR_LOG_MESSAGE_TYPE] : 3;
$result = ($this->conf[self::CONF_LOG_MONTHLY_ROTATION])
? error_log($message_prefix . $message . (($messageType != 0) ? (PHP_EOL) : ('')), $messageType, "{$this->conf[self::CONF_LOGGING_FILE]}." . date("Y-m") . ".log") //writes into a monthly rotating file
: error_log($message_prefix . $message . PHP_EOL, $messageType, "{$this->conf[self::CONF_LOGGING_FILE]}.log"); //writes into one file
}
// mailto admin. 'mail_for_admin_enabled' has to be an email
if ($level == 1 && $this->conf['mail_for_admin_enabled']) {
error_log($message_prefix . $message . PHP_EOL, 1, $this->conf['mail_for_admin_enabled']);
if ($level == 1 && $this->conf[self::CONF_MAIL_FOR_ADMIN_ENABLED]) {
error_log($message_prefix . $message . PHP_EOL, 1, $this->conf[self::CONF_MAIL_FOR_ADMIN_ENABLED]);
}
}
if ($result === false) {
throw new ErrorLogFailureException('error_log() failed');
}
}
// phpcs:enable
/** Alternative way:
Logging levels
Log level Description Set bit
Expand All @@ -336,5 +372,4 @@ public function log($level, $message, array $context = array())
4 00000100 Warnings and Trace
7 00000111 Warnings, Debug, Information and Trace
*/
// phpcs:enable
}
18 changes: 10 additions & 8 deletions test.php
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
<?php

use Seablast\Logger\Logger;

require 'vendor/autoload.php';

// Initialize the logger
$conf = array(
'error_log_message_type' => 3,
'logging_file' => './error_log',
'logging_level' => 0, // start with logging almost nothing for purpose of test looping
'logging_level_page_speed' => 5,
'log_monthly_rotation' => true,
'log_profiling_step' => 0.001,
'mail_for_admin_enabled' => true,
Logger::CONF_ERROR_LOG_MESSAGE_TYPE => 3,
Logger::CONF_LOGGING_FILE => './error_log', // extension .log will be added automatically
Logger::CONF_LOGGING_LEVEL => 0, // start with logging almost nothing for purpose of test looping
Logger::CONF_LOGGING_LEVEL_PAGE_SPEED => 5,
Logger::CONF_LOG_MONTHLY_ROTATION => true,
Logger::CONF_LOG_PROFILING_STEP => 0.00048,
Logger::CONF_MAIL_FOR_ADMIN_ENABLED => false,
);
$logger = new Seablast\Logger\Logger($conf);
$logger = new Logger($conf);

$severities = array('emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug');
// Loop through levels 1 to 5
Expand Down

0 comments on commit b1683d7

Please sign in to comment.