Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.44% covered (success)
94.44%
34 / 36
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Logger
94.44% covered (success)
94.44%
34 / 36
0.00% covered (danger)
0.00%
0 / 2
6.01
0.00% covered (danger)
0.00%
0 / 1
 instance
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
3.01
 __construct
96.30% covered (success)
96.30%
26 / 27
0.00% covered (danger)
0.00%
0 / 1
3
1<?php
2/**
3 * Instantiate the logger for your plugin.
4 *
5 * `$logger = \BrianHenryIE\WP_Logger\Logger::instance()`
6 * better:
7 * `$logger = \BrianHenryIE\WP_Logger\Logger::instance( $settings )`
8 *
9 * @see \BrianHenryIE\WP_Logger\Logger_Settings_Interface
10 * @see \BrianHenryIE\WP_Logger\Logger_Settings_Trait
11 * @see \BrianHenryIE\WP_Logger\WooCommerce_Logger_Settings_Interface
12 *
13 * @package brianhenryie/bh-wp-logger
14 */
15
16namespace BrianHenryIE\WP_Logger;
17
18use BrianHenryIE\WC_Logger\Log_Context_Handler;
19use BrianHenryIE\WC_Logger\WC_PSR_Logger;
20use BrianHenryIE\WP_Logger\API\BH_WP_PSR_Logger;
21use BrianHenryIE\WP_Logger\WP_Includes\Plugin_Logger_Actions;
22use BrianHenryIE\WP_Private_Uploads\BH_WP_Private_Uploads_Hooks;
23use BrianHenryIE\WP_Private_Uploads\Private_Uploads_Settings_Interface;
24use BrianHenryIE\WP_Private_Uploads\Private_Uploads_Settings_Trait;
25use BrianHenryIE\WP_Private_Uploads\Private_Uploads;
26use Katzgrau\KLogger\Logger as KLogger;
27use Psr\Log\LoggerInterface;
28use Psr\Log\NullLogger;
29
30/**
31 * Wraps parent class in a singleton so it only needs to be configured once.
32 */
33class Logger extends BH_WP_PSR_Logger implements API_Interface, LoggerInterface {
34
35    /**
36     * Singleton.
37     *
38     * @var Logger
39     */
40    protected static Logger $instance;
41
42    /**
43     * Initialize the logger and store the instance in the singleton variable.
44     * Settings are used when provided, inferred when null.
45     * Ideally settings should be provided the first time the logger is instantiated, then they do not need
46     * to be provided when accessing the singleton later on.
47     *
48     * @see Logger_Settings
49     * @see Plugins
50     *
51     * @param ?Logger_Settings_Interface $settings The loglevel, plugin name, slug, and basename.
52     *
53     * @return LoggerInterface Ideally a {@see \BrianHenryIE\WP_Logger\Logger} but `NullLogger` sometimes.
54     */
55    public static function instance( ?Logger_Settings_Interface $settings = null ): LoggerInterface {
56
57        if ( ! isset( self::$instance ) ) {
58
59            // Zero-config.
60            $settings ??= new class() implements Logger_Settings_Interface {
61                use Logger_Settings_Trait;
62            };
63
64            // TODO: This is wrong, the directory must be assumed to contain files and be kept private.
65            if ( 'none' === $settings->get_log_level() ) {
66                return new NullLogger();
67            }
68
69            $logger = new self( $settings );
70
71            self::$instance = $logger;
72
73            // Add the hooks.
74            new Plugin_Logger_Actions( self::$instance, $settings, self::$instance );
75        }
76
77        return self::$instance;
78    }
79
80    /**
81     * If log level is 'none', use NullLogger.
82     * If Settings is WooCommerce_Logger_Settings_Interface use WC_Logger, otherwise use KLogger.
83     *
84     * @param Logger_Settings_Interface $settings Basic settings required for the logger.
85     */
86    public function __construct( Logger_Settings_Interface $settings ) {
87
88        /**
89         * We are not using {@see is_plugin_active()} here because "Call to undefined function" error (it may be an admin function).
90         *
91         * @param string $plugin_basename The main plugin file's path relative to WP_PLUGIN_DIR.
92         */
93        $is_plugin_active = function ( string $plugin_basename ): bool {
94            /** @var array<int,string> $active_plugins */
95            $active_plugins = apply_filters( 'active_plugins', get_option( 'active_plugins', array() ) );
96            return in_array( $plugin_basename, $active_plugins, true );
97        };
98
99        if ( $settings instanceof WooCommerce_Logger_Settings_Interface && $is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
100
101            $logger = new WC_PSR_Logger( $settings );
102
103            // Add context to WooCommerce logs.
104            $wc_log_handler = new Log_Context_Handler( $settings );
105            add_filter( 'woocommerce_format_log_entry', array( $wc_log_handler, 'add_context_to_logs' ), 10, 2 );
106
107            // TODO: What's the log file name when it's a wc-log?
108
109        } else {
110
111            $log_directory       = wp_normalize_path( WP_CONTENT_DIR . '/uploads/logs' );
112            $log_level_threshold = $settings->get_log_level();
113
114            /**
115             * Add the `{context}` template string,
116             * then provide `'appendContext' => false` to Klogger (since it is already takes care of).
117             *
118             * @see KLogger::formatMessage()
119             */
120            $log_format = "{date} {level} {message}\n{context}";
121
122            /**
123             * `c` is chosen to match WooCommerce's choice.
124             *
125             * @see WC_Log_Handler::format_time()
126             */
127            $options = array(
128                'extension'     => 'log',
129                'prefix'        => "{$settings->get_plugin_slug()}-",
130                'dateFormat'    => 'c',
131                'logFormat'     => $log_format,
132                'appendContext' => false,
133            );
134
135            $logger = new KLogger( $log_directory, $log_level_threshold, $options );
136
137            // Make the logs directory inaccessible to the public.
138            $private_uploads_settings = new class( $settings ) implements Private_Uploads_Settings_Interface {
139                use Private_Uploads_Settings_Trait;
140
141                /**
142                 * Constructor.
143                 *
144                 * @param Logger_Settings_Interface $logger_settings The plugin logger settings, whose plugin slug we need.
145                 */
146                public function __construct(
147                    /**
148                     * The settings provided for the logger. We need the plugin slug as a uid for the private uploads instance.
149                     */
150                    protected Logger_Settings_Interface $logger_settings
151                ) {
152                }
153
154                /**
155                 * This is used as a unique id for the Private Uploads instance.
156                 */
157                public function get_plugin_slug(): string {
158                    return $this->logger_settings->get_plugin_slug() . '_logger';
159                }
160
161                /**
162                 * Use wp-content/uploads/logs as the logs directory.
163                 */
164                public function get_uploads_subdirectory_name(): string {
165                    return 'logs';
166                }
167            };
168
169            // Don't use the Private_Uploads singleton in case the parent plugin also needs it.
170            $private_uploads = new Private_Uploads( $private_uploads_settings, $this );
171            new BH_WP_Private_Uploads_Hooks( $private_uploads, $private_uploads_settings, $this );
172
173        }
174
175        parent::__construct( $settings, $logger );
176    }
177}