Skip to content

Commit

Permalink
Version 2 of monolog-slack (#9)
Browse files Browse the repository at this point in the history
This PR started in order to support monolog v2 but it ended up containing more stuff.
  • Loading branch information
gmponos authored Dec 15, 2019
1 parent 2e0d67f commit 9f82085
Show file tree
Hide file tree
Showing 17 changed files with 146 additions and 269 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
language: php

php:
- 7.1
- 7.2
- 7.3
- 7.4
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

All Notable changes to `monolog-slack` will be documented in this file see this [url](http://keepachangelog.com/)

## [v2.0.0-RC1] - NEXT RELEASE DATE

### Changes
- [BC] Minimum required version is PHP 7.2
- [BC] Minimum required version is monolog 2.0
- [BC] Remove custom Client implementation. Since PSR-18 exists I decided to use a standard client instead of a custom implementation.
- Everything under `Webthink\MonologSlack\Utility` has been removedWhatFailureGroupHandler
- SlackWebhookHandler only accepts a `Psr\Http\Client\ClientInterface`
- [BC] The `SlackWebhookHandler` previously was silently failing in case the client was throwing an exception. This behaviour has changed.
if you still want the handler to fail silently you will have to wrap it with the `WhatFailureGroupHandler` of monolog
- [BC] All classes are made final.

## [v1.3.0] - 2019-12-13

### Changes
Expand Down
24 changes: 19 additions & 5 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
Copyright 2018 George Bonos
# The MIT License (MIT)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Copyright (c) 2018 George Mponos <[email protected]>

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
> Permission is hereby granted, free of charge, to any person obtaining a copy
> of this software and associated documentation files (the "Software"), to deal
> in the Software without restriction, including without limitation the rights
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> copies of the Software, and to permit persons to whom the Software is
> furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in
> all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> THE SOFTWARE.
33 changes: 13 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,21 @@ Monolog already has a handler for Slack using Webhooks but I am not in favor of
Current package is not able to send more than 2000 characters but it is able to send until 2000 characters
and be well formatted.

**Flexibility**

- The Handler on this package allows you to pass your own PSR-18 client that can be configured in your own way.

**Performance**

- Monolog has the `WhatFailureGroupHandler` but I consider it simpler not to wrap my handler around another handler
and have a simpler and faster logic [see](https://github.com/Seldaek/monolog/issues/920)
- SlackWebhookHandler does not have timeouts and it executes retries when slack service is down [see](https://github.com/Seldaek/monolog/pull/846#issuecomment-373522968)
Because you are allowed to pass your own PSR-18 client you can have it configured in your own way.

**Formatting**

- Current package gives you the ability to add a custom formatter to the `SlackwebhookHandler` in order to format Attachments.
Monolog allows you to pass a formatter to SlackHandlers but the formatter is applied only to simple messages of slack
and they are not applied for Attachments.
- I have created my custom formatters and I tend to like the formatting of Slack Records that I have created more than the one that monolog has.
- I have created my own custom formatters. I like the formatting of Slack Records that I have more than the one that monolog has.

## Install

Expand All @@ -42,19 +45,22 @@ $ composer require webthink/monolog-slack
### Simple initialization
You can initialize a `SlackWebhookHandler` simple with the following lines:

`$handler = new SlackWebhookHandler('your_webhook_url');`
```php
$client = new PSR18Client(); // PSR18Client does not exist. Use your own implementation.
$requestFactory = new PSR17RequestFactory(); // PSR18Client does not exist. Use your own implementation.
$handler = new SlackWebhookHandler($client, $requestFactory, 'your_webhook_url');
```

## Formatters

### Inject custom formatter

Now if you need to pass a custom slack formatter then you need to do the following:
Now if you need to pass a custom slack formatter then you can to do the following:

`$hanlder->setFormatter($yourFormatter);`

- **Note-1:** The formatter passed inside the slack handler must be an instance of `SlackFormatterInterface`.
- **Note-2:** If you do not pass a custom Formatter SlackWebhookHandler users the `SlackLineFormatter` by default.
- **Note-3:** Some of the settings passed during constructing the Handler are overridden by the Formatter passed.
- **Note-2:** If you do not pass a custom Formatter SlackWebhookHandler uses the `SlackLineFormatter` by default.

### SlackLineFormatter

Expand All @@ -68,19 +74,6 @@ Now if you need to pass a custom slack formatter then you need to do the followi

![slacklongattachementformatter](docs/slacklongattachementformatter.PNG)

## HTTP Client.

### Initialize with a PSR-18 HTTP Client.

Inside `SlackWebhookHandler` you can inject your [PSR-18](https://www.php-fig.org/psr/psr-18) HTTP client.

```php
$handler = new SlackWebhookHandler('your_webhook_url', null, null, LogLevel::ERROR, true, $client);`
```

If no PSR-18 HTTP client is passed as argument then `SlackwebhookHandler` initializes a `\Http\Adapter\Guzzle6\Client` by default.
The initialized client has Timeout and Connection-Timeout to 1 second.

## Changelog

Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
Expand Down
10 changes: 6 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@
}
],
"require": {
"php": "^7.1",
"php": "^7.2",
"ext-json": "*",
"monolog/monolog": "^1.24 || ^2.0",
"php-http/guzzle6-adapter": "^2.0"
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5",
"webthink/codesniffer": "^2"
"webthink/codesniffer": "^2",
"guzzlehttp/psr7": "^1.5"
},
"autoload": {
"psr-4": {
Expand All @@ -31,7 +33,7 @@
}
},
"scripts": {
"tests": "php vendor/bin/phpunit --testsuite UnitTests",
"tests": "php vendor/bin/phpunit --testsuite UnitTests --color=always",
"phpcbf": "php vendor/bin/phpcbf src tests",
"phpcs": "php vendor/bin/phpcs src tests"
},
Expand Down
1 change: 1 addition & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<config name="ignore_warnings_on_exit" value="1" />

<rule ref="./vendor/webthink/codesniffer/src/Webthink/ruleset.xml">
<exclude name="PEAR.Commenting.FunctionComment" />
<exclude name="Webthink.PHP.ObjectCalls.ArrayCall" />
<exclude name="Webthink.WhiteSpace.OperatorSpacing" />
</rule>
Expand Down
14 changes: 3 additions & 11 deletions src/Formatter/AbstractSlackAttachmentFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public function format(array $record): array
* @param int $depth
* @return mixed
*/
protected function normalize($data, $depth = 0)
protected function normalize($data, int $depth = 0)
{
if ($data === null || is_scalar($data)) {
return $this->normalizeScalar($data);
Expand All @@ -108,11 +108,7 @@ protected function normalize($data, $depth = 0)
return $data;
}

/**
* @param Throwable $e
* @return array
*/
protected function bcNormalizeException(Throwable $e): array
protected function normalizeException(Throwable $e, int $depth = 0): array
{
return [
'class' => get_class($e),
Expand Down Expand Up @@ -145,10 +141,6 @@ protected function getAttachmentColor(int $level): string
return $logLevels[$level];
}

/**
* @param string $string
* @return string
*/
protected function truncateStringIfNeeded(string $string): string
{
if (strlen($string) > 1950) {
Expand All @@ -175,7 +167,7 @@ private function normalizeObject($data)
}

if ($data instanceof Throwable) {
return $this->bcNormalizeException($data);
return $this->normalizeException($data);
}

$class = get_class($data);
Expand Down
2 changes: 1 addition & 1 deletion src/Formatter/SlackLineFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*
* @author George Mponos <[email protected]>
*/
class SlackLineFormatter extends NormalizerFormatter implements SlackFormatterInterface
final class SlackLineFormatter extends NormalizerFormatter implements SlackFormatterInterface
{
/**
* Username to use as display for the webhook
Expand Down
2 changes: 1 addition & 1 deletion src/Formatter/SlackLongAttachmentFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*
* @author George Mponos <[email protected]>
*/
class SlackLongAttachmentFormatter extends AbstractSlackAttachmentFormatter
final class SlackLongAttachmentFormatter extends AbstractSlackAttachmentFormatter
{
/**
* @param array $record
Expand Down
6 changes: 1 addition & 5 deletions src/Formatter/SlackShortAttachmentFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@
*
* @author George Mponos <[email protected]>
*/
class SlackShortAttachmentFormatter extends AbstractSlackAttachmentFormatter
final class SlackShortAttachmentFormatter extends AbstractSlackAttachmentFormatter
{
/**
* @param array $record
* @return array
*/
protected function formatFields(array $record): array
{
$value = $this->truncateStringIfNeeded($this->toJson($record, true));
Expand Down
94 changes: 29 additions & 65 deletions src/Handler/SlackWebhookHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,91 +2,61 @@

namespace Webthink\MonologSlack\Handler;

use GuzzleHttp\Psr7\Request;
use GuzzleHttp\RequestOptions;
use Monolog\Formatter\FormatterInterface;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Handler\HandlerInterface;
use Monolog\Logger;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use Webthink\MonologSlack\Formatter\SlackFormatterInterface;
use Webthink\MonologSlack\Formatter\SlackLineFormatter;
use Webthink\MonologSlack\Utility\ClientInterface;

/**
* Sends notifications through a Slack Webhook.
*
* @author George Mponos <[email protected]>
*/
class SlackWebhookHandler extends AbstractProcessingHandler
final class SlackWebhookHandler extends AbstractProcessingHandler
{
/**
* @var string
*/
private $webhook;

/**
* @var string|null
* @var ClientInterface
*/
private $username;
private $client;

/**
* @var string|null
* @var string
*/
private $useCustomEmoji;
private $webhook;

/**
* @var \Webthink\MonologSlack\Utility\ClientInterface|\Psr\Http\Client\ClientInterface
* @var RequestFactoryInterface
*/
private $client;
private $requestFactory;

/**
* @param string $webhook The slack webhook URL
* @param string|null $username Display name that will be used on the slack message
* @param string|null $useCustomEmoji The custom emoji you want to use. Set null if you do not wish to use a custom one.
* @param int|string $level The minimum logging level at which this handler will be triggered
* @param ClientInterface $client
* @param RequestFactoryInterface $requestFactory
* @param string $webhook Slack Webhook string
* @param string|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 \Webthink\MonologSlack\Utility\ClientInterface|\Psr\Http\Client\ClientInterface|null $client
*/
public function __construct(
ClientInterface $client,
RequestFactoryInterface $requestFactory,
string $webhook,
?string $username = null,
?string $useCustomEmoji = null,
$level = Logger::ERROR,
bool $bubble = true,
$client = null
bool $bubble = true
) {
parent::__construct($level, $bubble);

$this->webhook = $webhook;
if (!$username !== null) {
@trigger_error('Argument $username is deprecated and will be remove on 2.x version. Instead pass your custom formatter.', E_USER_DEPRECATED);
}

if (!$useCustomEmoji !== null) {
@trigger_error('Argument $useCustomEmoji is deprecated and will be remove on 2.x version. Instead pass your custom formatter.', E_USER_DEPRECATED);
}

$this->username = $username;
$this->useCustomEmoji = $useCustomEmoji;

if ($client === null) {
$client = \Http\Adapter\Guzzle6\Client::createWithConfig([
RequestOptions::TIMEOUT => 1,
RequestOptions::CONNECT_TIMEOUT => 1,
RequestOptions::HTTP_ERRORS => false,
]);
}

if ($client instanceof ClientInterface) {
@trigger_error('Using the custom HTTP Client implementation is deprecated and will be removed on 2.x. Use a PSR-18 HTTP Client instead.', E_USER_DEPRECATED);
}

$this->client = $client;
$this->requestFactory = $requestFactory;
$this->webhook = $webhook;
}

/**
* @param FormatterInterface $formatter
* @return self
* @return HandlerInterface
* @throws \InvalidArgumentException
*/
public function setFormatter(FormatterInterface $formatter): HandlerInterface
Expand All @@ -98,32 +68,26 @@ public function setFormatter(FormatterInterface $formatter): HandlerInterface
return parent::setFormatter($formatter);
}

/**
* @param array $record
* @return void
*/
protected function write(array $record): void
{
if ($this->client instanceof \Psr\Http\Client\ClientInterface) {
$body = json_encode($record['formatted']);
if ($body === false) {
throw new \InvalidArgumentException('Could not format record to json');
};
$body = json_encode($record['formatted']);
if ($body === false) {
throw new \InvalidArgumentException('Could not format record to json');
};

$this->client->sendRequest(
new Request('POST', $this->webhook, ['Content-Type' => ['application/json']], $body)
);
return;
}
$request = $this->requestFactory->createRequest('POST', $this->webhook);
$request = $request->withHeader('Content-Type', ['application/json']);
$request->getBody()->write($body);
$request->getBody()->rewind();

$this->client->send($this->webhook, $record['formatted']);
$this->client->sendRequest($request);
}

/**
* @return SlackLineFormatter
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new SlackLineFormatter($this->username, $this->useCustomEmoji);
return new SlackLineFormatter();
}
}
Loading

0 comments on commit 9f82085

Please sign in to comment.