Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.94% covered (success)
93.94%
31 / 33
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Logger
93.94% covered (success)
93.94%
31 / 33
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
95.83% covered (success)
95.83%
23 / 24
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     * @param ?Logger_Settings_Interface $settings The loglevel, plugin name, slug, and basename.
49     *
50     * @return Logger
51     * @see Logger_Settings
52     * @see Plugins
53     */
54    public static function instance( ?Logger_Settings_Interface $settings = null ): LoggerInterface {
55
56        if ( ! isset( self::$instance ) ) {
57
58            // Zero-config.
59            $settings = $settings ?? new class() implements Logger_Settings_Interface {
60                use Logger_Settings_Trait;
61            };
62
63            // TODO: This is wrong, the directory must be assumed to contain files and be kept private.
64            if ( 'none' === $settings->get_log_level() ) {
65                return new NullLogger();
66            }
67
68            $logger = new self( $settings );
69
70            self::$instance = $logger;
71
72            // Add the hooks.
73            new Plugin_Logger_Actions( self::$instance, $settings, self::$instance );
74        }
75
76        return self::$instance;
77    }
78
79    /**
80     * If log level is 'none', use NullLogger.
81     * If Settings is WooCommerce_Logger_Settings_Interface use WC_Logger.
82     * 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        if ( $settings instanceof WooCommerce_Logger_Settings_Interface
89            && in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins', array() ) ), true ) ) {
90            // Does not use `is_plugin_active()` here because "Call to undefined function" error (maybe an admin function).
91
92            $logger = new WC_PSR_Logger( $settings );
93
94            // Add context to WooCommerce logs.
95            $wc_log_handler = new Log_Context_Handler( $settings );
96            add_filter( 'woocommerce_format_log_entry', array( $wc_log_handler, 'add_context_to_logs' ), 10, 2 );
97
98            // TODO: What's the log file name when it's a wc-log?
99
100        } else {
101
102            $log_directory       = wp_normalize_path( WP_CONTENT_DIR . '/uploads/logs' );
103            $log_level_threshold = $settings->get_log_level();
104
105            /**
106             * Add the `{context}` template string,
107             * then provide `'appendContext' => false` to Klogger (since it is already takes care of).
108             *
109             * @see \Katzgrau\KLogger\Logger::formatMessage()
110             */
111            $log_format = "{date} {level} {message}\n{context}";
112
113            /**
114             * `c` is chosen to match WooCommerce's choice.
115             *
116             * @see WC_Log_Handler::format_time()
117             */
118            $options = array(
119                'extension'     => 'log',
120                'prefix'        => "{$settings->get_plugin_slug()}-",
121                'dateFormat'    => 'c',
122                'logFormat'     => $log_format,
123                'appendContext' => false,
124            );
125
126            $logger = new KLogger( $log_directory, $log_level_threshold, $options );
127
128            // Make the logs directory inaccessible to the public.
129            $private_uploads_settings = new class( $settings ) implements Private_Uploads_Settings_Interface {
130                use Private_Uploads_Settings_Trait;
131
132                /**
133                 * The settings provided for the logger. We need the plugin slug as a uid for the private uploads instance.
134                 *
135                 * @var Logger_Settings_Interface
136                 */
137                protected Logger_Settings_Interface $logger_settings;
138
139                /**
140                 * Constructor.
141                 *
142                 * @param Logger_Settings_Interface $logger_settings The plugin logger settings, whose plugin slug we need.
143                 */
144                public function __construct( Logger_Settings_Interface $logger_settings ) {
145                    $this->logger_settings = $logger_settings;
146                }
147
148                /**
149                 * This is used as a unique id for the Private Uploads instance.
150                 */
151                public function get_plugin_slug(): string {
152                    return $this->logger_settings->get_plugin_slug() . '_logger';
153                }
154
155                /**
156                 * Use wp-content/uploads/logs as the logs directory.
157                 */
158                public function get_uploads_subdirectory_name(): string {
159                    return 'logs';
160                }
161            };
162
163            // Don't use the Private_Uploads singleton in case the parent plugin also needs it.
164            $private_uploads = new Private_Uploads( $private_uploads_settings, $this );
165            new BH_WP_Private_Uploads_Hooks( $private_uploads, $private_uploads_settings, $this );
166
167        }
168
169        parent::__construct( $settings, $logger );
170    }
171}