Merge branch 'wc-logger--rebased'

This commit is contained in:
Claudio Sanches 2017-01-12 17:54:20 -02:00
commit 7b95988811
35 changed files with 2805 additions and 189 deletions

File diff suppressed because one or more lines are too long

View File

@ -580,6 +580,74 @@ table.wc_status_table {
}
}
/**
* DB log viewer
*/
.wp-list-table.logs {
.log-level {
display: inline;
padding: .2em .6em .3em;
font-size: 80%;
font-weight: bold;
line-height: 1;
color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .2em;
&:empty {
display: none;
}
}
/**
* Add color to levels
*
* Descending severity:
* emergency, alert -> red
* critical, error -> orange
* warning, notice -> yellow
* info -> blue
* debug -> gree
*/
.log-level--emergency,
.log-level--alert {
background-color: #ff4136;
}
.log-level--critical,
.log-level--error {
background-color: #ff851b;
}
.log-level--warning,
.log-level--notice {
color: #222;
background-color: #ffdc00;
}
.log-level--info {
background-color: #0074d9;
}
.log-level--debug {
background-color: #3d9970;
}
// Adjust log table columns only when table is not collapsed
@media screen and ( min-width: 783px ) {
.column-timestamp {
width: 18%;
}
.column-level {
width: 14%;
}
.column-source {
width: 15%;
}
}
}
#log-viewer-select {
padding: 10px 0 8px;
line-height: 28px;

View File

@ -0,0 +1,61 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Abstract WC Log Handler Class
*
* @version 1.0.0
* @package WooCommerce/Abstracts
* @category Abstract Class
* @author WooThemes
*/
abstract class WC_Log_Handler {
/**
* Handle a log entry.
*
* @param int $timestamp Log timestamp.
* @param string $level emergency|alert|critical|error|warning|notice|info|debug
* @param string $message Log message.
* @param array $context Additional information for log handlers.
*
* @return bool False if value was not handled and true if value was handled.
*/
abstract public function handle( $timestamp, $level, $message, $context );
/**
* Formats a timestamp for use in log messages.
*
* @param int $timestamp Log timestamp.
* @return string Formatted time for use in log entry.
*/
protected static function format_time( $timestamp ) {
return date( 'c', $timestamp );
}
/**
* Builds a log entry text from level, timestamp and message.
*
* @param int $timestamp Log timestamp.
* @param string $level emergency|alert|critical|error|warning|notice|info|debug
* @param string $message Log message.
* @param array $context Additional information for log handlers.
*
* @return string Formatted log entry.
*/
protected static function format_entry( $timestamp, $level, $message, $context ) {
$time_string = self::format_time( $timestamp );
$level_string = strtoupper( $level );
$entry = "{$time_string} {$level_string} {$message}";
return apply_filters( 'woocommerce_format_log_entry', $entry, array(
'timestamp' => $timestamp,
'level' => $level,
'message' => $message,
'context' => $context,
) );
}
}

View File

@ -0,0 +1,355 @@
<?php
/**
* WooCommerce Log Table List
*
* @author WooThemes
* @category Admin
* @package WooCommerce/Admin
* @version 1.0.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WP_List_Table' ) ) {
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
}
class WC_Admin_Log_Table_List extends WP_List_Table {
/**
* Initialize the log table list.
*/
public function __construct() {
parent::__construct( array(
'singular' => __( 'log', 'woocommerce' ),
'plural' => __( 'logs', 'woocommerce' ),
'ajax' => false,
) );
}
/**
* Display level dropdown
*
* @global wpdb $wpdb
*/
public function level_dropdown() {
$levels = array(
array( 'value' => WC_Log_Levels::EMERGENCY, 'label' => __( 'Emergency', 'woocommerce' ) ),
array( 'value' => WC_Log_Levels::ALERT, 'label' => __( 'Alert', 'woocommerce' ) ),
array( 'value' => WC_Log_Levels::CRITICAL, 'label' => __( 'Critical', 'woocommerce' ) ),
array( 'value' => WC_Log_Levels::ERROR, 'label' => __( 'Error', 'woocommerce' ) ),
array( 'value' => WC_Log_Levels::WARNING, 'label' => __( 'Warning', 'woocommerce' ) ),
array( 'value' => WC_Log_Levels::NOTICE, 'label' => __( 'Notice', 'woocommerce' ) ),
array( 'value' => WC_Log_Levels::INFO, 'label' => __( 'Info', 'woocommerce' ) ),
array( 'value' => WC_Log_Levels::DEBUG, 'label' => __( 'Debug', 'woocommerce' ) ),
);
$selected_level = isset( $_REQUEST['level'] ) ? $_REQUEST['level'] : '';
?>
<label for="filter-by-level" class="screen-reader-text"><?php _e( 'Filter by level', 'woocommerce' ); ?></label>
<select name="level" id="filter-by-level">
<option<?php selected( $selected_level, '' ); ?> value=""><?php _e( 'All levels', 'woocommerce' ); ?></option>
<?php foreach ( $levels as $l ) {
printf( '<option%1$s value="%2$s">%3$s</option>',
selected( $selected_level, $l['value'], false ),
esc_attr( $l['value'] ),
esc_html( $l['label'] )
);
} ?>
</select>
<?php
}
/**
* Get list columns.
*
* @return array
*/
public function get_columns() {
return array(
'cb' => '<input type="checkbox" />',
'timestamp' => __( 'Timestamp', 'woocommerce' ),
'level' => __( 'Level', 'woocommerce' ),
'message' => __( 'Message', 'woocommerce' ),
'source' => __( 'Source', 'woocommerce' ),
);
}
/**
* Column cb.
*
* @param array $log
* @return string
*/
public function column_cb( $log ) {
return sprintf( '<input type="checkbox" name="log[]" value="%1$s" />', esc_attr( $log['log_id'] ) );
}
/**
* Timestamp column.
*
* @param array $log
* @return string
*/
public function column_timestamp( $log ) {
return esc_html( mysql2date(
get_option( 'date_format' ) . ' ' . get_option( 'time_format' ),
$log['timestamp']
) );
}
/**
* Level column.
*
* @param array $log
* @return string
*/
public function column_level( $log ) {
$level_key = WC_Log_Levels::get_severity_level( $log['level'] );
$levels = array(
'emergency' => __( 'Emergency', 'woocommerce' ),
'alert' => __( 'Alert', 'woocommerce' ),
'critical' => __( 'Critical', 'woocommerce' ),
'error' => __( 'Error', 'woocommerce' ),
'warning' => __( 'Warning', 'woocommerce' ),
'notice' => __( 'Notice', 'woocommerce' ),
'info' => __( 'Info', 'woocommerce' ),
'debug' => __( 'Debug', 'woocommerce' ),
);
if ( isset( $levels[ $level_key ] ) ) {
$level = $levels[ $level_key ];
$level_class = sanitize_html_class( 'log-level--' . $level_key );
return '<span class="log-level ' . $level_class . '">' . esc_html( $level ) . '</span>';
} else {
return '';
}
}
/**
* Message column.
*
* @param array $log
* @return string
*/
public function column_message( $log ) {
return esc_html( $log['message'] );
}
/**
* Source column.
*
* @param array $log
* @return string
*/
public function column_source( $log ) {
return esc_html( $log['source'] );
}
/**
* Get bulk actions.
*
* @return array
*/
protected function get_bulk_actions() {
return array(
'delete' => __( 'Delete', 'woocommerce' ),
);
}
/**
* Extra controls to be displayed between bulk actions and pagination.
*
* @param string $which
*/
protected function extra_tablenav( $which ) {
if ( 'top' === $which ) {
echo '<div class="alignleft actions">';
$this->level_dropdown();
$this->source_dropdown();
submit_button( __( 'Filter', 'woocommerce' ), '', 'filter-action', false );
echo '</div>';
}
}
/**
* Get a list of sortable columns.
*
* @return array
*/
protected function get_sortable_columns() {
return array(
'timestamp' => array( 'timestamp', true ),
'level' => array( 'level', true ),
'source' => array( 'source', true ),
);
}
/**
* Display source dropdown
*
* @global wpdb $wpdb
*/
protected function source_dropdown() {
global $wpdb;
$sources = $wpdb->get_col( "
SELECT DISTINCT source
FROM {$wpdb->prefix}woocommerce_log
WHERE source != ''
ORDER BY source ASC
" );
if ( ! empty( $sources ) ) {
$selected_source = isset( $_REQUEST['source'] ) ? $_REQUEST['source'] : '';
?>
<label for="filter-by-source" class="screen-reader-text"><?php _e( 'Filter by source', 'woocommerce' ); ?></label>
<select name="source" id="filter-by-source">
<option<?php selected( $selected_source, '' ); ?> value=""><?php _e( 'All sources', 'woocommerce' ); ?></option>
<?php foreach ( $sources as $s ) {
printf( '<option%1$s value="%2$s">%3$s</option>',
selected( $selected_source, $s, false ),
esc_attr( $s ),
esc_html( $s )
);
} ?>
</select>
<?php
}
}
/**
* Prepare table list items.
*
* @global wpdb $wpdb
*/
public function prepare_items() {
global $wpdb;
$this->prepare_column_headers();
$per_page = $this->get_items_per_page( 'woocommerce_status_log_items_per_page', 10 );
$where = $this->get_items_query_where();
$order = $this->get_items_query_order();
$limit = $this->get_items_query_limit();
$offset = $this->get_items_query_offset();
$query_items = "
SELECT log_id, timestamp, level, message, source
FROM {$wpdb->prefix}woocommerce_log
{$where} {$order} {$limit} {$offset}
";
$this->items = $wpdb->get_results( $query_items, ARRAY_A );
$query_count = "SELECT COUNT(log_id) FROM {$wpdb->prefix}woocommerce_log {$where}";
$total_items = $wpdb->get_var( $query_count );
$this->set_pagination_args( array(
'total_items' => $total_items,
'per_page' => $per_page,
'total_pages' => ceil( $total_items / $per_page ),
) );
}
/**
* Get prepared LIMIT clause for items query
*
* @global wpdb $wpdb
*
* @return string Prepared LIMIT clause for items query.
*/
protected function get_items_query_limit() {
global $wpdb;
$per_page = $this->get_items_per_page( 'woocommerce_status_log_items_per_page', 10 );
return $wpdb->prepare( 'LIMIT %d', $per_page );
}
/**
* Get prepared OFFSET clause for items query
*
* @global wpdb $wpdb
*
* @return string Prepared OFFSET clause for items query.
*/
protected function get_items_query_offset() {
global $wpdb;
$per_page = $this->get_items_per_page( 'woocommerce_status_log_items_per_page', 10 );
$current_page = $this->get_pagenum();
if ( 1 < $current_page ) {
$offset = $per_page * ( $current_page - 1 );
} else {
$offset = 0;
}
return $wpdb->prepare( 'OFFSET %d', $offset );
}
/**
* Get prepared ORDER BY clause for items query
*
* @return string Prepared ORDER BY clause for items query.
*/
protected function get_items_query_order() {
$valid_orders = array( 'level', 'source', 'timestamp' );
if ( ! empty( $_REQUEST['orderby'] ) && in_array( $_REQUEST['orderby'], $valid_orders ) ) {
$by = wc_clean( $_REQUEST['orderby'] );
} else {
$by = 'timestamp';
}
$by = esc_sql( $by );
if ( ! empty( $_REQUEST['order'] ) && 'asc' === strtolower( $_REQUEST['order'] ) ) {
$order = 'ASC';
} else {
$order = 'DESC';
}
return "ORDER BY {$by} {$order}";
}
/**
* Get prepared WHERE clause for items query
*
* @global wpdb $wpdb
*
* @return string Prepared WHERE clause for items query.
*/
protected function get_items_query_where() {
global $wpdb;
$where_conditions = array();
$where_values = array();
if ( ! empty( $_REQUEST['level'] ) && WC_Log_Levels::is_valid_level( $_REQUEST['level'] ) ) {
$where_conditions[] = 'level >= %d';
$where_values[] = WC_Log_Levels::get_level_severity( $_REQUEST['level'] );
}
if ( ! empty( $_REQUEST['source'] ) ) {
$where_conditions[] = 'source = %s';
$where_values[] = wc_clean( $_REQUEST['source'] );
}
if ( ! empty( $where_conditions ) ) {
return $wpdb->prepare( 'WHERE 1 = 1 AND ' . implode( ' AND ', $where_conditions ), $where_values );
} else {
return '';
}
}
/**
* Set _column_headers property for table list
*/
protected function prepare_column_headers() {
$this->_column_headers = array(
$this->get_columns(),
array(),
$this->get_sortable_columns(),
);
}
}

