Ver Fonte

Add FleepHookHandler

This commit adds a Handler for sending logs to fleep.io
conversations using their Webhook integration
(https://fleep.io/integrations/webhooks/).
Ando Roots há 11 anos atrás
pai
commit
7944eecbd8

+ 1 - 0
README.mdown

@@ -122,6 +122,7 @@ Handlers
 - _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.
+- _FleepHookHandler_: Logs records to a [Fleep](https://fleep.io/) conversation using Webhooks.
 
 ### Log specific servers and networked logging
 

+ 170 - 0
src/Monolog/Handler/FleepHookHandler.php

@@ -0,0 +1,170 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\LineFormatter;
+use Monolog\Logger;
+
+/**
+ * Sends logs to Fleep.io using WebHook integrations
+ *
+ * @see https://fleep.io/integrations/webhooks/ Fleep Webhooks Documentation
+ * @author Ando Roots <ando@sqroot.eu>
+ */
+class FleepHookHandler extends AbstractProcessingHandler
+{
+
+    /**
+     * Fleep.io webhooks URI
+     */
+    const HOOK_ENDPOINT = 'https://fleep.io/hook/';
+
+    /**
+     * @var string Webhook token (specifies the conversation where logs are sent)
+     */
+    protected $token;
+
+    /**
+     * @var string Full URI to the webhook endpoint (HOOK_ENDPOINT + token)
+     */
+    protected $url;
+
+    /**
+     * @var array Default options to Curl
+     */
+    protected $curlOptions = array(
+        CURLOPT_POST => true,
+        CURLOPT_HTTPHEADER => array(
+            'Content-Type: application/x-www-form-urlencoded'
+        ),
+        CURLOPT_RETURNTRANSFER => true
+    );
+
+    /**
+     * Construct a new Fleep.io Handler.
+     *
+     * You'll need a Fleep.op account to use this handler.
+     * For instructions on how to create a new web hook in your conversations
+     * see https://fleep.io/integrations/webhooks/
+     *
+     * @param string $token Webhook token (ex: mTZG6s-XRfKdNTJtpVyVaA)
+     * @param bool|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
+     * @throws \LogicException
+     */
+    public function __construct($token, $level = Logger::DEBUG, $bubble = true)
+    {
+        if (!extension_loaded('curl')) {
+            throw new \LogicException('The curl extension is needed to use FleepHookHandler');
+        }
+
+        $this->token = $token;
+        $this->url = self::HOOK_ENDPOINT . $this->token;
+
+        parent::__construct($level, $bubble);
+    }
+
+    /**
+     * @return string
+     */
+    public function getToken()
+    {
+        return $this->token;
+    }
+
+    /**
+     * @return array
+     */
+    public function getCurlOptions()
+    {
+        return $this->curlOptions;
+    }
+
+    /**
+     * Returns the default formatter to use with this handler
+     *
+     * Overloaded to remove empty context and extra arrays from the end of the log message.
+     *
+     * @author Ando Roots <ando@sqroot.eu>
+     * @return LineFormatter
+     */
+    public function getDefaultFormatter()
+    {
+        return new LineFormatter(null, null, true, true);
+
+    }
+
+    /**
+     * Handles a log record
+     *
+     * @author Ando Roots <ando@sqroot.eu>
+     * @param array $record
+     */
+    protected function write(array $record)
+    {
+        $this->send($record['formatted']);
+    }
+
+
+    /**
+     * Prepares the record for sending to Fleep
+     *
+     * @author Ando Roots <ando@sqroot.eu>
+     * @param string $message The formatted log message to send
+     */
+    protected function send($message)
+    {
+        $this->addCurlOptions(
+            array(
+                CURLOPT_POSTFIELDS => http_build_query(array('message' => $message)),
+                CURLOPT_URL => $this->url,
+            )
+        );
+
+        $this->execCurl($this->curlOptions);
+
+    }
+
+    /**
+     * Sends a new Curl request
+     *
+     * @author Ando Roots <ando@sqroot.eu>
+     * @param array $options Curl parameters, including the endpoint URL and POST payload
+     */
+    protected function execCurl(array $options)
+    {
+        $curl = curl_init();
+
+        curl_setopt_array($curl, $options);
+
+        curl_exec($curl);
+        curl_close($curl);
+    }
+
+
+    /**
+     * Adds or overwrites a curl option
+     *
+     * @author Ando Roots <ando@sqroot.eu>
+     * @param array $options An assoc array of Curl options, indexed by CURL_* constants
+     * @return $this
+     */
+    public function addCurlOptions(array $options)
+    {
+        $this->curlOptions = array_replace(
+            $this->curlOptions,
+            $options
+        );
+
+        return $this;
+    }
+}

+ 200 - 0
tests/Monolog/Handler/FleepHookHandlerTest.php

@@ -0,0 +1,200 @@
+<?php
+
+/*
+ * This file is part of the Monolog package.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Monolog\Handler;
+
+use Monolog\Formatter\LineFormatter;
+use Monolog\Logger;
+use Monolog\TestCase;
+
+/**
+ * Unit tests for the FleepHookHandler
+ *
+ * @author Ando Roots <ando@sqroot.eu>
+ * @coversDefaultClass \Monolog\Handler\FleepHookHandler
+ */
+class FleepHookHandlerTest extends TestCase
+{
+    /**
+     * Default token to use in tests
+     */
+    const TOKEN = '123abc';
+
+    /**
+     * @var FleepHookHandler
+     */
+    private $handler;
+
+    /**
+     * @var Logger
+     */
+    private $logger;
+
+    public function setUp()
+    {
+        parent::setUp();
+
+        if (!extension_loaded('curl')) {
+            $this->markTestSkipped('This test requires curl extension to run');
+        }
+
+        // Create instances of the handler and logger for convenience
+        $this->handler = new FleepHookHandler(self::TOKEN);
+        $this->logger = new Logger('test');
+        $this->logger->pushHandler($this->handler);
+
+    }
+
+    /**
+     * @covers ::__construct
+     */
+    public function testConstructorSetsExpectedDefaults()
+    {
+        $this->assertEquals(self::TOKEN, $this->handler->getToken());
+        $this->assertEquals(Logger::DEBUG, $this->handler->getLevel());
+        $this->assertEquals(true, $this->handler->getBubble());
+    }
+
+    /**
+     * @covers ::write
+     */
+    public function testWriteSendsFormattedMessageToFleep()
+    {
+        $handler = $this->mockHandler(array('send'));
+
+        $message = 'theCakeIsALie';
+        $handler->expects($this->once())
+            ->method('send')
+            ->with(
+                $this->callback(
+                    function ($message) {
+                        return strstr($message, 'theCakeIsALie') && strstr($message, 'channel.ALERT');
+                    }
+                )
+            );
+
+        $this->sendLog($handler, $message);
+    }
+
+    /**
+     * @covers ::getDefaultFormatter
+     */
+    public function testHandlerUsesLineFormatterWhichIgnoresEmptyArrays()
+    {
+        $record = array(
+            'message' => 'msg',
+            'context' => array(),
+            'level' => Logger::DEBUG,
+            'level_name' => Logger::getLevelName(Logger::DEBUG),
+            'channel' => 'channel',
+            'datetime' => new \DateTime(),
+            'extra' => array(),
+        );
+
+        $expectedFormatter = new LineFormatter(null, null, true, true);
+        $expected = $expectedFormatter->format($record);
+
+        $handlerFormatter = $this->handler->getDefaultFormatter();
+        $actual = $handlerFormatter->format($record);
+
+        $this->assertEquals($expected, $actual, 'Empty context and extra arrays should not be rendered');
+
+    }
+
+    /**
+     * Tests that the URL to which the message is posted is of correct format
+     *
+     * Example: https://fleep.io/hook/mTZG6s-XRfKdNTJtpVyVaV
+     * @covers ::__construct
+     */
+    public function testFleepEndpointUrlIsConstructedCorrectly()
+    {
+        $handler = $this->mockHandler(array('execCurl'));
+
+        $token = self::TOKEN;
+
+        // Set up expectation to execCurl: receive curlOpts array where URL is correct
+        $handler->expects($this->once())
+            ->method('execCurl')
+            ->with(
+                $this->callback(
+                    function (array $curlOpts) use ($token) {
+                        return $curlOpts[CURLOPT_URL] === FleepHookHandler::HOOK_ENDPOINT . $token;
+                    }
+                )
+            );
+        $this->sendLog($handler);
+    }
+
+    /**
+     * Tests that the log message is added to the POST content, under the 'message' key
+     *
+     * @covers ::send
+     */
+    public function testSendAddsMessageToCurlOpts()
+    {
+        $handler = $this->mockHandler(array('execCurl'));
+        $handler->expects($this->once())
+            ->method('execCurl')
+            ->with(
+                $this->callback(
+                    function ($curlOpts) {
+                        parse_str($curlOpts[CURLOPT_POSTFIELDS], $body);
+                        return isset($body['message']) && strstr($body['message'], 'msg');
+                    }
+                )
+            );
+
+        $this->sendLog($handler, 'msg');
+    }
+
+    /**
+     * @covers ::addCurlOptions
+     */
+    public function testAddCurlOptionsLeavesUnspecifiedOptionsIntact()
+    {
+        $this->handler->addCurlOptions(array(CURLOPT_PROXY => 'http://localhost:3128'));
+        $this->assertArrayHasKey(CURLOPT_POST, $this->handler->getCurlOptions(), 'addCurlOpts deleted a key!');
+    }
+
+    public function testAddCurlOptionsAddsANewCurlOption()
+    {
+        $proxy = 'http://localhost:3128';
+        $this->handler->addCurlOptions(array(CURLOPT_PROXY => $proxy));
+        $options = $this->handler->getCurlOptions();
+        $this->assertEquals($options[CURLOPT_PROXY], $proxy);
+    }
+
+    /**
+     * Helper method for simulating a long sending event from the logger
+     *
+     * @param FleepHookHandler $handler
+     * @param string $message
+     */
+    private function sendLog(FleepHookHandler $handler, $message = 'test')
+    {
+        $logger = new Logger('channel');
+        $logger->pushHandler($handler);
+        $logger->addAlert($message);
+    }
+
+    /**
+     * Helper method for constructing a new mock of FleepHookHandler
+     *
+     * @param array $methods
+     * @return \PHPUnit_Framework_MockObject_MockObject
+     */
+    private function mockHandler(array $methods)
+    {
+        return $this->getMock('Monolog\Handler\FleepHookHandler', $methods, array(self::TOKEN));
+    }
+
+}