1<?php declare(strict_types=1);
2
3/*
4 * This file is part of the Monolog package.
5 *
6 * (c) Jordi Boggiano <j.boggiano@seld.be>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Monolog\Handler;
13
14use Monolog\Formatter\FormatterInterface;
15use Monolog\Logger;
16use Monolog\Utils;
17use Monolog\Handler\Slack\SlackRecord;
18
19/**
20 * Sends notifications through Slack Webhooks
21 *
22 * @author Haralan Dobrev <hkdobrev@gmail.com>
23 * @see    https://api.slack.com/incoming-webhooks
24 */
25class SlackWebhookHandler extends AbstractProcessingHandler
26{
27    /**
28     * Slack Webhook token
29     * @var string
30     */
31    private $webhookUrl;
32
33    /**
34     * Instance of the SlackRecord util class preparing data for Slack API.
35     * @var SlackRecord
36     */
37    private $slackRecord;
38
39    /**
40     * @param string      $webhookUrl             Slack Webhook URL
41     * @param string|null $channel                Slack channel (encoded ID or name)
42     * @param string|null $username               Name of a bot
43     * @param bool        $useAttachment          Whether the message should be added to Slack as attachment (plain text otherwise)
44     * @param string|null $iconEmoji              The emoji name to use (or null)
45     * @param bool        $useShortAttachment     Whether the the context/extra messages added to Slack as attachments are in a short style
46     * @param bool        $includeContextAndExtra Whether the attachment should include context and extra data
47     * @param string[]    $excludeFields          Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2']
48     */
49    public function __construct(
50        string $webhookUrl,
51        ?string $channel = null,
52        ?string $username = null,
53        bool $useAttachment = true,
54        ?string $iconEmoji = null,
55        bool $useShortAttachment = false,
56        bool $includeContextAndExtra = false,
57        $level = Logger::CRITICAL,
58        bool $bubble = true,
59        array $excludeFields = array()
60    ) {
61        if (!extension_loaded('curl')) {
62            throw new MissingExtensionException('The curl extension is needed to use the SlackWebhookHandler');
63        }
64
65        parent::__construct($level, $bubble);
66
67        $this->webhookUrl = $webhookUrl;
68
69        $this->slackRecord = new SlackRecord(
70            $channel,
71            $username,
72            $useAttachment,
73            $iconEmoji,
74            $useShortAttachment,
75            $includeContextAndExtra,
76            $excludeFields
77        );
78    }
79
80    public function getSlackRecord(): SlackRecord
81    {
82        return $this->slackRecord;
83    }
84
85    public function getWebhookUrl(): string
86    {
87        return $this->webhookUrl;
88    }
89
90    /**
91     * {@inheritDoc}
92     */
93    protected function write(array $record): void
94    {
95        $postData = $this->slackRecord->getSlackData($record);
96        $postString = Utils::jsonEncode($postData);
97
98        $ch = curl_init();
99        $options = array(
100            CURLOPT_URL => $this->webhookUrl,
101            CURLOPT_POST => true,
102            CURLOPT_RETURNTRANSFER => true,
103            CURLOPT_HTTPHEADER => array('Content-type: application/json'),
104            CURLOPT_POSTFIELDS => $postString,
105        );
106        if (defined('CURLOPT_SAFE_UPLOAD')) {
107            $options[CURLOPT_SAFE_UPLOAD] = true;
108        }
109
110        curl_setopt_array($ch, $options);
111
112        Curl\Util::execute($ch);
113    }
114
115    public function setFormatter(FormatterInterface $formatter): HandlerInterface
116    {
117        parent::setFormatter($formatter);
118        $this->slackRecord->setFormatter($formatter);
119
120        return $this;
121    }
122
123    public function getFormatter(): FormatterInterface
124    {
125        $formatter = parent::getFormatter();
126        $this->slackRecord->setFormatter($formatter);
127
128        return $formatter;
129    }
130}
131