From c8b8e85f7827081bbd21d36363d5126c7551ac18 Mon Sep 17 00:00:00 2001 From: Remi Collet Date: Thu, 17 Nov 2016 11:43:54 +0100 Subject: [PATCH 01/27] enable 7.1 on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index de07fa93..0b053f5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ matrix: - php: 5.5 - php: 5.6 - php: 7 + - php: 7.1 - php: hhvm fast_finish: true From 393e243499c1c573a942a469e1656d9e95a84469 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Mon, 5 Sep 2016 02:56:57 +0300 Subject: [PATCH 02/27] Add a Slackbot handler This is the simplest way to log to Slack using the Slackbot. It supports only plain text with automatic linking of URLs and mentions. --- src/Monolog/Handler/SlackbotHandler.php | 84 +++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/Monolog/Handler/SlackbotHandler.php diff --git a/src/Monolog/Handler/SlackbotHandler.php b/src/Monolog/Handler/SlackbotHandler.php new file mode 100644 index 00000000..0cea9c28 --- /dev/null +++ b/src/Monolog/Handler/SlackbotHandler.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; + +/** + * Sends notifications through Slack's Slackbot + * + * @author Haralan Dobrev + * @see https://slack.com/apps/A0F81R8ET-slackbot + */ +class SlackbotHandler extends AbstractProcessingHandler +{ + /** + * The slug of the Slack team + * @var string + */ + private $slackTeam; + + /** + * Slackbot token + * @var string + */ + private $token; + + /** + * Slack channel name + * @var string + */ + private $channel; + + /** + * @param string $token Slackbot token + * @param string $channel Slack channel (encoded ID or name) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct( + $slackTeam, + $token, + $channel, + $level = Logger::CRITICAL, + $bubble = true + ) { + parent::__construct($level, $bubble); + + $this->slackTeam = $slackTeam; + $this->token = $token; + $this->channel = $channel; + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $slackbotUrl = sprintf( + 'https://%s.slack.com/services/hooks/slackbot?token=%s&channel=%s', + $this->slackTeam, + $this->token, + $this->channel + ); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $slackbotUrl); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $record['message']); + + Curl\Util::execute($ch); + } +} From f584e56ae14e665c2b5b13024962f33f03518af6 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Tue, 6 Sep 2016 23:21:11 +0300 Subject: [PATCH 03/27] Extract logic for preparing Slack data to SlackRecord Keep the protected interface of the SlackHandler intact. --- src/Monolog/Handler/Slack/SlackRecord.php | 221 ++++++++++++++++++++++ src/Monolog/Handler/SlackHandler.php | 184 ++++-------------- 2 files changed, 253 insertions(+), 152 deletions(-) create mode 100644 src/Monolog/Handler/Slack/SlackRecord.php diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php new file mode 100644 index 00000000..3c64ef4b --- /dev/null +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -0,0 +1,221 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Formatter\FormatterInterface; + +/** + * Slack record utility helping to log to Slack webhooks or API. + * + * @author Greg Kedzierski + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @see https://api.slack.com/docs/message-attachments + */ +class SlackRecord +{ + /** + * Slack channel (encoded ID or name) + * @var string + */ + private $channel; + + /** + * Name of a bot + * @var string + */ + private $username; + + /** + * Emoji icon name + * @var string + */ + private $iconEmoji; + + /** + * Whether the message should be added to Slack as attachment (plain text otherwise) + * @var bool + */ + private $useAttachment; + + /** + * Whether the the context/extra messages added to Slack as attachments are in a short style + * @var bool + */ + private $useShortAttachment; + + /** + * Whether the attachment should include context and extra data + * @var bool + */ + private $includeContextAndExtra; + + /** + * @var FormatterInterface + */ + private $formatter; + + /** + * @var LineFormatter + */ + private $lineFormatter; + + public function __construct( + $channel, + $username = 'Monolog', + $useAttachment = true, + $iconEmoji = null, + $useShortAttachment = false, + $includeContextAndExtra = false, + FormatterInterface $formatter = null + ) { + $this->channel = $channel; + $this->username = $username; + $this->iconEmoji = trim($iconEmoji, ':'); + $this->useAttachment = $useAttachment; + $this->useShortAttachment = $useShortAttachment; + $this->includeContextAndExtra = $includeContextAndExtra; + $this->formatter = $formatter; + + if ($this->includeContextAndExtra && $this->useShortAttachment) { + $this->lineFormatter = new LineFormatter(); + } + } + + public function getSlackData(array $record) + { + $dataArray = array( + 'channel' => $this->channel, + 'username' => $this->username, + 'text' => '', + 'attachments' => array(), + ); + + if ($this->formatter) { + $message = $this->formatter->format($record); + } else { + $message = $record['message']; + } + + if ($this->useAttachment) { + $attachment = array( + 'fallback' => $message, + 'color' => $this->getAttachmentColor($record['level']), + 'fields' => array(), + ); + + if ($this->useShortAttachment) { + $attachment['title'] = $record['level_name']; + $attachment['text'] = $message; + } else { + $attachment['title'] = 'Message'; + $attachment['text'] = $message; + $attachment['fields'][] = array( + 'title' => 'Level', + 'value' => $record['level_name'], + 'short' => true, + ); + } + + if ($this->includeContextAndExtra) { + if (!empty($record['extra'])) { + if ($this->useShortAttachment) { + $attachment['fields'][] = array( + 'title' => "Extra", + 'value' => $this->stringify($record['extra']), + 'short' => $this->useShortAttachment, + ); + } else { + // Add all extra fields as individual fields in attachment + foreach ($record['extra'] as $var => $val) { + $attachment['fields'][] = array( + 'title' => $var, + 'value' => $val, + 'short' => $this->useShortAttachment, + ); + } + } + } + + if (!empty($record['context'])) { + if ($this->useShortAttachment) { + $attachment['fields'][] = array( + 'title' => "Context", + 'value' => $this->stringify($record['context']), + 'short' => $this->useShortAttachment, + ); + } else { + // Add all context fields as individual fields in attachment + foreach ($record['context'] as $var => $val) { + $attachment['fields'][] = array( + 'title' => $var, + 'value' => $val, + 'short' => $this->useShortAttachment, + ); + } + } + } + } + + $dataArray['attachments'] = json_encode(array($attachment)); + } else { + $dataArray['text'] = $message; + } + + if ($this->iconEmoji) { + $dataArray['icon_emoji'] = ":{$this->iconEmoji}:"; + } + + return $dataArray; + } + + /** + * Returned a Slack message attachment color associated with + * provided level. + * + * @param int $level + * @return string + */ + public function getAttachmentColor($level) + { + switch (true) { + case $level >= Logger::ERROR: + return 'danger'; + case $level >= Logger::WARNING: + return 'warning'; + case $level >= Logger::INFO: + return 'good'; + default: + return '#e3e4e6'; + } + } + + /** + * Stringifies an array of key/value pairs to be used in attachment fields + * + * @param array $fields + * @return string + */ + public function stringify($fields) + { + $string = ''; + foreach ($fields as $var => $val) { + $string .= $var.': '.$this->lineFormatter->stringify($val)." | "; + } + + $string = rtrim($string, " |"); + + return $string; + } +} diff --git a/src/Monolog/Handler/SlackHandler.php b/src/Monolog/Handler/SlackHandler.php index 3de2576a..1bbe8241 100644 --- a/src/Monolog/Handler/SlackHandler.php +++ b/src/Monolog/Handler/SlackHandler.php @@ -12,7 +12,7 @@ namespace Monolog\Handler; use Monolog\Logger; -use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; /** * Sends notifications through Slack API @@ -29,45 +29,10 @@ class SlackHandler extends SocketHandler private $token; /** - * Slack channel (encoded ID or name) - * @var string + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord */ - private $channel; - - /** - * Name of a bot - * @var string - */ - private $username; - - /** - * Emoji icon name - * @var string - */ - private $iconEmoji; - - /** - * Whether the message should be added to Slack as attachment (plain text otherwise) - * @var bool - */ - private $useAttachment; - - /** - * Whether the the context/extra messages added to Slack as attachments are in a short style - * @var bool - */ - private $useShortAttachment; - - /** - * Whether the attachment should include context and extra data - * @var bool - */ - private $includeContextAndExtra; - - /** - * @var LineFormatter - */ - private $lineFormatter; + private $slackRecord; /** * @param string $token Slack API token @@ -81,25 +46,36 @@ class SlackHandler extends SocketHandler * @param bool $includeContextAndExtra Whether the attachment should include context and extra data * @throws MissingExtensionException If no OpenSSL PHP extension configured */ - public function __construct($token, $channel, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false) - { + public function __construct( + $token, + $channel, + $username = 'Monolog', + $useAttachment = true, + $iconEmoji = null, + $level = Logger::CRITICAL, + $bubble = true, + $useShortAttachment = false, + $includeContextAndExtra = false + ) { if (!extension_loaded('openssl')) { - throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); + throw new MissingExtensionException( + 'The OpenSSL PHP extension is required to use the SlackHandler' + ); } parent::__construct('ssl://slack.com:443', $level, $bubble); - $this->token = $token; - $this->channel = $channel; - $this->username = $username; - $this->iconEmoji = trim($iconEmoji, ':'); - $this->useAttachment = $useAttachment; - $this->useShortAttachment = $useShortAttachment; - $this->includeContextAndExtra = $includeContextAndExtra; + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $this->formatter + ); - if ($this->includeContextAndExtra && $this->useShortAttachment) { - $this->lineFormatter = new LineFormatter; - } + $this->token = $token; } /** @@ -136,88 +112,8 @@ class SlackHandler extends SocketHandler */ protected function prepareContentData($record) { - $dataArray = array( - 'token' => $this->token, - 'channel' => $this->channel, - 'username' => $this->username, - 'text' => '', - 'attachments' => array(), - ); - - if ($this->formatter) { - $message = $this->formatter->format($record); - } else { - $message = $record['message']; - } - - if ($this->useAttachment) { - $attachment = array( - 'fallback' => $message, - 'color' => $this->getAttachmentColor($record['level']), - 'fields' => array(), - ); - - if ($this->useShortAttachment) { - $attachment['title'] = $record['level_name']; - $attachment['text'] = $message; - } else { - $attachment['title'] = 'Message'; - $attachment['text'] = $message; - $attachment['fields'][] = array( - 'title' => 'Level', - 'value' => $record['level_name'], - 'short' => true, - ); - } - - if ($this->includeContextAndExtra) { - if (!empty($record['extra'])) { - if ($this->useShortAttachment) { - $attachment['fields'][] = array( - 'title' => "Extra", - 'value' => $this->stringify($record['extra']), - 'short' => $this->useShortAttachment, - ); - } else { - // Add all extra fields as individual fields in attachment - foreach ($record['extra'] as $var => $val) { - $attachment['fields'][] = array( - 'title' => $var, - 'value' => $val, - 'short' => $this->useShortAttachment, - ); - } - } - } - - if (!empty($record['context'])) { - if ($this->useShortAttachment) { - $attachment['fields'][] = array( - 'title' => "Context", - 'value' => $this->stringify($record['context']), - 'short' => $this->useShortAttachment, - ); - } else { - // Add all context fields as individual fields in attachment - foreach ($record['context'] as $var => $val) { - $attachment['fields'][] = array( - 'title' => $var, - 'value' => $val, - 'short' => $this->useShortAttachment, - ); - } - } - } - } - - $dataArray['attachments'] = json_encode(array($attachment)); - } else { - $dataArray['text'] = $message; - } - - if ($this->iconEmoji) { - $dataArray['icon_emoji'] = ":{$this->iconEmoji}:"; - } + $dataArray = $this->slackRecord->getSlackData($record); + $dataArray['token'] = $this->token; return $dataArray; } @@ -263,16 +159,7 @@ class SlackHandler extends SocketHandler */ protected function getAttachmentColor($level) { - switch (true) { - case $level >= Logger::ERROR: - return 'danger'; - case $level >= Logger::WARNING: - return 'warning'; - case $level >= Logger::INFO: - return 'good'; - default: - return '#e3e4e6'; - } + return $this->slackRecord->getAttachmentColor($level); } /** @@ -283,13 +170,6 @@ class SlackHandler extends SocketHandler */ protected function stringify($fields) { - $string = ''; - foreach ($fields as $var => $val) { - $string .= $var.': '.$this->lineFormatter->stringify($val)." | "; - } - - $string = rtrim($string, " |"); - - return $string; + return $this->slackRecord->stringify($fields); } } From 274f778b2429073231a39e3a1126c3f24d93707a Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Tue, 6 Sep 2016 23:48:20 +0300 Subject: [PATCH 04/27] Make channel in SlackRecord optional to allow for webhooks --- src/Monolog/Handler/Slack/SlackRecord.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index 3c64ef4b..95318849 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -96,12 +96,15 @@ class SlackRecord public function getSlackData(array $record) { $dataArray = array( - 'channel' => $this->channel, 'username' => $this->username, 'text' => '', 'attachments' => array(), ); + if ($this->channel) { + $dataArray['channel'] = $this->channel; + } + if ($this->formatter) { $message = $this->formatter->format($record); } else { From 4b671eb82c16bff4c941bcbf067921980400baed Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Tue, 6 Sep 2016 23:48:38 +0300 Subject: [PATCH 05/27] Add a Slack Webhooks handler using the same SlackRecord util --- src/Monolog/Handler/SlackWebhookHandler.php | 96 +++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/Monolog/Handler/SlackWebhookHandler.php diff --git a/src/Monolog/Handler/SlackWebhookHandler.php b/src/Monolog/Handler/SlackWebhookHandler.php new file mode 100644 index 00000000..1d5ab1a7 --- /dev/null +++ b/src/Monolog/Handler/SlackWebhookHandler.php @@ -0,0 +1,96 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; + +/** + * Sends notifications through Slack Webhooks + * + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + */ +class SlackWebhookHandler extends AbstractProcessingHandler +{ + /** + * Slack Webhook token + * @var string + */ + private $webhookUrl; + + /** + * Instance of the SlackRecord util class preparing data for Slack API. + * @var SlackRecord + */ + private $slackRecord; + + /** + * @param string $webhookUrl Slack Webhook URL + * @param string|null $channel Slack channel (encoded ID or name) + * @param string $username Name of a bot + * @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise) + * @param string|null $iconEmoji The emoji name to use (or null) + * @param bool $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style + * @param bool $includeContextAndExtra Whether the attachment should include context and extra data + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + */ + public function __construct( + $webhookUrl, + $channel = null, + $username = 'Monolog', + $useAttachment = true, + $iconEmoji = null, + $useShortAttachment = false, + $includeContextAndExtra = false, + $level = Logger::CRITICAL, + $bubble = true + ) { + parent::__construct($level, $bubble); + + $this->webhookUrl = $webhookUrl; + + $this->slackRecord = new SlackRecord( + $channel, + $username, + $useAttachment, + $iconEmoji, + $useShortAttachment, + $includeContextAndExtra, + $this->formatter + ); + } + + /** + * {@inheritdoc} + * + * @param array $record + */ + protected function write(array $record) + { + $postData = $this->slackRecord->getSlackData($record); + $postString = json_encode($postData); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $this->webhookUrl); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + if (defined('CURLOPT_SAFE_UPLOAD')) { + curl_setopt($ch, CURLOPT_SAFE_UPLOAD, true); + } + curl_setopt($ch, CURLOPT_POSTFIELDS, array('payload' => $postString)); + + Curl\Util::execute($ch); + } +} From 0956a74897fe3ef39ff4e5871d2e90b3c7cc95b7 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Tue, 6 Sep 2016 23:50:46 +0300 Subject: [PATCH 06/27] Adds new Slack handlers to the docs --- doc/02-handlers-formatters-processors.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/02-handlers-formatters-processors.md b/doc/02-handlers-formatters-processors.md index 9170d6df..bea968ac 100644 --- a/doc/02-handlers-formatters-processors.md +++ b/doc/02-handlers-formatters-processors.md @@ -32,7 +32,9 @@ - _PushoverHandler_: Sends mobile notifications via the [Pushover](https://www.pushover.net/) API. - _HipChatHandler_: Logs records to a [HipChat](http://hipchat.com) chat room using its API. - _FlowdockHandler_: Logs records to a [Flowdock](https://www.flowdock.com/) account. -- _SlackHandler_: Logs records to a [Slack](https://www.slack.com/) account. +- _SlackHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slack API. +- _SlackbotHandler_: Logs records to a [Slack](https://www.slack.com/) account using the Slackbot incoming hook. +- _SlackWebhookHandler_: Logs records to a [Slack](https://www.slack.com/) account using Slack Webhooks. - _MandrillHandler_: Sends emails via the Mandrill API using a [`Swift_Message`](http://swiftmailer.org/) instance. - _FleepHookHandler_: Logs records to a [Fleep](https://fleep.io/) conversation using Webhooks. - _IFTTTHandler_: Notifies an [IFTTT](https://ifttt.com/maker) trigger with the log channel, level name and message. From 115f6710b1eed135489716010d7958b838773b41 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Mon, 14 Nov 2016 23:06:48 +0200 Subject: [PATCH 07/27] Use constants for Slack colors --- src/Monolog/Handler/Slack/SlackRecord.php | 16 ++++++++++++---- tests/Monolog/Handler/SlackHandlerTest.php | 17 +++++++++-------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index 95318849..cc8493c1 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -25,6 +25,14 @@ use Monolog\Formatter\FormatterInterface; */ class SlackRecord { + const COLOR_DANGER = 'danger'; + + const COLOR_WARNING = 'warning'; + + const COLOR_GOOD = 'good'; + + const COLOR_DEFAULT = '#e3e4e6'; + /** * Slack channel (encoded ID or name) * @var string @@ -194,13 +202,13 @@ class SlackRecord { switch (true) { case $level >= Logger::ERROR: - return 'danger'; + return self::COLOR_DANGER; case $level >= Logger::WARNING: - return 'warning'; + return self::COLOR_WARNING; case $level >= Logger::INFO: - return 'good'; + return self::COLOR_GOOD; default: - return '#e3e4e6'; + return self::COLOR_DEFAULT; } } diff --git a/tests/Monolog/Handler/SlackHandlerTest.php b/tests/Monolog/Handler/SlackHandlerTest.php index a1fd8721..a2c98792 100644 --- a/tests/Monolog/Handler/SlackHandlerTest.php +++ b/tests/Monolog/Handler/SlackHandlerTest.php @@ -14,6 +14,7 @@ namespace Monolog\Handler; use Monolog\TestCase; use Monolog\Logger; use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; /** * @author Greg Kedzierski @@ -111,14 +112,14 @@ class SlackHandlerTest extends TestCase public function provideLevelColors() { return array( - array(Logger::DEBUG, '%23e3e4e6'), // escaped #e3e4e6 - array(Logger::INFO, 'good'), - array(Logger::NOTICE, 'good'), - array(Logger::WARNING, 'warning'), - array(Logger::ERROR, 'danger'), - array(Logger::CRITICAL, 'danger'), - array(Logger::ALERT, 'danger'), - array(Logger::EMERGENCY,'danger'), + array(Logger::DEBUG, urlencode(SlackRecord::COLOR_DEFAULT)), + array(Logger::INFO, SlackRecord::COLOR_GOOD), + array(Logger::NOTICE, SlackRecord::COLOR_GOOD), + array(Logger::WARNING, SlackRecord::COLOR_WARNING), + array(Logger::ERROR, SlackRecord::COLOR_DANGER), + array(Logger::CRITICAL, SlackRecord::COLOR_DANGER), + array(Logger::ALERT, SlackRecord::COLOR_DANGER), + array(Logger::EMERGENCY,SlackRecord::COLOR_DANGER), ); } From 000a21969f0a57e829434a5e7de6498afe4bef48 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Mon, 14 Nov 2016 23:07:21 +0200 Subject: [PATCH 08/27] Mark former public methods of SlackHandler as deprecated The SlackRecord could be used now --- src/Monolog/Handler/SlackHandler.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Monolog/Handler/SlackHandler.php b/src/Monolog/Handler/SlackHandler.php index 1bbe8241..249c3360 100644 --- a/src/Monolog/Handler/SlackHandler.php +++ b/src/Monolog/Handler/SlackHandler.php @@ -156,9 +156,15 @@ class SlackHandler extends SocketHandler * * @param int $level * @return string + * @deprecated Use underlying SlackRecord instead */ protected function getAttachmentColor($level) { + trigger_error( + 'SlackHandler::getAttachmentColor() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + return $this->slackRecord->getAttachmentColor($level); } @@ -167,9 +173,15 @@ class SlackHandler extends SocketHandler * * @param array $fields * @return string + * @deprecated Use underlying SlackRecord instead */ protected function stringify($fields) { + trigger_error( + 'SlackHandler::stringify() is deprecated. Use underlying SlackRecord instead.', + E_USER_DEPRECATED + ); + return $this->slackRecord->stringify($fields); } } From 862c0875d2b2ef2b690e074926ebe72a5cb0ec17 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Mon, 14 Nov 2016 23:08:40 +0200 Subject: [PATCH 09/27] Expose a getter for the SlackRecord in Slack handlers --- src/Monolog/Handler/SlackHandler.php | 5 +++++ src/Monolog/Handler/SlackWebhookHandler.php | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/Monolog/Handler/SlackHandler.php b/src/Monolog/Handler/SlackHandler.php index 249c3360..28fdfbea 100644 --- a/src/Monolog/Handler/SlackHandler.php +++ b/src/Monolog/Handler/SlackHandler.php @@ -78,6 +78,11 @@ class SlackHandler extends SocketHandler $this->token = $token; } + public function getSlackRecord() + { + return $this->slackRecord; + } + /** * {@inheritdoc} * diff --git a/src/Monolog/Handler/SlackWebhookHandler.php b/src/Monolog/Handler/SlackWebhookHandler.php index 1d5ab1a7..6b938d30 100644 --- a/src/Monolog/Handler/SlackWebhookHandler.php +++ b/src/Monolog/Handler/SlackWebhookHandler.php @@ -72,6 +72,11 @@ class SlackWebhookHandler extends AbstractProcessingHandler ); } + public function getSlackRecord() + { + return $this->slackRecord; + } + /** * {@inheritdoc} * From 2d1fbbe4232a5070f1b60b33a238a614a4a3b385 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Mon, 14 Nov 2016 23:19:45 +0200 Subject: [PATCH 10/27] Make SlackRecord::stringify() work without a line formatter --- src/Monolog/Handler/Slack/SlackRecord.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index cc8493c1..4b370b3b 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -216,10 +216,14 @@ class SlackRecord * Stringifies an array of key/value pairs to be used in attachment fields * * @param array $fields - * @return string + * @return string|null */ public function stringify($fields) { + if (!$this->lineFormatter) { + return null; + } + $string = ''; foreach ($fields as $var => $val) { $string .= $var.': '.$this->lineFormatter->stringify($val)." | "; From 7c2f58e7aa09e8aee2c000432ca086ebdc4452a0 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Mon, 14 Nov 2016 23:20:06 +0200 Subject: [PATCH 11/27] Add initial tests for SlackRecord --- .../Monolog/Handler/Slack/SlackRecordTest.php | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/Monolog/Handler/Slack/SlackRecordTest.php diff --git a/tests/Monolog/Handler/Slack/SlackRecordTest.php b/tests/Monolog/Handler/Slack/SlackRecordTest.php new file mode 100644 index 00000000..bb744d8f --- /dev/null +++ b/tests/Monolog/Handler/Slack/SlackRecordTest.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler\Slack; + +use Monolog\Logger; +use Monolog\TestCase; + +/** + * @coversDefaultClass Monolog\Handler\Slack\SlackRecord + */ +class SlackRecordTest extends TestCase +{ + public function dataGetAttachmentColor() + { + return array( + array(Logger::DEBUG, SlackRecord::COLOR_DEFAULT), + array(Logger::INFO, SlackRecord::COLOR_GOOD), + array(Logger::NOTICE, SlackRecord::COLOR_GOOD), + array(Logger::WARNING, SlackRecord::COLOR_WARNING), + array(Logger::ERROR, SlackRecord::COLOR_DANGER), + array(Logger::CRITICAL, SlackRecord::COLOR_DANGER), + array(Logger::ALERT, SlackRecord::COLOR_DANGER), + array(Logger::EMERGENCY, SlackRecord::COLOR_DANGER), + ); + } + /** + * @dataProvider dataGetAttachmentColor + * @param int $logLevel + * @param string $expectedColour RGB hex color or name of Slack color + * @covers ::getAttachmentColor + */ + public function testGetAttachmentColor($logLevel, $expectedColour) + { + $slackRecord = new SlackRecord('#test'); + $this->assertSame( + $expectedColour, + $slackRecord->getAttachmentColor($logLevel) + ); + } + + public function testStringifyReturnsNullWithNoLineFormatter() + { + $slackRecord = new SlackRecord('#test'); + $this->assertNull($slackRecord->stringify(array('foo' => 'bar'))); + } + + /** + * @return array + */ + public function dataStringify() + { + return array( + array(array(), ''), + array(array('foo' => 'bar'), 'foo: bar'), + array(array('Foo' => 'bAr'), 'Foo: bAr'), + ); + } + + /** + * @dataProvider dataStringify + */ + public function testStringifyWithLineFormatter($fields, $expectedResult) + { + $slackRecord = new SlackRecord( + '#test', + 'test', + true, + null, + true, + true + ); + + $this->assertSame($expectedResult, $slackRecord->stringify($fields)); + } +} From 97eb782e8c792e46bb3c3b54db94c8e6307d4595 Mon Sep 17 00:00:00 2001 From: Anton Nizhegorodov Date: Sun, 13 Nov 2016 22:12:31 +0200 Subject: [PATCH 12/27] No need for extra json_encode --- src/Monolog/Handler/Slack/SlackRecord.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index 4b370b3b..8d8ca7ee 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -179,7 +179,7 @@ class SlackRecord } } - $dataArray['attachments'] = json_encode(array($attachment)); + $dataArray['attachments'] = array($attachment); } else { $dataArray['text'] = $message; } From ec75076dedd92fd310f24e1eeb60214ae5ce3afd Mon Sep 17 00:00:00 2001 From: Anton Nizhegorodov Date: Sun, 13 Nov 2016 22:12:56 +0200 Subject: [PATCH 13/27] Add more tests to SlackRecordTest Conflicts: - tests/Monolog/Handler/Slack/SlackRecordTest.php - merged the tests --- .../Monolog/Handler/Slack/SlackRecordTest.php | 247 ++++++++++++++++++ 1 file changed, 247 insertions(+) diff --git a/tests/Monolog/Handler/Slack/SlackRecordTest.php b/tests/Monolog/Handler/Slack/SlackRecordTest.php index bb744d8f..2075df0f 100644 --- a/tests/Monolog/Handler/Slack/SlackRecordTest.php +++ b/tests/Monolog/Handler/Slack/SlackRecordTest.php @@ -11,6 +11,7 @@ namespace Monolog\Handler\Slack; +use Monolog\Formatter\FormatterInterface; use Monolog\Logger; use Monolog\TestCase; @@ -19,6 +20,13 @@ use Monolog\TestCase; */ class SlackRecordTest extends TestCase { + private $channel; + + protected function setUp() + { + $this->channel = 'monolog_alerts'; + } + public function dataGetAttachmentColor() { return array( @@ -47,12 +55,30 @@ class SlackRecordTest extends TestCase ); } + public function testAddsChannel() + { + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('channel', $data); + $this->assertSame($this->channel, $data['channel']); + } + public function testStringifyReturnsNullWithNoLineFormatter() { $slackRecord = new SlackRecord('#test'); $this->assertNull($slackRecord->stringify(array('foo' => 'bar'))); } + public function testAddsDefaultUsername() + { + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('username', $data); + $this->assertSame('Monolog', $data['username']); + } + /** * @return array */ @@ -81,4 +107,225 @@ class SlackRecordTest extends TestCase $this->assertSame($expectedResult, $slackRecord->stringify($fields)); } + + public function testAddsCustomUsername() + { + $username = 'Monolog bot'; + $record = new SlackRecord($this->channel, $username); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('username', $data); + $this->assertSame($username, $data['username']); + } + + public function testNoIcon() + { + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayNotHasKey('icon_emoji', $data); + } + + public function testAddsIcon() + { + $record = new SlackRecord($this->channel, 'Monolog', true, 'ghost'); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('icon_emoji', $data); + $this->assertSame(':ghost:', $data['icon_emoji']); + } + + public function testAddsEmptyTextIfUseAttachment() + { + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('text', $data); + $this->assertSame('', $data['text']); + } + + public function testAttachmentsEmptyIfNoAttachment() + { + $record = new SlackRecord($this->channel, 'Monolog', false); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('attachments', $data); + $this->assertSame(array(), $data['attachments']); + } + + public function testAddsOneAttachment() + { + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord()); + + $this->assertArrayHasKey('attachments', $data); + $this->assertArrayHasKey(0, $data['attachments']); + $this->assertInternalType('array', $data['attachments'][0]); + } + + public function testTextEqualsMessageIfNoFormatter() + { + $message = 'Test message'; + $record = new SlackRecord($this->channel, 'Monolog', false); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message, $data['text']); + } + + public function testTextEqualsFormatterOutput() + { + $formatter = $this->createMock(FormatterInterface::class); + $formatter + ->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message'] . 'test'; })); + + $message = 'Test message'; + $record = new SlackRecord($this->channel, 'Monolog', false, null, false, false, $formatter); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message . 'test', $data['text']); + } + + public function testAddsFallbackAndTextToAttachment() + { + $message = 'Test message'; + $record = new SlackRecord($this->channel); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertSame($message, $data['attachments'][0]['text']); + $this->assertSame($message, $data['attachments'][0]['fallback']); + } + + public function testMapsLevelToColorAttachmentColor() + { + $record = new SlackRecord($this->channel); + $errorLoggerRecord = $this->getRecord(Logger::ERROR); + $emergencyLoggerRecord = $this->getRecord(Logger::EMERGENCY); + $warningLoggerRecord = $this->getRecord(Logger::WARNING); + $infoLoggerRecord = $this->getRecord(Logger::INFO); + $debugLoggerRecord = $this->getRecord(Logger::DEBUG); + + $data = $record->getSlackData($errorLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']); + + $data = $record->getSlackData($emergencyLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DANGER, $data['attachments'][0]['color']); + + $data = $record->getSlackData($warningLoggerRecord); + $this->assertSame(SlackRecord::COLOR_WARNING, $data['attachments'][0]['color']); + + $data = $record->getSlackData($infoLoggerRecord); + $this->assertSame(SlackRecord::COLOR_GOOD, $data['attachments'][0]['color']); + + $data = $record->getSlackData($debugLoggerRecord); + $this->assertSame(SlackRecord::COLOR_DEFAULT, $data['attachments'][0]['color']); + } + + public function testAddsShortAttachmentWithoutContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord($this->channel, 'Monolog', true, null, true); + $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1))); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertSame($levelName, $attachment['title']); + $this->assertSame(array(), $attachment['fields']); + } + + public function testAddsShortAttachmentWithContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord($this->channel, 'Monolog', true, null, true, true); + $loggerRecord = $this->getRecord($level, 'test', array('test' => 1)); + $loggerRecord['extra'] = array('tags' => array('web')); + $data = $record->getSlackData($loggerRecord); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(2, $attachment['fields']); + $this->assertSame($levelName, $attachment['title']); + $this->assertSame( + array( + array( + 'title' => 'Extra', + 'value' => 'tags: ["web"]', + 'short' => true + ), + array( + 'title' => 'Context', + 'value' => 'test: 1', + 'short' => true + ) + ), + $attachment['fields'] + ); + } + + public function testAddsLongAttachmentWithoutContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord($this->channel, 'Monolog', true, null); + $data = $record->getSlackData($this->getRecord($level, 'test', array('test' => 1))); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(1, $attachment['fields']); + $this->assertSame('Message', $attachment['title']); + $this->assertSame( + array(array( + 'title' => 'Level', + 'value' => $levelName, + 'short' => true + )), + $attachment['fields'] + ); + } + + public function testAddsLongAttachmentWithContextAndExtra() + { + $level = Logger::ERROR; + $levelName = Logger::getLevelName($level); + $record = new SlackRecord($this->channel, 'Monolog', true, null, false, true); + $loggerRecord = $this->getRecord($level, 'test', array('test' => 1)); + $loggerRecord['extra'] = array('tags' => array('web')); + $data = $record->getSlackData($loggerRecord); + + $expectedFields = array( + array( + 'title' => 'Level', + 'value' => $levelName, + 'short' => true, + ), + array( + 'title' => 'tags', + 'value' => array('web'), + 'short' => false + ), + array( + 'title' => 'test', + 'value' => 1, + 'short' => false + ) + ); + + $attachment = $data['attachments'][0]; + $this->assertArrayHasKey('title', $attachment); + $this->assertArrayHasKey('fields', $attachment); + $this->assertCount(3, $attachment['fields']); + $this->assertSame('Message', $attachment['title']); + $this->assertSame( + $expectedFields, + $attachment['fields'] + ); + } } From 4ab8ed0a53fe308c6f1417ead393a3bada5e24fa Mon Sep 17 00:00:00 2001 From: Anton Nizhegorodov Date: Sun, 13 Nov 2016 22:32:40 +0200 Subject: [PATCH 14/27] Make sure extra/context variables are stringified when more than one level deep --- src/Monolog/Handler/Slack/SlackRecord.php | 6 +++--- tests/Monolog/Handler/Slack/SlackRecordTest.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index 8d8ca7ee..121dea11 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -96,7 +96,7 @@ class SlackRecord $this->includeContextAndExtra = $includeContextAndExtra; $this->formatter = $formatter; - if ($this->includeContextAndExtra && $this->useShortAttachment) { + if ($this->includeContextAndExtra) { $this->lineFormatter = new LineFormatter(); } } @@ -152,7 +152,7 @@ class SlackRecord foreach ($record['extra'] as $var => $val) { $attachment['fields'][] = array( 'title' => $var, - 'value' => $val, + 'value' => is_array($val) ? $this->lineFormatter->stringify($val) : $val, 'short' => $this->useShortAttachment, ); } @@ -171,7 +171,7 @@ class SlackRecord foreach ($record['context'] as $var => $val) { $attachment['fields'][] = array( 'title' => $var, - 'value' => $val, + 'value' => is_array($val) ? $this->lineFormatter->stringify($val) : $val, 'short' => $this->useShortAttachment, ); } diff --git a/tests/Monolog/Handler/Slack/SlackRecordTest.php b/tests/Monolog/Handler/Slack/SlackRecordTest.php index 2075df0f..1ef4121d 100644 --- a/tests/Monolog/Handler/Slack/SlackRecordTest.php +++ b/tests/Monolog/Handler/Slack/SlackRecordTest.php @@ -308,7 +308,7 @@ class SlackRecordTest extends TestCase ), array( 'title' => 'tags', - 'value' => array('web'), + 'value' => '["web"]', 'short' => false ), array( From 01a2ac25a2deddea55eada54b3a92eca90215866 Mon Sep 17 00:00:00 2001 From: Anton Nizhegorodov Date: Sun, 13 Nov 2016 23:02:58 +0200 Subject: [PATCH 15/27] Codereview fixes based on @stof comments --- src/Monolog/Handler/Slack/SlackRecord.php | 21 +++++++-------------- src/Monolog/Handler/SlackHandler.php | 17 +++-------------- src/Monolog/Handler/SlackbotHandler.php | 9 +++++---- 3 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index 121dea11..7e3eb7ca 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -35,7 +35,7 @@ class SlackRecord /** * Slack channel (encoded ID or name) - * @var string + * @var string|null */ private $channel; @@ -79,15 +79,8 @@ class SlackRecord */ private $lineFormatter; - public function __construct( - $channel, - $username = 'Monolog', - $useAttachment = true, - $iconEmoji = null, - $useShortAttachment = false, - $includeContextAndExtra = false, - FormatterInterface $formatter = null - ) { + public function __construct($channel = null, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, FormatterInterface $formatter = null) + { $this->channel = $channel; $this->username = $username; $this->iconEmoji = trim($iconEmoji, ':'); @@ -145,7 +138,7 @@ class SlackRecord $attachment['fields'][] = array( 'title' => "Extra", 'value' => $this->stringify($record['extra']), - 'short' => $this->useShortAttachment, + 'short' => true, ); } else { // Add all extra fields as individual fields in attachment @@ -153,7 +146,7 @@ class SlackRecord $attachment['fields'][] = array( 'title' => $var, 'value' => is_array($val) ? $this->lineFormatter->stringify($val) : $val, - 'short' => $this->useShortAttachment, + 'short' => false, ); } } @@ -164,7 +157,7 @@ class SlackRecord $attachment['fields'][] = array( 'title' => "Context", 'value' => $this->stringify($record['context']), - 'short' => $this->useShortAttachment, + 'short' => true, ); } else { // Add all context fields as individual fields in attachment @@ -172,7 +165,7 @@ class SlackRecord $attachment['fields'][] = array( 'title' => $var, 'value' => is_array($val) ? $this->lineFormatter->stringify($val) : $val, - 'short' => $this->useShortAttachment, + 'short' => false, ); } } diff --git a/src/Monolog/Handler/SlackHandler.php b/src/Monolog/Handler/SlackHandler.php index 28fdfbea..e8d585a7 100644 --- a/src/Monolog/Handler/SlackHandler.php +++ b/src/Monolog/Handler/SlackHandler.php @@ -46,21 +46,10 @@ class SlackHandler extends SocketHandler * @param bool $includeContextAndExtra Whether the attachment should include context and extra data * @throws MissingExtensionException If no OpenSSL PHP extension configured */ - public function __construct( - $token, - $channel, - $username = 'Monolog', - $useAttachment = true, - $iconEmoji = null, - $level = Logger::CRITICAL, - $bubble = true, - $useShortAttachment = false, - $includeContextAndExtra = false - ) { + public function __construct($token, $channel, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false) + { if (!extension_loaded('openssl')) { - throw new MissingExtensionException( - 'The OpenSSL PHP extension is required to use the SlackHandler' - ); + throw new MissingExtensionException('The OpenSSL PHP extension is required to use the SlackHandler'); } parent::__construct('ssl://slack.com:443', $level, $bubble); diff --git a/src/Monolog/Handler/SlackbotHandler.php b/src/Monolog/Handler/SlackbotHandler.php index 0cea9c28..de6051bc 100644 --- a/src/Monolog/Handler/SlackbotHandler.php +++ b/src/Monolog/Handler/SlackbotHandler.php @@ -40,10 +40,11 @@ class SlackbotHandler extends AbstractProcessingHandler private $channel; /** - * @param string $token Slackbot token - * @param string $channel Slack channel (encoded ID or name) - * @param int $level The minimum logging level at which this handler will be triggered - * @param bool $bubble Whether the messages that are handled can bubble up the stack or not + * @param string $slackTeam Slack team slug + * @param string $token Slackbot token + * @param string $channel Slack channel (encoded ID or name) + * @param int $level The minimum logging level at which this handler will be triggered + * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ public function __construct( $slackTeam, From 08b577c6572d8f6f0f5dcd25491b44933e36d108 Mon Sep 17 00:00:00 2001 From: Anton Nizhegorodov Date: Sun, 13 Nov 2016 23:42:09 +0200 Subject: [PATCH 16/27] Update SlackHandler tests && allow SlackRecord formatter change after construction --- src/Monolog/Handler/Slack/SlackRecord.php | 10 ++++++++++ src/Monolog/Handler/SlackHandler.php | 9 +++++++++ tests/Monolog/Handler/Slack/SlackRecordTest.php | 12 ++++++++++++ tests/Monolog/Handler/SlackHandlerTest.php | 13 ++++++++----- 4 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index 7e3eb7ca..ffe42ef2 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -226,4 +226,14 @@ class SlackRecord return $string; } + + /** + * Sets the formatter + * + * @param FormatterInterface $formatter + */ + public function setFormatter(FormatterInterface $formatter) + { + $this->formatter = $formatter; + } } diff --git a/src/Monolog/Handler/SlackHandler.php b/src/Monolog/Handler/SlackHandler.php index e8d585a7..c2c5a949 100644 --- a/src/Monolog/Handler/SlackHandler.php +++ b/src/Monolog/Handler/SlackHandler.php @@ -11,6 +11,7 @@ namespace Monolog\Handler; +use Monolog\Formatter\FormatterInterface; use Monolog\Logger; use Monolog\Handler\Slack\SlackRecord; @@ -178,4 +179,12 @@ class SlackHandler extends SocketHandler return $this->slackRecord->stringify($fields); } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } } diff --git a/tests/Monolog/Handler/Slack/SlackRecordTest.php b/tests/Monolog/Handler/Slack/SlackRecordTest.php index 1ef4121d..31a68db2 100644 --- a/tests/Monolog/Handler/Slack/SlackRecordTest.php +++ b/tests/Monolog/Handler/Slack/SlackRecordTest.php @@ -181,12 +181,24 @@ class SlackRecordTest extends TestCase ->method('format') ->will($this->returnCallback(function ($record) { return $record['message'] . 'test'; })); + $formatter2 = $this->createMock(FormatterInterface::class); + $formatter2 + ->expects($this->any()) + ->method('format') + ->will($this->returnCallback(function ($record) { return $record['message'] . 'test1'; })); + $message = 'Test message'; $record = new SlackRecord($this->channel, 'Monolog', false, null, false, false, $formatter); $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); $this->assertArrayHasKey('text', $data); $this->assertSame($message . 'test', $data['text']); + + $record->setFormatter($formatter2); + $data = $record->getSlackData($this->getRecord(Logger::WARNING, $message)); + + $this->assertArrayHasKey('text', $data); + $this->assertSame($message . 'test1', $data['text']); } public function testAddsFallbackAndTextToAttachment() diff --git a/tests/Monolog/Handler/SlackHandlerTest.php b/tests/Monolog/Handler/SlackHandlerTest.php index a2c98792..5de7885f 100644 --- a/tests/Monolog/Handler/SlackHandlerTest.php +++ b/tests/Monolog/Handler/SlackHandlerTest.php @@ -56,7 +56,10 @@ class SlackHandlerTest extends TestCase fseek($this->res, 0); $content = fread($this->res, 1024); - $this->assertRegexp('/token=myToken&channel=channel1&username=Monolog&text=&attachments=.*$/', $content); + $this->assertRegExp('/username=Monolog/', $content); + $this->assertRegExp('/channel=channel1/', $content); + $this->assertRegExp('/token=myToken/', $content); + $this->assertRegExp('/attachments/', $content); } public function testWriteContentUsesFormatterIfProvided() @@ -72,8 +75,8 @@ class SlackHandlerTest extends TestCase fseek($this->res, 0); $content2 = fread($this->res, 1024); - $this->assertRegexp('/token=myToken&channel=channel1&username=Monolog&text=test1.*$/', $content); - $this->assertRegexp('/token=myToken&channel=channel1&username=Monolog&text=foo--test2.*$/', $content2); + $this->assertRegexp('/text=test1/', $content); + $this->assertRegexp('/text=foo--test2/', $content2); } public function testWriteContentWithEmoji() @@ -83,7 +86,7 @@ class SlackHandlerTest extends TestCase fseek($this->res, 0); $content = fread($this->res, 1024); - $this->assertRegexp('/icon_emoji=%3Aalien%3A$/', $content); + $this->assertRegexp('/icon_emoji=%3Aalien%3A/', $content); } /** @@ -96,7 +99,7 @@ class SlackHandlerTest extends TestCase fseek($this->res, 0); $content = fread($this->res, 1024); - $this->assertRegexp('/color%22%3A%22'.$expectedColor.'/', $content); + $this->assertRegexp('/%5Bcolor%5D='.$expectedColor.'/', $content); } public function testWriteContentWithPlainTextMessage() From 1303dc6d72b4b1410061c380fe833b7ad07e2134 Mon Sep 17 00:00:00 2001 From: Anton Nizhegorodov Date: Mon, 14 Nov 2016 03:24:07 +0200 Subject: [PATCH 17/27] Refactor SlackRecord --- src/Monolog/Handler/Slack/SlackRecord.php | 84 +++++++++++++---------- 1 file changed, 47 insertions(+), 37 deletions(-) diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index ffe42ef2..63449164 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -115,59 +115,36 @@ class SlackRecord if ($this->useAttachment) { $attachment = array( 'fallback' => $message, + 'text' => $message, 'color' => $this->getAttachmentColor($record['level']), 'fields' => array(), ); if ($this->useShortAttachment) { $attachment['title'] = $record['level_name']; - $attachment['text'] = $message; } else { $attachment['title'] = 'Message'; - $attachment['text'] = $message; - $attachment['fields'][] = array( - 'title' => 'Level', - 'value' => $record['level_name'], - 'short' => true, - ); + $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name'], true); } if ($this->includeContextAndExtra) { - if (!empty($record['extra'])) { + foreach (array('extra', 'context') as $key) { + if (empty($record[$key])) { + continue; + } + if ($this->useShortAttachment) { - $attachment['fields'][] = array( - 'title' => "Extra", - 'value' => $this->stringify($record['extra']), - 'short' => true, + $attachment['fields'][] = $this->generateAttachmentField( + ucfirst($key), + $this->stringify($record[$key]), + true ); } else { // Add all extra fields as individual fields in attachment - foreach ($record['extra'] as $var => $val) { - $attachment['fields'][] = array( - 'title' => $var, - 'value' => is_array($val) ? $this->lineFormatter->stringify($val) : $val, - 'short' => false, - ); - } - } - } - - if (!empty($record['context'])) { - if ($this->useShortAttachment) { - $attachment['fields'][] = array( - 'title' => "Context", - 'value' => $this->stringify($record['context']), - 'short' => true, + $attachment['fields'] = array_merge( + $attachment['fields'], + $this->generateAttachmentFields($record[$key]) ); - } else { - // Add all context fields as individual fields in attachment - foreach ($record['context'] as $var => $val) { - $attachment['fields'][] = array( - 'title' => $var, - 'value' => is_array($val) ? $this->lineFormatter->stringify($val) : $val, - 'short' => false, - ); - } } } } @@ -236,4 +213,37 @@ class SlackRecord { $this->formatter = $formatter; } + + /** + * Generates attachment field + * + * @param $title + * @param $value + * @param $short + * @return array + */ + private function generateAttachmentField($title, $value, $short) + { + return array( + 'title' => $title, + 'value' => is_array($value) ? $this->lineFormatter->stringify($value) : $value, + 'short' => $short + ); + } + + /** + * Generates a collection of attachment fields from array + * + * @param array $data + * @return array + */ + private function generateAttachmentFields(array $data) + { + $fields = array(); + foreach ($data as $key => $value) { + $fields[] = $this->generateAttachmentField($key, $value, false); + } + + return $fields; + } } From d34de6bf302df3e818c1facb7b55af49f9b8131b Mon Sep 17 00:00:00 2001 From: Anton Nizhegorodov Date: Mon, 14 Nov 2016 03:33:04 +0200 Subject: [PATCH 18/27] Fix SlackRecordTest --- tests/Monolog/Handler/Slack/SlackRecordTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/Monolog/Handler/Slack/SlackRecordTest.php b/tests/Monolog/Handler/Slack/SlackRecordTest.php index 31a68db2..351b92d3 100644 --- a/tests/Monolog/Handler/Slack/SlackRecordTest.php +++ b/tests/Monolog/Handler/Slack/SlackRecordTest.php @@ -11,7 +11,6 @@ namespace Monolog\Handler\Slack; -use Monolog\Formatter\FormatterInterface; use Monolog\Logger; use Monolog\TestCase; @@ -175,13 +174,13 @@ class SlackRecordTest extends TestCase public function testTextEqualsFormatterOutput() { - $formatter = $this->createMock(FormatterInterface::class); + $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface'); $formatter ->expects($this->any()) ->method('format') ->will($this->returnCallback(function ($record) { return $record['message'] . 'test'; })); - $formatter2 = $this->createMock(FormatterInterface::class); + $formatter2 = $this->getMock('Monolog\\Formatter\\FormatterInterface'); $formatter2 ->expects($this->any()) ->method('format') From dd238892cf135c26b8a4c902709355c8afa9867a Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Tue, 15 Nov 2016 00:58:18 +0200 Subject: [PATCH 19/27] Add missing PHPDoc types --- src/Monolog/Handler/Slack/SlackRecord.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index 63449164..14325021 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -217,9 +217,9 @@ class SlackRecord /** * Generates attachment field * - * @param $title - * @param $value - * @param $short + * @param string $title + * @param string|array $value + * @param bool $short * @return array */ private function generateAttachmentField($title, $value, $short) From 5fa5e373051fedf5d261495c63277252526df149 Mon Sep 17 00:00:00 2001 From: Anton Nizhegorodov Date: Wed, 16 Nov 2016 01:31:40 +0200 Subject: [PATCH 20/27] Fix SlackHandler Slack API requires to json_encode the attachment field. --- src/Monolog/Handler/SlackHandler.php | 4 ++++ tests/Monolog/Handler/SlackHandlerTest.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Monolog/Handler/SlackHandler.php b/src/Monolog/Handler/SlackHandler.php index c2c5a949..79c61615 100644 --- a/src/Monolog/Handler/SlackHandler.php +++ b/src/Monolog/Handler/SlackHandler.php @@ -110,6 +110,10 @@ class SlackHandler extends SocketHandler $dataArray = $this->slackRecord->getSlackData($record); $dataArray['token'] = $this->token; + if (!empty($dataArray['attachments'])) { + $dataArray['attachments'] = json_encode($dataArray['attachments']); + } + return $dataArray; } diff --git a/tests/Monolog/Handler/SlackHandlerTest.php b/tests/Monolog/Handler/SlackHandlerTest.php index 5de7885f..b12b01f4 100644 --- a/tests/Monolog/Handler/SlackHandlerTest.php +++ b/tests/Monolog/Handler/SlackHandlerTest.php @@ -99,7 +99,7 @@ class SlackHandlerTest extends TestCase fseek($this->res, 0); $content = fread($this->res, 1024); - $this->assertRegexp('/%5Bcolor%5D='.$expectedColor.'/', $content); + $this->assertRegexp('/%22color%22%3A%22'.$expectedColor.'/', $content); } public function testWriteContentWithPlainTextMessage() From 506e1b99e44027a55f2921ae5ad5c2fa6d4fcbd0 Mon Sep 17 00:00:00 2001 From: Anton Nizhegorodov Date: Wed, 16 Nov 2016 01:35:25 +0200 Subject: [PATCH 21/27] Make message formatting behaviour same as previous version --- src/Monolog/Handler/SlackHandler.php | 8 ++++++++ src/Monolog/Handler/SlackWebhookHandler.php | 18 +++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Monolog/Handler/SlackHandler.php b/src/Monolog/Handler/SlackHandler.php index 79c61615..c2cca0c8 100644 --- a/src/Monolog/Handler/SlackHandler.php +++ b/src/Monolog/Handler/SlackHandler.php @@ -191,4 +191,12 @@ class SlackHandler extends SocketHandler return $this; } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } } diff --git a/src/Monolog/Handler/SlackWebhookHandler.php b/src/Monolog/Handler/SlackWebhookHandler.php index 6b938d30..f4c9f780 100644 --- a/src/Monolog/Handler/SlackWebhookHandler.php +++ b/src/Monolog/Handler/SlackWebhookHandler.php @@ -11,8 +11,8 @@ namespace Monolog\Handler; +use Monolog\Formatter\FormatterInterface; use Monolog\Logger; -use Monolog\Formatter\LineFormatter; use Monolog\Handler\Slack\SlackRecord; /** @@ -98,4 +98,20 @@ class SlackWebhookHandler extends AbstractProcessingHandler Curl\Util::execute($ch); } + + public function setFormatter(FormatterInterface $formatter) + { + parent::setFormatter($formatter); + $this->slackRecord->setFormatter($formatter); + + return $this; + } + + public function getFormatter() + { + $formatter = parent::getFormatter(); + $this->slackRecord->setFormatter($formatter); + + return $formatter; + } } From 8b2b13546e442c2321b23e74fe17570073213b6a Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Sat, 19 Nov 2016 13:13:39 +0200 Subject: [PATCH 22/27] Format constructors on a single line This is for consistency with the rest of the handlers. --- src/Monolog/Handler/SlackWebhookHandler.php | 13 ++----------- src/Monolog/Handler/SlackbotHandler.php | 9 ++------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/Monolog/Handler/SlackWebhookHandler.php b/src/Monolog/Handler/SlackWebhookHandler.php index f4c9f780..a2df0f1a 100644 --- a/src/Monolog/Handler/SlackWebhookHandler.php +++ b/src/Monolog/Handler/SlackWebhookHandler.php @@ -46,17 +46,8 @@ class SlackWebhookHandler extends AbstractProcessingHandler * @param int $level The minimum logging level at which this handler will be triggered * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct( - $webhookUrl, - $channel = null, - $username = 'Monolog', - $useAttachment = true, - $iconEmoji = null, - $useShortAttachment = false, - $includeContextAndExtra = false, - $level = Logger::CRITICAL, - $bubble = true - ) { + public function __construct($webhookUrl, $channel = null, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $useShortAttachment = false, $includeContextAndExtra = false, $level = Logger::CRITICAL, $bubble = true) + { parent::__construct($level, $bubble); $this->webhookUrl = $webhookUrl; diff --git a/src/Monolog/Handler/SlackbotHandler.php b/src/Monolog/Handler/SlackbotHandler.php index de6051bc..baead525 100644 --- a/src/Monolog/Handler/SlackbotHandler.php +++ b/src/Monolog/Handler/SlackbotHandler.php @@ -46,13 +46,8 @@ class SlackbotHandler extends AbstractProcessingHandler * @param int $level The minimum logging level at which this handler will be triggered * @param bool $bubble Whether the messages that are handled can bubble up the stack or not */ - public function __construct( - $slackTeam, - $token, - $channel, - $level = Logger::CRITICAL, - $bubble = true - ) { + public function __construct($slackTeam, $token, $channel, $level = Logger::CRITICAL, $bubble = true) + { parent::__construct($level, $bubble); $this->slackTeam = $slackTeam; From 26b526d9fb5acb7da74299f18e969fa3ea6f7e28 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Sat, 19 Nov 2016 13:39:27 +0200 Subject: [PATCH 23/27] Do not put empty Slack attachments array by default --- src/Monolog/Handler/Slack/SlackRecord.php | 1 - tests/Monolog/Handler/Slack/SlackRecordTest.php | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Monolog/Handler/Slack/SlackRecord.php b/src/Monolog/Handler/Slack/SlackRecord.php index 14325021..cb0b8717 100644 --- a/src/Monolog/Handler/Slack/SlackRecord.php +++ b/src/Monolog/Handler/Slack/SlackRecord.php @@ -99,7 +99,6 @@ class SlackRecord $dataArray = array( 'username' => $this->username, 'text' => '', - 'attachments' => array(), ); if ($this->channel) { diff --git a/tests/Monolog/Handler/Slack/SlackRecordTest.php b/tests/Monolog/Handler/Slack/SlackRecordTest.php index 351b92d3..d6c6c229 100644 --- a/tests/Monolog/Handler/Slack/SlackRecordTest.php +++ b/tests/Monolog/Handler/Slack/SlackRecordTest.php @@ -143,13 +143,12 @@ class SlackRecordTest extends TestCase $this->assertSame('', $data['text']); } - public function testAttachmentsEmptyIfNoAttachment() + public function testAttachmentsNotPresentIfNoAttachment() { $record = new SlackRecord($this->channel, 'Monolog', false); $data = $record->getSlackData($this->getRecord()); - $this->assertArrayHasKey('attachments', $data); - $this->assertSame(array(), $data['attachments']); + $this->assertArrayNotHasKey('attachments', $data); } public function testAddsOneAttachment() From 43e1e69bade020e99ec95663fb264af83095531d Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Sat, 19 Nov 2016 18:45:03 +0200 Subject: [PATCH 24/27] Add unit tests for SlackWebhookHandler --- .../Handler/SlackWebhookHandlerTest.php | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/Monolog/Handler/SlackWebhookHandlerTest.php diff --git a/tests/Monolog/Handler/SlackWebhookHandlerTest.php b/tests/Monolog/Handler/SlackWebhookHandlerTest.php new file mode 100644 index 00000000..cc9b7bab --- /dev/null +++ b/tests/Monolog/Handler/SlackWebhookHandlerTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\Slack\SlackRecord; + +/** + * @author Haralan Dobrev + * @see https://api.slack.com/incoming-webhooks + * @coversDefaultClass Monolog\Handler\SlackWebhookHandler + */ +class SlackWebhookHandlerTest extends TestCase +{ + const WEBHOOK_URL = 'https://hooks.slack.com/services/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E'; + + /** + * @covers ::__construct + * @covers ::getSlackRecord + */ + public function testConstructorMinimal() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $slackRecord = $handler->getSlackRecord(); + $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord); + $this->assertEquals(array( + 'username' => 'Monolog', + 'text' => '', + 'attachments' => array( + array( + 'fallback' => 'test', + 'text' => 'test', + 'color' => SlackRecord::COLOR_WARNING, + 'fields' => array( + array( + 'title' => 'Level', + 'value' => 'WARNING', + 'short' => true, + ), + ), + 'title' => 'Message', + ), + ), + ), $slackRecord->getSlackData($this->getRecord())); + } + + /** + * @covers ::__construct + * @covers ::getSlackRecord + */ + public function testConstructorFull() + { + $handler = new SlackWebhookHandler( + self::WEBHOOK_URL, + 'test-channel', + 'test-username', + false, + ':ghost:', + false, + false, + Logger::DEBUG, + false + ); + + $slackRecord = $handler->getSlackRecord(); + $this->assertInstanceOf('Monolog\Handler\Slack\SlackRecord', $slackRecord); + $this->assertEquals(array( + 'username' => 'test-username', + 'text' => 'test', + 'channel' => 'test-channel', + 'icon_emoji' => ':ghost:', + ), $slackRecord->getSlackData($this->getRecord())); + } + + /** + * @covers ::getFormatter + */ + public function testGetFormatter() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $formatter = $handler->getFormatter(); + $this->assertInstanceOf('Monolog\Formatter\FormatterInterface', $formatter); + } + + /** + * @covers ::setFormatter + */ + public function testSetFormatter() + { + $handler = new SlackWebhookHandler(self::WEBHOOK_URL); + $formatter = new LineFormatter(); + $handler->setFormatter($formatter); + $this->assertSame($formatter, $handler->getFormatter()); + } +} From 9fbc28642815907bd2be08935222351e115f07c4 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Sat, 19 Nov 2016 18:48:10 +0200 Subject: [PATCH 25/27] Add unit test for SlackbotHandler --- tests/Monolog/Handler/SlackbotHandlerTest.php | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/Monolog/Handler/SlackbotHandlerTest.php diff --git a/tests/Monolog/Handler/SlackbotHandlerTest.php b/tests/Monolog/Handler/SlackbotHandlerTest.php new file mode 100644 index 00000000..b1b02bde --- /dev/null +++ b/tests/Monolog/Handler/SlackbotHandlerTest.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Monolog\Handler; + +use Monolog\TestCase; +use Monolog\Logger; + +/** + * @author Haralan Dobrev + * @see https://slack.com/apps/A0F81R8ET-slackbot + * @coversDefaultClass Monolog\Handler\SlackbotHandler + */ +class SlackbotHandlerTest extends TestCase +{ + /** + * @covers ::__construct + */ + public function testConstructorMinimal() + { + $handler = new SlackbotHandler('test-team', 'test-token', 'test-channel'); + $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler); + } + + /** + * @covers ::__construct + */ + public function testConstructorFull() + { + $handler = new SlackbotHandler( + 'test-team', + 'test-token', + 'test-channel', + Logger::DEBUG, + false + ); + $this->assertInstanceOf('Monolog\Handler\AbstractProcessingHandler', $handler); + } +} From 45de570954c570deb66686f06da9854ea361779f Mon Sep 17 00:00:00 2001 From: Nils Adermann Date: Thu, 24 Nov 2016 17:28:53 +0100 Subject: [PATCH 26/27] Don't even try to attempt normalizing iterators or generators in context Iterators and Generators may not be rewindable, so foreach is not safe to use on them. Iterators and especially Generators may trigger irreversible actions on calling next(), so iterating over all values can potentially cause harm, e.g. imagine an iterator over a set of HTTP POST requests that are sent when the next value is requested . The only sufficiently safe thing to iterate and include here are primitive arrays. --- src/Monolog/Formatter/NormalizerFormatter.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Monolog/Formatter/NormalizerFormatter.php b/src/Monolog/Formatter/NormalizerFormatter.php index f037d54a..d4414882 100644 --- a/src/Monolog/Formatter/NormalizerFormatter.php +++ b/src/Monolog/Formatter/NormalizerFormatter.php @@ -70,17 +70,13 @@ class NormalizerFormatter implements FormatterInterface return $data; } - if (is_array($data) || $data instanceof \Traversable) { + if (is_array($data)) { $normalized = array(); $count = 1; - if ($data instanceof \Generator && !$data->valid()) { - return array('...' => 'Generator is already consumed, aborting'); - } - foreach ($data as $key => $value) { if ($count++ >= 1000) { - $normalized['...'] = 'Over 1000 items ('.($data instanceof \Generator ? 'generator function' : count($data).' total').'), aborting normalization'; + $normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization'; break; } $normalized[$key] = $this->normalize($value); From bad29cb8d18ab0315e6c477751418a82c850d558 Mon Sep 17 00:00:00 2001 From: Jordi Boggiano Date: Sat, 26 Nov 2016 01:15:39 +0100 Subject: [PATCH 27/27] Finalize 1.22 readme --- CHANGELOG.mdown | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.mdown b/CHANGELOG.mdown index 79bf6285..76ea0ead 100644 --- a/CHANGELOG.mdown +++ b/CHANGELOG.mdown @@ -1,12 +1,13 @@ -### 1.22.0 (2016-11-XX) +### 1.22.0 (2016-11-26) + * Added SlackbotHandler and SlackWebhookHandler to set up Slack integration more easily * Added MercurialProcessor to add mercurial revision and branch names to log records * Added support for AWS SDK v3 in DynamoDbHandler * Fixed fatal errors occuring when normalizing generators that have been fully consumed * Fixed RollbarHandler to include a level (rollbar level), monolog_level (original name), channel and datetime (unix) * Fixed RollbarHandler not flushing records automatically, calling close() explicitly is not necessary anymore * Fixed SyslogUdpHandler to avoid sending empty frames - * Fixed a few PHP7 compatibility issues + * Fixed a few PHP 7.0 and 7.1 compatibility issues ### 1.21.0 (2016-07-29)