Completed
Push — master ( 5bf3fa...62e4a6 )
by Sergey
10:10
created

Target::export()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
cc 4
eloc 13
nc 4
nop 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
    public function __construct($config = [])
78
    {
79
        $this->exportInterval = 1;
80
        parent::__construct($config);
81
    }
82
83
    /**
84
     * @inheritDoc
85
     */
86
    public function init()
87
    {
88
        parent::init();
89
        $this->httpClient = Instance::ensure($this->httpClient, Client::className());
90
        if ($this->substitutions === null) {
91
            $this->substitutions = $this->defaultSubstitutions();
92
        }
93
    }
94
95
    /**
96
     * @inheritDoc
97
     * @throws \yii\base\InvalidValueException
98
     */
99
    public function export()
100
    {
101
        foreach (array_map([$this, 'formatMessageRequest'], $this->messages) as $request) {
102
            $response = $this->httpClient
103
                ->post('https://api.telegram.org/bot' . $this->token . '/sendMessage', $request)
104
                ->setFormat(Client::FORMAT_JSON)
105
                ->send();
106
            if (!$response->getIsOk()) {
107
                if (isset($response->getData()['description'])) {
108
                    $description = $response->getData()['description'];
109
                } else {
110
                    $description = $response->getContent();
111
                }
112
                throw new InvalidValueException(
113
                    'Unable to send logs to Telegram: ' . $description, (int) $response->getStatusCode()
114
                );
115
            }
116
        }
117
    }
118
119
    /**
120
     * Returns a `sendMessage` request for Telegram.
121
     * @param array $message raw message.
122
     * @return array request as an array.
123
     */
124
    protected function formatMessageRequest($message)
125
    {
126
        $message = new Message($message, $this);
127
        $request = [
128
            'chat_id' => $this->chatId,
129
            'parse_mode' => 'Markdown',
130
            'disable_web_page_preview' => true,
131
            'disable_notification' => false,
132
        ];
133
        if (isset($this->enableNotification[$message->message[1]])) {
134
            $request['disable_notification'] = !$this->enableNotification[$message->message[1]];
135
        } elseif (is_bool($this->enableNotification)) {
136
            $request['disable_notification'] = !$this->enableNotification;
137
        }
138
        $request['text'] = preg_replace_callback('/{([^}]+)}([\n]*|$)/', function (array $matches) use ($message) {
139
            if (isset($this->substitutions[$matches[1]])) {
140
                $value = $this->substitute($matches[1], $message);
141
                if ($value !== '') {
142
                    return $value . $matches[2];
143
                }
144
            }
145
            return '';
146
        }, $this->template);
147
        return $request;
148
    }
149
150
    /**
151
     * Returns an array with the default substitutions.
152
     * @return array default substitutions.
153
     */
154
    protected function defaultSubstitutions()
155
    {
156
        return [
157
            'levelAndRequest' => [
158
                'title' => null,
159
                'short' => false,
160
                'wrapAsCode' => false,
161
                'value' => function (Message $message) {
162
                    if (isset($this->levelEmojis[$message->message[1]])) {
163
                        $value = $this->levelEmojis[$message->message[1]] . ' ';
164
                    } else {
165
                        $value = '*' . ucfirst($message->getLevel()) . '* @ ';
166
                    }
167
                    if ($message->getIsConsoleRequest()) {
168
                        $value .= '`' . $message->getCommandLine() . '`';
169
                    } else {
170
                        $value .= '[' . $message->getUrl() . '](' . $message->getUrl() . ')';
171
                    }
172
                    return $value;
173
                },
174
            ],
175
            'category' => [
176
                'emojiTitle' => '📖',
177
                'short' => true,
178
                'wrapAsCode' => false,
179
                'value' => function (Message $message) {
180
                    return '`' . $message->getCategory() . '`';
181
                },
182
            ],
183
            'user' => [
184
                'emojiTitle' => '🙂',
185
                'short' => true,
186
                'wrapAsCode' => false,
187
                'value' => function (Message $message) {
188
                    $value = [];
189
                    $ip = $message->getUserIp();
190
                    if ((string) $ip !== '') {
191
                        $value[] = $ip;
192
                    }
193
                    $id = $message->getUserId();
194
                    if ((string) $id !== '') {
195
                        $value[] = "ID: `{$id}`";
196
                    }
197
                    return implode(str_repeat(' ', 4), $value);
198
                },
199
            ],
200
            'stackTrace' => [
201
                'title' => 'Stack Trace',
202
                'short' => false,
203
                'wrapAsCode' => true,
204
                'value' => function (Message $message) {
205
                    return $message->getStackTrace();
206
                },
207
            ],
208
            'text' => [
209
                'title' => null,
210
                'short' => false,
211
                'wrapAsCode' => true,
212
                'value' => function (Message $message) {
213
                    return $message->getText();
214
                },
215
            ],
216
        ];
217
    }
218
219
    /**
220
     * Returns a substituted value.
221
     * @param string $name
222
     * @param Message $message
223
     * @return string
224
     */
225
    private function substitute($name, Message $message)
226
    {
227
        $config = $this->substitutions[$name];
228
        $value = (string) call_user_func($config['value'], $message);
229
        if ($value === '') {
230
            return '';
231
        }
232
233
        if ($config['wrapAsCode']) {
234
            if ($config['short']) {
235
                $value = '`' . $value . '`';
236
            } else {
237
                $value = "```text\n" . $value . "\n```";
238
            }
239
        }
240
        if (isset($config['title'])) {
241
            $separator = $config['short'] ? ' ' : "\n";
242
            $value = "*{$config['title']}*:{$separator}{$value}";
243
        } elseif (isset($config['emojiTitle'])) {
244
            $value = "{$config['emojiTitle']} {$value}";
245
        }
246
        return $value;
247
    }
248
}
249