View File

@ -77,6 +77,17 @@ class WC_Admin_Status {
* Show the logs page.
*/
public static function status_logs() {
if ( defined( 'WC_LOG_HANDLER' ) && 'WC_Log_Handler_DB' === WC_LOG_HANDLER ) {
self::status_logs_db();
} else {
self::status_logs_file();
}
}
/**
* Show the log page contents for file log handler.
*/
public static function status_logs_file() {
$logs = self::scan_log_files();
@ -95,6 +106,27 @@ class WC_Admin_Status {
include_once( 'views/html-admin-page-status-logs.php' );
}
/**
* Show the log page contents for db log handler.
*/
public static function status_logs_db() {
// Flush
if ( ! empty( $_REQUEST['flush-logs'] ) ) {
self::flush_db_logs();
}
// Bulk actions
if ( isset( $_GET['action'] ) && isset( $_GET['log'] ) ) {
self::log_table_bulk_actions();
}
$log_table_list = new WC_Admin_Log_Table_List();
$log_table_list->prepare_items();
include_once( 'views/html-admin-page-status-logs-db.php' );
}
/**
* Retrieve metadata from a file. Based on WP Core's get_file_data function.
* @since 2.1.1
@ -250,11 +282,46 @@ class WC_Admin_Status {
}
if ( ! empty( $_REQUEST['handle'] ) ) {
$logger = wc_get_logger();
$logger->remove( $_REQUEST['handle'] );
$log_handler = new WC_Log_Handler_File();
$log_handler->remove( $_REQUEST['handle'] );
}
wp_safe_redirect( esc_url_raw( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) );
exit();
}
/**
* Clear DB log table.
*
* @since 2.7.0
*/
private static function flush_db_logs() {
if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'woocommerce-status-logs' ) ) {
wp_die( __( 'Action failed. Please refresh the page and retry.', 'woocommerce' ) );
}
WC_Log_Handler_DB::flush();
wp_safe_redirect( esc_url_raw( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) );
exit();
}
/**
* Bulk DB log table actions.
*
* @since 2.7.0
*/
private static function log_table_bulk_actions() {
if ( empty( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'woocommerce-status-logs' ) ) {
wp_die( __( 'Action failed. Please refresh the page and retry.', 'woocommerce' ) );
}
$log_ids = array_map( 'absint', (array) $_GET['log'] );
if ( 'delete' === $_GET['action'] || 'delete' === $_GET['action2'] ) {
WC_Log_Handler_DB::delete( $log_ids );
wp_safe_redirect( esc_url_raw( admin_url( 'admin.php?page=wc-status&tab=logs' ) ) );
exit();
}
}
}

View File

