Skip to content

Commit

Permalink
Logging: Improve compatibility with multisite (#44735)
Browse files Browse the repository at this point in the history
* Add static method for getting the log directory

* Add filter for customizing the log directory path

* Handle case where constant is already defined

* Do directory creation on the fly instead of during install

* Replace all Core usages of WC_LOG_DIR

* Ensure each site's log handler setting is respected

* Add unit tests

* Fix legacy logger unit tests

* Update docs

* Regenerate docs manifest file (required by GitHub CI)

---------

Co-authored-by: Nestor Soriano <konamiman@konamiman.com>
  • Loading branch information
coreymckrill and Konamiman committed Feb 28, 2024
1 parent e1d8355 commit d581512
Show file tree
Hide file tree
Showing 21 changed files with 291 additions and 137 deletions.
9 changes: 2 additions & 7 deletions docs/docs-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@
{
"post_title": "Logging in WooCommerce",
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/extension-development/logging.md",
"hash": "2cb0d0d594481127d144a95ccf8ca8ca826711dbf25a42fdc69a75c7d200d99f",
"hash": "7f5777df46d83e49b024ae205111e0a0960d8c53466d351a8744999d256cb0c0",
"url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/extension-development/logging.md",
"id": "c684e2efba45051a4e1f98eb5e6ef6bab194f25c"
},
Expand Down Expand Up @@ -916,11 +916,6 @@
],
"categories": []
},
{
"category_slug": "utilities",
"category_title": "Utilities",
"categories": []
},
{
"content": "\nThis section covers general guidelines, and best practices to follow in order to ensure your product experience aligns with WooCommerce for ease of use, seamless integration, and strong adoption.\n\nWe strongly recommend you review the current [WooCommerce setup experience](https://woo.com/documentation/plugins/woocommerce/getting-started/) to get familiar with the user experience and taxonomy.\n\nWe also recommend you review the [WordPress core guidelines](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/) to ensure your product isn't breaking any rules, and review [this helpful resource](https://woo.com/document/grammar-punctuation-style-guide/) on content style.\n\n## General\n\nUse existing WordPress/WooCommerce UI, built in components (text fields, checkboxes, etc) and existing menu structures.\n\nPlugins which draw on WordPress' core design aesthetic will benefit from future updates to this design as WordPress continues to evolve. If you need to make an exception for your product, be prepared to provide a valid use case.\n\n- [WordPress Components library](https://wordpress.github.io/gutenberg/?path=/story/docs-introduction--page)\n- [Figma for WordPress](https://make.wordpress.org/design/2018/11/19/figma-for-wordpress/) | ([WordPress Design Library Figma](https://www.figma.com/file/e4tLacmlPuZV47l7901FEs/WordPress-Design-Library))\n- [WooCommerce Component Library](https://woocommerce.github.io/woocommerce-admin/)\n",
"category_slug": "user-experience-extensions",
Expand Down Expand Up @@ -1209,5 +1204,5 @@
"categories": []
}
],
"hash": "a485b51014a2262571751ae495976ca40aa8ffd4fddc7ee8ca8171ee51bd8984"
"hash": "2453d3ac64b6f1f4f4cd8efddfc166602f7182a9dff17218070fd2dccf8722e5"
}
2 changes: 1 addition & 1 deletion docs/extension-development/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ Uncheck the box here to turn off all logging. This is not recommended in most ci

Out-of-the-box, WooCommerce has two different log storage methods available:

* **File system** - Log entries are recorded to files. Files are differentiated by the `source` value for the log entry (see the "Adding logs" section below), and by the current date. The files are stored in `wp-content/uploads/wc-logs`, but this can be changed by defining the `WC_LOG_DIR` constant in your `wp-config.php` file with a custom path. Log files can be up to 5 MB in size, after which the log file will rotate.
* **File system** - Log entries are recorded to files. Files are differentiated by the `source` value for the log entry (see the "Adding logs" section below), and by the current date. The files are stored in the `wc-logs` subdirectory of the site's `uploads` directory. A custom directory can be defined using the `woocommerce_log_directory` filter hook. Log files can be up to 5 MB in size, after which the log file will rotate.
* **Database** - Log entries are recorded to the database, in the `{$wpdb->prefix}woocommerce_log` table.

