Completed
Push — psr3-logger ( bc9e3f )
by Armando
06:24
created

TelegramLog::initErrorLog()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 21

Duplication

Lines 21
Ratio 100 %

Importance

Changes 0
Metric Value
dl 21
loc 21
rs 8.9617
c 0
b 0
f 0
cc 6
nc 9
nop 1
1
<?php
2
/**
3
 * This file is part of the TelegramBot package.
4
 *
5
 * (c) Avtandil Kikabidze aka LONGMAN <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Longman\TelegramBot;
12
13
use Exception;
14
use Longman\TelegramBot\Exception\TelegramLogException;
15
use Monolog\Formatter\LineFormatter;
16
use Monolog\Handler\StreamHandler;
17
use Monolog\Logger;
18
use Psr\Log\LoggerInterface;
19
use Psr\Log\NullLogger;
20
21
/**
22
 * Class TelegramLog
23
 *
24
 * @todo Clean out all deprecated code in the near future!
25
 *
26
 * @method static void emergency(string $message, array $context = [])
27
 * @method static void alert(string $message, array $context = [])
28
 * @method static void critical(string $message, array $context = [])
29
 * @method static void error(string $message, array $context = [])
30
 * @method static void warning(string $message, array $context = [])
31
 * @method static void notice(string $message, array $context = [])
32
 * @method static void info(string $message, array $context = [])
33
 * @method static void debug(string $message, array $context = [])
34
 * @method static void update(string $message, array $context = [])
35
 */