@ -324,7 +324,7 @@ class WC_Admin_Report {
if ( $debug ) {
echo '<pre>';
print_r( $query );
wc_print_r( $query );
echo '</pre>';
}

View File

@ -0,0 +1,28 @@
<?php
/**
* Admin View: Page - Status Database Logs
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<form method="get" id="mainform" action="">
<?php $log_table_list->display(); ?>
<input type="hidden" name="page" value="wc-status" />
<input type="hidden" name="tab" value="logs" />
<?php submit_button( __( 'Flush all logs', 'woocommerce' ), 'delete', 'flush-logs' ); ?>
<?php wp_nonce_field( 'woocommerce-status-logs' ); ?>
</form>
<?php
wc_enqueue_js( "
jQuery( '#flush-logs' ).click( function() {
if ( window.confirm('" . esc_js( __( 'Are you sure you want to clear all logs from the database?', 'woocommerce' ) ) . "') ) {
return true;
}
return false;
});
" );

View File

@ -83,8 +83,11 @@ class WC_Autoloader {
$path = $this->include_path . 'admin/';
} elseif ( strpos( $class, 'wc_payment_token_' ) === 0 ) {
$path = $this->include_path . 'payment-tokens/';
} elseif ( strpos( $class, 'wc_log_handler_' ) === 0 ) {
$path = $this->include_path . 'log-handlers/';
}
if ( empty( $path ) || ( ! $this->load_file( $path . $file ) && strpos( $class, 'wc_' ) === 0 ) ) {
$this->load_file( $this->include_path . $file );
}

View File

@ -39,7 +39,10 @@ class WC_Background_Updater extends WP_Background_Process {
$logger = wc_get_logger();
if ( is_wp_error( $dispatched ) ) {
$logger->add( 'wc_db_updates', sprintf( 'Unable to dispatch WooCommerce updater: %s', $dispatched->get_error_message() ) );
$logger->error(
sprintf( 'Unable to dispatch WooCommerce updater: %s', $dispatched->get_error_message() ),
array( 'source' => 'wc_db_updates' )
);
}
}
@ -102,11 +105,11 @@ class WC_Background_Updater extends WP_Background_Process {
include_once( dirname( __FILE__ ) . '/wc-update-functions.php' );
if ( is_callable( $callback ) ) {
$logger->add( 'wc_db_updates', sprintf( 'Running %s callback', $callback ) );
$logger->info( sprintf( 'Running %s callback', $callback ), array( 'source' => 'wc_db_updates' ) );
call_user_func( $callback );
$logger->add( 'wc_db_updates', sprintf( 'Finished %s callback', $callback ) );
$logger->info( sprintf( 'Finished %s callback', $callback ), array( 'source' => 'wc_db_updates' ) );
} else {
$logger->add( 'wc_db_updates', sprintf( 'Could not find %s callback', $callback ) );
$logger->notice( sprintf( 'Could not find %s callback', $callback ), array( 'source' => 'wc_db_updates' ) );
}
return false;
@ -120,7 +123,7 @@ class WC_Background_Updater extends WP_Background_Process {
*/
protected function complete() {
$logger = wc_get_logger();
$logger->add( 'wc_db_updates', 'Data update complete' );
$logger->info( 'Data update complete', array( 'source' => 'wc_db_updates' ) );
WC_Install::update_db_version();
parent::complete();
}

View File

@ -1165,13 +1165,15 @@ class WC_Geo_IP {
/**
* Logging method.
*
* @param string $message
* @param string $message Log message.
* @param string $level Optional. Default 'info'.
* emergency|alert|critical|error|warning|notice|info|debug
*/
public static function log( $message ) {
public static function log( $message, $level = 'info' ) {
if ( empty( self::$log ) ) {
self::$log = wc_get_logger();
}
self::$log->add( 'geoip', $message );
self::$log->log( $level, $message, array( 'source' => 'geoip' ) );
}
/**
@ -1191,7 +1193,7 @@ class WC_Geo_IP {
$this->memory_buffer = fread( $this->filehandle, $s_array['size'] );
}
} else {
$this->log( 'GeoIP API: Can not open ' . $filename );
$this->log( 'GeoIP API: Can not open ' . $filename, 'error' );
}
}
@ -1550,7 +1552,7 @@ class WC_Geo_IP {
}
}
$this->log( 'GeoIP API: Error traversing database - perhaps it is corrupt?' );
$this->log( 'GeoIP API: Error traversing database - perhaps it is corrupt?', 'error' );
return false;
}
@ -1605,7 +1607,7 @@ class WC_Geo_IP {
}
}
$this->log( 'GeoIP API: Error traversing database - perhaps it is corrupt?' );
$this->log( 'GeoIP API: Error traversing database - perhaps it is corrupt?', 'error' );
return false;
}

View File

@ -185,7 +185,7 @@ class WC_Geolocation {
$logger = wc_get_logger();
if ( ! is_callable( 'gzopen' ) ) {
$logger->add( 'geolocation', 'Server does not support gzopen' );
$logger->notice( 'Server does not support gzopen', array( 'source' => 'geolocation' ) );
return;
}
@ -208,11 +208,14 @@ class WC_Geolocation {
gzclose( $gzhandle );
fclose( $handle );
} else {
$logger->add( 'geolocation', 'Unable to open database file' );
$logger->notice( 'Unable to open database file', array( 'source' => 'geolocation' ) );
}
@unlink( $tmp_database_path );
} else {
$logger->add( 'geolocation', 'Unable to download GeoIP Database: ' . $tmp_database_path->get_error_message() );
$logger->notice(
'Unable to download GeoIP Database: ' . $tmp_database_path->get_error_message(),
array( 'source' => 'geolocation' )
);
}
}
}

View File

@ -245,7 +245,10 @@ class WC_Install {
foreach ( self::get_db_update_callbacks() as $version => $update_callbacks ) {
if ( version_compare( $current_db_version, $version, '<' ) ) {
foreach ( $update_callbacks as $update_callback ) {
$logger->add( 'wc_db_updates', sprintf( 'Queuing %s - %s', $version, $update_callback ) );
$logger->info(
sprintf( 'Queuing %s - %s', $version, $update_callback ),
array( 'source' => 'wc_db_updates' )
);
self::$background_updater->push_to_queue( $update_callback );
$update_queued = true;
}
@ -601,6 +604,16 @@ CREATE TABLE {$wpdb->prefix}woocommerce_payment_tokenmeta (
PRIMARY KEY (meta_id),
KEY payment_token_id (payment_token_id),
KEY meta_key (meta_key($max_index_length))
) $collate;
CREATE TABLE {$wpdb->prefix}woocommerce_log (
log_id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
timestamp datetime NOT NULL,
level smallint(4) NOT NULL,
source varchar(255) NOT NULL,
message longtext NOT NULL,
context longtext NULL,
PRIMARY KEY (log_id),
KEY level (level)
) $collate;
";

View File

@ -0,0 +1,116 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Standard log levels
*
* @class WC_Log_Levels
* @version 1.0.0
* @package WooCommerce/Classes
* @category Class
* @author WooThemes
*/
abstract class WC_Log_Levels {
/**
* Log Levels
*
* Description of levels:
* 'emergency': System is unusable.
* 'alert': Action must be taken immediately.
* 'critical': Critical conditions.
* 'error': Error conditions.
* 'warning': Warning conditions.
* 'notice': Normal but significant condition.
* 'informational': Informational messages.
* 'debug': Debug-level messages.
*
* @see @link {https://tools.ietf.org/html/rfc5424}
*/
const EMERGENCY = 'emergency';
const ALERT = 'alert';
const CRITICAL = 'critical';
const ERROR = 'error';
const WARNING = 'warning';
const NOTICE = 'notice';
const INFO = 'info';
const DEBUG = 'debug';
/**
* Level strings mapped to integer severity.
*
* @var array
*/
protected static $level_to_severity = array(
self::EMERGENCY => 800,
self::ALERT => 700,
self::CRITICAL => 600,
self::ERROR => 500,
self::WARNING => 400,
self::NOTICE => 300,
self::INFO => 200,
self::DEBUG => 100,
);
/**
* Severity integers mapped to level strings.
*
* This is the inverse of $level_severity.
*
* @var array
*/
protected static $severity_to_level = array(
800 => self::EMERGENCY,
700 => self::ALERT,
600 => self::CRITICAL,
500 => self::ERROR,
400 => self::WARNING,
300 => self::NOTICE,
200 => self::INFO,
100 => self::DEBUG,
);
/**
* Validate a level string.
*
* @param string $level
* @return bool True if $level is a valid level.
*/
public static function is_valid_level( $level ) {
return array_key_exists( strtolower( $level ), self::$level_to_severity );
}
/**
* Translate level string to integer.
*
* @param string $level emergency|alert|critical|error|warning|notice|info|debug
* @return int 100 (debug) - 800 (emergency) or 0 if not recognized
*/
public static function get_level_severity( $level ) {
if ( self::is_valid_level( $level ) ) {
$severity = self::$level_to_severity[ strtolower( $level ) ];
} else {
$severity = 0;
}
return $severity;
}
/**
* Translate severity integer to level string.
*
* @param int $severity
* @return bool|string False if not recognized. Otherwise string representation of level.
*/
public static function get_severity_level( $severity ) {
if ( array_key_exists( $severity, self::$severity_to_level ) ) {
return self::$severity_to_level[ $severity ];
} else {
return false;
}
}
}

View File

@ -1,14 +1,13 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Allows log files to be written to for debugging purposes
* Provides logging capabilities for debugging purposes.
*
* @class WC_Logger
* @version 1.6.4
* @version 2.0.0
* @package WooCommerce/Classes
* @category Class
* @author WooThemes
@ -16,147 +15,223 @@ if ( ! defined( 'ABSPATH' ) ) {
class WC_Logger {
/**
* Stores open file _handles.
* Stores registered log handlers.
*
* @var array
* @access private
*/
private $_handles;
protected $handlers;
/**
* Minimum log level this handler will process.
*
* @var int Integer representation of minimum log level to handle.
*/
protected $threshold;
/**
* Constructor for the logger.
*/
public function __construct() {
$this->_handles = array();
}
/**
* Destructor.
*/
public function __destruct() {
foreach ( $this->_handles as $handle ) {
if ( is_resource( $handle ) ) {
fclose( $handle );
}
}
}
/**
* Open log file for writing.
*
* @param string $handle
* @param string $mode
* @return bool success
* @param array $handlers Optional. Array of log handlers. If $handlers is not provided,
* the filter 'woocommerce_register_log_handlers' will be used to define the handlers.
* If $handlers is provided, the filter will not be applied and the handlers will be
* used directly.
* @param string $threshold Optional. Define an explicit threshold. May be configured
* via WC_LOG_THRESHOLD. By default, all logs will be processed.
*/
protected function open( $handle, $mode = 'a' ) {
if ( isset( $this->_handles[ $handle ] ) ) {
public function __construct( $handlers = null, $threshold = null ) {
if ( null === $handlers ) {
$handlers = apply_filters( 'woocommerce_register_log_handlers', array() );
}
if ( null !== $threshold ) {
$threshold = WC_Log_Levels::get_level_severity( $threshold );
} elseif ( defined( 'WC_LOG_THRESHOLD' ) && WC_Log_Levels::is_valid_level( WC_LOG_THRESHOLD ) ) {
$threshold = WC_Log_Levels::get_level_severity( WC_LOG_THRESHOLD );
} else {
$threshold = null;
}
$this->handlers = $handlers;
$this->threshold = $threshold;
}
/**
* Determine whether to handle or ignore log.
*
* @param string $level emergency|alert|critical|error|warning|notice|info|debug
* @return bool True if the log should be handled.
*/
public function should_handle( $level ) {
if ( null === $this->threshold ) {
return true;
}
if ( ! file_exists( wc_get_log_file_path( $handle ) ) ) {
$temphandle = @fopen( wc_get_log_file_path( $handle ), 'w+' );
@fclose( $temphandle );
if ( defined( 'FS_CHMOD_FILE' ) ) {
@chmod( wc_get_log_file_path( $handle ), FS_CHMOD_FILE );
}
}
if ( $this->_handles[ $handle ] = @fopen( wc_get_log_file_path( $handle ), $mode ) ) {
return true;
}
return false;
return $this->threshold <= WC_Log_Levels::get_level_severity( $level );
}
/**
* Close a handle.
* Add a log entry.
*
* @param string $handle
* @return bool success
*/
protected function close( $handle ) {
$result = false;
if ( is_resource( $this->_handles[ $handle ] ) ) {
$result = fclose( $this->_handles[ $handle ] );
unset( $this->_handles[ $handle ] );
}
return $result;
}
/**
* Add a log entry to chosen file.
* This is not the preferred method for adding log messages. Please use log() or any one of
* the level methods (debug(), info(), etc.). This method may be deprecated in the future.
*
* @param string $handle
* @param string $message
*
* @return bool
*/
public function add( $handle, $message ) {
$result = false;
public function add( $handle, $message, $level = WC_Log_Levels::NOTICE ) {
$message = apply_filters( 'woocommerce_logger_add_message', $message, $handle );
$this->log( $level, $message, array( 'source' => $handle, '_legacy' => true ) );
wc_do_deprecated_action( 'woocommerce_log_add', array( $handle, $message ), '2.7', 'This action has been deprecated with no alternative.' );
return true;
}
if ( $this->open( $handle ) && is_resource( $this->_handles[ $handle ] ) ) {
$message = apply_filters( 'woocommerce_logger_add_message', $message, $handle );
$time = date_i18n( 'm-d-Y @ H:i:s -' ); // Grab Time
$result = fwrite( $this->_handles[ $handle ], $time . " " . $message . "\n" );
/**
* Add a log entry.
*
* @param string $level One of the following:
* 'emergency': System is unusable.
* 'alert': Action must be taken immediately.
* 'critical': Critical conditions.
* 'error': Error conditions.
* 'warning': Warning conditions.
* 'notice': Normal but significant condition.
* 'informational': Informational messages.
* 'debug': Debug-level messages.
* @param string $message Log message.
* @param array $context Optional. Additional information for log handlers.
*/
public function log( $level, $message, $context = array() ) {
if ( ! WC_Log_Levels::is_valid_level( $level ) ) {
$class = __CLASS__;
$method = __FUNCTION__;
wc_doing_it_wrong( "{$class}::{$method}", sprintf( __( 'WC_Logger::log was called with an invalid level "%s".', 'woocommerce' ), $level ), '2.7' );
}
do_action( 'woocommerce_log_add', $handle, $message );
if ( $this->should_handle( $level ) ) {
$timestamp = current_time( 'timestamp' );
return false !== $result;
foreach ( $this->handlers as $handler ) {
$handler->handle( $timestamp, $level, $message, $context );
}
}
}
/**
* Adds an emergency level message.
*
* System is unusable.
*
* @see WC_Logger::log
*/
public function emergency( $message, $context = array() ) {
$this->log( WC_Log_Levels::EMERGENCY, $message, $context );
}
/**
* Adds an alert level message.
*
* Action must be taken immediately.
* Example: Entire website down, database unavailable, etc.
*
* @see WC_Logger::log
*/
public function alert( $message, $context = array() ) {
$this->log( WC_Log_Levels::ALERT, $message, $context );
}
/**
* Adds a critical level message.
*
* Critical conditions.
* Example: Application component unavailable, unexpected exception.
*
* @see WC_Logger::log
*/
public function critical( $message, $context = array() ) {
$this->log( WC_Log_Levels::CRITICAL, $message, $context );
}
/**
* Adds an error level message.
*
* Runtime errors that do not require immediate action but should typically be logged
* and monitored.
*
* @see WC_Logger::log
*/
public function error( $message, $context = array() ) {
$this->log( WC_Log_Levels::ERROR, $message, $context );
}
/**
* Adds a warning level message.
*
* Exceptional occurrences that are not errors.
*
* Example: Use of deprecated APIs, poor use of an API, undesirable things that are not
* necessarily wrong.
*
* @see WC_Logger::log
*/
public function warning( $message, $context = array() ) {
$this->log( WC_Log_Levels::WARNING, $message, $context );
}
/**
* Adds a notice level message.
*
* Normal but significant events.
*
* @see WC_Logger::log
*/
public function notice( $message, $context = array() ) {
$this->log( WC_Log_Levels::NOTICE, $message, $context );
}
/**
* Adds a info level message.
*
* Interesting events.
* Example: User logs in, SQL logs.
*
* @see WC_Logger::log
*/
public function info( $message, $context = array() ) {
$this->log( WC_Log_Levels::INFO, $message, $context );
}
/**
* Adds a debug level message.
*
* Detailed debug information.
*
* @see WC_Logger::log
*/
public function debug( $message, $context = array() ) {
$this->log( WC_Log_Levels::DEBUG, $message, $context );
}
/**
* Clear entries from chosen file.
*
* @param string $handle
* @deprecated 2.7.0
*
* @return bool
*/
public function clear( $handle ) {
$result = false;
// Close the file if it's already open.
$this->close( $handle );
/**
* $this->open( $handle, 'w' ) == Open the file for writing only. Place the file pointer at the beginning of the file,
* and truncate the file to zero length.
*/
if ( $this->open( $handle, 'w' ) && is_resource( $this->_handles[ $handle ] ) ) {
$result = true;
}
do_action( 'woocommerce_log_clear', $handle );
return $result;
public function clear() {
wc_deprecated_function( 'WC_Logger::clear', '2.7', 'WC_Log_Handler_File::clear' );
return false;
}
/**
* Remove/delete the chosen file.
*
* @param string $handle
* @deprecated 2.7.0
*
* @return bool
*/
public function remove( $handle ) {
$removed = false;
$handle = wc_clean( $handle );
$file = wc_get_log_file_path( $handle );
if ( is_file( $file ) && is_writable( $file ) ) {
// Close first to be certain no processes keep it alive after it is unlinked.
$this->close( $handle );
$removed = unlink( $file );
} elseif ( is_file( trailingslashit( WC_LOG_DIR ) . sanitize_file_name( $handle . '.log' ) ) && is_writable( trailingslashit( WC_LOG_DIR ) . sanitize_file_name( $handle . '.log' ) ) ) {
$this->close( $handle );
$removed = unlink( trailingslashit( WC_LOG_DIR ) . sanitize_file_name( $handle . '.log' ) );
}
do_action( 'woocommerce_log_remove', $handle, $removed );
return $removed;
public function remove() {
wc_deprecated_function( 'WC_Logger::remove', '2.7', 'WC_Log_Handler_File::remove' );
return false;
}
}

View File

@ -418,7 +418,7 @@ class WC_Email extends WC_Settings_API {
$content = $emogrifier->emogrify();
} catch ( Exception $e ) {
$logger = wc_get_logger();
$logger->add( 'emogrifier', $e->getMessage() );
$logger->error( $e->getMessage(), array( 'source' => 'emogrifier' ) );
}
}
return $content;

View File

@ -74,14 +74,17 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
/**
* Logging method.
* @param string $message
*
* @param string $message Log message.
* @param string $level Optional. Default 'info'.
* emergency|alert|critical|error|warning|notice|info|debug
*/
public static function log( $message ) {
public static function log( $message, $level = 'info' ) {
if ( self::$log_enabled ) {
if ( empty( self::$log ) ) {
self::$log = wc_get_logger();
}
self::$log->add( 'paypal', $message );
self::$log->log( $level, $message, array( 'source' => 'paypal' ) );
}
}
@ -281,7 +284,7 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
$order = wc_get_order( $order_id );
if ( ! $this->can_refund_order( $order ) ) {
$this->log( 'Refund Failed: No transaction ID' );
$this->log( 'Refund Failed: No transaction ID', 'error' );
return new WP_Error( 'error', __( 'Refund failed: No transaction ID', 'woocommerce' ) );
}
@ -290,11 +293,11 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
$result = WC_Gateway_Paypal_API_Handler::refund_transaction( $order, $amount, $reason );
if ( is_wp_error( $result ) ) {
$this->log( 'Refund Failed: ' . $result->get_error_message() );
$this->log( 'Refund Failed: ' . $result->get_error_message(), 'error' );
return new WP_Error( 'error', $result->get_error_message() );
}
$this->log( 'Refund Result: ' . print_r( $result, true ) );
$this->log( 'Refund Result: ' . wc_print_r( $result, true ) );
switch ( strtolower( $result->ACK ) ) {
case 'success':
@ -320,12 +323,12 @@ class WC_Gateway_Paypal extends WC_Payment_Gateway {
$result = WC_Gateway_Paypal_API_Handler::do_capture( $order );
if ( is_wp_error( $result ) ) {
$this->log( 'Capture Failed: ' . $result->get_error_message() );
$this->log( 'Capture Failed: ' . $result->get_error_message(), 'error' );
$order->add_order_note( sprintf( __( 'Payment could not captured: %s', 'woocommerce' ), $result->get_error_message() ) );
return;
}
$this->log( 'Capture Result: ' . print_r( $result, true ) );
$this->log( 'Capture Result: ' . wc_print_r( $result, true ) );
if ( ! empty( $result->PAYMENTSTATUS ) ) {
switch ( $result->PAYMENTSTATUS ) {

View File

@ -88,7 +88,7 @@ class WC_Gateway_Paypal_API_Handler {
)
);
WC_Gateway_Paypal::log( 'DoCapture Response: ' . print_r( $raw_response, true ) );
WC_Gateway_Paypal::log( 'DoCapture Response: ' . wc_print_r( $raw_response, true ) );
if ( empty( $raw_response['body'] ) ) {
return new WP_Error( 'paypal-api', 'Empty Response' );
@ -120,7 +120,7 @@ class WC_Gateway_Paypal_API_Handler {
)
);
WC_Gateway_Paypal::log( 'Refund Response: ' . print_r( $raw_response, true ) );
WC_Gateway_Paypal::log( 'Refund Response: ' . wc_print_r( $raw_response, true ) );
if ( empty( $raw_response['body'] ) ) {
return new WP_Error( 'paypal-api', 'Empty Response' );

View File

@ -91,8 +91,8 @@ class WC_Gateway_Paypal_IPN_Handler extends WC_Gateway_Paypal_Response {
// Post back to get a response.
$response = wp_safe_remote_post( $this->sandbox ? 'https://www.sandbox.paypal.com/cgi-bin/webscr' : 'https://www.paypal.com/cgi-bin/webscr', $params );
WC_Gateway_Paypal::log( 'IPN Request: ' . print_r( $params, true ) );
WC_Gateway_Paypal::log( 'IPN Response: ' . print_r( $response, true ) );
WC_Gateway_Paypal::log( 'IPN Request: ' . wc_print_r( $params, true ) );
WC_Gateway_Paypal::log( 'IPN Response: ' . wc_print_r( $response, true ) );
// Check to see if the request was valid.
if ( ! is_wp_error( $response ) && $response['response']['code'] >= 200 && $response['response']['code'] < 300 && strstr( $response['body'], 'VERIFIED' ) ) {

View File

@ -88,7 +88,7 @@ class WC_Gateway_Paypal_PDT_Handler extends WC_Gateway_Paypal_Response {
$transaction_result = $this->validate_transaction( $transaction );
WC_Gateway_Paypal::log( 'PDT Transaction Result: ' . print_r( $transaction_result, true ) );
WC_Gateway_Paypal::log( 'PDT Transaction Result: ' . wc_print_r( $transaction_result, true ) );
update_post_meta( $order->get_id(), '_paypal_status', $status );
update_post_meta( $order->get_id(), '_transaction_id', $transaction );
@ -96,7 +96,7 @@ class WC_Gateway_Paypal_PDT_Handler extends WC_Gateway_Paypal_Response {
if ( $transaction_result ) {
if ( 'completed' === $status ) {
if ( $order->get_total() != $amount ) {
WC_Gateway_Paypal::log( 'Payment error: Amounts do not match (amt ' . $amount . ')' );
WC_Gateway_Paypal::log( 'Payment error: Amounts do not match (amt ' . $amount . ')', 'error' );
$this->payment_on_hold( $order, sprintf( __( 'Validation error: PayPal amounts do not match (amt %s).', 'woocommerce' ), $amount ) );
} else {
$this->payment_complete( $order, $transaction, __( 'PDT payment completed', 'woocommerce' ) );

View File

@ -45,7 +45,7 @@ class WC_Gateway_Paypal_Request {
public function get_request_url( $order, $sandbox = false ) {
$paypal_args = http_build_query( array_filter( $this->get_paypal_args( $order ) ), '', '&' );
WC_Gateway_Paypal::log( 'PayPal Request Args for order ' . $order->get_order_number() . ': ' . print_r( $paypal_args, true ) );
WC_Gateway_Paypal::log( 'PayPal Request Args for order ' . $order->get_order_number() . ': ' . wc_print_r( $paypal_args, true ) );
if ( $sandbox ) {
return 'https://www.sandbox.paypal.com/cgi-bin/webscr?test_ipn=1&' . $paypal_args;

View File

@ -30,7 +30,7 @@ abstract class WC_Gateway_Paypal_Response {
// Nothing was found.
} else {
WC_Gateway_Paypal::log( 'Error: Order ID and key were not found in "custom".' );
WC_Gateway_Paypal::log( 'Order ID and key were not found in "custom".', 'error' );
return false;
}
@ -41,7 +41,7 @@ abstract class WC_Gateway_Paypal_Response {
}
if ( ! $order || $order->get_order_key() !== $order_key ) {
WC_Gateway_Paypal::log( 'Error: Order Keys do not match.' );
WC_Gateway_Paypal::log( 'Order Keys do not match.', 'error' );
return false;
}

View File

@ -48,7 +48,7 @@ return array(
'type' => 'checkbox',
'label' => __( 'Enable logging', 'woocommerce' ),
'default' => 'no',
'description' => sprintf( __( 'Log PayPal events, such as IPN requests, inside %s', 'woocommerce' ), '<code>' . wc_get_log_file_path( 'paypal' ) . '</code>' ),
'description' => sprintf( __( 'Log PayPal events, such as IPN requests, inside %s', 'woocommerce' ), '<code>' . WC_Log_Handler_File::get_log_file_path( 'paypal' ) . '</code>' ),
),
'advanced' => array(
'title' => __( 'Advanced options', 'woocommerce' ),

View File

@ -0,0 +1,153 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Handles log entries by writing to database.
*
* @class WC_Log_Handler_DB
* @version 1.0.0
* @package WooCommerce/Classes/Log_Handlers
* @category Class
* @author WooThemes
*/
class WC_Log_Handler_DB extends WC_Log_Handler {
/**
* Handle a log entry.
*
* @param int $timestamp Log timestamp.
* @param string $level emergency|alert|critical|error|warning|notice|info|debug
* @param string $message Log message.
* @param array $context {
* Additional information for log handlers.
*
* @type string $source Optional. Source will be available in log table.
* If no source is provided, attempt to provide sensible default.
* }
*
* @see WC_Log_Handler_DB::get_log_source() for default source.
*
* @return bool False if value was not handled and true if value was handled.
*/
public function handle( $timestamp, $level, $message, $context ) {
if ( isset( $context['source'] ) && $context['source'] ) {
$source = $context['source'];
} else {
$source = $this->get_log_source();
}
return $this->add( $timestamp, $level, $message, $source, $context );
}
/**
* Add a log entry to chosen file.
*
* @param int $timestamp Log timestamp.
* @param string $level emergency|alert|critical|error|warning|notice|info|debug
* @param string $message Log message.
* @param string $source Log source. Useful for filtering and sorting.
* @param array $context {
* Context will be serialized and stored in database.
* }
*
* @return bool True if write was successful.
*/
protected static function add( $timestamp, $level, $message, $source, $context ) {
global $wpdb;
$insert = array(
'timestamp' => date( 'Y-m-d H:i:s', $timestamp ),
'level' => WC_Log_Levels::get_level_severity( $level ),
'message' => $message,
'source' => $source,
);
$format = array(
'%s',
'%d',
'%s',
'%s',
'%s', // possible serialized context
);
if ( ! empty( $context ) ) {
$insert['context'] = serialize( $context );
}
return false !== $wpdb->insert( "{$wpdb->prefix}woocommerce_log", $insert, $format );
}
/**
* Clear all logs from the DB.
*
* @return bool True if flush was successful.
*/
public static function flush() {
global $wpdb;
return $wpdb->query( "TRUNCATE TABLE {$wpdb->prefix}woocommerce_log" );
}
/**
* Delete selected logs from DB.
*
* @param int|string|array Log ID or array of Log IDs to be deleted.
*
* @return bool
*/
public static function delete( $log_ids ) {
global $wpdb;
if ( ! is_array( $log_ids ) ) {
$log_ids = array( $log_ids );
}
$format = array_fill( 0, count( $log_ids ), '%d' );
$query_in = '(' . implode( ',', $format ) . ')';
$query = $wpdb->prepare(
"DELETE FROM {$wpdb->prefix}woocommerce_log WHERE log_id IN {$query_in}",
$log_ids
);
return $wpdb->query( $query );
}
/**
* Get appropriate source based on file name.
*
* Try to provide an appropriate source in case none is provided.
*
* @return string Text to use as log source. "" (empty string) if none is found.
*/
protected static function get_log_source() {
static $ignore_files = array( 'class-wc-log-handler-db', 'class-wc-logger' );
/**
* PHP < 5.3.6 correct behavior
* @see http://php.net/manual/en/function.debug-backtrace.php#refsect1-function.debug-backtrace-parameters
*/
if ( defined( 'DEBUG_BACKTRACE_IGNORE_ARGS' ) ) {
$debug_backtrace_arg = DEBUG_BACKTRACE_IGNORE_ARGS;
} else {
$debug_backtrace_arg = false;
}
$trace = debug_backtrace( $debug_backtrace_arg );
foreach ( $trace as $t ) {
if ( isset( $t['file'] ) ) {
$filename = pathinfo( $t['file'], PATHINFO_FILENAME );
if ( ! in_array( $filename, $ignore_files ) ) {
return $filename;
}
}
}
return '';
}
}

View File

@ -0,0 +1,216 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Handles log entries by sending an email.
*
* WARNING!
* This log handler has known limitations.
*
* Log messages are aggregated and sent once per request (if necessary). If the site experiences a
* problem, the log email may never be sent. This handler should be used with another handler which
* stores logs in order to prevent loss.
*
* It is not recommended to use this handler on a high traffic site. There will be a maximum of 1
* email sent per request per handler, but that could still be a dangerous amount of emails under
* heavy traffic. Do not confuse this handler with an appropriate monitoring solution!
*
* If you understand these limitations, feel free to use this handler or borrow parts of the design
* to implement your own!
*
* @class WC_Log_Handler_Email
* @version 1.0.0
* @package WooCommerce/Classes/Log_Handlers
* @category Class
* @author WooThemes
*/
class WC_Log_Handler_Email extends WC_Log_Handler {
/**
* Minimum log level this handler will process.
*
* @var int Integer representation of minimum log level to handle.
*/
protected $threshold;
/**
* Stores email recipients.
*
* @var array
*/
protected $recipients = array();
/**
* Stores log messages.
*
* @var array
*/
protected $logs = array();
/**
* Stores integer representation of maximum logged level.
*
* @var int
*/
protected $max_severity = null;
/**
* Constructor for log handler.
*
* @param string|array $recipients Optional. Email(s) to receive log messages. Defaults to site admin email.
* @param string $threshold Optional. Minimum level that should receive log messages.
* Default 'alert'. One of: emergency|alert|critical|error|warning|notice|info|debug
*/
public function __construct( $recipients = null, $threshold = 'alert' ) {
if ( null === $recipients ) {
$recipients = get_option( 'admin_email' );
}
if ( is_array( $recipients ) ) {
foreach ( $recipients as $recipient ) {
$this->add_email( $recipient );
}
} else {
$this->add_email( $recipients );
}
$this->set_threshold( $threshold );
add_action( 'shutdown', array( $this, 'send_log_email' ) );
}
/**
* Set handler severity threshold.
*
* @param string $level emergency|alert|critical|error|warning|notice|info|debug
*/
public function set_threshold( $level ) {
$this->threshold = WC_Log_Levels::get_level_severity( $level );
}
/**
* Determine whether handler should handle log.
*
* @param string $level emergency|alert|critical|error|warning|notice|info|debug
* @return bool True if the log should be handled.
*/
protected function should_handle( $level ) {
return $this->threshold <= WC_Log_Levels::get_level_severity( $level );
}
/**
* Handle a log entry.
*
* @param int $timestamp Log timestamp.
* @param string $level emergency|alert|critical|error|warning|notice|info|debug
* @param string $message Log message.
* @param array $context Optional. Additional information for log handlers.
*
* @return bool False if value was not handled and true if value was handled.
*/
public function handle( $timestamp, $level, $message, $context ) {
if ( $this->should_handle( $level ) ) {
$this->add_log( $timestamp, $level, $message, $context );
return true;
}
return false;
}
/**
* Send log email.
*
* @return bool True if email is successfully sent otherwise false.
*/
public function send_log_email() {
$result = false;
if ( ! empty( $this->logs ) ) {
$subject = $this->get_subject();
$body = $this->get_body();
$result = wp_mail( $this->recipients, $subject, $body );
$this->clear_logs();
}
return $result;
}
/**
* Build subject for log email.
*
* @return string subject
*/
protected function get_subject() {
$site_name = get_bloginfo( 'name' );
$max_level = strtoupper( WC_Log_Levels::get_severity_level( $this->max_severity ) );
$log_count = count( $this->logs );
return sprintf(
_n(
'[%1$s] %2$s: %3$s WooCommerce log message',
'[%1$s] %2$s: %3$s WooCommerce log messages',
$log_count,
'woocommerce'
),
$site_name,
$max_level,
$log_count
);
}
/**
* Build body for log email.
*
* @return string body
*/
protected function get_body() {
$site_name = get_bloginfo( 'name' );
$entries = implode( PHP_EOL, $this->logs );
$log_count = count( $this->logs );
return _n(
'You have received the following WooCommerce log message:',
'You have received the following WooCommerce log messages:',
$log_count,
'woocommerce'
)
. PHP_EOL
. PHP_EOL
. $entries
. PHP_EOL
. PHP_EOL
. sprintf( __( 'Visit %s admin area:', 'woocommerce' ), $site_name )
. PHP_EOL
. admin_url();
}
/**
* Adds an email to the list of recipients.
*
* @param string email Email address to add
*/
public function add_email( $email ) {
array_push( $this->recipients, $email );
}
/**
* Add log message.
*/
protected function add_log( $timestamp, $level, $message, $context ) {
$this->logs[] = $this->format_entry( $timestamp, $level, $message, $context );
$log_severity = WC_Log_Levels::get_level_severity( $level );
if ( $this->max_severity < $log_severity ) {
$this->max_severity = $log_severity;
}
}
/**
* Clear log messages.
*/
protected function clear_logs() {
$this->logs = array();
}
}

View File

@ -0,0 +1,374 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Handles log entries by writing to a file.
*
* @class WC_Log_Handler_File
* @version 1.0.0
* @package WooCommerce/Classes/Log_Handlers
* @category Class
* @author WooThemes
*/
class WC_Log_Handler_File extends WC_Log_Handler {
/**
* Stores open file handles.
*
* @var array
*/
protected $handles = array();
/**
* File size limit for log files in bytes.
*
* @var int
*/
protected $log_size_limit;
/**
* Cache logs that could not be written.
*
* If a log is written too early in the request, pluggable functions may be unavailable. These
* logs will be cached and written on 'plugins_loaded' action.
*
* @var array
*/
protected $cached_logs = array();
/**
* Constructor for the logger.
*
* @param int $log_size_limit Optional. Size limit for log files. Default 5mb.
*/
public function __construct( $log_size_limit = null ) {
if ( null === $log_size_limit ) {
$log_size_limit = 5 * 1024 * 1024;
}
$this->log_size_limit = $log_size_limit;
add_action( 'plugins_loaded', array( $this, 'write_cached_logs' ) );
}
/**
* Destructor.
*
* Cleans up open file handles.
*/
public function __destruct() {
foreach ( $this->handles as $handle ) {
if ( is_resource( $handle ) ) {
fclose( $handle );
}
}
}
/**
* Handle a log entry.
*
* @param int $timestamp Log timestamp.
* @param string $level emergency|alert|critical|error|warning|notice|info|debug
* @param string $message Log message.
* @param array $context {
* Additional information for log handlers.
*
* @type string $source Optional. Determines log file to write to. Default 'log'.
* @type bool $_legacy Optional. Default false. True to use outdated log format
* orignally used in deprecated WC_Logger::add calls.
* }
*
* @return bool False if value was not handled and true if value was handled.
*/
public function handle( $timestamp, $level, $message, $context ) {
if ( isset( $context['source'] ) && $context['source'] ) {
$handle = $context['source'];
} else {
$handle = 'log';
}
$entry = self::format_entry( $timestamp, $level, $message, $context );
return $this->add( $entry, $handle );
}
/**
* Builds a log entry text from timestamp, level and message.
*
* @param int $timestamp Log timestamp.
* @param string $level emergency|alert|critical|error|warning|notice|info|debug
* @param string $message Log message.
* @param array $context Additional information for log handlers.
*
* @return string Formatted log entry.
*/
protected static function format_entry( $timestamp, $level, $message, $context ) {
if ( isset( $context['_legacy'] ) && true === $context['_legacy'] ) {
if ( isset( $context['source'] ) && $context['source'] ) {
$handle = $context['source'];
} else {
$handle = 'log';
}
$message = apply_filters( 'woocommerce_logger_add_message', $message, $handle );
$time = date_i18n( 'm-d-Y @ H:i:s' );
$entry = "{$time} - {$message}";
} else {
$entry = parent::format_entry( $timestamp, $level, $message, $context );
}
return $entry;
}
/**
* Open log file for writing.
*
* @param string $handle Log handle.
* @param string $mode Optional. File mode. Default 'a'.
* @return bool Success.
*/
protected function open( $handle, $mode = 'a' ) {
if ( $this->is_open( $handle ) ) {
return true;
}
$file = self::get_log_file_path( $handle );
if ( $file ) {
if ( ! file_exists( $file ) ) {
$temphandle = @fopen( $file, 'w+' );
@fclose( $temphandle );
if ( defined( 'FS_CHMOD_FILE' ) ) {
@chmod( $file, FS_CHMOD_FILE );
}
}
if ( $this->handles[ $handle ] = @fopen( $file, $mode ) ) {
return true;
}
}
return false;
}
/**
* Check if a handle is open.
*
* @param string $handle Log handle.
* @return bool True if $handle is open.
*/
protected function is_open( $handle ) {
return array_key_exists( $handle, $this->handles );
}
/**
* Close a handle.
*
* @param string $handle
* @return bool success
*/
protected function close( $handle ) {
$result = false;
if ( $this->is_open( $handle ) && is_resource( $this->handles[ $handle ] ) ) {
$result = fclose( $this->handles[ $handle ] );
unset( $this->handles[ $handle ] );
}
return $result;
}
/**
* Add a log entry to chosen file.
*
* @param string $entry Log entry text
* @param string $handle Log entry handle
*
* @return bool True if write was successful.
*/
protected function add( $entry, $handle ) {
$result = false;
if ( $this->should_rotate( $handle ) ) {
$this->log_rotate( $handle );
}
if ( $this->open( $handle ) && is_resource( $this->handles[ $handle ] ) ) {
$result = fwrite( $this->handles[ $handle ], $entry . PHP_EOL );
} else {
$this->cache_log( $entry, $handle );
}
return false !== $result;
}
/**
* Clear entries from chosen file.
*
* @param string $handle
*
* @return bool
*/
public function clear( $handle ) {
$result = false;
// Close the file if it's already open.
$this->close( $handle );
/**
* $this->open( $handle, 'w' ) == Open the file for writing only. Place the file pointer at
* the beginning of the file, and truncate the file to zero length.
*/
if ( $this->open( $handle, 'w' ) && is_resource( $this->handles[ $handle ] ) ) {
$result = true;
}
do_action( 'woocommerce_log_clear', $handle );
return $result;
}
/**
* Remove/delete the chosen file.
*
* @param string $handle
*
* @return bool
*/
public function remove( $handle ) {
$removed = false;
$file = self::get_log_file_path( $handle );
if ( $file ) {
if ( is_file( $file ) && is_writable( $file ) ) {
// Close first to be certain no processes keep it alive after it is unlinked.
$this->close( $handle );
$removed = unlink( $file );
} elseif ( is_file( trailingslashit( WC_LOG_DIR ) . $handle . '.log' ) && is_writable( trailingslashit( WC_LOG_DIR ) . $handle . '.log' ) ) {
$this->close( $handle );
$removed = unlink( trailingslashit( WC_LOG_DIR ) . $handle . '.log' );
}
do_action( 'woocommerce_log_remove', $handle, $removed );
}
return $removed;
}
/**
* Check if log file should be rotated.
*
* Compares the size of the log file to determine whether it is over the size limit.
*
* @param string $handle Log handle
* @return bool True if if should be rotated.
*/
protected function should_rotate( $handle ) {
$file = self::get_log_file_path( $handle );
if ( $file ) {
if ( $this->is_open( $handle ) ) {
$file_stat = fstat( $this->handles[ $handle ] );
return $file_stat['size'] > $this->log_size_limit;
} elseif ( file_exists( $file ) ) {
return filesize( $file ) > $this->log_size_limit;
} else {
return false;
}
} else {
return false;
}
}
/**
* Rotate log files.
*
* Logs are rotatated by prepending '.x' to the '.log' suffix.
* The current log plus 10 historical logs are maintained.
* For example:
* base.9.log -> [ REMOVED ]
* base.8.log -> base.9.log
* ...
* base.0.log -> base.1.log
* base.log -> base.0.log
*
* @param string $handle Log handle
*/
protected function log_rotate( $handle ) {
for ( $i = 8; $i >= 0; $i-- ) {
$this->increment_log_infix( $handle, $i );
}
$this->increment_log_infix( $handle );
}
/**
* Increment a log file suffix.
*
* @param string $handle Log handle
* @param null|int $number Optional. Default null. Log suffix number to be incremented.
* @return bool True if increment was successful, otherwise false.
*/
protected function increment_log_infix( $handle, $number = null ) {
if ( null === $number ) {
$suffix = '';
$next_suffix = '.0';
} else {
$suffix = '.' . $number;
$next_suffix = '.' . ($number + 1);
}
$rename_from = self::get_log_file_path( "{$handle}{$suffix}" );
$rename_to = self::get_log_file_path( "{$handle}{$next_suffix}" );
if ( $this->is_open( $rename_from ) ) {
$this->close( $rename_from );
}
if ( is_writable( $rename_from ) ) {
return rename( $rename_from, $rename_to );
} else {
return false;
}
}
/**
* Get a log file path.
*
* @param string $handle Log name.
* @return bool|string The log file path or false if path cannot be determined.
*/
public static function get_log_file_path( $handle ) {
if ( function_exists( 'wp_hash' ) ) {
return trailingslashit( WC_LOG_DIR ) . $handle . '-' . sanitize_file_name( wp_hash( $handle ) ) . '.log';
}
return false;
}
/**
* Cache log to write later.
*
* @param string $entry Log entry text
* @param string $handle Log entry handle
*/
protected function cache_log( $entry, $handle ) {
$this->cached_logs[] = array(
'entry' => $entry,
'handle' => $handle,
);
}
/**
* Write cached logs.
*/
public function write_cached_logs() {
foreach ( $this->cached_logs as $log ) {
$this->add( $log['entry'], $log['handle'] );
}
}
}

View File

@ -757,11 +757,12 @@ function get_woocommerce_api_url( $path ) {
* Get a log file path.
*
* @since 2.2
*
* @param string $handle name.
* @return string the log file path.
*/
function wc_get_log_file_path( $handle ) {
return trailingslashit( WC_LOG_DIR ) . sanitize_file_name( $handle ) . '-' . sanitize_file_name( wp_hash( $handle ) ) . '.log';
return WC_Log_Handler_File::get_log_file_path( $handle );
}
/**
@ -1416,13 +1417,74 @@ function wc_get_rounding_precision() {
* @return WC_Logger
*/
function wc_get_logger() {
if ( ! class_exists( 'WC_Logger' ) ) {
include_once( dirname( __FILE__ ) . '/class-wc-logger.php' );
static $logger = null;
if ( null === $logger ) {
$class = apply_filters( 'woocommerce_logging_class', 'WC_Logger' );
$logger = new $class;
}
$class = apply_filters( 'woocommerce_logging_class', 'WC_Logger' );
return new $class;
return $logger;
}
/**
* Prints human-readable information about a variable.
*
* Some server environments blacklist some debugging functions. This function provides a safe way to
* turn an expression into a printable, readable form without calling blacklisted functions.
*
* @since 2.7
*
* @param mixed $expression The expression to be printed.
* @param bool $return Optional. Default false. Set to true to return the human-readable string.
* @return string|bool False if expression could not be printed. True if the expression was printed.
* If $return is true, a string representation will be returned.
*/
function wc_print_r( $expression, $return = false ) {
$alternatives = array(
array( 'func' => 'print_r', 'args' => array( $expression, true ) ),
array( 'func' => 'var_export', 'args' => array( $expression, true ) ),
array( 'func' => 'json_encode', 'args' => array( $expression ) ),
array( 'func' => 'serialize', 'args' => array( $expression ) ),
);
$alternatives = apply_filters( 'woocommerce_print_r_alternatives', $alternatives, $expression );
foreach ( $alternatives as $alternative ) {
if ( function_exists( $alternative['func'] ) ) {
$res = call_user_func_array( $alternative['func'], $alternative['args'] );
if ( $return ) {
return $res;
} else {
echo $res;
return true;
}
}
}
return false;
}
/**
* Registers the default log handler.
*
* @since 2.7
* @param array $handlers
* @return array
*/
function wc_register_default_log_handler( $handlers ) {
if ( defined( 'WC_LOG_HANDLER' ) && class_exists( WC_LOG_HANDLER ) ) {
$handler_class = WC_LOG_HANDLER;
$default_handler = new $handler_class();
} else {
$default_handler = new WC_Log_Handler_File();
}
array_push( $handlers, $default_handler );
return $handlers;
}
add_filter( 'woocommerce_register_log_handlers', 'wc_register_default_log_handler' );
/**
* Store user agents. Used for tracker.
* @since 2.7.0

View File

@ -0,0 +1,137 @@
<?php
/**
* Class WC_Tests_Log_Handler_DB
* @package WooCommerce\Tests\Log
* @since 2.7.0
*/
class WC_Tests_Log_Handler_DB extends WC_Unit_Test_Case {
public function setUp() {
parent::setUp();
WC_Log_Handler_DB::flush();
}
public function tearDown() {
WC_Log_Handler_DB::flush();
parent::tearDown();
}
/**
* Test handle writes to database correctly.
*
* @since 2.7.0
*/
public function test_handle() {
global $wpdb;
$handler = new WC_Log_Handler_DB( array( 'threshold' => 'debug' ) );
$time = time();
$context = array( 1, 2, 'a', 'b', 'key' => 'value' );
$handler->handle( $time, 'debug', 'msg_debug', array( 'source' => 'source_debug' ) );
$handler->handle( $time, 'info', 'msg_info', array( 'source' => 'source_info' ) );
$handler->handle( $time, 'notice', 'msg_notice', array( 'source' => 'source_notice' ) );
$handler->handle( $time, 'warning', 'msg_warning', array( 'source' => 'source_warning' ) );
$handler->handle( $time, 'error', 'msg_error', array( 'source' => 'source_error' ) );
$handler->handle( $time, 'critical', 'msg_critical', array( 'source' => 'source_critical' ) );
$handler->handle( $time, 'alert', 'msg_alert', array( 'source' => 'source_alert' ) );
$handler->handle( $time, 'emergency', 'msg_emergency', array( 'source' => 'source_emergency' ) );
$handler->handle( $time, 'debug', 'context_test', $context );
$log_entries = $wpdb->get_results( "SELECT timestamp, level, message, source, context FROM {$wpdb->prefix}woocommerce_log", ARRAY_A );
$expected_ts = date( 'Y-m-d H:i:s', $time );
$expected = array(
array(
'timestamp' => $expected_ts,
'level' => WC_Log_Levels::get_level_severity( 'debug' ),
'message' => 'msg_debug',
'source' => 'source_debug',
'context' => serialize( array( 'source' => 'source_debug' ) ),
),
array(
'timestamp' => $expected_ts,
'level' => WC_Log_Levels::get_level_severity( 'info' ),
'message' => 'msg_info',
'source' => 'source_info',
'context' => serialize( array( 'source' => 'source_info' ) ),
),
array(
'timestamp' => $expected_ts,
'level' => WC_Log_Levels::get_level_severity( 'notice' ),
'message' => 'msg_notice',
'source' => 'source_notice',
'context' => serialize( array( 'source' => 'source_notice' ) ),
),
array(
'timestamp' => $expected_ts,
'level' => WC_Log_Levels::get_level_severity( 'warning' ),
'message' => 'msg_warning',
'source' => 'source_warning',
'context' => serialize( array( 'source' => 'source_warning' ) ),
),
array(
'timestamp' => $expected_ts,
'level' => WC_Log_Levels::get_level_severity( 'error' ),
'message' => 'msg_error',
'source' => 'source_error',
'context' => serialize( array( 'source' => 'source_error' ) ),
),
array(
'timestamp' => $expected_ts,
'level' => WC_Log_Levels::get_level_severity( 'critical' ),
'message' => 'msg_critical',
'source' => 'source_critical',
'context' => serialize( array( 'source' => 'source_critical' ) ),
),
array(
'timestamp' => $expected_ts,
'level' => WC_Log_Levels::get_level_severity( 'alert' ),
'message' => 'msg_alert',
'source' => 'source_alert',
'context' => serialize( array( 'source' => 'source_alert' ) ),
),
array(
'timestamp' => $expected_ts,
'level' => WC_Log_Levels::get_level_severity( 'emergency' ),
'message' => 'msg_emergency',
'source' => 'source_emergency',
'context' => serialize( array( 'source' => 'source_emergency' ) ),
),
array(
'timestamp' => $expected_ts,
'level' => WC_Log_Levels::get_level_severity( 'debug' ),
'message' => 'context_test',
'source' => pathinfo( __FILE__, PATHINFO_FILENAME ),
'context' => serialize( $context ),
),
);
$this->assertEquals( $log_entries, $expected );
}
/**
* Test flush.
*
* @since 2.7.0
*/
public function test_flush() {
global $wpdb;
$handler = new WC_Log_Handler_DB( array( 'threshold' => 'debug' ) );
$time = time();
$handler->handle( $time, 'debug', '', array() );
$log_entries = $wpdb->get_results( "SELECT timestamp, level, message, source FROM {$wpdb->prefix}woocommerce_log" );
$this->assertCount( 1, $log_entries );
WC_Log_Handler_DB::flush();
$log_entries = $wpdb->get_results( "SELECT timestamp, level, message, source FROM {$wpdb->prefix}woocommerce_log" );
$this->assertCount( 0, $log_entries );
}
}

View File

@ -0,0 +1,245 @@
<?php
/**
* Class WC_Tests_Log_Handler_Email
* @package WooCommerce\Tests\Log
* @since 2.7.0
*/
class WC_Tests_Log_Handler_Email extends WC_Unit_Test_Case {
function setUp() {
parent::setUp();
reset_phpmailer_instance();
}
function tearDown() {
reset_phpmailer_instance();
parent::tearDown();
}
/**
* Test handle sends email correctly.
*
* @since 2.7.0
*/
public function test_handle() {
$mailer = tests_retrieve_phpmailer_instance();
$time = time();
$site_name = get_bloginfo( 'name' );
$handler = new WC_Log_Handler_Email();
$handler->handle( $time, 'emergency', 'msg_emergency', array() );
$handler->handle( $time, 'emergency', 'msg_emergency 2', array() );
$handler->send_log_email();
$this->assertEquals(
(
'You have received the following WooCommerce log messages:'
. PHP_EOL
. PHP_EOL
. date( 'c', $time ) . ' EMERGENCY msg_emergency'
. PHP_EOL
. date( 'c', $time ) . ' EMERGENCY msg_emergency 2'
. PHP_EOL
. PHP_EOL
. "Visit {$site_name} admin area:"
. PHP_EOL
. admin_url()
. PHP_EOL
),
$mailer->get_sent( 0 )->body
);
$this->assertEquals(
"[{$site_name}] EMERGENCY: 2 WooCommerce log messages",
$mailer->get_sent( 0 )->subject
);
$this->assertEquals( get_option( 'admin_email' ), $mailer->get_recipient( 'to' )->address );
$handler->handle( $time, 'emergency', 'msg_emergency', array() );
$handler->send_log_email();
$this->assertEquals(
(
'You have received the following WooCommerce log message:'
. PHP_EOL
. PHP_EOL
. date( 'c', $time ) . ' EMERGENCY msg_emergency'
. PHP_EOL
. PHP_EOL
. "Visit {$site_name} admin area:"
. PHP_EOL
. admin_url()
. PHP_EOL
),
$mailer->get_sent( 1 )->body
);
}
/**
* Test email subject
*
* @since 2.7.0
*/
public function test_email_subject() {
$mailer = tests_retrieve_phpmailer_instance();
$time = time();
$site_name = get_bloginfo( 'name' );
$handler = new WC_Log_Handler_Email( null, WC_Log_Levels::DEBUG );
$handler->handle( $time, 'debug', '', array() );
$handler->send_log_email();
$handler->handle( $time, 'alert', '', array() );
$handler->handle( $time, 'critical', '', array() );
$handler->handle( $time, 'debug', '', array() );
$handler->send_log_email();
$this->assertEquals(
"[{$site_name}] DEBUG: 1 WooCommerce log message",
$mailer->get_sent( 0 )->subject
);
$this->assertEquals(
"[{$site_name}] ALERT: 3 WooCommerce log messages",
$mailer->get_sent( 1 )->subject
);
}
/**
* Test multiple recipients receive emails.
*
* @since 2.7.0
*/
public function test_multiple_recipients() {
$mailer = tests_retrieve_phpmailer_instance();
$handler = new WC_Log_Handler_Email( array(
'first@test.com',
'Second Recipient <second@test.com>',
) );
$handler->handle( time(), 'emergency', '', array() );
$handler->send_log_email();
$first_recipient = $mailer->get_recipient( 'to', 0, 0 );
$second_recipient = $mailer->get_recipient( 'to', 0, 1 );
$this->assertEquals( 'first@test.com', $first_recipient->address );
$this->assertEquals( 'second@test.com', $second_recipient->address );
$this->assertEquals( 'Second Recipient', $second_recipient->name );
}
/**
* Test single recipient receives emails.
*
* @since 2.7.0
*/
public function test_single_recipient() {
$mailer = tests_retrieve_phpmailer_instance();
$handler = new WC_Log_Handler_Email( 'User <user@test.com>' );
$handler->handle( time(), 'emergency', '', array() );
$handler->send_log_email();
$recipient = $mailer->get_recipient( 'to' );
$this->assertEquals( 'user@test.com', $recipient->address );
$this->assertEquals( 'User', $recipient->name );
}
/**
* Test threshold.
*
* @since 2.7.0
*/
public function test_threshold() {
$mailer = tests_retrieve_phpmailer_instance();
$handler = new WC_Log_Handler_Email( null, 'notice' );
$handler->handle( time(), 'info', '', array() );
$handler->send_log_email();
// Info should not be handled, get_sent is false
$this->assertFalse( $mailer->get_sent( 0 ) );
$handler->handle( time(), 'notice', '', array() );
$handler->send_log_email();
$this->assertObjectHasAttribute( 'body', $mailer->get_sent( 0 ) );
}
/**
* Test set_threshold().
*
* @since 2.7.0
*/
public function test_set_threshold() {
$mailer = tests_retrieve_phpmailer_instance();
$handler = new WC_Log_Handler_Email( null, 'notice' );
$handler->handle( time(), 'info', '', array() );
$handler->send_log_email();
// Info should not be handled, get_sent is false
$this->assertFalse( $mailer->get_sent( 0 ) );
$handler->set_threshold( 'info' );
$handler->handle( time(), 'info', '', array() );
$handler->send_log_email();
$this->assertObjectHasAttribute( 'body', $mailer->get_sent( 0 ) );
}
/**
* Test send_log_email clears logs.
*
* Send log email could be called multiple times during a request. The same log should not be
* sent multiple times.
*
* @since 2.7.0
*/
public function test_multiple_send_log() {
$mailer = tests_retrieve_phpmailer_instance();
$handler = new WC_Log_Handler_Email();
$time = time();
$handler->handle( $time, 'emergency', 'message 1', array() );
$handler->send_log_email();
$handler->handle( $time, 'emergency', 'message 2', array() );
$handler->send_log_email();
$site_name = get_bloginfo( 'name' );
$this->assertEquals(
(
'You have received the following WooCommerce log message:'
. PHP_EOL
. PHP_EOL
. date( 'c', $time ) . ' EMERGENCY message 1'
. PHP_EOL
. PHP_EOL
. "Visit {$site_name} admin area:"
. PHP_EOL
. admin_url()
. PHP_EOL
),
$mailer->get_sent( 0 )->body
);
$this->assertEquals(
(
'You have received the following WooCommerce log message:'
. PHP_EOL
. PHP_EOL
. date( 'c', $time ) . ' EMERGENCY message 2'
. PHP_EOL
. PHP_EOL
. "Visit {$site_name} admin area:"
. PHP_EOL
. admin_url()
. PHP_EOL
),
$mailer->get_sent( 1 )->body
);
}
}

View File

@ -0,0 +1,204 @@
<?php
/**
* Class WC_Tests_Log_Handler_File
* @package WooCommerce\Tests\Log
* @since 2.7.0
*/
class WC_Tests_Log_Handler_File extends WC_Unit_Test_Case {
public function tearDown() {
$log_files = array(
'unit-tests',
'log',
'_test_clear',
'_test_remove',
'_test_log_rotate',
'_test_log_rotate.0',
'_test_log_rotate.1',
'_test_log_rotate.2',
'_test_log_rotate.3',
'_test_log_rotate.4',
'_test_log_rotate.5',
'_test_log_rotate.6',
'_test_log_rotate.7',
'_test_log_rotate.8',
'_test_log_rotate.9',
);
foreach ( $log_files as $file ) {
$file_path = WC_Log_Handler_File::get_log_file_path( $file );
if ( file_exists( $file_path ) && is_writable( $file_path ) ) {
unlink( $file_path );
}
}
parent::tearDown();
}
public function read_content( $handle ) {
return file_get_contents( WC_Log_Handler_File::get_log_file_path( $handle ) );
}
/**
* Test _legacy format.
*
* @since 2.7.0
*/
public function test_legacy_format() {
$handler = new WC_Log_Handler_File( array( 'threshold' => 'debug' ) );
$handler->handle( time(), 'info', 'this is a message', array( 'source' => 'unit-tests', '_legacy' => true ) );
$this->assertStringMatchesFormat( '%d-%d-%d @ %d:%d:%d - %s', $this->read_content( 'unit-tests' ) );
$this->assertStringEndsWith( ' - this is a message' . PHP_EOL, $this->read_content( 'unit-tests' ) );
}
/**
* Test clear().
*
* @since 2.7.0
*/
public function test_clear() {
$handler = new WC_Log_Handler_File();
$log_name = '_test_clear';
$handler->handle( time(), 'debug', 'debug', array( 'source' => $log_name ) );
$handler->clear( $log_name );
$this->assertEquals( '', $this->read_content( $log_name ) );
}
/**
* Test remove().
*
* @since 2.7.0
*/
public function test_remove() {
$handler = new WC_Log_Handler_File();
$log_name = '_test_remove';
$handler->handle( time(), 'debug', 'debug', array( 'source' => $log_name ) );
$handler->remove( $log_name );
$this->assertFileNotExists( WC_Log_Handler_File::get_log_file_path( $log_name ) );
}
/**
* Test handle writes to default file correctly.
*
* @since 2.7.0
*/
public function test_writes_file() {
$handler = new WC_Log_Handler_File();
$time = time();
$handler->handle( $time, 'debug', 'debug', array() );
$handler->handle( $time, 'info', 'info', array() );
$handler->handle( $time, 'notice', 'notice', array() );
$handler->handle( $time, 'warning', 'warning', array() );
$handler->handle( $time, 'error', 'error', array() );
$handler->handle( $time, 'critical', 'critical', array() );
$handler->handle( $time, 'alert', 'alert', array() );
$handler->handle( $time, 'emergency', 'emergency', array() );
$log_content = $this->read_content( 'log' );
$this->assertStringMatchesFormatFile( dirname( __FILE__ ) . '/test_log_expected.txt', $log_content );
}
/**
* Test 'source' context determines log file.
*
* @since 2.7.0
*/
public function test_log_file_source() {
$handler = new WC_Log_Handler_File();
$time = time();
$context_source = array( 'source' => 'unit-tests' );
$handler->handle( $time, 'debug', 'debug', $context_source );
$handler->handle( $time, 'info', 'info', $context_source );
$handler->handle( $time, 'notice', 'notice', $context_source );
$handler->handle( $time, 'warning', 'warning', $context_source );
$handler->handle( $time, 'error', 'error', $context_source );
$handler->handle( $time, 'critical', 'critical', $context_source );
$handler->handle( $time, 'alert', 'alert', $context_source );
$handler->handle( $time, 'emergency', 'emergency', $context_source );
$log_content = $this->read_content( 'unit-tests' );
$this->assertStringMatchesFormatFile( dirname( __FILE__ ) . '/test_log_expected.txt', $log_content );
}
/**
* Test multiple handlers don't conflict on log writing.
*
* @since 2.7.0
*/
public function test_multiple_handlers() {
$handler_a = new WC_Log_Handler_File();
$handler_b = new WC_Log_Handler_File();
$time = time();
$context_source = array( 'source' => 'unit-tests' );
// Different loggers should not conflict.
$handler_a->handle( $time, 'debug', 'debug', $context_source );
$handler_b->handle( $time, 'info', 'info', $context_source );
$handler_a->handle( $time, 'notice', 'notice', $context_source );
$handler_b->handle( $time, 'warning', 'warning', $context_source );
$handler_a->handle( $time, 'error', 'error', $context_source );
$handler_b->handle( $time, 'critical', 'critical', $context_source );
$handler_a->handle( $time, 'alert', 'alert', $context_source );
$handler_b->handle( $time, 'emergency', 'emergency', $context_source );
$log_content = $this->read_content( 'unit-tests' );
$this->assertStringMatchesFormatFile( dirname( __FILE__ ) . '/test_log_expected.txt', $log_content );
}
/**
* Test log_rotate()
*
* Ensure logs are rotated correctly when limit is surpassed.
*
* @since 2.7.0
*/
public function test_log_rotate() {
// Handler with log size limit of 5mb
$handler = new WC_Log_Handler_File( 5 * 1024 * 1024 );
$time = time();
$log_name = '_test_log_rotate';
$base_log_file = WC_Log_Handler_File::get_log_file_path( $log_name );
// Create log file larger than 5mb to ensure log is rotated
$handle = fopen( $base_log_file, 'w' );
fseek( $handle, 5 * 1024 * 1024 );
fwrite( $handle, '_base_log_file_contents_' );
fclose( $handle );
// Write some files to ensure they've rotated correctly
for ( $i = 0; $i < 10; $i++ ) {
file_put_contents( WC_Log_Handler_File::get_log_file_path( $log_name . ".{$i}" ), $i );
}
$context_source = array( 'source' => $log_name );
$handler->handle( $time, 'emergency', 'emergency', $context_source );
$this->assertFileExists( WC_Log_Handler_File::get_log_file_path( $log_name ) );
// Ensure the handled log is correct
$this->assertStringEndsWith( 'EMERGENCY emergency' . PHP_EOL, $this->read_content( $log_name ) );
// Ensure other logs have rotated correctly
$this->assertEquals( '_base_log_file_contents_', trim( $this->read_content( $log_name . '.0' ) ) );
for ( $i = 1; $i < 10; $i++ ) {
$this->assertEquals( $i - 1, $this->read_content( $log_name . ".{$i}" ) );
}
}
/**
* Test get_log_file_path().
*
* @since 2.7.0
*/
public function test_get_log_file_path() {
$log_dir = trailingslashit( WC_LOG_DIR );
$hash_name = sanitize_file_name( wp_hash( 'unit-tests' ) );
$this->assertEquals( $log_dir . 'unit-tests-' . $hash_name . '.log', WC_Log_Handler_File::get_log_file_path( 'unit-tests' ) );
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* Class WC_Tests_Logger
* @package WooCommerce\Tests\Log
* @since 2.7.0
*/
class WC_Tests_Log_Levels extends WC_Unit_Test_Case {
/**
* Test get_level_severity().
*
* @since 2.7.0
*/
public function test_get_level_severity() {
$this->assertEquals( 0, WC_Log_Levels::get_level_severity( 'unrecognized level' ) );
$this->assertEquals( 100, WC_Log_Levels::get_level_severity( 'debug' ) );
$this->assertEquals( 200, WC_Log_Levels::get_level_severity( 'info' ) );
$this->assertEquals( 300, WC_Log_Levels::get_level_severity( 'notice' ) );
$this->assertEquals( 400, WC_Log_Levels::get_level_severity( 'warning' ) );
$this->assertEquals( 500, WC_Log_Levels::get_level_severity( 'error' ) );
$this->assertEquals( 600, WC_Log_Levels::get_level_severity( 'critical' ) );
$this->assertEquals( 700, WC_Log_Levels::get_level_severity( 'alert' ) );
$this->assertEquals( 800, WC_Log_Levels::get_level_severity( 'emergency' ) );
}
/**
* Test get_severity_level().
*
* @since 2.7.0
*/
public function test_get_severity_level() {
$this->assertFalse( WC_Log_Levels::get_severity_level( 0 ) );
$this->assertEquals( 'debug', WC_Log_Levels::get_severity_level( 100 ) );
$this->assertEquals( 'info', WC_Log_Levels::get_severity_level( 200 ) );
$this->assertEquals( 'notice', WC_Log_Levels::get_severity_level( 300 ) );
$this->assertEquals( 'warning', WC_Log_Levels::get_severity_level( 400 ) );
$this->assertEquals( 'error', WC_Log_Levels::get_severity_level( 500 ) );
$this->assertEquals( 'critical', WC_Log_Levels::get_severity_level( 600 ) );
$this->assertEquals( 'alert', WC_Log_Levels::get_severity_level( 700 ) );
$this->assertEquals( 'emergency', WC_Log_Levels::get_severity_level( 800 ) );
}
/**
* Test is_valid_level().
*
* @since 2.7.0
*/
public function test_is_valid_level() {
$this->assertFalse( WC_Log_Levels::is_valid_level( 'unrecognized level' ) );
$this->assertTrue( WC_Log_Levels::is_valid_level( 'debug' ) );
$this->assertTrue( WC_Log_Levels::is_valid_level( 'info' ) );
$this->assertTrue( WC_Log_Levels::is_valid_level( 'notice' ) );
$this->assertTrue( WC_Log_Levels::is_valid_level( 'warning' ) );
$this->assertTrue( WC_Log_Levels::is_valid_level( 'error' ) );
$this->assertTrue( WC_Log_Levels::is_valid_level( 'critical' ) );
$this->assertTrue( WC_Log_Levels::is_valid_level( 'alert' ) );
$this->assertTrue( WC_Log_Levels::is_valid_level( 'emergency' ) );
}
}

View File

@ -0,0 +1,303 @@
<?php
/**
* Class WC_Tests_Logger
* @package WooCommerce\Tests\Log
* @since 2.3
*/
class WC_Tests_Logger extends WC_Unit_Test_Case {
/**
* Test add().
*
* @since 2.4
*/
public function test_add() {
$time = time();
$handler = $this
->getMockBuilder( 'WC_Log_Handler' )
->setMethods( array( 'handle' ) )
->getMock();
$handler
->expects( $this->once() )
->method( 'handle' )
->with(
$this->greaterThanOrEqual( $time ),
$this->equalTo( 'notice' ),
$this->equalTo( 'this is a message' ),
$this->equalTo( array( 'source' => 'unit-tests', '_legacy' => true ) )
);
$log = new WC_Logger( array( $handler ), 'debug' );
$log->add( 'unit-tests', 'this is a message' );
}
/**
* Test clear().
*
* @since 2.4
*/
public function test_clear() {
$log = new WC_Logger( null, 'debug' );
$log->clear( 'log' );
$this->setExpectedDeprecated( 'WC_Logger::clear' );
}
/**
* Test log() complains for bad levels.
*
* @since 2.7.0
*/
public function test_bad_level() {
$log = new WC_Logger( null, 'debug' );
$log->log( 'this-is-an-invalid-level', '' );
$this->setExpectedIncorrectUsage( 'WC_Logger::log' );
}
/**
* Test log().
*
* @since 2.7.0
*/
public function test_log() {
$handler = $this->create_mock_handler();
$log = new WC_Logger( array( $handler ), 'debug' );
$log->log( 'debug', 'debug message' );
$log->log( 'info', 'info message' );
$log->log( 'notice', 'notice message' );
$log->log( 'warning', 'warning message' );
$log->log( 'error', 'error message' );
$log->log( 'critical', 'critical message' );
$log->log( 'alert', 'alert message' );
$log->log( 'emergency', 'emergency message' );
}
/**
* Test logs passed to all handlers.
*
* @since 2.7.0
*/
public function test_log_handlers() {
$false_handler = $this
->getMockBuilder( 'WC_Log_Handler' )
->setMethods( array( 'handle' ) )
->getMock();
$false_handler->expects( $this->exactly( 8 ) )->method( 'handle' )->will( $this->returnValue( false ) );
$true_handler = $this
->getMockBuilder( 'WC_Log_Handler' )
->setMethods( array( 'handle' ) )
->getMock();
$false_handler->expects( $this->exactly( 8 ) )->method( 'handle' )->will( $this->returnValue( true ) );
$final_handler = $this
->getMockBuilder( 'WC_Log_Handler' )
->setMethods( array( 'handle' ) )
->getMock();
$final_handler->expects( $this->exactly( 8 ) )->method( 'handle' );
$log = new WC_Logger( array( $false_handler, $true_handler, $final_handler ), 'debug' );
$log->debug( 'debug' );
$log->info( 'info' );
$log->notice( 'notice' );
$log->warning( 'warning' );
$log->error( 'error' );
$log->critical( 'critical' );
$log->alert( 'alert' );
$log->emergency( 'emergency' );
}
/**
* Test WC_Logger->[debug..emergency] methods
*
* @since 2.7.0
*/
public function test_level_methods() {
$handler = $this->create_mock_handler();
$log = new WC_Logger( array( $handler ), 'debug' );
$log->debug( 'debug message' );
$log->info( 'info message' );
$log->notice( 'notice message' );
$log->warning( 'warning message' );
$log->error( 'error message' );
$log->critical( 'critical message' );
$log->alert( 'alert message' );
$log->emergency( 'emergency message' );
}
/**
* Test 'woocommerce_register_log_handlers' filter.
*
* @since 2.7.0
*/
public function test_woocommerce_register_log_handlers_filter() {
add_filter( 'woocommerce_register_log_handlers', array( $this, 'return_assertion_handlers' ) );
$log = new WC_Logger( null, 'debug' );
$log->debug( 'debug' );
$log->info( 'info' );
$log->notice( 'notice' );
$log->warning( 'warning' );
$log->error( 'error' );
$log->critical( 'critical' );
$log->alert( 'alert' );
$log->emergency( 'emergency' );
remove_filter( 'woocommerce_register_log_handlers', array( $this, 'return_assertion_handlers' ) );
}
/**
* Test no filtering by default
*
* @since 2.7.0
*/
public function test_threshold_defaults() {
$time = time();
// Test no filtering by default
delete_option( 'woocommerce_log_threshold' );
$handler = $this
->getMockBuilder( 'WC_Log_Handler' )
->setMethods( array( 'handle' ) )
->getMock();
$handler
->expects( $this->at( 0 ) )
->method( 'handle' )
->with(
$this->greaterThanOrEqual( $time ),
$this->equalTo( 'bad-level' ),
$this->equalTo( 'bad-level message' ),
$this->equalTo( array() )
);
$handler
->expects( $this->at( 1 ) )
->method( 'handle' )
->with(
$this->greaterThanOrEqual( $time ),
$this->equalTo( 'debug' ),
$this->equalTo( 'debug message' ),
$this->equalTo( array() )
);
$log = new WC_Logger( array( $handler ) );
// An invalid level has the minimum severity, but should not be filtered.
$log->log( 'bad-level', 'bad-level message' );
// Bad level also complains.
$this->setExpectedIncorrectUsage( 'WC_Logger::log' );
// Debug is lowest recognized level
$log->debug( 'debug message' );
}
/**
* Helper for 'woocommerce_register_log_handlers' filter test.
*
* Returns an array of 1 mocked handler.
* The handler expects to receive exactly 8 messages (1 for each level).
*
* @since 2.7.0
*
* @return WC_Log_Handler[] array of mocked handlers.
*/
public function return_assertion_handlers() {
$handler = $this
->getMockBuilder( 'WC_Log_Handler' )
->setMethods( array( 'handle' ) )
->getMock();
$handler->expects( $this->exactly( 8 ) )->method( 'handle' );
return array( $handler );
}
/**
* Mock handler that expects sequential calls to each level.
*
* Calls should have the message '[level] message'
*
* @since 2.7.0
*
* @return WC_Log_Handler mock object
*/
public function create_mock_handler() {
$time = time();
$handler = $this
->getMockBuilder( 'WC_Log_Handler' )
->setMethods( array( 'handle' ) )
->getMock();
$handler
->expects( $this->at( 0 ) )
->method( 'handle' )
->with(
$this->greaterThanOrEqual( $time ),
$this->equalTo( 'debug' ),
$this->equalTo( 'debug message' ),
$this->equalTo( array() )
);
$handler
->expects( $this->at( 1 ) )
->method( 'handle' )
->with(
$this->greaterThanOrEqual( $time ),
$this->equalTo( 'info' ),
$this->equalTo( 'info message' ),
$this->equalTo( array() )
);
$handler
->expects( $this->at( 2 ) )
->method( 'handle' )
->with(
$this->greaterThanOrEqual( $time ),
$this->equalTo( 'notice' ),
$this->equalTo( 'notice message' ),
$this->equalTo( array() )
);
$handler
->expects( $this->at( 3 ) )
->method( 'handle' )
->with(
$this->greaterThanOrEqual( $time ),
$this->equalTo( 'warning' ),
$this->equalTo( 'warning message' ),
$this->equalTo( array() )
);
$handler
->expects( $this->at( 4 ) )
->method( 'handle' )
->with(
$this->greaterThanOrEqual( $time ),
$this->equalTo( 'error' ),
$this->equalTo( 'error message' ),
$this->equalTo( array() )
);
$handler
->expects( $this->at( 5 ) )
->method( 'handle' )
->with(
$this->greaterThanOrEqual( $time ),
$this->equalTo( 'critical' ),
$this->equalTo( 'critical message' ),
$this->equalTo( array() )
);
$handler
->expects( $this->at( 6 ) )
->method( 'handle' )
->with(
$this->greaterThanOrEqual( $time ),
$this->equalTo( 'alert' ),
$this->equalTo( 'alert message' ),
$this->equalTo( array() )
);
$handler
->expects( $this->at( 7 ) )
->method( 'handle' )
->with(
$this->greaterThanOrEqual( $time ),
$this->equalTo( 'emergency' ),
$this->equalTo( 'emergency message' ),
$this->equalTo( array() )
);
return $handler;
}
}

View File

@ -0,0 +1,8 @@
%d-%d-%dT%d:%d:%d+%d:%d DEBUG debug
%d-%d-%dT%d:%d:%d+%d:%d INFO info
%d-%d-%dT%d:%d:%d+%d:%d NOTICE notice
%d-%d-%dT%d:%d:%d+%d:%d WARNING warning
%d-%d-%dT%d:%d:%d+%d:%d ERROR error
%d-%d-%dT%d:%d:%d+%d:%d CRITICAL critical
%d-%d-%dT%d:%d:%d+%d:%d ALERT alert
%d-%d-%dT%d:%d:%d+%d:%d EMERGENCY emergency

View File

@ -238,6 +238,20 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
$this->assertEquals( $log_dir . 'unit-tests-' . $hash_name . '.log', wc_get_log_file_path( 'unit-tests' ) );
}
/**
* Test wc_get_logger().
*
* @since 2.7.0
*/
public function test_wc_get_logger() {
$log_a = wc_get_logger();
$log_b = wc_get_logger();
$this->assertInstanceOf( 'WC_Logger', $log_a );
$this->assertInstanceOf( 'WC_Logger', $log_b );
$this->assertSame( $log_a, $log_b, '`wc_get_logger()` should return the same instance' );
}
/**
* Test wc_get_core_supported_themes().
*
@ -286,4 +300,84 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
// With legacy methods.
$this->assertEquals( 0, wc_get_shipping_method_count( true ) );
}
/**
* Test wc_print_r()
*
* @since 2.7.0
*/
public function test_wc_print_r() {
$arr = array( 1, 2, 'a', 'b', 'c' => 'd' );
// This filter will sequentially remove handlers, allowing us to test as though our
// functions were accumulatively blacklisted, adding one on each call.
add_filter( 'woocommerce_print_r_alternatives', array( $this, 'filter_wc_print_r_alternatives' ) );
$this->expectOutputString(
print_r( $arr, true ),
$return_value = wc_print_r( $arr )
);
$this->assertTrue( $return_value );
ob_clean();
$this->expectOutputString(
var_export( $arr, true ),
$return_value = wc_print_r( $arr )
);
$this->assertTrue( $return_value );
ob_clean();
$this->expectOutputString(
json_encode( $arr ),
$return_value = wc_print_r( $arr )
);
$this->assertTrue( $return_value );
ob_clean();
$this->expectOutputString(
serialize( $arr ),
$return_value = wc_print_r( $arr )
);
$this->assertTrue( $return_value );
ob_clean();
$this->expectOutputString(
'',
$return_value = wc_print_r( $arr )
);
$this->assertFalse( $return_value );
ob_clean();
// Reset filter to include all handlers
$this->filter_wc_print_r_alternatives( array(), true );
$this->assertEquals( print_r( $arr, true ), wc_print_r( $arr, true ) );
$this->assertEquals( var_export( $arr, true ), wc_print_r( $arr, true ) );
$this->assertEquals( json_encode( $arr ), wc_print_r( $arr, true ) );
$this->assertEquals( serialize( $arr ), wc_print_r( $arr, true ) );
$this->assertFalse( wc_print_r( $arr, true ) );
remove_filter( 'woocommerce_print_r_alternatives', array( $this, 'filter_wc_print_r_alternatives' ) );
}
/**
* Filter function to help test wc_print_r alternatives via filter.
*
* On the first run, all alternatives are returned.
* On each successive run, an alternative is excluded from the beginning.
* Eventually, no handlers are returned.
*
* @param array $alternatives Input array of alternatives.
* @param bool $reset Optional. Default false. True to reset excluded alternatives.
* @return array|bool Alternatives. True on reset.
*/
public function filter_wc_print_r_alternatives( $alternatives, $reset = false ) {
static $skip = 0;
if ( $reset ) {
$skip = 0;
return true;
}
return array_slice( $alternatives, $skip++ );
}
}

View File

@ -1,40 +0,0 @@
<?php
/**
* Class Log.
* @package WooCommerce\Tests\Util
* @since 2.3
*/
class WC_Tests_Log extends WC_Unit_Test_Case {
public function read_content( $handle ) {
return file_get_contents( wc_get_log_file_path( $handle ) );
}
/**
* Test add().
*
* @since 2.4
*/
public function test_add() {
$log = wc_get_logger();
$log->add( 'unit-tests', 'this is a message' );
$this->assertStringMatchesFormat( '%d-%d-%d @ %d:%d:%d - %s', $this->read_content( 'unit-tests' ) );
$this->assertStringEndsWith( ' - this is a message' . PHP_EOL, $this->read_content( 'unit-tests' ) );
}
/**
* Test clear().
*
* @since 2.4
*/
public function test_clear() {
$log = wc_get_logger();
$log->add( 'unit-tests', 'this is a message' );
$log->clear( 'unit-tests' );
$this->assertEquals( '', $this->read_content( 'unit-tests' ) );
}
}

View File

@ -286,6 +286,7 @@ final class WooCommerce {
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-shipping-method.php' ); // A Shipping method
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-payment-gateway.php' ); // A Payment gateway
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-integration.php' ); // An integration with a service
include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-log-handler.php' );
include_once( WC_ABSPATH . 'includes/class-wc-product-factory.php' ); // Product factory
include_once( WC_ABSPATH . 'includes/class-wc-payment-tokens.php' ); // Payment tokens controller
include_once( WC_ABSPATH . 'includes/class-wc-shipping-zone.php' );