Logging: Improve compatibility with multisite (#44735)

* 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>
This commit is contained in:
Corey McKrill 2024-02-28 03:31:31 -08:00 committed by GitHub
parent e1d83555a2
commit d581512171
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 291 additions and 137 deletions

View File

@ -350,7 +350,7 @@
{ {
"post_title": "Logging in WooCommerce", "post_title": "Logging in WooCommerce",
"edit_url": "https://github.com/woocommerce/woocommerce/edit/trunk/docs/extension-development/logging.md", "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", "url": "https://raw.githubusercontent.com/woocommerce/woocommerce/trunk/docs/extension-development/logging.md",
"id": "c684e2efba45051a4e1f98eb5e6ef6bab194f25c" "id": "c684e2efba45051a4e1f98eb5e6ef6bab194f25c"
}, },
@ -916,11 +916,6 @@
], ],
"categories": [] "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", "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", "category_slug": "user-experience-extensions",
@ -1209,5 +1204,5 @@
"categories": [] "categories": []
} }
], ],
"hash": "a485b51014a2262571751ae495976ca40aa8ffd4fddc7ee8ca8171ee51bd8984" "hash": "2453d3ac64b6f1f4f4cd8efddfc166602f7182a9dff17218070fd2dccf8722e5"
} }

View File

@ -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: 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. * **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. 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.

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Improve compatibility of the logging system with multisite

View File

