├── .gitignore
├── .php_cs
├── .travis.yml
├── phpunit.xml.dist
├── LICENSE
├── composer.json
├── src
└── Mero
│ └── Monolog
│ ├── Formatter
│ └── HtmlFormatter.php
│ └── Handler
│ └── TelegramHandler.php
├── tests
└── Mero
│ └── Monolog
│ └── Handler
│ ├── TestCase.php
│ └── TelegramHandlerTest.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /composer.lock
3 | /.php_cs.cache
4 |
--------------------------------------------------------------------------------
/.php_cs:
--------------------------------------------------------------------------------
1 | exclude('vendor')
5 | ->in(__DIR__)
6 | ;
7 |
8 | return PhpCsFixer\Config::create()
9 | ->setRules([
10 | '@PSR2' => true,
11 | 'array_syntax' => ['syntax' => 'short'],
12 | ])
13 | ->setFinder($finder)
14 | ;
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | sudo: false
4 |
5 | cache:
6 | directories:
7 | - $HOME/.composer/cache
8 |
9 | php:
10 | - 5.6
11 | - 7.0
12 | - 7.1
13 | - 7.2
14 | - nightly
15 |
16 | matrix:
17 | fast_finish: true
18 | allow_failures:
19 | - php: nightly
20 |
21 | before_install:
22 | - composer self-update
23 |
24 | install:
25 | - composer install --prefer-dist
26 |
27 | script:
28 | - composer test
29 |
30 | after_script:
31 | - php ./vendor/bin/coveralls -v
32 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ./tests/Mero/Monolog/
10 |
11 |
12 |
13 |
14 | ./src/Mero/Monolog/
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Rafael Mello
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mero/telegram-handler",
3 | "description": "Monolog handler to send log by Telegram",
4 | "keywords": [
5 | "telegram",
6 | "monolog",
7 | "logging",
8 | "log"
9 | ],
10 | "type": "library",
11 | "license": "MIT",
12 | "support": {
13 | "issues": "https://github.com/merorafael/telegram-handler/issues",
14 | "source": "https://github.com/merorafael/telegram-handler"
15 | },
16 | "authors": [
17 | {
18 | "name": "Rafael Mello",
19 | "email": "merorafael@gmail.com"
20 | }
21 | ],
22 | "require": {
23 | "ext-curl": "*",
24 | "php": ">=5.6 || >=7.0 || >=8.0",
25 | "monolog/monolog": "^1.20"
26 | },
27 | "require-dev": {
28 | "phpunit/phpunit": "^5.6",
29 | "satooshi/php-coveralls": "~1.0"
30 | },
31 | "autoload": {
32 | "psr-4": {
33 | "Mero\\Monolog\\": "src/Mero/Monolog"
34 | }
35 | },
36 | "autoload-dev": {
37 | "psr-4": {
38 | "Mero\\Monolog\\": "tests/Mero/Monolog"
39 | }
40 | },
41 | "suggest": {
42 | "symfony/yaml": "Needed to use HtmlFormatter feature"
43 | },
44 | "extra": {
45 | "branch-alias": {
46 | "dev-master": "0.4.x-dev"
47 | }
48 | },
49 | "scripts": {
50 | "test": [
51 | "phpunit"
52 | ]
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Mero/Monolog/Formatter/HtmlFormatter.php:
--------------------------------------------------------------------------------
1 |
12 | *
13 | * @see https://core.telegram.org/bots/api#formatting-options
14 | */
15 | class HtmlFormatter extends NormalizerFormatter
16 | {
17 | /**
18 | * @inheritDoc
19 | */
20 | public function __construct($dateFormat = null)
21 | {
22 | parent::__construct($dateFormat);
23 | }
24 |
25 | /**
26 | * Formats a log record.
27 | *
28 | * @param array $record A record to format
29 | * @return mixed The formatted record
30 | */
31 | public function format(array $record)
32 | {
33 | $output = "{$record['level_name']}".PHP_EOL;
34 | $output .= "Message: {$record['message']}".PHP_EOL;
35 | $output .= "Time: {$record['datetime']->format($this->dateFormat)}".PHP_EOL;
36 | $output .= "Channel: {$record['channel']}".PHP_EOL;
37 |
38 | if ($record['context']) {
39 | $output .= PHP_EOL;
40 | $output .= "[context]".PHP_EOL;
41 | $output .= Yaml::dump($record['context']);
42 | }
43 | if ($record['extra']) {
44 | $output .= PHP_EOL;
45 | $output .= "[context]".PHP_EOL;
46 | $output .= Yaml::dump($record['extra']);
47 | }
48 |
49 | return $output;
50 | }
51 |
52 | /**
53 | * Formats a set of log records.
54 | *
55 | * @param array $records A set of records to format
56 | * @return mixed The formatted set of records
57 | */
58 | public function formatBatch(array $records)
59 | {
60 | $message = '';
61 | foreach ($records as $record) {
62 | $message .= $this->format($record);
63 | }
64 |
65 | return $message;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/tests/Mero/Monolog/Handler/TestCase.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | namespace Mero\Monolog\Handler;
13 |
14 | use Monolog\Logger;
15 | use PHPUnit\Framework\TestCase as PHPUnitTestCase;
16 |
17 | class TestCase extends PHPUnitTestCase
18 | {
19 | /**
20 | * @return array Record
21 | */
22 | protected function getRecord($level = Logger::WARNING, $message = 'test', $context = [])
23 | {
24 | return [
25 | 'message' => $message,
26 | 'context' => $context,
27 | 'level' => $level,
28 | 'level_name' => Logger::getLevelName($level),
29 | 'channel' => 'test',
30 | 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))),
31 | 'extra' => [],
32 | ];
33 | }
34 |
35 | /**
36 | * @return array
37 | */
38 | protected function getMultipleRecords()
39 | {
40 | return [
41 | $this->getRecord(Logger::DEBUG, 'debug message 1'),
42 | $this->getRecord(Logger::DEBUG, 'debug message 2'),
43 | $this->getRecord(Logger::INFO, 'information'),
44 | $this->getRecord(Logger::WARNING, 'warning'),
45 | $this->getRecord(Logger::ERROR, 'error'),
46 | ];
47 | }
48 |
49 | /**
50 | * @return Monolog\Formatter\FormatterInterface
51 | */
52 | protected function getIdentityFormatter()
53 | {
54 | $formatter = $this
55 | ->getMockBuilder('Monolog\\Formatter\\FormatterInterface')
56 | ->getMock();
57 | $formatter->expects($this->any())
58 | ->method('format')
59 | ->will($this->returnCallback(function ($record) {
60 | return $record['message'];
61 | }));
62 |
63 | return $formatter;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/Mero/Monolog/Handler/TelegramHandlerTest.php:
--------------------------------------------------------------------------------
1 |
11 | *
12 | * @see https://core.telegram.org/bots/api
13 | */
14 | class TelegramHandlerTest extends TestCase
15 | {
16 | /**
17 | * @var resource
18 | */
19 | private $res;
20 |
21 | /**
22 | * @var TelegramHandler
23 | */
24 | private $handler;
25 |
26 | public function setUp()
27 | {
28 | if (!extension_loaded('curl')) {
29 | $this->markTestSkipped('This test requires curl to run');
30 | }
31 | $this->handler = new TelegramHandler('myToken', 'myChat', Logger::DEBUG, true);
32 | }
33 |
34 | public function testCreateHandler()
35 | {
36 | $this->assertInstanceOf(TelegramHandler::class, $this->handler);
37 | }
38 |
39 | public function testWriteHeader()
40 | {
41 | $class = new \ReflectionClass(TelegramHandler::class);
42 | $method = $class->getMethod('buildHeader');
43 | $method->setAccessible(true);
44 |
45 | $header = $method->invoke($this->handler, 'test');
46 |
47 | $this->assertContains('Content-Type: application/json', $header);
48 | $this->assertContains('Content-Length: 4', $header);
49 | }
50 |
51 | public function testWriteContent()
52 | {
53 | $this->handler->setFormatter($this->getIdentityFormatter());
54 |
55 | $class = new \ReflectionClass(TelegramHandler::class);
56 | $method = $class->getMethod('buildContent');
57 | $method->setAccessible(true);
58 |
59 | $content = $method->invoke($this->handler, ['formatted' => 'test1']);
60 |
61 | $this->assertRegexp('/{"chat_id":"myChat","text":"test1"}$/', $content);
62 | }
63 |
64 | public function testWriteContentWithTelegramFormatter()
65 | {
66 | $this->handler->setFormatter(new HtmlFormatter());
67 |
68 | $class = new \ReflectionClass(TelegramHandler::class);
69 | $method = $class->getMethod('buildContent');
70 | $method->setAccessible(true);
71 |
72 | $content = $method->invoke($this->handler, ['formatted' => 'test1']);
73 |
74 | $this->assertRegexp('/{"chat_id":"myChat","text":"test1","parse_mode":"HTML"}$/', $content);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Mero/Monolog/Handler/TelegramHandler.php:
--------------------------------------------------------------------------------
1 |
15 | *
16 | * @see https://core.telegram.org/bots/api
17 | */
18 | class TelegramHandler extends AbstractProcessingHandler
19 | {
20 | /**
21 | * @var string Telegram API token
22 | */
23 | private $token;
24 |
25 | /**
26 | * @var int Chat identifier
27 | */
28 | private $chatId;
29 |
30 | /**
31 | * @var int Request timeout
32 | */
33 | private $timeout;
34 |
35 | /**
36 | * @param string $token Telegram API token
37 | * @param int $chatId Chat identifier
38 | * @param int $level The minimum logging level at which this handler will be triggered
39 | * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
40 | *
41 | * @throws MissingExtensionException If the PHP cURL extension is not loaded
42 | */
43 | public function __construct(
44 | $token,
45 | $chatId,
46 | $level = Logger::CRITICAL,
47 | $bubble = true
48 | ) {
49 | if (!extension_loaded('curl')) {
50 | throw new MissingExtensionException('The cURL PHP extension is required to use the TelegramHandler');
51 | }
52 |
53 | $this->token = $token;
54 | $this->chatId = $chatId;
55 | $this->timeout = 0;
56 |
57 | parent::__construct($level, $bubble);
58 | }
59 |
60 | /**
61 | * Define a timeout to Telegram send message request.
62 | *
63 | * @param int $timeout Request timeout
64 | *
65 | * @return TelegramHandler
66 | */
67 | public function setTimeout($timeout)
68 | {
69 | $this->timeout = $timeout;
70 | return $this;
71 | }
72 |
73 | /**
74 | * Builds the header of the API Call.
75 | *
76 | * @param string $content
77 | *
78 | * @return array
79 | */
80 | protected function buildHeader($content)
81 | {
82 | return [
83 | 'Content-Type: application/json',
84 | 'Content-Length: '.strlen($content),
85 | ];
86 | }
87 |
88 | /**
89 | * Builds the body of API call.
90 | *
91 | * @param array $record
92 | *
93 | * @return string
94 | */
95 | protected function buildContent(array $record)
96 | {
97 | $content = [
98 | 'chat_id' => $this->chatId,
99 | 'text' => $record['formatted'],
100 | ];
101 |
102 | if ($this->formatter instanceof HtmlFormatter) {
103 | $content['parse_mode'] = 'HTML';
104 | }
105 |
106 | return json_encode($content);
107 | }
108 |
109 | /**
110 | * {@inheritdoc}
111 | *
112 | * @param array $record
113 | */
114 | protected function write(array $record)
115 | {
116 | $content = $this->buildContent($record);
117 |
118 | $ch = curl_init();
119 |
120 | curl_setopt($ch, CURLOPT_HTTPHEADER, $this->buildHeader($content));
121 | curl_setopt($ch, CURLOPT_URL, sprintf('https://api.telegram.org/bot%s/sendMessage', $this->token));
122 | curl_setopt($ch, CURLOPT_POST, true);
123 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
124 | curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
125 | curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
126 |
127 | Curl\Util::execute($ch);
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | TelegramHandler
2 | ===============
3 |
4 | [](https://insight.sensiolabs.com/projects/d7f41933-3e48-4c2d-befc-35aba76bf0ef)
5 | [](https://travis-ci.org/merorafael/telegram-handler)
6 | [](https://coveralls.io/github/merorafael/telegram-handler?branch=master)
7 | [](https://packagist.org/packages/mero/telegram-handler)
8 | [](https://packagist.org/packages/mero/telegram-handler)
9 | [](https://packagist.org/packages/mero/telegram-handler)
10 |
11 | Monolog handler to send log by Telegram.
12 |
13 | Requirements
14 | ------------
15 |
16 | - PHP 5.6 or above
17 | - cURL extension
18 |
19 | Instalation with composer
20 | -------------------------
21 |
22 | 1. Open your project directory;
23 | 2. Run `composer require mero/telegram-handler` to add `TelegramHandler` in your project vendor;
24 | 3. Add `symfony/yaml` dependency if you need use the `\Mero\Monolog\Formatter\HtmlFormatter`.
25 |
26 | Declaring handler object
27 | ------------------------
28 |
29 | To declare this handler, you need to know the bot token and the chat identifier(chat_id) to
30 | which the log will be sent.
31 |
32 | ```php
33 | // ...
34 | $handler = new \Mero\Monolog\Handler\TelegramHandler('', , );
35 | // ...
36 | ```
37 |
38 | **Example:**
39 |
40 | ```php
41 | setFormatter(new \Mero\Monolog\Formatter\HtmlFormatter());
51 | $handler->setTimeout(30);
52 | $log->pushHandler($handler);
53 |
54 | $log->debug('Message log');
55 | ```
56 |
57 | The above example is using HtmlFormatter for Telegram API. This feature is added on 0.3.0 release and
58 | you can use declaring handler formatter to use `\Mero\Monolog\Formatter\HtmlFormatter` class.
59 |
60 | You can set the timeout for Telegram request using `setTimeout` method, implemented on `TelegramHandler`. This feature is implemented on 0.4.0 release and this use is not required.
61 |
62 | Creating a bot
63 | --------------
64 |
65 | To use this handler, you need to create your bot on telegram and receive the Bot API access token.
66 | To do this, start a conversation with **@BotFather**.
67 |
68 | **Conversation example:**
69 |
70 | In the example below, I'm talking to **@BotFather**. to create a bot named "Cronus Bot" with user "@cronus_bot".
71 |
72 | ```
73 | Me: /newbot
74 | ---
75 | @BotFather: Alright, a new bot. How are we going to call it? Please choose a name for your bot.
76 | ---
77 | Me: Cronus Bot
78 | ---
79 | @BotFather: Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example:
80 | TetrisBot or tetris_bot.
81 | ---
82 | Me: cronus_bot
83 | ---
84 | @BotFather: Done! Congratulations on your new bot. You will find it at telegram.me/cronus_bot. You can now add a
85 | description, about section and profile picture for your bot, see /help for a list of commands. By the way, when
86 | you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure
87 | the bot is fully operational before you do this.
88 |
89 | Use this token to access the HTTP API:
90 | 000000000:XXXXX-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
91 |
92 | For a description of the Bot API, see this page: https://core.telegram.org/bots/api
93 | ```
94 |
95 | Give a chat identifier
96 | ----------------------
97 |
98 | To retrieve the chat_id in which the log will be sent, the recipient user will first need a conversation with
99 | the bot. After the conversation has started, make the request below to know the chat_id of that conversation.
100 |
101 | **URL:** https://api.telegram.org/bot_token_/getUpdates
102 |
103 | **Example:**
104 |
105 | ```
106 | Request
107 | -------
108 | POST https://api.telegram.org/bot000000000:XXXXX-xxxxxxxxxxxxxxxxxxxxxxxxxxxxx/getUpdates
109 |
110 | Response
111 | --------
112 | {
113 | "ok": true,
114 | "result": [
115 | {
116 | "update_id": 141444845,
117 | "message": {
118 | "message_id": 111,
119 | "from": {
120 | "id": 111111111,
121 | "first_name": "Rafael",
122 | "last_name": "Mello",
123 | "username": "merorafael"
124 | },
125 | "chat": {
126 | "id": 111111111,
127 | "first_name": "Rafael",
128 | "last_name": "Mello",
129 | "username": "merorafael",
130 | "type": "private"
131 | },
132 | "date": 1480701504,
133 | "text": "test"
134 | }
135 | }
136 | ]
137 | }
138 | ```
139 |
140 | In the above request, the chat_id is represented by the number "111111111".
141 |
--------------------------------------------------------------------------------