If you change this setting, and you already have some log entries, those entries will not be migrated to the other storage method, but neither will they be deleted.
Expand Down
4 changes: 4 additions & 0 deletions plugins/woocommerce/changelog/try-log-file-multisite
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: update

Improve compatibility of the logging system with multisite
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
* @package WooCommerce\Admin\Logs
*/

use Automattic\WooCommerce\Utilities\LoggingUtil;

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

$log_directory = LoggingUtil::get_log_directory();

?>
<?php if ( $logs ) : ?>
<div id="log-viewer-select">
Expand All @@ -25,7 +29,7 @@
<select class="wc-enhanced-select" name="log_file">
<?php foreach ( $logs as $log_key => $log_file ) : ?>
<?php
$timestamp = filemtime( WC_LOG_DIR . $log_file );
$timestamp = filemtime( $log_directory . $log_file );
$date = sprintf(
/* translators: 1: last access date 2: last access time 3: last access timezone abbreviation */
__( '%1$s at %2$s %3$s', 'woocommerce' ),
Expand All @@ -43,7 +47,7 @@
<div class="clear"></div>
</div>
<div id="log-viewer">
<pre><?php echo esc_html( file_get_contents( WC_LOG_DIR . $viewed_log ) ); ?></pre>
<pre><?php echo esc_html( file_get_contents( $log_directory . $viewed_log ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents ?></pre>
</div>
<?php else : ?>
<div class="updated woocommerce-message inline"><p><?php esc_html_e( 'There are currently no logs to view.', 'woocommerce' ); ?></p></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,14 @@
if ( $environment['log_directory_writable'] ) {
echo '<mark class="yes"><span class="dashicons dashicons-yes"></span> <code class="private">' . esc_html( $environment['log_directory'] ) . '</code></mark> ';
} else {
/* Translators: %1$s: Log directory, %2$s: Log directory constant */
echo '<mark class="error"><span class="dashicons dashicons-warning"></span> ' . sprintf( esc_html__( 'To allow logging, make %1$s writable or define a custom %2$s.', 'woocommerce' ), '<code>' . esc_html( $environment['log_directory'] ) . '</code>', '<code>WC_LOG_DIR</code>' ) . '</mark>';
printf(
'<mark class="error"><span class="dashicons dashicons-warning"></span> %s</mark>',
sprintf(
// Translators: %s: Log directory path.
esc_html__( 'To allow logging, make %s writable.', 'woocommerce' ),
'<code>' . esc_html( $environment['log_directory'] ) . '</code>'
)
);
}
?>
</td>
Expand Down
10 changes: 0 additions & 10 deletions plugins/woocommerce/includes/class-wc-install.php
Original file line number Diff line number Diff line change
Expand Up @@ -1876,16 +1876,6 @@ private static function create_files() {
'file' => 'index.html',
'content' => '',
),
array(
'base' => WC_LOG_DIR,
'file' => '.htaccess',
'content' => 'deny from all',
),
array(
'base' => WC_LOG_DIR,
'file' => 'index.html',
'content' => '',
),
array(
'base' => $upload_dir['basedir'] . '/woocommerce_uploads',
'file' => '.htaccess',
Expand Down
51 changes: 39 additions & 12 deletions plugins/woocommerce/includes/class-wc-logger.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,24 @@ class WC_Logger implements WC_Logger_Interface {
* @param string $threshold Optional. Define an explicit threshold. May be configured via WC_LOG_THRESHOLD. By default, all logs will be processed.
*/
public function __construct( $handlers = null, $threshold = null ) {
if ( null === $handlers ) {
if ( is_array( $handlers ) ) {
$this->handlers = $handlers;
}

if ( is_string( $threshold ) ) {
$this->threshold = $threshold;
}
}

/**
* Get an array of log handler instances.
*
* @return WC_Log_Handler_Interface[]
*/
protected function get_handlers() {
if ( ! is_null( $this->handlers ) ) {
$handlers = $this->handlers;
} else {
$default_handler = LoggingUtil::get_default_handler();
$handler_instance = new $default_handler();

Expand All @@ -50,12 +67,12 @@ public function __construct( $handlers = null, $threshold = null ) {
$handlers = apply_filters( 'woocommerce_register_log_handlers', array( $handler_instance ) );
}

$register_handlers = array();
$registered_handlers = array();

if ( ! empty( $handlers ) && is_array( $handlers ) ) {
foreach ( $handlers as $handler ) {
if ( $handler instanceof WC_Log_Handler_Interface ) {
$register_handlers[] = $handler;
$registered_handlers[] = $handler;
} else {
wc_doing_it_wrong(
__METHOD__,
Expand All @@ -71,12 +88,22 @@ public function __construct( $handlers = null, $threshold = null ) {
}
}

return $registered_handlers;
}

/**
* Get the log threshold as a numerical level severity.
*
* @return int
*/
protected function get_threshold() {
$threshold = $this->threshold;

if ( ! WC_Log_Levels::is_valid_level( $threshold ) ) {
$threshold = LoggingUtil::get_level_threshold();
}

$this->handlers = $register_handlers;
$this->threshold = WC_Log_Levels::get_level_severity( $threshold );
return WC_Log_Levels::get_level_severity( $threshold );
}

/**
Expand All @@ -90,11 +117,9 @@ protected function should_handle( $level ) {
return false;
}

if ( null === $this->threshold ) {
return true;
}
$threshold = $this->get_threshold();

return $this->threshold <= WC_Log_Levels::get_level_severity( $level );
return $threshold <= WC_Log_Levels::get_level_severity( $level );
}

/**
Expand Down Expand Up @@ -148,7 +173,7 @@ public function log( $level, $message, $context = array() ) {
if ( $this->should_handle( $level ) ) {
$timestamp = time();

foreach ( $this->handlers as $handler ) {
foreach ( $this->get_handlers() as $handler ) {
/**
* Filter the logging message. Returning null will prevent logging from occurring since 5.3.
*
Expand Down Expand Up @@ -296,11 +321,13 @@ public function clear( $source = '' ) {
if ( ! $source ) {
return false;
}
foreach ( $this->handlers as $handler ) {

foreach ( $this->get_handlers() as $handler ) {
if ( is_callable( array( $handler, 'clear' ) ) ) {
$handler->clear( $source );
}
}

return true;
}

Expand All @@ -313,7 +340,7 @@ public function clear_expired_logs() {
$days = LoggingUtil::get_retention_period();
$timestamp = strtotime( "-{$days} days" );

foreach ( $this->handlers as $handler ) {
foreach ( $this->get_handlers() as $handler ) {
if ( is_callable( array( $handler, 'delete_logs_before_timestamp' ) ) ) {
$handler->delete_logs_before_timestamp( $timestamp );
}
Expand Down
13 changes: 11 additions & 2 deletions plugins/woocommerce/includes/class-woocommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
use Automattic\WooCommerce\Internal\Utilities\WebhookUtil;
use Automattic\WooCommerce\Internal\Admin\Marketplace;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Utilities\TimeUtil;
use Automattic\WooCommerce\Utilities\{ LoggingUtil, TimeUtil };

/**
* Main WooCommerce Class.
Expand Down Expand Up @@ -366,10 +366,19 @@ private function define_constants() {
$this->define( 'WC_DISCOUNT_ROUNDING_MODE', 2 );
$this->define( 'WC_TAX_ROUNDING_MODE', 'yes' === get_option( 'woocommerce_prices_include_tax', 'no' ) ? 2 : 1 );
$this->define( 'WC_DELIMITER', '|' );
$this->define( 'WC_LOG_DIR', $upload_dir['basedir'] . '/wc-logs/' );
$this->define( 'WC_SESSION_CACHE_GROUP', 'wc_session_id' );
$this->define( 'WC_TEMPLATE_DEBUG_MODE', false );

/**
* As of 8.8.0, it is preferable to use the `woocommerce_log_directory` filter hook to change the log
* directory. WC_LOG_DIR_CUSTOM is a back-compatibility measure so we can tell if `WC_LOG_DIR` has been
* defined outside of WC Core.
*/
if ( defined( 'WC_LOG_DIR' ) ) {
$this->define( 'WC_LOG_DIR_CUSTOM', true );
}
$this->define( 'WC_LOG_DIR', LoggingUtil::get_log_directory() );

// These three are kept defined for compatibility, but are no longer used.
$this->define( 'WC_NOTICE_MIN_PHP_VERSION', '7.2' );
$this->define( 'WC_NOTICE_MIN_WP_VERSION', '5.2' );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Utilities\LoggingUtil;

if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
Expand Down Expand Up @@ -251,13 +252,14 @@ public function clear( $handle ) {
* @return bool
*/
public function remove( $handle ) {
$removed = false;
$logs = $this->get_log_files();
$handle = sanitize_title( $handle );
$removed = false;
$logs = $this->get_log_files();
$log_directory = LoggingUtil::get_log_directory();
$handle = sanitize_title( $handle );

if ( isset( $logs[ $handle ] ) && $logs[ $handle ] ) {
$file = realpath( trailingslashit( WC_LOG_DIR ) . $logs[ $handle ] );
if ( 0 === stripos( $file, realpath( trailingslashit( WC_LOG_DIR ) ) ) && is_file( $file ) && is_writable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
$file = realpath( trailingslashit( $log_directory ) . $logs[ $handle ] );
if ( 0 === stripos( $file, realpath( trailingslashit( $log_directory ) ) ) && is_file( $file ) && is_writable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable
$this->close( $file ); // Close first to be certain no processes keep it alive after it is unlinked.
$removed = unlink( $file ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_unlink
}
Expand Down Expand Up @@ -349,8 +351,10 @@ protected function increment_log_infix( $handle, $number = null ) {
* @return bool|string The log file path or false if path cannot be determined.
*/
public static function get_log_file_path( $handle ) {
$log_directory = LoggingUtil::get_log_directory();

if ( function_exists( 'wp_hash' ) ) {
return trailingslashit( WC_LOG_DIR ) . self::get_log_file_name( $handle );
return trailingslashit( $log_directory ) . self::get_log_file_name( $handle );
} else {
wc_doing_it_wrong( __METHOD__, __( 'This method should not be called before plugins_loaded.', 'woocommerce' ), '3.0' );
return false;
Expand Down Expand Up @@ -410,13 +414,14 @@ public static function delete_logs_before_timestamp( $timestamp = 0 ) {
return;
}

$log_files = self::get_log_files();
$log_files = self::get_log_files();
$log_directory = LoggingUtil::get_log_directory();

foreach ( $log_files as $log_file ) {
$last_modified = filemtime( trailingslashit( WC_LOG_DIR ) . $log_file );
$last_modified = filemtime( trailingslashit( $log_directory ) . $log_file );

if ( $last_modified < $timestamp ) {
@unlink( trailingslashit( WC_LOG_DIR ) . $log_file ); // @codingStandardsIgnoreLine.
@unlink( trailingslashit( $log_directory ) . $log_file ); // @codingStandardsIgnoreLine.
}
}
}
Expand All @@ -428,7 +433,9 @@ public static function delete_logs_before_timestamp( $timestamp = 0 ) {
* @return array
*/
public static function get_log_files() {
$files = @scandir( WC_LOG_DIR ); // @codingStandardsIgnoreLine.
$log_directory = LoggingUtil::get_log_directory();

$files = @scandir( $log_directory ); // @codingStandardsIgnoreLine.
$result = array();

if ( ! empty( $files ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -870,15 +870,16 @@ public function get_environment_info_per_fields( $fields ) {
}

$database_version = wc_get_server_database_version();
$log_directory = LoggingUtil::get_log_directory();

// Return all environment info. Described by JSON Schema.
return array(
'home_url' => get_option( 'home' ),
'site_url' => get_option( 'siteurl' ),
'store_id' => get_option( \WC_Install::STORE_ID_OPTION, null ),
'version' => WC()->version,
'log_directory' => WC_LOG_DIR,
'log_directory_writable' => (bool) @fopen( WC_LOG_DIR . 'test-log.log', 'a' ), // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_read_fopen
'log_directory' => $log_directory,
'log_directory_writable' => wp_is_writable( $log_directory ),
'wp_version' => get_bloginfo( 'version' ),
'wp_multisite' => is_multisite(),
'wp_memory_limit' => $wp_memory_limit,
Expand Down
2 changes: 1 addition & 1 deletion plugins/woocommerce/includes/wc-deprecated-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -1156,7 +1156,7 @@ function wc_register_default_log_handler( $handlers = array() ) {
function wc_get_log_file_path( $handle ) {
wc_deprecated_function( 'wc_get_log_file_path', '8.6.0' );

$directory = trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) );
$directory = LoggingUtil::get_log_directory();
$file_id = LoggingUtil::generate_log_file_id( $handle, null, time() );
$hash = LoggingUtil::generate_log_file_hash( $file_id );

Expand Down

0 comments on commit d581512

Please sign in to comment.