Viewing file: SlackRecord.php (10.44 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
<?php declare(strict_types=1);
/* * 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\Slack;
use Monolog\Logger; use Monolog\Utils; use Monolog\Formatter\NormalizerFormatter; use Monolog\Formatter\FormatterInterface;
/** * Slack record utility helping to log to Slack webhooks or API. * * @author Greg Kedzierski <greg@gregkedzierski.com> * @author Haralan Dobrev <hkdobrev@gmail.com> * @see https://api.slack.com/incoming-webhooks * @see https://api.slack.com/docs/message-attachments * * @phpstan-import-type FormattedRecord from \Monolog\Handler\AbstractProcessingHandler * @phpstan-import-type Record from \Monolog\Logger */ class SlackRecord { public const COLOR_DANGER = 'danger';
public const COLOR_WARNING = 'warning';
public const COLOR_GOOD = 'good';
public const COLOR_DEFAULT = '#e3e4e6';
/** * Slack channel (encoded ID or name) * @var string|null */ private $channel;
/** * Name of a bot * @var string|null */ private $username;
/** * User icon e.g. 'ghost', 'http://example.com/user.png' * @var string|null */ private $userIcon;
/** * 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;
/** * Dot separated list of fields to exclude from slack message. E.g. ['context.field1', 'extra.field2'] * @var string[] */ private $excludeFields;
/** * @var ?FormatterInterface */ private $formatter;
/** * @var NormalizerFormatter */ private $normalizerFormatter;
/** * @param string[] $excludeFields */ public function __construct( ?string $channel = null, ?string $username = null, bool $useAttachment = true, ?string $userIcon = null, bool $useShortAttachment = false, bool $includeContextAndExtra = false, array $excludeFields = array(), ?FormatterInterface $formatter = null ) { $this ->setChannel($channel) ->setUsername($username) ->useAttachment($useAttachment) ->setUserIcon($userIcon) ->useShortAttachment($useShortAttachment) ->includeContextAndExtra($includeContextAndExtra) ->excludeFields($excludeFields) ->setFormatter($formatter);
if ($this->includeContextAndExtra) { $this->normalizerFormatter = new NormalizerFormatter(); } }
/** * Returns required data in format that Slack * is expecting. * * @phpstan-param FormattedRecord $record * @phpstan-return mixed[] */ public function getSlackData(array $record): array { $dataArray = array(); $record = $this->removeExcludedFields($record);
if ($this->username) { $dataArray['username'] = $this->username; }
if ($this->channel) { $dataArray['channel'] = $this->channel; }
if ($this->formatter && !$this->useAttachment) { /** @phpstan-ignore-next-line */ $message = $this->formatter->format($record); } else { $message = $record['message']; }
if ($this->useAttachment) { $attachment = array( 'fallback' => $message, 'text' => $message, 'color' => $this->getAttachmentColor($record['level']), 'fields' => array(), 'mrkdwn_in' => array('fields'), 'ts' => $record['datetime']->getTimestamp(), 'footer' => $this->username, 'footer_icon' => $this->userIcon, );
if ($this->useShortAttachment) { $attachment['title'] = $record['level_name']; } else { $attachment['title'] = 'Message'; $attachment['fields'][] = $this->generateAttachmentField('Level', $record['level_name']); }
if ($this->includeContextAndExtra) { foreach (array('extra', 'context') as $key) { if (empty($record[$key])) { continue; }
if ($this->useShortAttachment) { $attachment['fields'][] = $this->generateAttachmentField( (string) $key, $record[$key] ); } else { // Add all extra fields as individual fields in attachment $attachment['fields'] = array_merge( $attachment['fields'], $this->generateAttachmentFields($record[$key]) ); } } }
$dataArray['attachments'] = array($attachment); } else { $dataArray['text'] = $message; }
if ($this->userIcon) { if (filter_var($this->userIcon, FILTER_VALIDATE_URL)) { $dataArray['icon_url'] = $this->userIcon; } else { $dataArray['icon_emoji'] = ":{$this->userIcon}:"; } }
return $dataArray; }
/** * Returns a Slack message attachment color associated with * provided level. */ public function getAttachmentColor(int $level): string { switch (true) { case $level >= Logger::ERROR: return static::COLOR_DANGER; case $level >= Logger::WARNING: return static::COLOR_WARNING; case $level >= Logger::INFO: return static::COLOR_GOOD; default: return static::COLOR_DEFAULT; } }
/** * Stringifies an array of key/value pairs to be used in attachment fields * * @param mixed[] $fields */ public function stringify(array $fields): string { /** @var Record $fields */ $normalized = $this->normalizerFormatter->format($fields);
$hasSecondDimension = count(array_filter($normalized, 'is_array')); $hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric'));
return $hasSecondDimension || $hasNonNumericKeys ? Utils::jsonEncode($normalized, JSON_PRETTY_PRINT|Utils::DEFAULT_JSON_FLAGS) : Utils::jsonEncode($normalized, Utils::DEFAULT_JSON_FLAGS); }
/** * Channel used by the bot when posting * * @param ?string $channel * * @return static */ public function setChannel(?string $channel = null): self { $this->channel = $channel;
return $this; }
/** * Username used by the bot when posting * * @param ?string $username * * @return static */ public function setUsername(?string $username = null): self { $this->username = $username;
return $this; }
public function useAttachment(bool $useAttachment = true): self { $this->useAttachment = $useAttachment;
return $this; }
public function setUserIcon(?string $userIcon = null): self { $this->userIcon = $userIcon;
if (\is_string($userIcon)) { $this->userIcon = trim($userIcon, ':'); }
return $this; }
public function useShortAttachment(bool $useShortAttachment = false): self { $this->useShortAttachment = $useShortAttachment;
return $this; }
public function includeContextAndExtra(bool $includeContextAndExtra = false): self { $this->includeContextAndExtra = $includeContextAndExtra;
if ($this->includeContextAndExtra) { $this->normalizerFormatter = new NormalizerFormatter(); }
return $this; }
/** * @param string[] $excludeFields */ public function excludeFields(array $excludeFields = []): self { $this->excludeFields = $excludeFields;
return $this; }
public function setFormatter(?FormatterInterface $formatter = null): self { $this->formatter = $formatter;
return $this; }
/** * Generates attachment field * * @param string|mixed[] $value * * @return array{title: string, value: string, short: false} */ private function generateAttachmentField(string $title, $value): array { $value = is_array($value) ? sprintf('```%s```', substr($this->stringify($value), 0, 1990)) : $value;
return array( 'title' => ucfirst($title), 'value' => $value, 'short' => false, ); }
/** * Generates a collection of attachment fields from array * * @param mixed[] $data * * @return array<array{title: string, value: string, short: false}> */ private function generateAttachmentFields(array $data): array { /** @var Record $data */ $normalized = $this->normalizerFormatter->format($data);
$fields = array(); foreach ($normalized as $key => $value) { $fields[] = $this->generateAttachmentField((string) $key, $value); }
return $fields; }
/** * Get a copy of record with fields excluded according to $this->excludeFields * * @phpstan-param FormattedRecord $record * * @return mixed[] */ private function removeExcludedFields(array $record): array { foreach ($this->excludeFields as $field) { $keys = explode('.', $field); $node = &$record; $lastKey = end($keys); foreach ($keys as $key) { if (!isset($node[$key])) { break; } if ($lastKey === $key) { unset($node[$key]); break; } $node = &$node[$key]; } }
return $record; } }
|