From 393e243499c1c573a942a469e1656d9e95a84469 Mon Sep 17 00:00:00 2001 From: Haralan Dobrev Date: Mon, 5 Sep 2016 02:56:57 +0300 Subject: [PATCH 01/24] 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 02/24] 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 03/24] 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 04/24] 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 05/24] 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 06/24] 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 07/24] 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 08/24] 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 09/24] 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 10/24] 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 11/24] 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 12/24] 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 13/24] 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 14/24] 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 15/24] 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 16/24] 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 17/24] 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 18/24] 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 19/24] 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 20/24] 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 21/24] 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 22/24] 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 23/24] 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 24/24] 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); + } +}