360 lines
9.2 KiB
PHP
360 lines
9.2 KiB
PHP
<?php
|
|
defined( 'WPINC' ) OR exit;
|
|
|
|
/**
|
|
* Encapsulates the logic required to maintain and read log files.
|
|
*/
|
|
class DG_Logger {
|
|
|
|
/**
|
|
* @var string Name of the log purge action.
|
|
*/
|
|
const PurgeLogsAction = 'document-gallery_purge-logs';
|
|
|
|
/**
|
|
* Appends DG log file if logging is enabled.
|
|
*
|
|
* @param int $level The level of serverity (should be passed using DG_LogLevel consts).
|
|
* @param string $entry Value to be logged.
|
|
* @param bool $stacktrace Whether to include full stack trace.
|
|
* @param bool $force Whether to ignore logging flag and log no matter what.
|
|
* Only should be used when the user *must* know about something.
|
|
*/
|
|
public static function writeLog( $level, $entry, $stacktrace = false, $force = false ) {
|
|
if ( $force || self::logEnabled() ) {
|
|
$fp = fopen( self::getLogFileName(), 'a' );
|
|
if ( false !== $fp ) {
|
|
$fields = array( time(), $level, $entry );
|
|
|
|
$trace = debug_backtrace( false );
|
|
if ( $stacktrace ) {
|
|
unset( $trace[0] );
|
|
$fields[] = self::getStackTraceString( $trace );
|
|
} else {
|
|
// Ignore first item from backtrace as it's this function which is redundant.
|
|
$caller = $trace[1];
|
|
|
|
$class = isset( $caller['class'] ) ? $caller['class'] : '';
|
|
$type = isset( $caller['type'] ) ? $caller['type'] : '';
|
|
$caller = $class . $type . $caller['function'];
|
|
|
|
$fields[2] = '(' . $caller . ') ' . $fields[2];
|
|
}
|
|
|
|
fputcsv( $fp, $fields );
|
|
fclose( $fp );
|
|
} // TODO: else
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads the current blog's log file, placing the values in to a 2-dimensional array.
|
|
*
|
|
* @param int $skip How many lines to skip before returning rows.
|
|
* @param int $limit Max number of lines to read.
|
|
*
|
|
* @return string[][]|null The rows from the log file or null if failed to open log.
|
|
*/
|
|
public static function readLog( $skip = 0, $limit = PHP_INT_MAX ) {
|
|
$ret = null;
|
|
$fp = @fopen( self::getLogFileName(), 'r' );
|
|
|
|
if ( $fp !== false ) {
|
|
$ret = array();
|
|
while ( count( $ret ) < $limit && false !== ( $fields = fgetcsv( $fp ) ) ) {
|
|
if ( $skip > 0 ) {
|
|
$skip --;
|
|
continue;
|
|
}
|
|
|
|
if ( ! is_null( $fields ) ) {
|
|
$ret[] = $fields;
|
|
}
|
|
}
|
|
|
|
@fclose( $fp );
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Clears the log file for the active blog.
|
|
*/
|
|
public static function clearLog() {
|
|
// we don't care if the file actually exists -- it won't when we're done
|
|
@unlink( self::getLogFileName() );
|
|
}
|
|
|
|
/**
|
|
* Clears the log file for all blogs.
|
|
*/
|
|
public static function clearLogs() {
|
|
// we don't care if the files actually exist -- they won't when we're done
|
|
foreach ( DG_Util::getBlogIds() as $id ) {
|
|
@unlink( self::getLogFileName( $id ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Truncates all blog logs to the current purge interval.
|
|
*
|
|
* TODO: This is a memory hog. Consider switching to stream filter.
|
|
*/
|
|
public static function purgeExpiredEntries() {
|
|
self::writeLog( DG_LogLevel::Detail, 'Beginning scheduled log file purge.' );
|
|
|
|
$blogs = array( null );
|
|
if ( is_multisite() ) {
|
|
$blogs = DG_Util::getBlogIds();
|
|
}
|
|
|
|
// truncate each blog's log file
|
|
$time = time();
|
|
foreach ( $blogs as $blog ) {
|
|
$blog_num = ! is_null( $blog ) ? $blog : get_current_blog_id();
|
|
$options = self::getOptions( $blog );
|
|
$purge_time = $time - $options['purge_interval'] * DAY_IN_SECONDS;
|
|
|
|
// purging is disabled for this blog
|
|
if ( $purge_time >= $time ) {
|
|
continue;
|
|
}
|
|
|
|
// do purge for this blog
|
|
$file = self::getLogFileName( $blog_num );
|
|
if ( file_exists( $file ) ) {
|
|
$fp = @fopen( $file, 'r' );
|
|
|
|
if ( $fp !== false ) {
|
|
$truncate = false;
|
|
$offset = 0;
|
|
|
|
// find the first non-expired entry
|
|
while ( ( $fields = fgetcsv( $fp ) ) !== false ) {
|
|
if ( ! is_null( $fields ) && intval( $fields[0] ) >= $purge_time ) {
|
|
// we've reached the recent entries -- nothing beyond here will be removed
|
|
break;
|
|
}
|
|
|
|
$offset = @ftell( $fp );
|
|
if ( false === $offset ) {
|
|
break;
|
|
}
|
|
|
|
$truncate = true;
|
|
}
|
|
|
|
@fclose( $fp );
|
|
|
|
// if any expired entries exist -- remove them from the file
|
|
if ( $truncate ) {
|
|
self::writeLog( DG_LogLevel::Detail, "Purging log entries for blog #$blog_num." );
|
|
$data = file_get_contents( $file, false, null, $offset );
|
|
file_put_contents( $file, $data, LOCK_EX );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generally not necessary to call external to this class -- only use if generating
|
|
* log entry will take significant resources and you want to avoid this operation
|
|
* if it will not actually be logged.
|
|
*
|
|
* @return bool Whether debug logging is currently enabled.
|
|
*/
|
|
public static function logEnabled() {
|
|
$options = self::getOptions();
|
|
|
|
return $options['enabled'];
|
|
}
|
|
|
|
/**
|
|
* Gets logging options.
|
|
*
|
|
* @param int $blog ID of the blog to be retrieved in multisite env.
|
|
*
|
|
* @return mixed[] Logger options for the blog.
|
|
*/
|
|
public static function getOptions( $blog = null ) {
|
|
$options = DocumentGallery::getOptions( $blog );
|
|
|
|
return $options['logging'];
|
|
}
|
|
|
|
/**
|
|
* @param $id int The ID of the blog to retrieve log file name for. Defaults to current blog.
|
|
*
|
|
* @return string Full path to log file for current blog.
|
|
*/
|
|
private static function getLogFileName( $id = null ) {
|
|
$id = ! is_null( $id ) ? $id : get_current_blog_id();
|
|
|
|
return DG_PATH . 'log/' . $id . '.log';
|
|
}
|
|
|
|
/**
|
|
* @param mixed[][] $trace Array containing stack trace to be converted to string.
|
|
*
|
|
* @return string The stack trace in human-readable form.
|
|
*/
|
|
private static function getStackTraceString( $trace ) {
|
|
$trace_str = '';
|
|
$i = 1;
|
|
|
|
foreach ( $trace as $node ) {
|
|
$trace_str .= "#$i ";
|
|
|
|
$file = '';
|
|
if ( isset( $node['file'] ) ) {
|
|
// convert to relative path from WP root
|
|
$file = str_replace( ABSPATH, '', $node['file'] );
|
|
}
|
|
|
|
if ( isset( $node['line'] ) ) {
|
|
$file .= "({$node['line']})";
|
|
}
|
|
|
|
if ( $file ) {
|
|
$trace_str .= "$file: ";
|
|
}
|
|
|
|
if ( isset( $node['class'] ) ) {
|
|
$trace_str .= "{$node['class']}{$node['type']}";
|
|
}
|
|
|
|
if ( isset( $node['function'] ) ) {
|
|
// only include args for first item in stack trace
|
|
$args = '';
|
|
if ( 1 === $i && isset( $node['args'] ) ) {
|
|
$args = implode( ', ', array_map( array( __CLASS__, 'print_r' ), $node['args'] ) );
|
|
}
|
|
|
|
$trace_str .= "{$node['function']}($args)" . PHP_EOL;
|
|
}
|
|
$i ++;
|
|
}
|
|
|
|
return $trace_str;
|
|
}
|
|
|
|
/**
|
|
* Wraps print_r passing true for the return argument.
|
|
*
|
|
* @param mixed $v Value to be printed.
|
|
*
|
|
* @return string Printed value.
|
|
*/
|
|
private static function print_r( $v ) {
|
|
return preg_replace( '/\s+/', ' ', print_r( $v, true ) );
|
|
}
|
|
|
|
/**
|
|
* Blocks instantiation. All functions are static.
|
|
*/
|
|
private function __construct() {
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* LogLevel acts as an enumeration of all possible log levels.
|
|
*/
|
|
class DG_LogLevel {
|
|
/**
|
|
* @var int Log level for anything that doesn't indicate a problem.
|
|
*/
|
|
const Detail = 0;
|
|
|
|
/**
|
|
* @var int Log level for anything that is a minor issue.
|
|
*/
|
|
const Warning = 1;
|
|
|
|
/**
|
|
* @var int Log level for when something went wrong.
|
|
*/
|
|
const Error = 2;
|
|
|
|
/**
|
|
* @var ReflectionClass Backs the getter.
|
|
*/
|
|
private static $ref = null;
|
|
|
|
/**
|
|
* @return ReflectionClass Instance of reflection class for this class.
|
|
*/
|
|
private static function getReflectionClass() {
|
|
if ( is_null( self::$ref ) ) {
|
|
self::$ref = new ReflectionClass( __CLASS__ );
|
|
}
|
|
|
|
return self::$ref;
|
|
}
|
|
|
|
/**
|
|
* @var int[] Backs the getter.
|
|
*/
|
|
private static $levels = null;
|
|
|
|
/**
|
|
* @return int[] Associative array containing all log level names mapped to their int value.
|
|
*/
|
|
public static function getLogLevels() {
|
|
if ( is_null( self::$levels ) ) {
|
|
$ref = self::getReflectionClass();
|
|
self::$levels = $ref->getConstants();
|
|
}
|
|
|
|
return self::$levels;
|
|
}
|
|
|
|
/**
|
|
* @param string $name Name to be checked for validity.
|
|
*
|
|
* @return bool Whether given name represents valid log level.
|
|
*/
|
|
public static function isValidName( $name ) {
|
|
return array_key_exists( $name, self::getLogLevels() );
|
|
}
|
|
|
|
/**
|
|
* @param int $value Value to be checked for validity.
|
|
*
|
|
* @return bool Whether given value represents valid log level.
|
|
*/
|
|
public static function isValidValue( $value ) {
|
|
return ( false !== array_search( $value, self::getLogLevels() ) );
|
|
}
|
|
|
|
/**
|
|
* @param string $name The name for which to retrieve a value.
|
|
*
|
|
* @return int|null The value associated with the given name.
|
|
*/
|
|
public static function getValueByName( $name ) {
|
|
$levels = self::getLogLevels();
|
|
|
|
return array_key_exists( $name, self::getLogLevels() ) ? $levels[ $name ] : null;
|
|
}
|
|
|
|
/**
|
|
* @param int $value The value for which to retrieve a name.
|
|
*
|
|
* @return string|null The name associated with the given value.
|
|
*/
|
|
public static function getNameByValue( $value ) {
|
|
$ret = array_search( $value, self::getLogLevels() );
|
|
|
|
return ( false !== $ret ) ? $ret : null;
|
|
}
|
|
|
|
/**
|
|
* Blocks instantiation. All functions are static.
|
|
*/
|
|
private function __construct() {
|
|
|
|
}
|
|
} |