36
class TelegramLog
37
{
38
    /**
39
     * Logger instance
40
     *
41
     * @var LoggerInterface|Logger
42
     */
43
    protected static $logger;
44
45
    /**
46
     * Logger instance for update
47
     *
48
     * @var LoggerInterface|Logger
49
     */
50
    protected static $update_logger;
51
52
    /**
53
     * Path for error log
54
     *
55
     * @var string
56
     * @deprecated
57
     */
58
    protected static $error_log_path;
59
60
    /**
61
     * Path for debug log
62
     *
63
     * @var string
64
     * @deprecated
65
     */
66
    protected static $debug_log_path;
67
68
    /**
69
     * Path for update log
70
     *
71
     * @var string
72
     * @deprecated
73
     */
74
    protected static $update_log_path;
75
76
    /**
77
     * Temporary stream handle for debug log
78
     *
79
     * @var resource|null
80
     */
81
    protected static $debug_log_temp_stream_handle;
82
83
    /**
84
     * Initialise Logger instance, optionally passing an existing one.
85
     *
86
     * @param LoggerInterface|null $logger
87
     * @param LoggerInterface|null $update_logger
88
     */
89
    public static function initialize(LoggerInterface $logger = null, LoggerInterface $update_logger = null)
90
    {
91
        // Clearly deprecated code still being executed.
92
        if ($logger === null) {
93
            (defined('PHPUNIT_TESTSUITE') && PHPUNIT_TESTSUITE) || trigger_error('A PSR-3 compatible LoggerInterface object must be provided. Initialise with a preconfigured logger instance.', E_USER_DEPRECATED);
94
            $logger = new Logger('bot_log');
95
        } elseif ($logger instanceof Logger) {
96
            foreach ($logger->getHandlers() as $handler) {
97
                if (method_exists($handler, 'getLevel') && $handler->getLevel() === Logger::ERROR) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Monolog\Handler\HandlerInterface as the method getLevel() does only exist in the following implementations of said interface: Monolog\Handler\AbstractHandler, Monolog\Handler\AbstractProcessingHandler, Monolog\Handler\AbstractSyslogHandler, Monolog\Handler\AmqpHandler, Monolog\Handler\BrowserConsoleHandler, Monolog\Handler\BufferHandler, Monolog\Handler\ChromePHPHandler, Monolog\Handler\CouchDBHandler, Monolog\Handler\CubeHandler, Monolog\Handler\DeduplicationHandler, Monolog\Handler\DoctrineCouchDBHandler, Monolog\Handler\DynamoDbHandler, Monolog\Handler\ElasticSearchHandler, Monolog\Handler\ErrorLogHandler, Monolog\Handler\ExceptionTestHandler, Monolog\Handler\FilterHandler, Monolog\Handler\FingersCrossedHandler, Monolog\Handler\FirePHPHandler, Monolog\Handler\FleepHookHandler, Monolog\Handler\FlowdockHandler, Monolog\Handler\GelfHandler, Monolog\Handler\GroupHandler, Monolog\Handler\HipChatHandler, Monolog\Handler\IFTTTHandler, Monolog\Handler\InsightOpsHandler, Monolog\Handler\LogEntriesHandler, Monolog\Handler\LogglyHandler, Monolog\Handler\MailHandler, Monolog\Handler\MandrillHandler, Monolog\Handler\MongoDBHandler, Monolog\Handler\NativeMailerHandler, Monolog\Handler\NewRelicHandler, Monolog\Handler\NullHandler, Monolog\Handler\PHPConsoleHandler, Monolog\Handler\PsrHandler, Monolog\Handler\PushoverHandler, Monolog\Handler\RavenHandler, Monolog\Handler\RedisHandler, Monolog\Handler\RollbarHandler, Monolog\Handler\RotatingFileHandler, Monolog\Handler\SamplingHandler, Monolog\Handler\SlackHandler, Monolog\Handler\SlackWebhookHandler, Monolog\Handler\SlackbotHandler, Monolog\Handler\SocketHandler, Monolog\Handler\StreamHandler, Monolog\Handler\StubNewRelicHandler, Monolog\Handler\StubNewR...HandlerWithoutExtension, Monolog\Handler\SwiftMailerHandler, Monolog\Handler\SyslogHandler, Monolog\Handler\SyslogUdpHandler, Monolog\Handler\TestChromePHPHandler, Monolog\Handler\TestFirePHPHandler, Monolog\Handler\TestHandler, Monolog\Handler\WhatFailureGroupHandler, Monolog\Handler\ZendMonitorHandler.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
98
                    self::$error_log_path = 'true';
0 ignored issues
show
Deprecated Code introduced by
The property Longman\TelegramBot\TelegramLog::$error_log_path has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
99
                }
100
                if (method_exists($handler, 'getLevel') && $handler->getLevel() === Logger::DEBUG) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Monolog\Handler\HandlerInterface as the method getLevel() does only exist in the following implementations of said interface: Monolog\Handler\AbstractHandler, Monolog\Handler\AbstractProcessingHandler, Monolog\Handler\AbstractSyslogHandler, Monolog\Handler\AmqpHandler, Monolog\Handler\BrowserConsoleHandler, Monolog\Handler\BufferHandler, Monolog\Handler\ChromePHPHandler, Monolog\Handler\CouchDBHandler, Monolog\Handler\CubeHandler, Monolog\Handler\DeduplicationHandler, Monolog\Handler\DoctrineCouchDBHandler, Monolog\Handler\DynamoDbHandler, Monolog\Handler\ElasticSearchHandler, Monolog\Handler\ErrorLogHandler, Monolog\Handler\ExceptionTestHandler, Monolog\Handler\FilterHandler, Monolog\Handler\FingersCrossedHandler, Monolog\Handler\FirePHPHandler, Monolog\Handler\FleepHookHandler, Monolog\Handler\FlowdockHandler, Monolog\Handler\GelfHandler, Monolog\Handler\GroupHandler, Monolog\Handler\HipChatHandler, Monolog\Handler\IFTTTHandler, Monolog\Handler\InsightOpsHandler, Monolog\Handler\LogEntriesHandler, Monolog\Handler\LogglyHandler, Monolog\Handler\MailHandler, Monolog\Handler\MandrillHandler, Monolog\Handler\MongoDBHandler, Monolog\Handler\NativeMailerHandler, Monolog\Handler\NewRelicHandler, Monolog\Handler\NullHandler, Monolog\Handler\PHPConsoleHandler, Monolog\Handler\PsrHandler, Monolog\Handler\PushoverHandler, Monolog\Handler\RavenHandler, Monolog\Handler\RedisHandler, Monolog\Handler\RollbarHandler, Monolog\Handler\RotatingFileHandler, Monolog\Handler\SamplingHandler, Monolog\Handler\SlackHandler, Monolog\Handler\SlackWebhookHandler, Monolog\Handler\SlackbotHandler, Monolog\Handler\SocketHandler, Monolog\Handler\StreamHandler, Monolog\Handler\StubNewRelicHandler, Monolog\Handler\StubNewR...HandlerWithoutExtension, Monolog\Handler\SwiftMailerHandler, Monolog\Handler\SyslogHandler, Monolog\Handler\SyslogUdpHandler, Monolog\Handler\TestChromePHPHandler, Monolog\Handler\TestFirePHPHandler, Monolog\Handler\TestHandler, Monolog\Handler\WhatFailureGroupHandler, Monolog\Handler\ZendMonitorHandler.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
101
                    self::$debug_log_path = 'true';
0 ignored issues
show
Deprecated Code introduced by
The property Longman\TelegramBot\TelegramLog::$debug_log_path has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
102
                }
103
            }
104
        }
105
106
        // Fallback to NullLogger.
107
        self::$logger        = $logger ?: new NullLogger();
108
        self::$update_logger = $update_logger ?: new NullLogger();
109
    }
