Completed
Push — master ( 1651f3...fcfc7d )
by Sergey
03:17
created

Target::insertIntoPayload()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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