@ -5,10 +5,14 @@
* @package WooCommerce\Admin\Logs * @package WooCommerce\Admin\Logs
*/ */
use Automattic\WooCommerce\Utilities\LoggingUtil;
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
$log_directory = LoggingUtil::get_log_directory();
?> ?>
<?php if ( $logs ) : ?> <?php if ( $logs ) : ?>
<div id="log-viewer-select"> <div id="log-viewer-select">
@ -25,7 +29,7 @@ if ( ! defined( 'ABSPATH' ) ) {
<select class="wc-enhanced-select" name="log_file"> <select class="wc-enhanced-select" name="log_file">
<?php foreach ( $logs as $log_key => $log_file ) : ?> <?php foreach ( $logs as $log_key => $log_file ) : ?>
<?php <?php
$timestamp = filemtime( WC_LOG_DIR . $log_file ); $timestamp = filemtime( $log_directory . $log_file );
$date = sprintf( $date = sprintf(
/* translators: 1: last access date 2: last access time 3: last access timezone abbreviation */ /* translators: 1: last access date 2: last access time 3: last access timezone abbreviation */
__( '%1$s at %2$s %3$s', 'woocommerce' ), __( '%1$s at %2$s %3$s', 'woocommerce' ),
@ -43,7 +47,7 @@ if ( ! defined( 'ABSPATH' ) ) {
<div class="clear"></div> <div class="clear"></div>
</div> </div>
<div id="log-viewer"> <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> </div>
<?php else : ?> <?php else : ?>
<div class="updated woocommerce-message inline"><p><?php esc_html_e( 'There are currently no logs to view.', 'woocommerce' ); ?></p></div> <div class="updated woocommerce-message inline"><p><?php esc_html_e( 'There are currently no logs to view.', 'woocommerce' ); ?></p></div>

View File

@ -120,8 +120,14 @@ $inactive_plugins_count = is_countable( $inactive_plugins ) ? count( $inactive_p
if ( $environment['log_directory_writable'] ) { 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> '; echo '<mark class="yes"><span class="dashicons dashicons-yes"></span> <code class="private">' . esc_html( $environment['log_directory'] ) . '</code></mark> ';
} else { } else {
/* Translators: %1$s: Log directory, %2$s: Log directory constant */ printf(
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>'; '<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> </td>

View File

@ -1876,16 +1876,6 @@ $hpos_table_schema;
'file' => 'index.html', 'file' => 'index.html',
'content' => '', 'content' => '',
), ),
array(
'base' => WC_LOG_DIR,
'file' => '.htaccess',
'content' => 'deny from all',
),
array(
'base' => WC_LOG_DIR,
'file' => 'index.html',
'content' => '',
),
array( array(
'base' => $upload_dir['basedir'] . '/woocommerce_uploads', 'base' => $upload_dir['basedir'] . '/woocommerce_uploads',
'file' => '.htaccess', 'file' => '.htaccess',

View File

@ -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. * @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 ) { 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(); $default_handler = LoggingUtil::get_default_handler();
$handler_instance = new $default_handler(); $handler_instance = new $default_handler();
@ -50,12 +67,12 @@ class WC_Logger implements WC_Logger_Interface {
$handlers = apply_filters( 'woocommerce_register_log_handlers', array( $handler_instance ) ); $handlers = apply_filters( 'woocommerce_register_log_handlers', array( $handler_instance ) );
} }
$register_handlers = array(); $registered_handlers = array();
if ( ! empty( $handlers ) && is_array( $handlers ) ) { if ( ! empty( $handlers ) && is_array( $handlers ) ) {
foreach ( $handlers as $handler ) { foreach ( $handlers as $handler ) {
if ( $handler instanceof WC_Log_Handler_Interface ) { if ( $handler instanceof WC_Log_Handler_Interface ) {
$register_handlers[] = $handler; $registered_handlers[] = $handler;
} else { } else {
wc_doing_it_wrong( wc_doing_it_wrong(
__METHOD__, __METHOD__,
@ -71,12 +88,22 @@ class WC_Logger implements WC_Logger_Interface {
} }
} }
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 ) ) { if ( ! WC_Log_Levels::is_valid_level( $threshold ) ) {
$threshold = LoggingUtil::get_level_threshold(); $threshold = LoggingUtil::get_level_threshold();
} }
$this->handlers = $register_handlers; return WC_Log_Levels::get_level_severity( $threshold );
$this->threshold = WC_Log_Levels::get_level_severity( $threshold );
} }
/** /**
@ -90,11 +117,9 @@ class WC_Logger implements WC_Logger_Interface {
return false; return false;
} }
if ( null === $this->threshold ) { $threshold = $this->get_threshold();
return true;
}
return $this->threshold <= WC_Log_Levels::get_level_severity( $level ); return $threshold <= WC_Log_Levels::get_level_severity( $level );
} }
/** /**
@ -148,7 +173,7 @@ class WC_Logger implements WC_Logger_Interface {
if ( $this->should_handle( $level ) ) { if ( $this->should_handle( $level ) ) {
$timestamp = time(); $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. * Filter the logging message. Returning null will prevent logging from occurring since 5.3.
* *
@ -296,11 +321,13 @@ class WC_Logger implements WC_Logger_Interface {
if ( ! $source ) { if ( ! $source ) {
return false; return false;
} }
foreach ( $this->handlers as $handler ) {
foreach ( $this->get_handlers() as $handler ) {
if ( is_callable( array( $handler, 'clear' ) ) ) { if ( is_callable( array( $handler, 'clear' ) ) ) {
$handler->clear( $source ); $handler->clear( $source );
} }
} }
return true; return true;
} }
@ -313,7 +340,7 @@ class WC_Logger implements WC_Logger_Interface {
$days = LoggingUtil::get_retention_period(); $days = LoggingUtil::get_retention_period();
$timestamp = strtotime( "-{$days} days" ); $timestamp = strtotime( "-{$days} days" );
foreach ( $this->handlers as $handler ) { foreach ( $this->get_handlers() as $handler ) {
if ( is_callable( array( $handler, 'delete_logs_before_timestamp' ) ) ) { if ( is_callable( array( $handler, 'delete_logs_before_timestamp' ) ) ) {
$handler->delete_logs_before_timestamp( $timestamp ); $handler->delete_logs_before_timestamp( $timestamp );
} }

View File

@ -23,7 +23,7 @@ use Automattic\WooCommerce\Internal\Settings\OptionSanitizer;
use Automattic\WooCommerce\Internal\Utilities\WebhookUtil; use Automattic\WooCommerce\Internal\Utilities\WebhookUtil;
use Automattic\WooCommerce\Internal\Admin\Marketplace; use Automattic\WooCommerce\Internal\Admin\Marketplace;
use Automattic\WooCommerce\Proxies\LegacyProxy; use Automattic\WooCommerce\Proxies\LegacyProxy;
use Automattic\WooCommerce\Utilities\TimeUtil; use Automattic\WooCommerce\Utilities\{ LoggingUtil, TimeUtil };
/** /**
* Main WooCommerce Class. * Main WooCommerce Class.
@ -366,10 +366,19 @@ final class WooCommerce {
$this->define( 'WC_DISCOUNT_ROUNDING_MODE', 2 ); $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_TAX_ROUNDING_MODE', 'yes' === get_option( 'woocommerce_prices_include_tax', 'no' ) ? 2 : 1 );
$this->define( 'WC_DELIMITER', '|' ); $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_SESSION_CACHE_GROUP', 'wc_session_id' );
$this->define( 'WC_TEMPLATE_DEBUG_MODE', false ); $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. // 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_PHP_VERSION', '7.2' );
$this->define( 'WC_NOTICE_MIN_WP_VERSION', '5.2' ); $this->define( 'WC_NOTICE_MIN_WP_VERSION', '5.2' );

View File

@ -6,6 +6,7 @@
*/ */
use Automattic\Jetpack\Constants; use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Utilities\LoggingUtil;
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly. exit; // Exit if accessed directly.
@ -253,11 +254,12 @@ class WC_Log_Handler_File extends WC_Log_Handler {
public function remove( $handle ) { public function remove( $handle ) {
$removed = false; $removed = false;
$logs = $this->get_log_files(); $logs = $this->get_log_files();
$log_directory = LoggingUtil::get_log_directory();
$handle = sanitize_title( $handle ); $handle = sanitize_title( $handle );
if ( isset( $logs[ $handle ] ) && $logs[ $handle ] ) { if ( isset( $logs[ $handle ] ) && $logs[ $handle ] ) {
$file = realpath( trailingslashit( WC_LOG_DIR ) . $logs[ $handle ] ); $file = realpath( trailingslashit( $log_directory ) . $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 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. $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 $removed = unlink( $file ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_unlink
} }
@ -349,8 +351,10 @@ class WC_Log_Handler_File extends WC_Log_Handler {
* @return bool|string The log file path or false if path cannot be determined. * @return bool|string The log file path or false if path cannot be determined.
*/ */
public static function get_log_file_path( $handle ) { public static function get_log_file_path( $handle ) {
$log_directory = LoggingUtil::get_log_directory();
if ( function_exists( 'wp_hash' ) ) { 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 { } else {
wc_doing_it_wrong( __METHOD__, __( 'This method should not be called before plugins_loaded.', 'woocommerce' ), '3.0' ); wc_doing_it_wrong( __METHOD__, __( 'This method should not be called before plugins_loaded.', 'woocommerce' ), '3.0' );
return false; return false;
@ -411,12 +415,13 @@ class WC_Log_Handler_File extends WC_Log_Handler {
} }
$log_files = self::get_log_files(); $log_files = self::get_log_files();
$log_directory = LoggingUtil::get_log_directory();
foreach ( $log_files as $log_file ) { 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 ) { if ( $last_modified < $timestamp ) {
@unlink( trailingslashit( WC_LOG_DIR ) . $log_file ); // @codingStandardsIgnoreLine. @unlink( trailingslashit( $log_directory ) . $log_file ); // @codingStandardsIgnoreLine.
} }
} }
} }
@ -428,7 +433,9 @@ class WC_Log_Handler_File extends WC_Log_Handler {
* @return array * @return array
*/ */
public static function get_log_files() { 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(); $result = array();
if ( ! empty( $files ) ) { if ( ! empty( $files ) ) {

View File

@ -870,6 +870,7 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
} }
$database_version = wc_get_server_database_version(); $database_version = wc_get_server_database_version();
$log_directory = LoggingUtil::get_log_directory();
// Return all environment info. Described by JSON Schema. // Return all environment info. Described by JSON Schema.
return array( return array(
@ -877,8 +878,8 @@ class WC_REST_System_Status_V2_Controller extends WC_REST_Controller {
'site_url' => get_option( 'siteurl' ), 'site_url' => get_option( 'siteurl' ),
'store_id' => get_option( \WC_Install::STORE_ID_OPTION, null ), 'store_id' => get_option( \WC_Install::STORE_ID_OPTION, null ),
'version' => WC()->version, 'version' => WC()->version,
'log_directory' => WC_LOG_DIR, 'log_directory' => $log_directory,
'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_writable' => wp_is_writable( $log_directory ),
'wp_version' => get_bloginfo( 'version' ), 'wp_version' => get_bloginfo( 'version' ),
'wp_multisite' => is_multisite(), 'wp_multisite' => is_multisite(),
'wp_memory_limit' => $wp_memory_limit, 'wp_memory_limit' => $wp_memory_limit,

View File

@ -1156,7 +1156,7 @@ function wc_register_default_log_handler( $handlers = array() ) {
function wc_get_log_file_path( $handle ) { function wc_get_log_file_path( $handle ) {
wc_deprecated_function( 'wc_get_log_file_path', '8.6.0' ); 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() ); $file_id = LoggingUtil::generate_log_file_id( $handle, null, time() );
$hash = LoggingUtil::generate_log_file_hash( $file_id ); $hash = LoggingUtil::generate_log_file_hash( $file_id );

View File

@ -4,6 +4,7 @@ declare( strict_types = 1 );
namespace Automattic\WooCommerce\Internal\Admin\Logging\FileV2; namespace Automattic\WooCommerce\Internal\Admin\Logging\FileV2;
use Automattic\Jetpack\Constants; use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Admin\Logging\Settings;
use PclZip; use PclZip;
use WC_Cache_Helper; use WC_Cache_Helper;
use WP_Error; use WP_Error;
@ -75,20 +76,6 @@ class FileController {
*/ */
private const SEARCH_CACHE_KEY = 'logs_previous_search'; private const SEARCH_CACHE_KEY = 'logs_previous_search';
/**
* The absolute path to the log directory.
*
* @var string
*/
private $log_directory;
/**
* Class FileController
*/
public function __construct() {
$this->log_directory = trailingslashit( Constants::get_constant( 'WC_LOG_DIR' ) );
}
/** /**
* Get the file size limit that determines when to rotate a file. * Get the file size limit that determines when to rotate a file.
* *
@ -141,7 +128,7 @@ class FileController {
} }
if ( ! $file instanceof File ) { if ( ! $file instanceof File ) {
$new_path = $this->log_directory . $this->generate_filename( $source, $time ); $new_path = Settings::get_log_directory() . $this->generate_filename( $source, $time );
$file = new File( $new_path ); $file = new File( $new_path );
} }
@ -217,7 +204,7 @@ class FileController {
$args = wp_parse_args( $args, self::DEFAULTS_GET_FILES ); $args = wp_parse_args( $args, self::DEFAULTS_GET_FILES );
$pattern = $args['source'] . '*.log'; $pattern = $args['source'] . '*.log';
$paths = glob( $this->log_directory . $pattern ); $paths = glob( Settings::get_log_directory() . $pattern );
if ( false === $paths ) { if ( false === $paths ) {
return new WP_Error( return new WP_Error(
@ -332,14 +319,15 @@ class FileController {
* @return File[] * @return File[]
*/ */
public function get_files_by_id( array $file_ids ): array { public function get_files_by_id( array $file_ids ): array {
$log_directory = Settings::get_log_directory();
$paths = array(); $paths = array();
foreach ( $file_ids as $file_id ) { foreach ( $file_ids as $file_id ) {
// Look for the standard filename format first, which includes a hash. // Look for the standard filename format first, which includes a hash.
$glob = glob( $this->log_directory . $file_id . '-*.log' ); $glob = glob( $log_directory . $file_id . '-*.log' );
if ( ! $glob ) { if ( ! $glob ) {
$glob = glob( $this->log_directory . $file_id . '.log' ); $glob = glob( $log_directory . $file_id . '.log' );
} }
if ( is_array( $glob ) ) { if ( is_array( $glob ) ) {
@ -423,7 +411,7 @@ class FileController {
$created_pattern = $created ? '-' . gmdate( 'Y-m-d', $created ) . '-' : ''; $created_pattern = $created ? '-' . gmdate( 'Y-m-d', $created ) . '-' : '';
$rotation_pattern = $this->log_directory . $source . $rotations_pattern . $created_pattern . '*.log'; $rotation_pattern = Settings::get_log_directory() . $source . $rotations_pattern . $created_pattern . '*.log';
$rotation_paths = glob( $rotation_pattern ); $rotation_paths = glob( $rotation_pattern );
$rotation_files = $this->convert_paths_to_objects( $rotation_paths ); $rotation_files = $this->convert_paths_to_objects( $rotation_paths );
foreach ( $rotation_files as $rotation_file ) { foreach ( $rotation_files as $rotation_file ) {
@ -462,7 +450,7 @@ class FileController {
* @return array|WP_Error * @return array|WP_Error
*/ */
public function get_file_sources() { public function get_file_sources() {
$paths = glob( $this->log_directory . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
if ( false === $paths ) { if ( false === $paths ) {
return new WP_Error( return new WP_Error(
'wc_log_directory_error', 'wc_log_directory_error',
@ -671,7 +659,7 @@ class FileController {
*/ */
public function get_log_directory_size(): int { public function get_log_directory_size(): int {
$bytes = 0; $bytes = 0;
$path = realpath( $this->log_directory ); $path = realpath( Settings::get_log_directory() );
if ( wp_is_writable( $path ) ) { if ( wp_is_writable( $path ) ) {
$iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $path, \FilesystemIterator::SKIP_DOTS ) ); $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $path, \FilesystemIterator::SKIP_DOTS ) );

View File

@ -8,8 +8,10 @@ use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\File;
use Automattic\WooCommerce\Internal\Admin\Logging\LogHandlerFileV2; use Automattic\WooCommerce\Internal\Admin\Logging\LogHandlerFileV2;
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\FileController; use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\FileController;
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods; use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
use Automattic\WooCommerce\Proxies\LegacyProxy;
use WC_Admin_Settings; use WC_Admin_Settings;
use WC_Log_Handler_DB, WC_Log_Handler_File, WC_Log_Levels; use WC_Log_Handler_DB, WC_Log_Handler_File, WC_Log_Levels;
use WP_Filesystem_Base;
/** /**
* Settings class. * Settings class.
@ -44,6 +46,52 @@ class Settings {
self::add_action( 'wc_logs_load_tab', array( $this, 'save_settings' ) ); self::add_action( 'wc_logs_load_tab', array( $this, 'save_settings' ) );
} }
/**
* Get the directory for storing log files.
*
* The `wp_upload_dir` function takes into account the possibility of multisite, and handles changing
* the directory if the context is switched to a different site in the network mid-request.
*
* @return string The full directory path, with trailing slash.
*/
public static function get_log_directory(): string {
if ( true === Constants::get_constant( 'WC_LOG_DIR_CUSTOM' ) ) {
$dir = Constants::get_constant( 'WC_LOG_DIR' );
} else {
$upload_dir = wc_get_container()->get( LegacyProxy::class )->call_function( 'wp_upload_dir' );
/**
* Filter to change the directory for storing WooCommerce's log files.
*
* @param string $dir The full directory path, with trailing slash.
*
* @since 8.8.0
*/
$dir = apply_filters( 'woocommerce_log_directory', $upload_dir['basedir'] . '/wc-logs/' );
}
$dir = trailingslashit( $dir );
$realpath = realpath( $dir );
if ( false === $realpath ) {
$result = wp_mkdir_p( $dir );
if ( true === $result ) {
// Create infrastructure to prevent listing contents of the logs directory.
require_once ABSPATH . 'wp-admin/includes/file.php';
global $wp_filesystem;
if ( ! $wp_filesystem instanceof WP_Filesystem_Base ) {
WP_Filesystem();
}
$wp_filesystem->put_contents( $dir . '.htaccess', 'deny from all' );
$wp_filesystem->put_contents( $dir . 'index.html', '' );
}
}
return $dir;
}
/** /**
* The definitions used by WC_Admin_Settings to render and save settings controls. * The definitions used by WC_Admin_Settings to render and save settings controls.
* *
@ -242,7 +290,7 @@ class Settings {
*/ */
private function get_filesystem_settings_definitions(): array { private function get_filesystem_settings_definitions(): array {
$location_info = array(); $location_info = array();
$directory = trailingslashit( Constants::get_constant( 'WC_LOG_DIR' ) ); $directory = self::get_log_directory();
$location_info[] = sprintf( $location_info[] = sprintf(
// translators: %s is a location in the filesystem. // translators: %s is a location in the filesystem.
@ -257,13 +305,6 @@ class Settings {
$location_info[] = __( '⚠️ This directory does not appear to be writable.', 'woocommerce' ); $location_info[] = __( '⚠️ This directory does not appear to be writable.', 'woocommerce' );
} }
$location_info[] = sprintf(
// translators: %1$s is a code variable. %2$s is the name of a file.
__( 'Change the location by defining the %1$s constant in your %2$s file with a new path.', 'woocommerce' ),
'<code>WC_LOG_DIR</code>',
'<code>wp-config.php</code>'
);
$location_info[] = sprintf( $location_info[] = sprintf(
// translators: %s is an amount of computer disk space, e.g. 5 KB. // translators: %s is an amount of computer disk space, e.g. 5 KB.
__( 'Directory size: %s', 'woocommerce' ), __( 'Directory size: %s', 'woocommerce' ),

View File

@ -83,6 +83,15 @@ final class LoggingUtil {
return File::generate_hash( $file_id ); return File::generate_hash( $file_id );
} }
/**
* Get the directory for storing log files.
*
* @return string The full directory path, with trailing slash.
*/
public static function get_log_directory(): string {
return Settings::get_log_directory();
}
/** /**
* Calculate the size, in bytes, of the log directory. * Calculate the size, in bytes, of the log directory.
* *

View File

@ -5,6 +5,8 @@
* @package WooCommerce\Tests * @package WooCommerce\Tests
*/ */
use Automattic\WooCommerce\Utilities\LoggingUtil;
/** /**
* Class WC_Tests_Log_Handler_File * Class WC_Tests_Log_Handler_File
* @package WooCommerce\Tests\Log * @package WooCommerce\Tests\Log
@ -224,7 +226,7 @@ class WC_Tests_Log_Handler_File extends WC_Unit_Test_Case {
* @since 3.0.0 * @since 3.0.0
*/ */
public function test_get_log_file_path() { public function test_get_log_file_path() {
$log_dir = trailingslashit( WC_LOG_DIR ); $log_dir = LoggingUtil::get_log_directory();
$date_suffix = gmdate( 'Y-m-d', time() ); $date_suffix = gmdate( 'Y-m-d', time() );
$hash_name = sanitize_file_name( wp_hash( 'unit-tests' ) ); $hash_name = sanitize_file_name( wp_hash( 'unit-tests' ) );
$this->assertEquals( $log_dir . 'unit-tests-' . $date_suffix . '-' . $hash_name . '.log', WC_Log_Handler_File::get_log_file_path( 'unit-tests' ) ); $this->assertEquals( $log_dir . 'unit-tests-' . $date_suffix . '-' . $hash_name . '.log', WC_Log_Handler_File::get_log_file_path( 'unit-tests' ) );

View File

@ -6,6 +6,7 @@
*/ */
use Automattic\Jetpack\Constants; use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Admin\Logging\Settings;
/** /**
* Class WC_Tests_Logger * Class WC_Tests_Logger
@ -39,7 +40,7 @@ class WC_Tests_Logger extends WC_Unit_Test_Case {
* @return void * @return void
*/ */
private static function delete_all_log_files(): void { private static function delete_all_log_files(): void {
$files = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $files = glob( Settings::get_log_directory() . '*.log' );
foreach ( $files as $file ) { foreach ( $files as $file ) {
unlink( $file ); unlink( $file );
} }
@ -81,7 +82,7 @@ class WC_Tests_Logger extends WC_Unit_Test_Case {
* @since 2.4 * @since 2.4
*/ */
public function test_clear() { public function test_clear() {
$path = trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . 'unit-tests.log'; $path = Settings::get_log_directory() . 'unit-tests.log';
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_file_put_contents
file_put_contents( $path, 'Test file content.' ); file_put_contents( $path, 'Test file content.' );
$this->assertFileExists( $path ); $this->assertFileExists( $path );
@ -184,7 +185,7 @@ class WC_Tests_Logger extends WC_Unit_Test_Case {
*/ */
public function test_woocommerce_register_log_handlers_filter() { public function test_woocommerce_register_log_handlers_filter() {
add_filter( 'woocommerce_register_log_handlers', array( $this, 'return_assertion_handlers' ) ); add_filter( 'woocommerce_register_log_handlers', array( $this, 'return_assertion_handlers' ) );
$log = new WC_Logger( null, 'debug' ); $log = new WC_Logger();
$log->debug( 'debug' ); $log->debug( 'debug' );
$log->info( 'info' ); $log->info( 'info' );
$log->notice( 'notice' ); $log->notice( 'notice' );
@ -252,8 +253,9 @@ class WC_Tests_Logger extends WC_Unit_Test_Case {
->setMethods( array( 'handle' ) ) ->setMethods( array( 'handle' ) )
->getMock(); ->getMock();
$handler->expects( $this->never() )->method( 'handle' ); $handler->expects( $this->never() )->method( 'handle' );
new WC_Logger( array( $handler ) ); $logger = new WC_Logger( array( $handler ) );
$this->setExpectedIncorrectUsage( 'WC_Logger::__construct' ); $logger->debug( 'debug' );
$this->setExpectedIncorrectUsage( 'WC_Logger::get_handlers' );
} }
/** /**
@ -267,11 +269,15 @@ class WC_Tests_Logger extends WC_Unit_Test_Case {
* @return WC_Log_Handler[] array of mocked handlers. * @return WC_Log_Handler[] array of mocked handlers.
*/ */
public function return_assertion_handlers() { public function return_assertion_handlers() {
static $handler;
if ( ! $handler instanceof WC_Log_Handler_Interface ) {
$handler = $this $handler = $this
->getMockBuilder( 'WC_Log_Handler_Interface' ) ->getMockBuilder( 'WC_Log_Handler_Interface' )
->setMethods( array( 'handle' ) ) ->setMethods( array( 'handle' ) )
->getMock(); ->getMock();
$handler->expects( $this->exactly( 8 ) )->method( 'handle' ); $handler->expects( $this->exactly( 8 ) )->method( 'handle' );
}
return array( $handler ); return array( $handler );
} }

View File

@ -284,7 +284,7 @@ class WC_Tests_Core_Functions extends WC_Unit_Test_Case {
public function test_wc_get_log_file_path() { public function test_wc_get_log_file_path() {
$this->setExpectedDeprecated( 'wc_get_log_file_path' ); $this->setExpectedDeprecated( 'wc_get_log_file_path' );
$log_dir = trailingslashit( WC_LOG_DIR ); $log_dir = LoggingUtil::get_log_directory();
$hash_name = sanitize_file_name( wp_hash( 'unit-tests' ) ); $hash_name = sanitize_file_name( wp_hash( 'unit-tests' ) );
$file_id = LoggingUtil::generate_log_file_id( 'unit-tests', null, time() ); $file_id = LoggingUtil::generate_log_file_id( 'unit-tests', null, time() );
$hash = LoggingUtil::generate_log_file_hash( $file_id ); $hash = LoggingUtil::generate_log_file_hash( $file_id );

View File

@ -4,7 +4,7 @@ declare( strict_types = 1 );
namespace Automattic\WooCommerce\Tests\Internal\Admin\Logging\FileV2; namespace Automattic\WooCommerce\Tests\Internal\Admin\Logging\FileV2;
use Automattic\Jetpack\Constants; use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Admin\Logging\LogHandlerFileV2; use Automattic\WooCommerce\Internal\Admin\Logging\{ LogHandlerFileV2, Settings };
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\{ File, FileController }; use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\{ File, FileController };
use WC_Unit_Test_Case; use WC_Unit_Test_Case;
@ -66,7 +66,7 @@ class FileControllerTest extends WC_Unit_Test_Case {
* @return void * @return void
*/ */
private static function delete_all_log_files(): void { private static function delete_all_log_files(): void {
$files = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $files = glob( Settings::get_log_directory() . '*.log' );
foreach ( $files as $file ) { foreach ( $files as $file ) {
unlink( $file ); unlink( $file );
} }
@ -82,7 +82,7 @@ class FileControllerTest extends WC_Unit_Test_Case {
$result = $this->sut->write_to_file( $source, $content ); $result = $this->sut->write_to_file( $source, $content );
$this->assertTrue( $result ); $this->assertTrue( $result );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 1, $paths ); $this->assertCount( 1, $paths );
$path = reset( $paths ); $path = reset( $paths );
@ -108,12 +108,12 @@ class FileControllerTest extends WC_Unit_Test_Case {
'other2' => 'unit-testing-' . gmdate( 'Y-m-d', strtotime( '-2 days' ) ) . '-' . $hash . '.log', 'other2' => 'unit-testing-' . gmdate( 'Y-m-d', strtotime( '-2 days' ) ) . '-' . $hash . '.log',
); );
foreach ( $existing_files as $filename ) { foreach ( $existing_files as $filename ) {
$path = Constants::get_constant( 'WC_LOG_DIR' ) . $filename; $path = Settings::get_log_directory() . $filename;
$resource = fopen( $path, 'a' ); $resource = fopen( $path, 'a' );
fclose( $resource ); fclose( $resource );
} }
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 3, $paths ); $this->assertCount( 3, $paths );
$source = 'unit-testing'; $source = 'unit-testing';
@ -122,10 +122,10 @@ class FileControllerTest extends WC_Unit_Test_Case {
$result = $this->sut->write_to_file( $source, $content, $time ); $result = $this->sut->write_to_file( $source, $content, $time );
$this->assertTrue( $result ); $this->assertTrue( $result );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 3, $paths ); $this->assertCount( 3, $paths );
$target_path = Constants::get_constant( 'WC_LOG_DIR' ) . $existing_files['target']; $target_path = Settings::get_log_directory() . $existing_files['target'];
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
$actual_content = file_get_contents( $target_path ); $actual_content = file_get_contents( $target_path );
$this->assertEquals( $content . "\n", $actual_content ); $this->assertEquals( $content . "\n", $actual_content );
@ -136,7 +136,7 @@ class FileControllerTest extends WC_Unit_Test_Case {
*/ */
public function test_write_to_file_needs_rotation() { public function test_write_to_file_needs_rotation() {
$time = time(); $time = time();
$path = Constants::get_constant( 'WC_LOG_DIR' ) . 'unit-testing-' . gmdate( 'Y-m-d', $time ) . '-' . wp_hash( 'cheddar' ) . '.log'; $path = Settings::get_log_directory() . 'unit-testing-' . gmdate( 'Y-m-d', $time ) . '-' . wp_hash( 'cheddar' ) . '.log';
$resource = fopen( $path, 'a' ); $resource = fopen( $path, 'a' );
$existing_content = random_bytes( 200 ) . "\n"; $existing_content = random_bytes( 200 ) . "\n";
@ -144,7 +144,7 @@ class FileControllerTest extends WC_Unit_Test_Case {
fwrite( $resource, $existing_content ); fwrite( $resource, $existing_content );
fclose( $resource ); fclose( $resource );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 1, $paths ); $this->assertCount( 1, $paths );
// Set the file size low to induce log rotation. // Set the file size low to induce log rotation.
@ -157,7 +157,7 @@ class FileControllerTest extends WC_Unit_Test_Case {
$result = $this->sut->write_to_file( $source, $new_content, $time ); $result = $this->sut->write_to_file( $source, $new_content, $time );
$this->assertTrue( $result ); $this->assertTrue( $result );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 2, $paths ); $this->assertCount( 2, $paths );
foreach ( $paths as $path ) { foreach ( $paths as $path ) {
@ -248,7 +248,7 @@ class FileControllerTest extends WC_Unit_Test_Case {
public function test_get_files_by_id() { public function test_get_files_by_id() {
// Create a log file with an accompanying rotation. // Create a log file with an accompanying rotation.
$this->handler->handle( time(), 'debug', '1', array( 'source' => 'unit-testing1' ) ); $this->handler->handle( time(), 'debug', '1', array( 'source' => 'unit-testing1' ) );
$file1_path = glob( Constants::get_constant( 'WC_LOG_DIR' ) . '*.log' ); $file1_path = glob( Settings::get_log_directory() . '*.log' );
$file1 = new File( reset( $file1_path ) ); $file1 = new File( reset( $file1_path ) );
$file1->rotate(); $file1->rotate();
$this->handler->handle( time(), 'debug', '1', array( 'source' => 'unit-testing1' ) ); $this->handler->handle( time(), 'debug', '1', array( 'source' => 'unit-testing1' ) );
@ -297,8 +297,8 @@ class FileControllerTest extends WC_Unit_Test_Case {
* @testdox The get_file_rotations method should return an associative array of File instances. * @testdox The get_file_rotations method should return an associative array of File instances.
*/ */
public function test_get_file_rotations() { public function test_get_file_rotations() {
$target_file_path = Constants::get_constant( 'WC_LOG_DIR' ) . 'test-file.log'; $target_file_path = Settings::get_log_directory() . 'test-file.log';
$other_file_path = Constants::get_constant( 'WC_LOG_DIR' ) . 'other-test-file.log'; $other_file_path = Settings::get_log_directory() . 'other-test-file.log';
$oldest_file = new File( $target_file_path ); $oldest_file = new File( $target_file_path );
$oldest_file->write( 'test' ); $oldest_file->write( 'test' );
@ -315,7 +315,7 @@ class FileControllerTest extends WC_Unit_Test_Case {
$other_file = new File( $other_file_path ); $other_file = new File( $other_file_path );
$other_file->write( 'test' ); $other_file->write( 'test' );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 4, $paths ); $this->assertCount( 4, $paths );
$file_id = 'test-file'; $file_id = 'test-file';
@ -415,17 +415,17 @@ class FileControllerTest extends WC_Unit_Test_Case {
*/ */
public function test_get_log_directory_size(): void { public function test_get_log_directory_size(): void {
// Non-log files that should be in the log directory. // Non-log files that should be in the log directory.
$htaccess = wp_filesize( Constants::get_constant( 'WC_LOG_DIR' ) . '.htaccess' ); $htaccess = wp_filesize( Settings::get_log_directory() . '.htaccess' );
$index = wp_filesize( Constants::get_constant( 'WC_LOG_DIR' ) . 'index.html' ); $index = wp_filesize( Settings::get_log_directory() . 'index.html' );
$path = Constants::get_constant( 'WC_LOG_DIR' ) . 'unit-testing-1.log'; $path = Settings::get_log_directory() . 'unit-testing-1.log';
$resource = fopen( $path, 'a' ); $resource = fopen( $path, 'a' );
$existing_content = random_bytes( 200 ); $existing_content = random_bytes( 200 );
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite
fwrite( $resource, $existing_content ); fwrite( $resource, $existing_content );
fclose( $resource ); fclose( $resource );
$path = Constants::get_constant( 'WC_LOG_DIR' ) . 'unit-testing-2.log'; $path = Settings::get_log_directory() . 'unit-testing-2.log';
$resource = fopen( $path, 'a' ); $resource = fopen( $path, 'a' );
$existing_content = random_bytes( 300 ); $existing_content = random_bytes( 300 );
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_read_fwrite

View File

@ -5,6 +5,7 @@ namespace Automattic\WooCommerce\Tests\Internal\Admin\Logging\FileV2;
use Automattic\Jetpack\Constants; use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\File; use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\File;
use Automattic\WooCommerce\Internal\Admin\Logging\Settings;
use WC_Unit_Test_Case; use WC_Unit_Test_Case;
// phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fopen, WordPress.WP.AlternativeFunctions.file_system_read_fclose // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_read_fopen, WordPress.WP.AlternativeFunctions.file_system_read_fclose
@ -20,7 +21,7 @@ class FileTest extends WC_Unit_Test_Case {
*/ */
public function tearDown(): void { public function tearDown(): void {
// Delete all created files and directories. // Delete all created files and directories.
$files = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*' ); $files = glob( Settings::get_log_directory() . '*' );
foreach ( $files as $file ) { foreach ( $files as $file ) {
if ( is_dir( $file ) ) { if ( is_dir( $file ) ) {
rmdir( $file ); rmdir( $file );
@ -41,7 +42,7 @@ class FileTest extends WC_Unit_Test_Case {
$hash = wp_hash( 'cheddar' ); $hash = wp_hash( 'cheddar' );
yield 'standard filename, no rotation' => array( yield 'standard filename, no rotation' => array(
Constants::get_constant( 'WC_LOG_DIR' ) . 'test-Source_1-1-2023-10-23-' . $hash . '.log', Settings::get_log_directory() . 'test-Source_1-1-2023-10-23-' . $hash . '.log',
array( array(
'basename' => 'test-Source_1-1-2023-10-23-' . $hash . '.log', 'basename' => 'test-Source_1-1-2023-10-23-' . $hash . '.log',
'source' => 'test-Source_1-1', 'source' => 'test-Source_1-1',
@ -52,7 +53,7 @@ class FileTest extends WC_Unit_Test_Case {
), ),
); );
yield 'standard filename, with rotation' => array( yield 'standard filename, with rotation' => array(
Constants::get_constant( 'WC_LOG_DIR' ) . 'test-Source_1-1.3-2023-10-23-' . $hash . '.log', Settings::get_log_directory() . 'test-Source_1-1.3-2023-10-23-' . $hash . '.log',
array( array(
'basename' => 'test-Source_1-1.3-2023-10-23-' . $hash . '.log', 'basename' => 'test-Source_1-1.3-2023-10-23-' . $hash . '.log',
'source' => 'test-Source_1-1', 'source' => 'test-Source_1-1',
@ -63,7 +64,7 @@ class FileTest extends WC_Unit_Test_Case {
), ),
); );
yield 'non-standard filename, no rotation' => array( yield 'non-standard filename, no rotation' => array(
Constants::get_constant( 'WC_LOG_DIR' ) . 'test-Source_1-1-' . $hash . '.log', Settings::get_log_directory() . 'test-Source_1-1-' . $hash . '.log',
array( array(
'basename' => 'test-Source_1-1-' . $hash . '.log', 'basename' => 'test-Source_1-1-' . $hash . '.log',
'source' => 'test-Source_1-1-' . $hash, 'source' => 'test-Source_1-1-' . $hash,
@ -74,7 +75,7 @@ class FileTest extends WC_Unit_Test_Case {
), ),
); );
yield 'non-standard filename, with rotation' => array( yield 'non-standard filename, with rotation' => array(
Constants::get_constant( 'WC_LOG_DIR' ) . 'test-Source_1-1-' . $hash . '.5.log', Settings::get_log_directory() . 'test-Source_1-1-' . $hash . '.5.log',
array( array(
'basename' => 'test-Source_1-1-' . $hash . '.5.log', 'basename' => 'test-Source_1-1-' . $hash . '.5.log',
'source' => 'test-Source_1-1-' . $hash, 'source' => 'test-Source_1-1-' . $hash,
@ -145,7 +146,7 @@ class FileTest extends WC_Unit_Test_Case {
* and whether the file is readable and writable. * and whether the file is readable and writable.
*/ */
public function test_file_readable_writable() { public function test_file_readable_writable() {
$filename = Constants::get_constant( 'WC_LOG_DIR' ) . 'test-file.log'; $filename = Settings::get_log_directory() . 'test-file.log';
$file = new File( $filename ); $file = new File( $filename );
$this->assertFalse( $file->is_readable() ); $this->assertFalse( $file->is_readable() );
@ -162,7 +163,7 @@ class FileTest extends WC_Unit_Test_Case {
* @testdox Check that writing to a file that doesn't exist yet creates that file. * @testdox Check that writing to a file that doesn't exist yet creates that file.
*/ */
public function test_write_new_file() { public function test_write_new_file() {
$filename = Constants::get_constant( 'WC_LOG_DIR' ) . 'test-file.log'; $filename = Settings::get_log_directory() . 'test-file.log';
$content = "You can't take the sky from me"; $content = "You can't take the sky from me";
$file = new File( $filename ); $file = new File( $filename );
@ -183,7 +184,7 @@ class FileTest extends WC_Unit_Test_Case {
* @testdox Check that writing to a file that already exists appends the new content to the existing content. * @testdox Check that writing to a file that already exists appends the new content to the existing content.
*/ */
public function test_write_existing_file() { public function test_write_existing_file() {
$filename = Constants::get_constant( 'WC_LOG_DIR' ) . 'test-file.log'; $filename = Settings::get_log_directory() . 'test-file.log';
$content1 = 'Shiny'; $content1 = 'Shiny';
$content2 = 'Scratch'; $content2 = 'Scratch';
@ -212,8 +213,8 @@ class FileTest extends WC_Unit_Test_Case {
* @testdox Check that the has_standard_filename method correctly identifies standard and nonstandard filenames. * @testdox Check that the has_standard_filename method correctly identifies standard and nonstandard filenames.
*/ */
public function test_has_standard_filename() { public function test_has_standard_filename() {
$standard = Constants::get_constant( 'WC_LOG_DIR' ) . 'test-Source_1-1.3-2023-10-23-' . wp_hash( 'cheddar' ) . '.log'; $standard = Settings::get_log_directory() . 'test-Source_1-1.3-2023-10-23-' . wp_hash( 'cheddar' ) . '.log';
$nonstandard = Constants::get_constant( 'WC_LOG_DIR' ) . 'test-Source_1-1-' . wp_hash( 'cheddar' ) . '.5.log'; $nonstandard = Settings::get_log_directory() . 'test-Source_1-1-' . wp_hash( 'cheddar' ) . '.5.log';
$file = new File( $standard ); $file = new File( $standard );
$this->assertTrue( $file->has_standard_filename() ); $this->assertTrue( $file->has_standard_filename() );
@ -226,7 +227,7 @@ class FileTest extends WC_Unit_Test_Case {
* @testdox Check that get_stream returns a PHP resource representation of the file. * @testdox Check that get_stream returns a PHP resource representation of the file.
*/ */
public function test_get_and_close_stream() { public function test_get_and_close_stream() {
$filename = Constants::get_constant( 'WC_LOG_DIR' ) . 'test-file.log'; $filename = Settings::get_log_directory() . 'test-file.log';
$file = new File( $filename ); $file = new File( $filename );
// File doesn't exist yet. // File doesn't exist yet.
@ -248,7 +249,7 @@ class FileTest extends WC_Unit_Test_Case {
* @testdox Check that rotating a file without a rotation market will rename it to include a rotation marker. * @testdox Check that rotating a file without a rotation market will rename it to include a rotation marker.
*/ */
public function test_rotate_current_file() { public function test_rotate_current_file() {
$filename = Constants::get_constant( 'WC_LOG_DIR' ) . 'test-file.log'; $filename = Settings::get_log_directory() . 'test-file.log';
$file = new File( $filename ); $file = new File( $filename );
$file->write( 'test' ); $file->write( 'test' );
@ -272,7 +273,7 @@ class FileTest extends WC_Unit_Test_Case {
* @testdox Check that rotating a file with a rotation market will rename it to increment the rotation marker. * @testdox Check that rotating a file with a rotation market will rename it to increment the rotation marker.
*/ */
public function test_rotate_older_file() { public function test_rotate_older_file() {
$filename = Constants::get_constant( 'WC_LOG_DIR' ) . 'test-file.2.log'; $filename = Settings::get_log_directory() . 'test-file.2.log';
$file = new File( $filename ); $file = new File( $filename );
$file->write( 'test' ); $file->write( 'test' );
@ -296,19 +297,19 @@ class FileTest extends WC_Unit_Test_Case {
* @testdox The delete method should delete the file from the filesystem. * @testdox The delete method should delete the file from the filesystem.
*/ */
public function test_delete_existing_file() { public function test_delete_existing_file() {
$filename = Constants::get_constant( 'WC_LOG_DIR' ) . 'test-file.log'; $filename = Settings::get_log_directory() . 'test-file.log';
$file = new File( $filename ); $file = new File( $filename );
$file->write( 'test' ); $file->write( 'test' );
$this->assertTrue( $file->is_readable() ); $this->assertTrue( $file->is_readable() );
$files = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $files = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 1, $files ); $this->assertCount( 1, $files );
$result = $file->delete(); $result = $file->delete();
$this->assertTrue( $result ); $this->assertTrue( $result );
$this->assertFalse( $file->is_readable() ); $this->assertFalse( $file->is_readable() );
$files = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $files = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 0, $files ); $this->assertCount( 0, $files );
} }
} }

View File

@ -4,7 +4,7 @@ declare( strict_types = 1 );
namespace Automattic\WooCommerce\Tests\Internal\Admin\Logging; namespace Automattic\WooCommerce\Tests\Internal\Admin\Logging;
use Automattic\Jetpack\Constants; use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Admin\Logging\LogHandlerFileV2; use Automattic\WooCommerce\Internal\Admin\Logging\{ LogHandlerFileV2, Settings };
use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\File; use Automattic\WooCommerce\Internal\Admin\Logging\FileV2\File;
use WC_Unit_Test_Case; use WC_Unit_Test_Case;
@ -56,7 +56,7 @@ class LogHandlerFileV2Test extends WC_Unit_Test_Case {
* @return void * @return void
*/ */
private static function delete_all_log_files(): void { private static function delete_all_log_files(): void {
$files = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $files = glob( Settings::get_log_directory() . '*.log' );
foreach ( $files as $file ) { foreach ( $files as $file ) {
unlink( $file ); unlink( $file );
} }
@ -110,7 +110,7 @@ class LogHandlerFileV2Test extends WC_Unit_Test_Case {
$input['context'] $input['context']
); );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 1, $paths ); $this->assertCount( 1, $paths );
$parsed = File::parse_path( reset( $paths ) ); $parsed = File::parse_path( reset( $paths ) );
@ -138,7 +138,7 @@ MESSAGE;
array() array()
); );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 1, $paths ); $this->assertCount( 1, $paths );
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
@ -239,7 +239,7 @@ MESSAGE;
$input, $input,
); );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 1, $paths ); $this->assertCount( 1, $paths );
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
@ -258,16 +258,16 @@ MESSAGE;
$this->sut->handle( strtotime( '-4 days' ), 'debug', 'duck', array( 'source' => 'duck' ) ); $this->sut->handle( strtotime( '-4 days' ), 'debug', 'duck', array( 'source' => 'duck' ) );
$this->sut->handle( time(), 'debug', 'goose', array( 'source' => 'goose' ) ); $this->sut->handle( time(), 'debug', 'goose', array( 'source' => 'goose' ) );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 4, $paths ); $this->assertCount( 4, $paths );
$result = $this->sut->clear( 'duck' ); $result = $this->sut->clear( 'duck' );
$this->assertEquals( 3, $result ); $this->assertEquals( 3, $result );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 2, $paths ); // New log gets created when old logs are deleted! $this->assertCount( 2, $paths ); // New log gets created when old logs are deleted!
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . 'wc_logger*.log' ); $paths = glob( Settings::get_log_directory() . 'wc_logger*.log' );
$this->assertCount( 1, $paths ); $this->assertCount( 1, $paths );
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
@ -290,16 +290,16 @@ MESSAGE;
$this->sut->handle( $current_time, 'debug', 'new!', array( 'source' => 'source5' ) ); $this->sut->handle( $current_time, 'debug', 'new!', array( 'source' => 'source5' ) );
$this->sut->handle( $current_time, 'debug', 'new!', array( 'source' => 'source6' ) ); $this->sut->handle( $current_time, 'debug', 'new!', array( 'source' => 'source6' ) );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 6, $paths ); $this->assertCount( 6, $paths );
$result = $this->sut->delete_logs_before_timestamp( strtotime( '-3 days' ) ); $result = $this->sut->delete_logs_before_timestamp( strtotime( '-3 days' ) );
$this->assertEquals( 4, $result ); $this->assertEquals( 4, $result );
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $paths = glob( Settings::get_log_directory() . '*.log' );
$this->assertCount( 3, $paths ); // New log gets created when old logs are deleted! $this->assertCount( 3, $paths ); // New log gets created when old logs are deleted!
$paths = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . 'wc_logger*.log' ); $paths = glob( Settings::get_log_directory() . 'wc_logger*.log' );
$this->assertCount( 1, $paths ); $this->assertCount( 1, $paths );
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents

View File

@ -73,12 +73,76 @@ class SettingsTest extends WC_Unit_Test_Case {
* @return void * @return void
*/ */
private static function delete_all_log_files(): void { private static function delete_all_log_files(): void {
$files = glob( trailingslashit( realpath( Constants::get_constant( 'WC_LOG_DIR' ) ) ) . '*.log' ); $upload_dir = wp_upload_dir( null, false );
$log_directory = $upload_dir['basedir'] . '/wc-logs/';
$files = glob( $log_directory . '*.log' );
foreach ( $files as $file ) { foreach ( $files as $file ) {
unlink( $file ); unlink( $file );
} }
} }
/**
* @testdox Check that the get_log_directory method returns the correct directory path.
*/
public function test_get_log_directory_path(): void {
Constants::set_constant( 'WC_LOG_DIR_CUSTOM', true );
Constants::set_constant( 'WC_LOG_DIR', '/a/test/path/constant' );
$actual = Settings::get_log_directory();
$this->assertEquals( '/a/test/path/constant/', $actual );
Constants::clear_constants();
$callback = fn() => '/a/test/path/filter';
add_filter( 'woocommerce_log_directory', $callback );
$actual = Settings::get_log_directory();
$this->assertEquals( '/a/test/path/filter/', $actual );
remove_filter( 'woocommerce_log_directory', $callback );
$this->register_legacy_proxy_function_mocks(
array(
'wp_upload_dir' => fn() => array( 'basedir' => '/a/test/path' ),
)
);
$actual = Settings::get_log_directory();
$this->assertEquals( '/a/test/path/wc-logs/', $actual );
$this->reset_legacy_proxy_mocks();
}
/**
* @testdox Check that the get_log_directory method creates the directory if it doesn't exist yet.
*/
public function test_get_log_directory_creation() {
$upload_dir = wp_upload_dir();
$path = $upload_dir['basedir'] . '/wc-logs-test/';
$this->assertFalse( wp_is_writable( $path ) );
$callback = fn() => $path;
add_filter( 'woocommerce_log_directory', $callback );
$actual_path = Settings::get_log_directory();
$this->assertEquals( $path, $actual_path );
$this->assertTrue( wp_is_writable( $actual_path ) );
remove_filter( 'woocommerce_log_directory', $callback );
// The GLOB_BRACE flag is not available in some environments.
$files = array_merge(
glob( $path . '*' ),
glob( $path . '.[!.,!..]*' )
);
$this->assertCount( 2, $files );
$this->assertContains( $path . '.htaccess', $files );
$this->assertContains( $path . 'index.html', $files );
foreach ( $files as $file ) {
unlink( $file );
}
rmdir( $path );
}
/** /**
* @testdox Check that log entries can be recorded while logging is enabled. * @testdox Check that log entries can be recorded while logging is enabled.
*/ */