110
111
    /**
112
     * Initialise error log (deprecated)
113
     *
114
     * @param string $path
115
     *
116
     * @return LoggerInterface
117
     * @throws Exception
118
     *
119
     * @deprecated Initialise a preconfigured logger instance instead.
120
     */
121 View Code Duplication
    public static function initErrorLog($path)
122
    {
123
        (defined('PHPUNIT_TESTSUITE') && PHPUNIT_TESTSUITE) || trigger_error(__METHOD__ . ' is deprecated and will be removed soon. Initialise with a preconfigured logger instance instead using "TelegramLog::initialize($logger)".', E_USER_DEPRECATED);
124
125
        if ($path === null || $path === '') {
126
            throw new TelegramLogException('Empty path for error log');
127
        }
128
        self::initialize();
129
130
        // Deprecated code used as fallback.
131
        if (self::$logger instanceof Logger) {
132
            self::$error_log_path = $path;
0 ignored issues
show
Deprecated Code introduced by
The property Longman\TelegramBot\TelegramLog::$error_log_path has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
133
134
            self::$logger->pushHandler(
135
                (new StreamHandler(self::$error_log_path, Logger::ERROR))
0 ignored issues
show
Deprecated Code introduced by
The property Longman\TelegramBot\TelegramLog::$error_log_path has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
136
                    ->setFormatter(new LineFormatter(null, null, true))
137
            );
138
        }
139
140
        return self::$logger;
141
    }
142
143
    /**
144
     * Initialise debug log (deprecated)
145
     *
146
     * @param string $path
147
     *
148
     * @return LoggerInterface
149
     * @throws Exception
150
     *
151
     * @deprecated Initialise a preconfigured logger instance instead.
152
     */
153 View Code Duplication
    public static function initDebugLog($path)
154
    {
155
        (defined('PHPUNIT_TESTSUITE') && PHPUNIT_TESTSUITE) || trigger_error(__METHOD__ . ' is deprecated and will be removed soon. Initialise with a preconfigured logger instance instead using "TelegramLog::initialize($logger)".', E_USER_DEPRECATED);
156
157
        if ($path === null || $path === '') {
158
            throw new TelegramLogException('Empty path for debug log');
159
        }
160
        self::initialize();
161
162
        // Deprecated code used as fallback.
163
        if (self::$logger instanceof Logger) {
164
            self::$debug_log_path = $path;
0 ignored issues
show
Deprecated Code introduced by
The property Longman\TelegramBot\TelegramLog::$debug_log_path has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
165
166
            self::$logger->pushHandler(
167
                (new StreamHandler(self::$debug_log_path, Logger::DEBUG))
0 ignored issues
show
Deprecated Code introduced by
The property Longman\TelegramBot\TelegramLog::$debug_log_path has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
168
                    ->setFormatter(new LineFormatter(null, null, true))
169
            );
170
        }
171
172
        return self::$logger;
173
    }
174
175
    /**
176
     * Initialise update log (deprecated)
177
     *
178
     * @param string $path
179
     *
180
     * @return LoggerInterface
181
     * @throws Exception
182
     *
183
     * @deprecated Initialise a preconfigured logger instance instead.
184
     */
185
    public static function initUpdateLog($path)
186
    {
187
        (defined('PHPUNIT_TESTSUITE') && PHPUNIT_TESTSUITE) || trigger_error(__METHOD__ . ' is deprecated and will be removed soon. Initialise with a preconfigured logger instance instead using "TelegramLog::initialize($logger)".', E_USER_DEPRECATED);
188
189
        if ($path === null || $path === '') {
190
            throw new TelegramLogException('Empty path for update log');
191
        }
192
        self::$update_log_path = $path;
0 ignored issues
show
Deprecated Code introduced by
The property Longman\TelegramBot\TelegramLog::$update_log_path has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
193
194
        if (self::$update_logger === null || self::$update_logger instanceof NullLogger) {
195
            self::$update_logger = new Logger('bot_update_log');
196
197
            self::$update_logger->pushHandler(
198
                (new StreamHandler(self::$update_log_path, Logger::INFO))
0 ignored issues
show
Deprecated Code introduced by
The property Longman\TelegramBot\TelegramLog::$update_log_path has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
199
                    ->setFormatter(new LineFormatter('%message%' . PHP_EOL))
200
            );
201
        }
202
203
        return self::$update_logger;
204
    }
205
206
    /**
207
     * Get the stream handle of the temporary debug output
208
     *
209
     * @return mixed The stream if debug is active, else false
210
     */
211
    public static function getDebugLogTempStream()
212
    {
213
        if (self::$debug_log_temp_stream_handle === null) {
214
            self::$debug_log_temp_stream_handle = fopen('php://temp', 'w+b');
215
        }
216
217
        return self::$debug_log_temp_stream_handle;
218
    }
