Last active
August 29, 2015 14:14
-
-
Save korchasa/f9eebc49aefec4b5ba6e to your computer and use it in GitHub Desktop.
HipChat API v2 formatter and handler for Monolog
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php namespace App; | |
use Monolog\Formatter\HtmlFormatter; | |
/** | |
* Serializes a log message to HipChat message | |
* | |
* @see http://logstash.net/ | |
* @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb | |
* | |
* @author Tim Mower <[email protected]> | |
*/ | |
class HipChatLogFormatter extends HtmlFormatter | |
{ | |
function format(array $record) | |
{ | |
$record = parent::format($record); | |
return str_replace( | |
['<h1', '</h1>'], | |
['<strong', '</strong>'], | |
$record | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php namespace App; | |
use Monolog\Logger; | |
use Monolog\Handler\SocketHandler; | |
/** | |
* Sends notifications through the hipchat api to a hipchat room | |
* | |
* Notes: | |
* API token - HipChat API token | |
* Room - HipChat Room Id or name, where messages are sent | |
* Name - Name used to send the message (from) | |
* notify - Should the message trigger a notification in the clients | |
* | |
* @author Rafael Dohms <[email protected]> | |
* @see https://www.hipchat.com/docs/api | |
*/ | |
class HipChatLogHandler extends SocketHandler | |
{ | |
/** | |
* The maximum allowed length for the name used in the "from" field. | |
*/ | |
const MAXIMUM_NAME_LENGTH = 15; | |
/** | |
* The maximum allowed length for the message. | |
*/ | |
const MAXIMUM_MESSAGE_LENGTH = 9500; | |
/** | |
* @var string | |
*/ | |
private $token; | |
/** | |
* @var array | |
*/ | |
private $room; | |
/** | |
* @var boolean | |
*/ | |
private $notify; | |
/** | |
* @var string | |
*/ | |
private $format; | |
/** | |
* @var string | |
*/ | |
private $host; | |
/** | |
* @param string $token HipChat API Token | |
* @param string $room The room that should be alerted of the message (Id or Name) | |
* @param bool $notify Trigger a notification in clients or not | |
* @param int $level The minimum logging level at which this handler will be triggered | |
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not | |
* @param Boolean $useSSL Whether to connect via SSL. | |
* @param string $format The format of the messages (default to text, can be set to html if you have html in the messages) | |
* @param string $host The HipChat server hostname. | |
*/ | |
public function __construct($token, $room, $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com') | |
{ | |
$connectionString = $useSSL ? 'ssl://'.$host.':443' : $host.':80'; | |
parent::__construct($connectionString, $level, $bubble); | |
$this->token = $token; | |
$this->notify = $notify; | |
$this->room = $room; | |
$this->format = $format; | |
$this->host = $host; | |
} | |
/** | |
* {@inheritdoc} | |
* | |
* @param array $record | |
* @return string | |
*/ | |
protected function generateDataStream($record) | |
{ | |
$content = $this->buildContent($record); | |
return $this->buildHeader($content) . $content; | |
} | |
/** | |
* Builds the body of API call | |
* | |
* @param array $record | |
* @return string | |
*/ | |
private function buildContent($record) | |
{ | |
$dataArray = array( | |
'notify' => $this->notify, | |
'message' => $record['formatted'], | |
'message_format' => $this->format, | |
'color' => $this->getAlertColor($record['level']), | |
); | |
return http_build_query($dataArray); | |
} | |
/** | |
* Builds the header of the API Call | |
* | |
* @param string $content | |
* @return string | |
*/ | |
private function buildHeader($content) | |
{ | |
$header = "POST /v2/room/".$this->room."/notification?auth_token=".$this->token." HTTP/1.1\r\n"; | |
$header .= "Host: {$this->host}\r\n"; | |
$header .= "Content-Type: application/x-www-form-urlencoded\r\n"; | |
$header .= "Content-Length: " . strlen($content) . "\r\n"; | |
$header .= "\r\n"; | |
return $header; | |
} | |
/** | |
* Assigns a color to each level of log records. | |
* | |
* @param integer $level | |
* @return string | |
*/ | |
protected function getAlertColor($level) | |
{ | |
switch (true) { | |
case $level >= Logger::ERROR: | |
return 'red'; | |
case $level >= Logger::WARNING: | |
return 'yellow'; | |
case $level >= Logger::INFO: | |
return 'green'; | |
case $level == Logger::DEBUG: | |
return 'gray'; | |
default: | |
return 'yellow'; | |
} | |
} | |
/** | |
* {@inheritdoc} | |
* | |
* @param array $record | |
*/ | |
protected function write(array $record) | |
{ | |
parent::write($record); | |
$this->closeSocket(); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function handleBatch(array $records) | |
{ | |
if (count($records) == 0) { | |
return true; | |
} | |
$batchRecords = $this->combineRecords($records); | |
$handled = false; | |
foreach ($batchRecords as $batchRecord) { | |
if ($this->isHandling($batchRecord)) { | |
$this->write($batchRecord); | |
$handled = true; | |
} | |
} | |
if (!$handled) { | |
return false; | |
} | |
return false === $this->bubble; | |
} | |
/** | |
* Combines multiple records into one. Error level of the combined record | |
* will be the highest level from the given records. Datetime will be taken | |
* from the first record. | |
* | |
* @param $records | |
* @return array | |
*/ | |
private function combineRecords($records) | |
{ | |
$batchRecord = null; | |
$batchRecords = array(); | |
$messages = array(); | |
$formattedMessages = array(); | |
$level = 0; | |
$levelName = null; | |
$datetime = null; | |
foreach ($records as $record) { | |
$record = $this->processRecord($record); | |
if ($record['level'] > $level) { | |
$level = $record['level']; | |
$levelName = $record['level_name']; | |
} | |
if (null === $datetime) { | |
$datetime = $record['datetime']; | |
} | |
$messages[] = $record['message']; | |
$messgeStr = implode(PHP_EOL, $messages); | |
$formattedMessages[] = $this->getFormatter()->format($record); | |
$formattedMessageStr = implode('', $formattedMessages); | |
$batchRecord = array( | |
'message' => $messgeStr, | |
'formatted' => $formattedMessageStr, | |
'context' => array(), | |
'extra' => array(), | |
); | |
if (!$this->validateStringLength($batchRecord['formatted'], static::MAXIMUM_MESSAGE_LENGTH)) { | |
// Pop the last message and implode the remainging messages | |
$lastMessage = array_pop($messages); | |
$lastFormattedMessage = array_pop($formattedMessages); | |
$batchRecord['message'] = implode(PHP_EOL, $messages); | |
$batchRecord['formatted'] = implode('', $formattedMessages); | |
$batchRecords[] = $batchRecord; | |
$messages = array($lastMessage); | |
$formattedMessages = array($lastFormattedMessage); | |
$batchRecord = null; | |
} | |
} | |
if (null !== $batchRecord) { | |
$batchRecords[] = $batchRecord; | |
} | |
// Set the max level and datetime for all records | |
foreach ($batchRecords as &$batchRecord) { | |
$batchRecord = array_merge( | |
$batchRecord, | |
array( | |
'level' => $level, | |
'level_name' => $levelName, | |
'datetime' => $datetime | |
) | |
); | |
} | |
return $batchRecords; | |
} | |
/** | |
* Validates the length of a string. | |
* | |
* If the `mb_strlen()` function is available, it will use that, as HipChat | |
* allows UTF-8 characters. Otherwise, it will fall back to `strlen()`. | |
* | |
* Note that this might cause false failures in the specific case of using | |
* a valid name with less than 16 characters, but 16 or more bytes, on a | |
* system where `mb_strlen()` is unavailable. | |
* | |
* @param string $str | |
* @param int $length | |
* | |
* @return bool | |
*/ | |
private function validateStringLength($str, $length) | |
{ | |
if (function_exists('mb_strlen')) { | |
return (mb_strlen($str) <= $length); | |
} | |
return (strlen($str) <= $length); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$handler = new App\HipChatLogHandler( | |
Config::get('services.hipchat.token'), | |
Config::get('services.hipchat.room_id'), | |
$notify = true, | |
\Monolog\Logger::CRITICAL, | |
$bubble = true, | |
$useSSL = true, | |
'html' | |
); | |
$handler->setFormatter(new App\HipChatLogFormatter()); | |
Log::getMonolog()->pushHandler($handler); | |
$exception = new \Exception('We all die!'); | |
Log::critical($exception->getMessage(), ['trace' => $exception->getTraceAsString()]); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment