Completed
Push — master ( 22943e...7f30e4 )
by Sergey
02:27
created

SlackTarget::insertField()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 16
cts 16
cp 1
rs 9.0534
c 0
b 0
f 0
cc 4
eloc 14
nc 4
nop 5
crap 4
1
<?php
2
/**
3
 * Slack log target for Yii 2
4
 *
5
 * @see       https://github.com/sergeymakinen/yii2-slack-log
6
 * @copyright Copyright (c) 2016-2017 Sergey Makinen (https://makinen.ru)
7
 * @license   https://github.com/sergeymakinen/yii2-slack-log/blob/master/LICENSE MIT License
8
 */
9
10
namespace sergeymakinen\log;
11
12
use yii\base\InvalidValueException;
13
use yii\di\Instance;
14
use yii\helpers\Json;
15
use yii\httpclient\Client;
16
use yii\log\Logger;
17
use yii\log\Target;
18
19
class SlackTarget extends Target
20
{
21
    /**
22
     * @var Client|array|string Yii HTTP client configuration.
23
     * This can be a component ID, a configuration array or a Client instance.
24
     * @since 1.2
25
     */
26
    public $httpClient = [
27
        'class' => 'yii\httpclient\Client',
28
    ];
29
30
    /**
31
     * @var string incoming webhook URL.
32
     */
33
    public $webhookUrl;
34
35
    /**
36
     * @var string displayed username.
37
     */
38
    public $username;
39
40
    /**
41
     * @var string icon URL.
42
     */
43
    public $iconUrl;
44
45
    /**
46
     * @var string icon emoji.
47
     */
48
    public $iconEmoji;
49
50
    /**
51
     * @var string channel or direct message name.
52
     */
53
    public $channel;
54
55
    /**
56
     * @var string[] colors per a logger level.
57
     */
58
    public $colors = [
59
        Logger::LEVEL_ERROR => 'danger',
60
        Logger::LEVEL_WARNING => 'warning',
61
    ];
62
63
    /**
64
     * @inheritDoc
65
     */
66
    public $exportInterval = 50;
67
68
    /**
69
     * @inheritDoc
70
     */
71 5
    public function init()
72
    {
73 5
        parent::init();
74 5
        $this->httpClient = Instance::ensure($this->httpClient, Client::className());
75 5
    }
76
77
    /**
78
     * @inheritDoc
79
     */
80 2
    public function export()
81
    {
82 2
        $response = $this->httpClient->post(
83 2
            $this->webhookUrl,
84 2
            Json::encode($this->getPayload()),
85 2
            ['Content-Type: application/json; charset=UTF-8']
86 2
        )->send();
87 2
        if (!$response->getIsOk()) {
88 1
            throw new InvalidValueException(
89 1
                'Unable to send logs to Slack: ' . $response->getContent(), (int) $response->getStatusCode()
90 1
            );
91
        }
92 1
    }
93
94
    /**
95
     * Encodes special chars in a message as HTML entities.
96
     * @param string $message
97
     * @return string
98
     * @deprecated 1.3
99
     * @see encode()
100
     */
101 1
    protected function encodeMessage($message)
102
    {
103 1
        return $this->encode($message);
104
    }
105
106
    /**
107
     * Encodes special chars in a string as HTML entities.
108
     * @param string $string
109
     * @return string
110
     * @since 1.3
111
     */
112 5
    protected function encode($string)
113
    {
114 5
        return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
115
    }
116
117
    /**
118
     * Returns a Slack API payload.
119
     * @return array
120
     * @since 1.2
121
     */
122 3
    protected function getPayload()
123
    {
124
        $payload = [
125 3
            'parse' => 'none',
126 3
            'attachments' => array_map([$this, 'formatMessageAttachment'], $this->messages),
127 3
        ];
128 3
        $this
129 3
            ->insertIntoPayload($payload, 'username', $this->username)
130 3
            ->insertIntoPayload($payload, 'icon_url', $this->iconUrl)
131 3
            ->insertIntoPayload($payload, 'icon_emoji', $this->iconEmoji)
132 3
            ->insertIntoPayload($payload, 'channel', $this->channel);
133 3
        return $payload;
134
    }
135
136
    /**
137
     * Returns a properly formatted message attachment for Slack API.
138
     * @param array $message
139
     * @return array
140
     */
141 4
    protected function formatMessageAttachment($message)
142
    {
143 4
        $message = new Message($message, $this);
144
        $attachment = [
145 4
            'fallback' => $this->encode($this->formatMessage($message->message)),
146 4
            'title' => ucwords($message->getLevel()),
147 4
            'fields' => [],
148 4
            'text' => "```\n" . $this->encode($message->getText()) . "\n```",
149 4
            'footer' => static::className(),
150 4
            'ts' => (int) round($message->getTimestamp()),
151
            'mrkdwn_in' => [
152 4
                'fields',
153 4
                'text',
154 4
            ],
155 4
        ];
156 4
        if ($message->getIsConsoleRequest()) {
157 1
            $attachment['author_name'] = $message->getCommandLine();
158 1
        } else {
159 4
            $attachment['author_name'] = $attachment['author_link'] = $message->getUrl();
160
        }
161 4
        if (isset($this->colors[$message->message[1]])) {
162 3
            $attachment['color'] = $this->colors[$message->message[1]];
163 3
        }
164 4
        $this
165 4
            ->insertField($attachment, 'Level', $message->getLevel(), true, false)
166 4
            ->insertField($attachment, 'Category', $message->getCategory(), true)
167 4
            ->insertField($attachment, 'Prefix', $message->getPrefix(), true)
168 4
            ->insertField($attachment, 'User IP', $message->getUserIp(), true, false)
169 4
            ->insertField($attachment, 'User ID', $message->getUserId(), true, false)
170 4
            ->insertField($attachment, 'Session ID', $message->getSessionId(), true)
171 4
            ->insertField($attachment, 'Stack Trace', $message->getStackTrace(), false);
172 4
        return $attachment;
173
    }
174
175
    /**
176
     * Inserts the new attachment field if the value is not empty.
177
     * @param array $attachment
178
     * @param string $title
179
     * @param string|null $value
180
     * @param bool $short
181
     * @param bool $encodeValue
182
     * @return $this
183
     */
184 4
    private function insertField(array &$attachment, $title, $value, $short, $encodeValue = true)
185
    {
186 4
        if ((string) $value === '') {
187 4
            return $this;
188 1
        }
189
190 4
        if ($encodeValue) {
191 4
            $value = $this->encode($value);
192 4
            if ($short) {
193 4
                $value = '`' . $value . '`';
194 4
            } else {
195 1
                $value = "```\n" . $value . "\n```";
196
            }
197 4
        }
198 4
        $attachment['fields'][] = [
199 4
            'title' => $title,
200 4
            'value' => $value,
201 4
            'short' => $short,
202
        ];
203 4
        return $this;
204
    }
205
206
    /**
207
     * Copies the value to the payload if the value is set.
208
     * @param array $payload
209
     * @param string $name
210
     * @param string $value
211
     * @return $this
212
     */
213 3
    private function insertIntoPayload(array &$payload, $name, $value)
214
    {
215 3
        if ((string) $value !== '') {
216 3
            $payload[$name] = $value;
217 3
        }
218 3
        return $this;
219
    }
220
}
221