Completed
Push — master ( ad0924...094bb7 )
by Craig
01:40
created

Logger::log()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 12
nc 4
nop 3
1
<?php
2
3
namespace League\CLImate;
4
5
use Psr\Log\AbstractLogger;
6
use Psr\Log\InvalidArgumentException;
7
use Psr\Log\LogLevel;
8
use function array_key_exists;
9
use function is_array;
10
use function str_replace;
11
use function strpos;
12
13
/**
14
 * A PSR-3 compatible logger that uses CLImate for output.
15
 */
16
class Logger extends AbstractLogger
17
{
18
    /**
19
     * @var array $levels Conversion of the level strings to their numeric representations.
20
     */
21
    private $levels = [
22
        LogLevel::EMERGENCY => 1,
23
        LogLevel::ALERT => 2,
24
        LogLevel::CRITICAL => 3,
25
        LogLevel::ERROR => 4,
26
        LogLevel::WARNING => 5,
27
        LogLevel::NOTICE => 6,
28
        LogLevel::INFO => 7,
29
        LogLevel::DEBUG => 8,
30
    ];
31
32
    /**
33
     * @var int $level Ignore logging attempts at a level less than this.
34
     */
35
    private $level;
36
37
    /**
38
     * @var CLImate $climate The underlying climate instance we are using for output.
39
     */
40
    private $climate;
41
42
    /**
43
     * Create a new Logger instance.
44
     *
45
     * @param string $level One of the LogLevel constants
46
     * @param CLImate $climate An existing CLImate instance to use for output
47
     */
48
    public function __construct($level = LogLevel::INFO, CLImate $climate = null)
49
    {
50
        $this->level = $this->convertLevel($level);
51
52
        if ($climate === null) {
53
            $climate = new CLImate;
54
        }
55
        $this->climate = $climate;
56
57
        # Define some default styles to use for the output
58
        $commands = [
59
            "emergency" => ["white", "bold", "background_red"],
60
            "alert" => ["white", "background_yellow"],
61
            "critical" => ["red", "bold"],
62
            "error" => ["red"],
63
            "warning" => "yellow",
64
            "notice" => "light_cyan",
65
            "info" => "green",
66
            "debug" => "dark_gray",
67
        ];
68
69
        # If any of the required styles are not defined then define them now
70
        foreach ($commands as $command => $style) {
71
            if (!$this->climate->style->get($command)) {
72
                $this->climate->style->addCommand($command, $style);
73
            }
74
        }
75
    }
76
77
78
    /**
79
     * Get a numeric log level for the passed parameter.
80
     *
81
     * @param string $level One of the LogLevel constants
82
     *
83
     * @return int
84
     */
85
    private function convertLevel($level)
86
    {
87
        # If this is one of the defined string log levels then return it's numeric value
88
        if (!array_key_exists($level, $this->levels)) {
89
            throw new InvalidArgumentException("Unknown log level: {$level}");
90
        }
91
92
        return $this->levels[$level];
93
    }
94
95
96
    /**
97
     * Get a new instance logging at a different level
98
     *
99
     * @param string $level One of the LogLevel constants
100
     *
101
     * @return Logger
102
     */
103
    public function withLogLevel($level)
104
    {
105
        $logger = clone $this;
106
        $logger->level = $this->convertLevel($level);
107
        return $logger;
108
    }
109
110
111
    /**
112
     * Log messages to a CLImate instance.
113
     *
114
     * @param string $level One of the LogLevel constants
115
     * @param string|object $message If an object is passed it must implement __toString()
116
     * @param array $context Placeholders to be substituted in the message
117
     *
118
     * @return void
119
     */
120
    public function log($level, $message, array $context = [])
121
    {
122
        if ($this->convertLevel($level) > $this->level) {
123
            return;
124
        }
125
126
        # Handle objects implementing __toString
127
        $message = (string)$message;
128
129
        # Handle any placeholders in the $context array
130
        foreach ($context as $key => $val) {
131
            $placeholder = "{" . $key . "}";
132
133
            # If this context key is used as a placeholder, then replace it, and remove it from the $context array
134
            if (strpos($message, $placeholder) !== false) {
135
                $val = (string)$val;
136
                $message = str_replace($placeholder, $val, $message);
137
                unset($context[$key]);
138
            }
139
        }
140
141
        # Send the message to the climate instance
142
        $this->climate->{$level}($message);
143
144
        # Append any context information not used as placeholders
145
        $this->outputRecursiveContext($level, $context, 1);
146
    }
147
148
149
    /**
150
     * Handle recursive arrays in the logging context.
151
     *
152
     * @param string $level One of the LogLevel constants
153
     * @param array $context The array of context to output
154
     * @param int $indent The current level of indentation to be used
155
     *
156
     * @return void
157
     */
158
    private function outputRecursiveContext($level, array $context, $indent)
159
    {
160
        foreach ($context as $key => $val) {
161
            $this->climate->tab($indent);
162
163
            $this->climate->{$level}()->inline("{$key}: ");
164
165
            if (is_array($val)) {
166
                $this->climate->{$level}("[");
167
                $this->outputRecursiveContext($level, $val, $indent + 1);
168
                $this->climate->tab($indent)->{$level}("]");
169
            } else {
170
                $this->climate->{$level}((string)$val);
171
            }
172
        }
173
    }
174
}
175