226 lines
5.0 KiB
PHP
226 lines
5.0 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Class MC4WP_Debug_Log
|
|
*
|
|
* Simple logging class which writes to a file, loosely based on PSR-3.
|
|
*/
|
|
class MC4WP_Debug_Log {
|
|
|
|
|
|
/**
|
|
* Detailed debug information
|
|
*/
|
|
const DEBUG = 100;
|
|
|
|
/**
|
|
* Interesting events
|
|
*
|
|
* Examples: Visitor subscribed
|
|
*/
|
|
const INFO = 200;
|
|
|
|
/**
|
|
* Exceptional occurrences that are not errors
|
|
*
|
|
* Examples: User already subscribed
|
|
*/
|
|
const WARNING = 300;
|
|
|
|
/**
|
|
* Runtime errors
|
|
*/
|
|
const ERROR = 400;
|
|
|
|
/**
|
|
* Logging levels from syslog protocol defined in RFC 5424
|
|
*
|
|
* @var array $levels Logging levels
|
|
*/
|
|
protected static $levels = array(
|
|
self::DEBUG => 'DEBUG',
|
|
self::INFO => 'INFO',
|
|
self::WARNING => 'WARNING',
|
|
self::ERROR => 'ERROR',
|
|
);
|
|
|
|
/**
|
|
* @var string The file to which messages should be written.
|
|
*/
|
|
public $file;
|
|
|
|
/**
|
|
* @var int Only write messages with this level or higher
|
|
*/
|
|
public $level;
|
|
|
|
/**
|
|
* @var resource
|
|
*/
|
|
protected $stream;
|
|
|
|
/**
|
|
* MC4WP_Debug_Log constructor.
|
|
*
|
|
* @param string $file
|
|
* @param mixed $level;
|
|
*/
|
|
public function __construct( $file, $level = self::DEBUG ) {
|
|
$this->file = $file;
|
|
$this->level = self::to_level( $level );
|
|
}
|
|
|
|
/**
|
|
* @param mixed $level
|
|
* @param string $message
|
|
* @return boolean
|
|
*/
|
|
public function log( $level, $message ) {
|
|
$level = self::to_level( $level );
|
|
|
|
// only log if message level is higher than log level
|
|
if ( $level < $this->level ) {
|
|
return false;
|
|
}
|
|
|
|
// obfuscate email addresses in log message since log might be public.
|
|
$message = mc4wp_obfuscate_email_addresses( (string) $message );
|
|
|
|
// first, get rid of everything between "invisible" tags
|
|
$message = preg_replace( '/<(?:style|script|head)>.+?<\/(?:style|script|head)>/is', '', $message );
|
|
|
|
// then, strip tags (while retaining content of these tags)
|
|
$message = strip_tags( $message );
|
|
$message = trim( $message );
|
|
|
|
/**
|
|
* Modifies the message that is written to the debug log.
|
|
* Return an empty string to skip logging this message altogether.
|
|
*
|
|
* @param string $message
|
|
*/
|
|
$message = apply_filters( 'mc4wp_debug_log_message', $message );
|
|
if ( empty( $message ) ) {
|
|
return false;
|
|
}
|
|
|
|
// generate line
|
|
$level_name = self::get_level_name( $level );
|
|
$datetime = gmdate( 'Y-m-d H:i:s', time() + ( get_option( 'gmt_offset', 0 ) * HOUR_IN_SECONDS ) );
|
|
$message = sprintf( '[%s] %s: %s', $datetime, $level_name, $message ) . PHP_EOL;
|
|
|
|
// did we open stream yet?
|
|
if ( ! is_resource( $this->stream ) ) {
|
|
// attempt to open stream
|
|
$this->stream = @fopen( $this->file, 'c+' );
|
|
if ( ! is_resource( $this->stream ) ) {
|
|
return false;
|
|
}
|
|
|
|
// make sure first line of log file is a PHP tag + exit statement (to prevent direct file access)
|
|
$line = fgets( $this->stream );
|
|
$php_exit_string = '<?php exit; ?>';
|
|
if ( strpos( $line, $php_exit_string ) !== 0 ) {
|
|
rewind( $this->stream );
|
|
fwrite( $this->stream, $php_exit_string . PHP_EOL . $line );
|
|
}
|
|
|
|
// place pointer at end of file
|
|
fseek( $this->stream, 0, SEEK_END );
|
|
}
|
|
|
|
// lock file while we write, ignore errors (not much we can do)
|
|
flock( $this->stream, LOCK_EX );
|
|
|
|
// write the message to the file
|
|
fwrite( $this->stream, $message );
|
|
|
|
// unlock file again, but don't close it for remainder of this request
|
|
flock( $this->stream, LOCK_UN );
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @param string $message
|
|
* @return boolean
|
|
*/
|
|
public function warning( $message ) {
|
|
return $this->log( self::WARNING, $message );
|
|
}
|
|
|
|
/**
|
|
* @param string $message
|
|
* @return boolean
|
|
*/
|
|
public function info( $message ) {
|
|
return $this->log( self::INFO, $message );
|
|
}
|
|
|
|
/**
|
|
* @param string $message
|
|
* @return boolean
|
|
*/
|
|
public function error( $message ) {
|
|
return $this->log( self::ERROR, $message );
|
|
}
|
|
|
|
/**
|
|
* @param string $message
|
|
* @return boolean
|
|
*/
|
|
public function debug( $message ) {
|
|
return $this->log( self::DEBUG, $message );
|
|
}
|
|
|
|
/**
|
|
* Converts PSR-3 levels to local ones if necessary
|
|
*
|
|
* @param string|int Level number or name (PSR-3)
|
|
* @return int
|
|
*/
|
|
public static function to_level( $level ) {
|
|
if ( is_string( $level ) ) {
|
|
$level = strtoupper( $level );
|
|
if ( defined( __CLASS__ . '::' . $level ) ) {
|
|
return constant( __CLASS__ . '::' . $level );
|
|
}
|
|
|
|
throw new InvalidArgumentException( 'Level "' . $level . '" is not defined, use one of: ' . implode( ', ', array_keys( self::$levels ) ) );
|
|
}
|
|
|
|
return $level;
|
|
}
|
|
|
|
/**
|
|
* Gets the name of the logging level.
|
|
*
|
|
* @param int $level
|
|
* @return string
|
|
*/
|
|
public static function get_level_name( $level ) {
|
|
if ( ! isset( self::$levels[ $level ] ) ) {
|
|
throw new InvalidArgumentException( 'Level "' . $level . '" is not defined, use one of: ' . implode( ', ', array_keys( self::$levels ) ) );
|
|
}
|
|
|
|
return self::$levels[ $level ];
|
|
}
|
|
|
|
/**
|
|
* Tests if the log file is writable
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function test() {
|
|
$handle = @fopen( $this->file, 'a' );
|
|
$writable = false;
|
|
|
|
if ( is_resource( $handle ) ) {
|
|
$writable = true;
|
|
fclose( $handle );
|
|
}
|
|
|
|
return $writable;
|
|
}
|
|
}
|