219
220
    /**
221
     * Write the temporary debug stream to log and close the stream handle
222
     *
223
     * @param string $message Message (with placeholder) to write to the debug log
224
     */
225
    public static function endDebugLogTempStream($message = '%s')
226
    {
227
        if (is_resource(self::$debug_log_temp_stream_handle)) {
228
            rewind(self::$debug_log_temp_stream_handle);
229
            self::debug(sprintf($message, stream_get_contents(self::$debug_log_temp_stream_handle)));
230
            fclose(self::$debug_log_temp_stream_handle);
231
            self::$debug_log_temp_stream_handle = null;
232
        }
233
    }
234
235
    /**
236
     * Is error log active
237
     *
238
     * @return bool
239
     *
240
     * @deprecated Initialise a preconfigured logger instance instead.
241
     */
242
    public static function isErrorLogActive()
243
    {
244
        (defined('PHPUNIT_TESTSUITE') && PHPUNIT_TESTSUITE) || trigger_error(__METHOD__ . ' is deprecated and will be removed soon. Initialise with a preconfigured logger instance instead using "TelegramLog::initialize($logger)".', E_USER_DEPRECATED);
245
        return self::$error_log_path !== null;
0 ignored issues
show
Deprecated Code introduced by
The property Longman\TelegramBot\TelegramLog::$error_log_path has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
246
    }
247
248
    /**
249
     * Is debug log active
250
     *
251
     * @return bool
252
     *
253
     * @deprecated Initialise a preconfigured logger instance instead.
254
     */
255
    public static function isDebugLogActive()
256
    {
257
        (defined('PHPUNIT_TESTSUITE') && PHPUNIT_TESTSUITE) || trigger_error(__METHOD__ . ' is deprecated and will be removed soon. Initialise with a preconfigured logger instance instead using "TelegramLog::initialize($logger)".', E_USER_DEPRECATED);
258
        return self::$debug_log_path !== null;
0 ignored issues
show
Deprecated Code introduced by
The property Longman\TelegramBot\TelegramLog::$debug_log_path has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
259
    }
260
261
    /**
262
     * Is update log active
263
     *
264
     * @return bool
265
     *
266
     * @deprecated Initialise a preconfigured logger instance instead.
267
     */
268
    public static function isUpdateLogActive()
269
    {
270
        (defined('PHPUNIT_TESTSUITE') && PHPUNIT_TESTSUITE) || trigger_error(__METHOD__ . ' is deprecated and will be removed soon. Initialise with a preconfigured logger instance instead using "TelegramLog::initialize($logger)".', E_USER_DEPRECATED);
271
        return self::$update_log_path !== null;
0 ignored issues
show
Deprecated Code introduced by
The property Longman\TelegramBot\TelegramLog::$update_log_path has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
272
    }
273
274
    /**
275
     * Handle any logging method call.
276
     *
277
     * @param string $name
278
     * @param array  $arguments
279
     */
280
    public static function __callStatic($name, array $arguments)
281
    {
282
        // Get the correct logger instance.
283
        $logger = null;
0 ignored issues
show
Unused Code introduced by
$logger is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
284
        if (in_array($name, ['emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug',], true)) {
285
            $logger = self::$logger;
286
        } elseif ($name === 'update') {
287
            $logger = self::$update_logger;
288
            $name   = 'info';
289
        } else {
290
            return;
291
        }
292
293
        self::initialize(self::$logger, self::$update_logger);
294
295
        // Replace any placeholders from the passed context.
296
        if (count($arguments) >= 2) {
297
            if (is_array($arguments[1])) {
298
                $arguments[0] = self::interpolate($arguments[0], $arguments[1]);
299
            } else {
300
                // @todo Old parameter passing active, should be removed in the near future.
301
                $arguments[0] = vsprintf($arguments[0], array_splice($arguments, 1));
302
            }
303
        }
304
305
        call_user_func_array([$logger, $name], $arguments);
306
    }
307
308
    /**
309
     * Interpolates context values into the message placeholders.
310
     *
311
     * @see https://www.php-fig.org/psr/psr-3/#12-message
312
     *
313
     * @param string $message
314
     * @param array  $context
315
     *
316
     * @return string
317
     */
318
    protected static function interpolate($message, array $context = [])
319
    {
320
        // build a replacement array with braces around the context keys
321
        $replace = [];
322
        foreach ($context as $key => $val) {
323
            // check that the value can be casted to string
324
            if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
325
                $replace['{' . $key . '}'] = $val;
326
            }
327
        }
328
329
        // interpolate replacement values into the message and return
330
        return strtr($message, $replace);
331
    }
332
}
333