Log::now()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php declare(strict_types=1);
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 Stringable;
19
use Throwable;
20
use function constant;
21
use function date_create_immutable;
22
use function spl_object_id;
23
use function strtoupper;
24
use function strtr;
25
use function timezone_open;
26
27
/**
28
 * Class Log for logging different types of application messages.
29
 *
30
 * $log->notice('foo');
31
 * $log->warn('bar');
32
 *
33
 *  CONFIGURATION PARAMETERS (Log class)
34
 *
35
 *      -   processors (array)
36
 *          An array of log processors. Every processor is defined in array with it's own
37
 *          configuration parameters, but ALL must have the following:
38
 *
39
 *          -   class       (string)    [required]
40
 *              The name of the log processor class.
41
 *              Can create multiple same instances with different config
42
 *              parameters.
43
 *
44
 *          -   levels      (integer)    [optional], default: -1 (for all levels)
45
 *              Packed integer for bitwise comparison. See the constants in this
46
 *              class.
47
 *
48
 *              Example: Log::INFO | Log::ERROR | Log::ALERT
49
 *              Processor with these log levels will store only
50
 *              info, error and warning type messages.
51
 *
52
 *      -   dateformat  (string)    [optional], default: d/m/Y H:i:s.u
53
 *          The date format for the log message.
54
 *
55
 *      -   timezone    (string)    [optional], default: UTC
56
 *          The desired timezone for the DateTimeZone object.
57
 *
58
 *
59
 *  CONFIGURATION PARAMETERS (Processor class)
60
 *  Every processor may have its own specific parameters.
61
 *
62
 */
63
class Log implements Logger
64
{
65
    use LoggerTrait;
66
67
    private const DATE_FORMAT = 'd/m/Y H:i:s.u';
68
    private DateTimeZone $timezone;
69
70
    /**
71
     * Creates all requested log processors.
72
     *
73
     * @param array<int, Processor>  $processors a list of log processors
74
     * @param string $dateformat The date format for the messages
75
     * @param string $timezone   The timezone for the messages
76
     */
77
    public function __construct(
78
        private array $processors = [],
79
        private string $dateformat = self::DATE_FORMAT,
80
        string $timezone = 'UTC')
81
    {
82
        $this->timezone = @timezone_open($timezone) ?: timezone_open('UTC');
83
        foreach ($processors as $i => $processor) {
84
            unset($this->processors[$i]);
85
            $this->attach(new $processor['class']($processor));
86
        }
87
    }
88
89
    public function log($level, string|Stringable $message, array $context = []): void
90
    {
91
        try {
92
            $levelName = strtoupper($level);
93
            $level = constant('static::' . $levelName);
94
        } catch (Throwable) {
95 9
            $levelName = 'LOG';
96
            $level = -1;
97 9
        }
98 9
        foreach ($this->processors as $processor) {
99 9
            $processor->update([
100
               'level' => $level,
101 9
               'levelname' => $levelName,
102 1
               'message' => $this->formatMessage($message, $context),
103
               'timestamp' => $this->now(),
104
           ]);
105 9
        }
106 3
    }
107
108 9
    public function attach(Processor $processor): Logger
109
    {
110 3
        if (0 !== $processor->levels()) {
111
            $this->processors[spl_object_id($processor)] = $processor;
112 3
        }
113 3
        return $this;
114
    }
115
116 3
    public function detach(Processor $processor): Logger
117
    {
118
        unset($this->processors[spl_object_id($processor)]);
119 3
        return $this;
120
    }
121
122 3
    public function exception(Throwable $e, Processor $processor = null): void
123 3
    {
124 1
        $this->attach($processor ??= new Cli([]));
125 1
        $this->critical($e->getMessage() . PHP_EOL . ' [Trace]: ' . $e->getTraceAsString());
126 1
        $this->detach($processor);
127
    }
128
129 3
    /**
130 3
     * Parses the message as in the interface specification.
131 3
     *
132 3
     * @param object|string $message A string or object that implements __toString
133 3
     * @param array         $params  [optional] Arbitrary data with key-value pairs replacements
134
     *
135
     * @return string
136 3
     */
137 3
    private function formatMessage(object|string $message, array $params = []): string
138
    {
139
        $replacements = [];
140
        foreach ($params as $k => $v) {
141
            $replacements['{' . $k . '}'] = $v;
142
        }
143
        return strtr((string)$message, $replacements);
144
    }
145
146
    private function now(): string
147 3
    {
148
        return date_create_immutable('now', $this->timezone)
149 3
            ->format($this->dateformat);
150 3
    }
151
}
152