Target::insertIntoPayload()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 3
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 2
rs 10
c 0
b 0
f 0
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 5
    public function __construct($config = [])
69
    {
70 5
        $this->exportInterval = 20;
71 5
        parent::__construct($config);
72 5
    }
73
74
    /**
75
     * @inheritDoc
76
     * @throws \yii\base\InvalidConfigException
77
     */
78 5
    public function init()
79
    {
80 5
        parent::init();
81 5
        $this->httpClient = Instance::ensure($this->httpClient, Client::className());
82 5
    }
83
84
    /**
85
     * @inheritDoc
86
     * @throws \yii\base\InvalidValueException
87
     */
88 2
    public function export()
89
    {
90 2
        $response = $this->httpClient
91 2
            ->post($this->webhookUrl, $this->getPayload())
92 2
            ->setFormat(Client::FORMAT_JSON)
93 2
            ->send();
94 2
        if (!$response->getIsOk()) {
95 1
            throw new InvalidValueException(
96 1
                'Unable to send logs to Slack: ' . $response->getContent(), (int) $response->getStatusCode()
97 1
            );
98
        }
99 1
    }
100
101
    /**
102
     * Encodes special chars in a string as HTML entities.
103
     * @param string $string
104
     * @return string
105
     * @since 1.3
106
     */
107 1
    protected function encode($string)
108
    {
109 1
        return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
110
    }
111
112
    /**
113
     * Returns a Slack API payload.
114
     * @return array payload.
115
     * @since 1.2
116
     */
117 1
    protected function getPayload()
118
    {
119
        $payload = [
120 1
            'parse' => 'none',
121 1
            'attachments' => array_map([$this, 'formatMessageAttachment'], $this->messages),
122 1
        ];
123 1
        $this
124 1
            ->insertIntoPayload($payload, 'username', $this->username)
125 1
            ->insertIntoPayload($payload, 'icon_url', $this->iconUrl)
126 1
            ->insertIntoPayload($payload, 'icon_emoji', $this->iconEmoji)
127 1
            ->insertIntoPayload($payload, 'channel', $this->channel);
128 1
        return $payload;
129
    }
130
131
    /**
132
     * Returns a properly formatted message attachment for Slack API.
133
     * @param array $message raw message.
134
     * @return array Slack message attachment.
135
     */
136 2
    protected function formatMessageAttachment($message)
137
    {
138 2
        $message = new Message($message, $this);
139
        $attachment = [
140 2
            'fallback' => $this->encode($this->formatMessage($message->message)),
141 2
            'title' => ucwords($message->getLevel()),
142 2
            'fields' => [],
143 2
            'text' => "```\n" . $this->encode($message->getText()) . "\n```",
144 2
            'footer' => \Yii::$app->id,
145 2
            'ts' => (int) round($message->getTimestamp()),
146
            'mrkdwn_in' => [
147 2
                'fields',
148 2
                'text',
149 2
            ],
150 2
        ];
151 2
        if ($message->getIsConsoleRequest()) {
152 1
            $attachment['author_name'] = $message->getCommandLine();
153 1
        } else {
154 2
            $attachment['author_name'] = $attachment['author_link'] = $message->getUrl();
155
        }
156 2
        if (isset($this->colors[$message->message[1]])) {
157 1
            $attachment['color'] = $this->colors[$message->message[1]];
158 1
        }
159 2
        $this
160 2
            ->insertField($attachment, 'Level', $message->getLevel(), true, false)
161 2
            ->insertField($attachment, 'Category', $message->getCategory(), true)
162 2
            ->insertField($attachment, 'Prefix', $message->getPrefix(), true)
163 2
            ->insertField($attachment, 'User IP', $message->getUserIp(), true, false)
164 2
            ->insertField($attachment, 'User ID', $message->getUserId(), true, false)
165 2
            ->insertField($attachment, 'Session ID', $message->getSessionId(), true, false)
166 2
            ->insertField($attachment, 'Stack Trace', $message->getStackTrace(), false);
167 2
        return $attachment;
168
    }
169
170
    /**
171
     * Inserts the new attachment field if the value is not empty.
172
     * @param array $attachment
173
     * @param string $title
174
     * @param string|null $value
175
     * @param bool $short
176
     * @param bool $wrapAsCode
177
     * @return $this
178
     */
179 4
    private function insertField(array &$attachment, $title, $value, $short, $wrapAsCode = true)
180
    {
181 4
        if ((string) $value === '') {
182 4
            return $this;
183
        }
184
185 4
        $value = $this->encode($value);
186 4
        if ($wrapAsCode) {
187 4
            if ($short) {
188 4
                $value = '`' . $value . '`';
189 4
            } else {
190 1
                $value = "```\n" . $value . "\n```";
191
            }
192 4
        }
193 4
        $attachment['fields'][] = [
194 4
            'title' => $title,
195 4
            'value' => $value,
196 4
            'short' => $short,
197
        ];
198 4
        return $this;
199
    }
200
201
    /**
202
     * Copies the value to the payload if the value is set.
203
     * @param array $payload
204
     * @param string $name
205
     * @param string $value
206
     * @return $this
207
     */
208 3
    private function insertIntoPayload(array &$payload, $name, $value)
209
    {
210 3
        if ((string) $value !== '') {
211 3
            $payload[$name] = $value;
212 3
        }
213 3
        return $this;
214
    }
215
}
216