Passed
Push — master ( 0541b3...deb180 )
by Mihail
18:22 queued 09:14
created

Log.php (2 issues)

1
<?php
2
3
/*
4
 * This file is part of the Koded package.
5
 *
6
 * (c) Mihail Binev <[email protected]>
7
 *
8
 * Please view the LICENSE distributed with this source code
9
 * for the full copyright and license information.
10
 *
11
 */
12
13
namespace Koded\Logging;
14
15
use Koded\Logging\Processors\{Cli, Processor};
16
use DateTimeZone;
17
use Psr\Log\LoggerTrait;
18
use Throwable;
19
20
/**
21
 * Class Log for logging different types of application messages.
22
 *
23
 * $log->notice('foo');
24
 * $log->warn('bar');
25
 *
26
 *  CONFIGURATION PARAMETERS (Log class)
27
 *
28
 *      -   deferred (bool)         [optional], default: false
29
 *          A flag to set the Log instance how to dump messages.
30
 *          Set to TRUE if you want to process all accumulated messages
31
 *          at shutdown time. Otherwise, the default behavior is to process
32
 *          the message immediately after the LoggerInterface method is called.
33
 *
34
 *      -   loggers (array)
35
 *          An array of log processors. Every processor is defined in array with it's own
36
 *          configuration parameters, but ALL must have the following:
37
 *
38
 *          -   class       (string)    [required]
39
 *              The name of the log processor class.
40
 *              Can create multiple same instances with different config
41
 *              parameters.
42
 *
43
 *          -   levels      (integer)    [optional], default: -1 (for all levels)
44
 *              Packed integer for bitwise comparison. See the constants in this
45
 *              class.
46
 *
47
 *              Example: Log::INFO | Log::ERROR | Log::ALERT
48
 *              Processor with these log levels will store only
49
 *              info, error and warning type messages.
50
 *
51
 *      -   dateformat  (string)    [optional], default: d/m/Y H:i:s.u
52
 *          The date format for the log message.
53
 *
54
 *      -   timezone    (string)    [optional], default: UTC
55
 *          The desired timezone for the DateTimeZone object.
56
 *
57
 *
58
 *  CONFIGURATION PARAMETERS (Processor class)
59
 *  Every processor may have it's own specific parameters.
60
 *
61
 */
62
class Log implements Logger
63
{
64
    use LoggerTrait;
65
66
    /**
67
     * @var bool Flag to control the messages processing
68
     */
69
    private bool $deferred = false;
70
71
    /**
72
     * @var string The date format for the message.
73
     */
74
    private string $dateFormat;
75
76
    /**
77
     * @var DateTimeZone Valid timezone for the message.
78
     */
79
    private DateTimeZone|bool $timezone;
80
81
    /**
82
     * @var Processor[] Hash with all registered log processors.
83
     */
84
    private array $processors = [];
85
86
    /**
87
     * @var array List with all accumulated messages.
88
     */
89
    private array $messages = [];
90
91
    /**
92
     * Creates all requested log processors.
93
     *
94
     * @param array $settings
95 9
     */
96
    public function __construct(array $settings)
97 9
    {
98 9
        $this->deferred = (bool)($settings['deferred'] ?? false);
99 9
        $this->dateFormat = (string)($settings['dateformat'] ?? 'd/m/Y H:i:s.u');
100
        if (false === $this->timezone = @\timezone_open((string)($settings['timezone'] ?? 'UTC'))) {
0 ignored issues
show
Documentation Bug introduced by
It seems like @timezone_open((string)$...s['timezone'] ?? 'UTC') can also be of type false. However, the property $timezone is declared as type DateTimeZone. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
101 9
            $this->timezone = \timezone_open('UTC');
102 1
        }
103
        foreach ((array)($settings['loggers'] ?? []) as $processor) {
104
            $this->attach(new $processor['class']($processor));
105 9
        }
106 3
        if ($this->deferred) {
107
            \register_shutdown_function([$this, 'process']);
108 9
        }
109
    }
110 3
111
    public function attach(Processor $processor): Logger
112 3
    {
113 3
        if (0 !== $processor->levels()) {
114
            $this->processors[\spl_object_hash($processor)] = $processor;
115
        }
116 3
        return $this;
117
    }
118
119 3
    public function log($level, $message, array $context = [])
120
    {
121
        try {
122 3
            $levelName = \strtoupper($level);
123 3
            $level = \constant('static::' . $levelName);
124 1
        } catch (Throwable) {
125 1
            $levelName = 'LOG';
126 1
            $level = -1;
127
        }
128
        $this->messages[] = [
129 3
            'level'     => $level,
130 3
            'levelname' => $levelName,
131 3
            'message'   => $this->formatMessage($message, $context),
132 3
            'timestamp' => \date_create_immutable('now', $this->timezone ?: null)->format($this->dateFormat),
0 ignored issues
show
It seems like $this->timezone ?: null can also be of type true; however, parameter $timezone of date_create_immutable() does only seem to accept DateTimeZone|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

132
            'timestamp' => \date_create_immutable('now', /** @scrutinizer ignore-type */ $this->timezone ?: null)->format($this->dateFormat),
Loading history...
133 3
        ];
134
        $this->deferred || $this->process();
135
    }
136 3
137 3
    public function process(): void
138
    {
139
        foreach ($this->processors as $processor) {
140
            $processor->update($this->messages);
141
        }
142
        $this->messages = [];
143
    }
144
145
    public function exception(Throwable $e, Processor $processor = null): void
146
    {
147 3
        $logger = $processor ?? new Cli([]);
148
        $message = $e->getMessage() . PHP_EOL . ' -- [Trace]: ' . $e->getTraceAsString();
149 3
150 3
        $this->attach($logger)->critical($message);
151 1
        $this->process();
152
        $this->detach($logger);
153
    }
154 3
155
    public function detach(Processor $processor): Logger
156
    {
157 1
        unset($this->processors[\spl_object_hash($processor)]);
158
        return $this;
159 1
    }
160 1
161
    /**
162
     * Parses the message as in the interface specification.
163 1
     *
164 1
     * @param object|string $message A string or object that implements __toString
165
     * @param array         $params  [optional] Arbitrary data with key-value pairs replacements
166 1
     *
167
     * @return string
168 1
     */
169 1
    private function formatMessage(object|string $message, array $params = []): string
170
    {
171 1
        $replacements = [];
172 1
        foreach ($params as $k => $v) {
173 1
            $replacements['{' . $k . '}'] = $v;
174 1
        }
175
        return \strtr((string)$message, $replacements);
176 2
    }
177
}
178