Target::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Telegram log target for Yii 2
4
 *
5
 * @see       https://github.com/sergeymakinen/yii2-telegram-log
6
 * @copyright Copyright (c) 2017 Sergey Makinen (https://makinen.ru)
7
 * @license   https://github.com/sergeymakinen/yii2-telegram-log/blob/master/LICENSE MIT License
8
 */
9
10
namespace sergeymakinen\yii\telegramlog;
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
class Target extends \yii\log\Target
19
{
20
    /**
21
     * @var Client|array|string Yii HTTP client configuration.
22
     * This can be a component ID, a configuration array or a Client instance.
23
     */
24
    public $httpClient = [
25
        'class' => 'yii\httpclient\Client',
26
    ];
27
28
    /**
29
     * @var string bot token.
30
     * @see https://core.telegram.org/bots/api#authorizing-your-bot
31
     */
32
    public $token;
33
34
    /**
35
     * @var int|string unique identifier for the target chat or username of the target channel
36
     * (in the format `{@}channelusername`).
37
     */
38
    public $chatId;
39
40
    /**
41
     * @var string log message template.
42
     */
43
    public $template = '{levelAndRequest}
44
45
{text}
46
47
{category}
48
{user}
49
{stackTrace}';
50
51
    /**
52
     * @var array log message template substitutions.
53
     * [[defaultSubstitutions()]] will be used by default.
54
     */
55
    public $substitutions;
56
57
    /**
58
     * @var bool|bool[] whether to send the message silently (`bool` or an array of `bool` per a logger level).
59
     * iOS users will not receive a notification, Android users will receive a notification with no sound.
60
     */
61
    public $enableNotification = true;
62
63
    /**
64
     * @var string[] level emoji per a logger level.
65
     * @since 2.0
66
     */
67
    public $levelEmojis = [
68
        Logger::LEVEL_ERROR => '☠️',
69
        Logger::LEVEL_WARNING => '⚠️',
70
        Logger::LEVEL_INFO => 'ℹ️',
71
        Logger::LEVEL_TRACE => '📝',
72
    ];
73
74
    /**
75
     * @inheritDoc
76
     */
77 5
    public function __construct($config = [])
78
    {
79 5
        $this->exportInterval = 1;
80 5
        parent::__construct($config);
81 5
    }
82
83
    /**
84
     * @inheritDoc
85
     */
86 5
    public function init()
87
    {
88 5
        parent::init();
89 5
        $this->httpClient = Instance::ensure($this->httpClient, Client::className());
90 5
        if ($this->substitutions === null) {
91 2
            $this->substitutions = $this->defaultSubstitutions();
92 2
        }
93 5
    }
94
95
    /**
96
     * @inheritDoc
97
     * @throws \yii\base\InvalidValueException
98
     */
99 2
    public function export()
100
    {
101 2
        foreach (array_map([$this, 'formatMessageRequest'], $this->messages) as $request) {
102 2
            $response = $this->httpClient
103 2
                ->post('https://api.telegram.org/bot' . $this->token . '/sendMessage', $request)
104 2
                ->setFormat(Client::FORMAT_JSON)
105 2
                ->send();
106 2
            if (!$response->getIsOk()) {
107 2
                if (isset($response->getData()['description'])) {
108 2
                    $description = $response->getData()['description'];
109 2
                } else {
110 1
                    $description = $response->getContent();
111
                }
112 2
                throw new InvalidValueException(
113 2
                    'Unable to send logs to Telegram: ' . $description, (int) $response->getStatusCode()
114 2
                );
115
            }
116 1
        }
117 1
    }
118
119
    /**
120
     * Returns a `sendMessage` request for Telegram.
121
     * @param array $message raw message.
122
     * @return array request as an array.
123
     */
124 5
    protected function formatMessageRequest($message)
125
    {
126 5
        $message = new Message($message, $this);
127
        $request = [
128 5
            'chat_id' => $this->chatId,
129 5
            'parse_mode' => 'Markdown',
130 5
            'disable_web_page_preview' => true,
131 5
            'disable_notification' => false,
132 5
        ];
133 5
        if (isset($this->enableNotification[$message->message[1]])) {
134 1
            $request['disable_notification'] = !$this->enableNotification[$message->message[1]];
135 5
        } elseif (is_bool($this->enableNotification)) {
136 1
            $request['disable_notification'] = !$this->enableNotification;
137 1
        }
138
        $request['text'] = preg_replace_callback('/{([^}]+)}([\n]*|$)/', function (array $matches) use ($message) {
139 5
            if (isset($this->substitutions[$matches[1]])) {
140 5
                $value = $this->substitute($matches[1], $message);
141 5
                if ($value !== '') {
142 5
                    return $value . $matches[2];
143
                }
144 3
            }
145 4
            return '';
146 5
        }, $this->template);
147 5
        return $request;
148
    }
149
150
    /**
151
     * Returns an array with the default substitutions.
152
     * @return array default substitutions.
153
     */
154 2
    protected function defaultSubstitutions()
155
    {
156
        return [
157
            'levelAndRequest' => [
158 2
                'title' => null,
159 2
                'short' => false,
160 2
                'wrapAsCode' => false,
161
                'value' => function (Message $message) {
162 2
                    if (isset($this->levelEmojis[$message->message[1]])) {
163 1
                        $value = $this->levelEmojis[$message->message[1]] . ' ';
164 1
                    } else {
165 1
                        $value = '*' . ucfirst($message->getLevel()) . '* @ ';
166
                    }
167 2
                    if ($message->getIsConsoleRequest()) {
168 1
                        $value .= '`' . $message->getCommandLine() . '`';
169 1
                    } else {
170 1
                        $value .= '[' . $message->getUrl() . '](' . $message->getUrl() . ')';
171
                    }
172 2
                    return $value;
173 2
                },
174 2
            ],
175
            'category' => [
176 2
                'emojiTitle' => '📖',
177 2
                'short' => true,
178 2
                'wrapAsCode' => false,
179
                'value' => function (Message $message) {
180 2
                    return '`' . $message->getCategory() . '`';
181 2
                },
182 2
            ],
183
            'user' => [
184 2
                'emojiTitle' => '🙂',
185 2
                'short' => true,
186 2
                'wrapAsCode' => false,
187
                'value' => function (Message $message) {
188 2
                    $value = [];
189 2
                    $ip = $message->getUserIp();
190 2
                    if ((string) $ip !== '') {
191 1
                        $value[] = $ip;
192 1
                    }
193 2
                    $id = $message->getUserId();
194 2
                    if ((string) $id !== '') {
195 1
                        $value[] = "ID: `{$id}`";
196 1
                    }
197 2
                    return implode(str_repeat(' ', 4), $value);
198 2
                },
199 2
            ],
200
            'stackTrace' => [
201 2
                'title' => 'Stack Trace',
202 2
                'short' => false,
203 2
                'wrapAsCode' => true,
204
                'value' => function (Message $message) {
205 2
                    return $message->getStackTrace();
206 2
                },
207 2
            ],
208
            'text' => [
209 2
                'title' => null,
210 2
                'short' => false,
211 2
                'wrapAsCode' => true,
212 2
                'value' => function (Message $message) {
213 2
                    return $message->getText();
214 2
                },
215 2
            ],
216 2
        ];
217
    }
218
219
    /**
220
     * Returns a substituted value.
221
     * @param string $name
222
     * @param Message $message
223
     * @return string
224
     */
225 5
    private function substitute($name, Message $message)
226
    {
227 5
        $config = $this->substitutions[$name];
228 5
        $value = (string) call_user_func($config['value'], $message);
229 5
        if ($value === '') {
230 3
            return '';
231
        }
232
233 5
        if ($config['wrapAsCode']) {
234 5
            if ($config['short']) {
235 3
                $value = '`' . $value . '`';
236 3
            } else {
237 5
                $value = "```text\n" . $value . "\n```";
238
            }
239 5
        }
240 5
        if (isset($config['title'])) {
241 5
            $separator = $config['short'] ? ' ' : "\n";
242 5
            $value = "*{$config['title']}*:{$separator}{$value}";
243 5
        } elseif (isset($config['emojiTitle'])) {
244 3
            $value = "{$config['emojiTitle']} {$value}";
245 3
        }
246 5
        return $value;
247
    }
248
}
249