added Monolog
parent
34ffbb4f36
commit
4949b1b70a
@ -0,0 +1,239 @@
|
||||
<?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;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use Monolog\Handler\AbstractHandler;
|
||||
use Monolog\Registry;
|
||||
|
||||
/**
|
||||
* Monolog error handler
|
||||
*
|
||||
* A facility to enable logging of runtime errors, exceptions and fatal errors.
|
||||
*
|
||||
* Quick setup: <code>ErrorHandler::register($logger);</code>
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class ErrorHandler
|
||||
{
|
||||
private $logger;
|
||||
|
||||
private $previousExceptionHandler;
|
||||
private $uncaughtExceptionLevel;
|
||||
|
||||
private $previousErrorHandler;
|
||||
private $errorLevelMap;
|
||||
private $handleOnlyReportedErrors;
|
||||
|
||||
private $hasFatalErrorHandler;
|
||||
private $fatalLevel;
|
||||
private $reservedMemory;
|
||||
private $lastFatalTrace;
|
||||
private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);
|
||||
|
||||
public function __construct(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a new ErrorHandler for a given Logger
|
||||
*
|
||||
* By default it will handle errors, exceptions and fatal errors
|
||||
*
|
||||
* @param LoggerInterface $logger
|
||||
* @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
|
||||
* @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling
|
||||
* @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling
|
||||
* @return ErrorHandler
|
||||
*/
|
||||
public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null)
|
||||
{
|
||||
//Forces the autoloader to run for LogLevel. Fixes an autoload issue at compile-time on PHP5.3. See https://github.com/Seldaek/monolog/pull/929
|
||||
class_exists('\\Psr\\Log\\LogLevel', true);
|
||||
|
||||
$handler = new static($logger);
|
||||
if ($errorLevelMap !== false) {
|
||||
$handler->registerErrorHandler($errorLevelMap);
|
||||
}
|
||||
if ($exceptionLevel !== false) {
|
||||
$handler->registerExceptionHandler($exceptionLevel);
|
||||
}
|
||||
if ($fatalLevel !== false) {
|
||||
$handler->registerFatalHandler($fatalLevel);
|
||||
}
|
||||
|
||||
return $handler;
|
||||
}
|
||||
|
||||
public function registerExceptionHandler($level = null, $callPrevious = true)
|
||||
{
|
||||
$prev = set_exception_handler(array($this, 'handleException'));
|
||||
$this->uncaughtExceptionLevel = $level;
|
||||
if ($callPrevious && $prev) {
|
||||
$this->previousExceptionHandler = $prev;
|
||||
}
|
||||
}
|
||||
|
||||
public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1, $handleOnlyReportedErrors = true)
|
||||
{
|
||||
$prev = set_error_handler(array($this, 'handleError'), $errorTypes);
|
||||
$this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
|
||||
if ($callPrevious) {
|
||||
$this->previousErrorHandler = $prev ?: true;
|
||||
}
|
||||
|
||||
$this->handleOnlyReportedErrors = $handleOnlyReportedErrors;
|
||||
}
|
||||
|
||||
public function registerFatalHandler($level = null, $reservedMemorySize = 20)
|
||||
{
|
||||
register_shutdown_function(array($this, 'handleFatalError'));
|
||||
|
||||
$this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
|
||||
$this->fatalLevel = $level;
|
||||
$this->hasFatalErrorHandler = true;
|
||||
}
|
||||
|
||||
protected function defaultErrorLevelMap()
|
||||
{
|
||||
return array(
|
||||
E_ERROR => LogLevel::CRITICAL,
|
||||
E_WARNING => LogLevel::WARNING,
|
||||
E_PARSE => LogLevel::ALERT,
|
||||
E_NOTICE => LogLevel::NOTICE,
|
||||
E_CORE_ERROR => LogLevel::CRITICAL,
|
||||
E_CORE_WARNING => LogLevel::WARNING,
|
||||
E_COMPILE_ERROR => LogLevel::ALERT,
|
||||
E_COMPILE_WARNING => LogLevel::WARNING,
|
||||
E_USER_ERROR => LogLevel::ERROR,
|
||||
E_USER_WARNING => LogLevel::WARNING,
|
||||
E_USER_NOTICE => LogLevel::NOTICE,
|
||||
E_STRICT => LogLevel::NOTICE,
|
||||
E_RECOVERABLE_ERROR => LogLevel::ERROR,
|
||||
E_DEPRECATED => LogLevel::NOTICE,
|
||||
E_USER_DEPRECATED => LogLevel::NOTICE,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function handleException($e)
|
||||
{
|
||||
$this->logger->log(
|
||||
$this->uncaughtExceptionLevel === null ? LogLevel::ERROR : $this->uncaughtExceptionLevel,
|
||||
sprintf('Uncaught Exception %s: "%s" at %s line %s', Utils::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()),
|
||||
array('exception' => $e)
|
||||
);
|
||||
|
||||
if ($this->previousExceptionHandler) {
|
||||
call_user_func($this->previousExceptionHandler, $e);
|
||||
}
|
||||
|
||||
exit(255);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function handleError($code, $message, $file = '', $line = 0, $context = array())
|
||||
{
|
||||
if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries
|
||||
if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
|
||||
$level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL;
|
||||
$this->logger->log($level, self::codeToString($code).': '.$message, array('code' => $code, 'message' => $message, 'file' => $file, 'line' => $line));
|
||||
} else {
|
||||
// http://php.net/manual/en/function.debug-backtrace.php
|
||||
// As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added.
|
||||
// Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'.
|
||||
$trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
array_shift($trace); // Exclude handleError from trace
|
||||
$this->lastFatalTrace = $trace;
|
||||
}
|
||||
|
||||
if ($this->previousErrorHandler === true) {
|
||||
return false;
|
||||
} elseif ($this->previousErrorHandler) {
|
||||
return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function handleFatalError()
|
||||
{
|
||||
$this->reservedMemory = null;
|
||||
|
||||
$lastError = error_get_last();
|
||||
if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) {
|
||||
$this->logger->log(
|
||||
$this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel,
|
||||
'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
|
||||
array('code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace)
|
||||
);
|
||||
|
||||
if ($this->logger instanceof Logger) {
|
||||
foreach ($this->logger->getHandlers() as $handler) {
|
||||
if ($handler instanceof AbstractHandler) {
|
||||
$handler->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static function codeToString($code)
|
||||
{
|
||||
switch ($code) {
|
||||
case E_ERROR:
|
||||
return 'E_ERROR';
|
||||
case E_WARNING:
|
||||
return 'E_WARNING';
|
||||
case E_PARSE:
|
||||
return 'E_PARSE';
|
||||
case E_NOTICE:
|
||||
return 'E_NOTICE';
|
||||
case E_CORE_ERROR:
|
||||
return 'E_CORE_ERROR';
|
||||
case E_CORE_WARNING:
|
||||
return 'E_CORE_WARNING';
|
||||
case E_COMPILE_ERROR:
|
||||
return 'E_COMPILE_ERROR';
|
||||
case E_COMPILE_WARNING:
|
||||
return 'E_COMPILE_WARNING';
|
||||
case E_USER_ERROR:
|
||||
return 'E_USER_ERROR';
|
||||
case E_USER_WARNING:
|
||||
return 'E_USER_WARNING';
|
||||
case E_USER_NOTICE:
|
||||
return 'E_USER_NOTICE';
|
||||
case E_STRICT:
|
||||
return 'E_STRICT';
|
||||
case E_RECOVERABLE_ERROR:
|
||||
return 'E_RECOVERABLE_ERROR';
|
||||
case E_DEPRECATED:
|
||||
return 'E_DEPRECATED';
|
||||
case E_USER_DEPRECATED:
|
||||
return 'E_USER_DEPRECATED';
|
||||
}
|
||||
|
||||
return 'Unknown PHP error';
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
<?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\Formatter;
|
||||
|
||||
use Monolog\Logger;
|
||||
|
||||
/**
|
||||
* Formats a log message according to the ChromePHP array format
|
||||
*
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*/
|
||||
class ChromePHPFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Translates Monolog log levels to Wildfire levels.
|
||||
*/
|
||||
private $logLevels = array(
|
||||
Logger::DEBUG => 'log',
|
||||
Logger::INFO => 'info',
|
||||
Logger::NOTICE => 'info',
|
||||
Logger::WARNING => 'warn',
|
||||
Logger::ERROR => 'error',
|
||||
Logger::CRITICAL => 'error',
|
||||
Logger::ALERT => 'error',
|
||||
Logger::EMERGENCY => 'error',
|
||||
);
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
// Retrieve the line and file if set and remove them from the formatted extra
|
||||
$backtrace = 'unknown';
|
||||
if (isset($record['extra']['file'], $record['extra']['line'])) {
|
||||
$backtrace = $record['extra']['file'].' : '.$record['extra']['line'];
|
||||
unset($record['extra']['file'], $record['extra']['line']);
|
||||
}
|
||||
|
||||
$message = array('message' => $record['message']);
|
||||
if ($record['context']) {
|
||||
$message['context'] = $record['context'];
|
||||
}
|
||||
if ($record['extra']) {
|
||||
$message['extra'] = $record['extra'];
|
||||
}
|
||||
if (count($message) === 1) {
|
||||
$message = reset($message);
|
||||
}
|
||||
|
||||
return array(
|
||||
$record['channel'],
|
||||
$message,
|
||||
$backtrace,
|
||||
$this->logLevels[$record['level']],
|
||||
);
|
||||
}
|
||||
|
||||
public function formatBatch(array $records)
|
||||
{
|
||||
$formatted = array();
|
||||
|
||||
foreach ($records as $record) {
|
||||
$formatted[] = $this->format($record);
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
<?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\Formatter;
|
||||
|
||||
use Elastica\Document;
|
||||
|
||||
/**
|
||||
* Format a log message into an Elastica Document
|
||||
*
|
||||
* @author Jelle Vink <jelle.vink@gmail.com>
|
||||
*/
|
||||
class ElasticaFormatter extends NormalizerFormatter
|
||||
{
|
||||
/**
|
||||
* @var string Elastic search index name
|
||||
*/
|
||||
protected $index;
|
||||
|
||||
/**
|
||||
* @var string Elastic search document type
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* @param string $index Elastic Search index name
|
||||
* @param string $type Elastic Search document type
|
||||
*/
|
||||
public function __construct($index, $type)
|
||||
{
|
||||
// elasticsearch requires a ISO 8601 format date with optional millisecond precision.
|
||||
parent::__construct('Y-m-d\TH:i:s.uP');
|
||||
|
||||
$this->index = $index;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
$record = parent::format($record);
|
||||
|
||||
return $this->getDocument($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter index
|
||||
* @return string
|
||||
*/
|
||||
public function getIndex()
|
||||
{
|
||||
return $this->index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter type
|
||||
* @return string
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a log message into an Elastica Document
|
||||
*
|
||||
* @param array $record Log message
|
||||
* @return Document
|
||||
*/
|
||||
protected function getDocument($record)
|
||||
{
|
||||
$document = new Document();
|
||||
$document->setData($record);
|
||||
$document->setType($this->type);
|
||||
$document->setIndex($this->index);
|
||||
|
||||
return $document;
|
||||
}
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
<?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\Formatter;
|
||||
|
||||
/**
|
||||
* formats the record to be used in the FlowdockHandler
|
||||
*
|
||||
* @author Dominik Liebler <liebler.dominik@gmail.com>
|
||||
*/
|
||||
class FlowdockFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $source;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sourceEmail;
|
||||
|
||||
/**
|
||||
* @param string $source
|
||||
* @param string $sourceEmail
|
||||
*/
|
||||
public function __construct($source, $sourceEmail)
|
||||
{
|
||||
$this->source = $source;
|
||||
$this->sourceEmail = $sourceEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
$tags = array(
|
||||
'#logs',
|
||||
'#' . strtolower($record['level_name']),
|
||||
'#' . $record['channel'],
|
||||
);
|
||||
|
||||
foreach ($record['extra'] as $value) {
|
||||
$tags[] = '#' . $value;
|
||||
}
|
||||
|
||||
$subject = sprintf(
|
||||
'in %s: %s - %s',
|
||||
$this->source,
|
||||
$record['level_name'],
|
||||
$this->getShortMessage($record['message'])
|
||||
);
|
||||
|
||||
$record['flowdock'] = array(
|
||||
'source' => $this->source,
|
||||
'from_address' => $this->sourceEmail,
|
||||
'subject' => $subject,
|
||||
'content' => $record['message'],
|
||||
'tags' => $tags,
|
||||
'project' => $this->source,
|
||||
);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatBatch(array $records)
|
||||
{
|
||||
$formatted = array();
|
||||
|
||||
foreach ($records as $record) {
|
||||
$formatted[] = $this->format($record);
|
||||
}
|
||||
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getShortMessage($message)
|
||||
{
|
||||
static $hasMbString;
|
||||
|
||||
if (null === $hasMbString) {
|
||||
$hasMbString = function_exists('mb_strlen');
|
||||
}
|
||||
|
||||
$maxLength = 45;
|
||||
|
||||
if ($hasMbString) {
|
||||
if (mb_strlen($message, 'UTF-8') > $maxLength) {
|
||||
$message = mb_substr($message, 0, $maxLength - 4, 'UTF-8') . ' ...';
|
||||
}
|
||||
} else {
|
||||
if (strlen($message) > $maxLength) {
|
||||
$message = substr($message, 0, $maxLength - 4) . ' ...';
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
<?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\Formatter;
|
||||
|
||||
/**
|
||||
* Class FluentdFormatter
|
||||
*
|
||||
* Serializes a log message to Fluentd unix socket protocol
|
||||
*
|
||||
* Fluentd config:
|
||||
*
|
||||
* <source>
|
||||
* type unix
|
||||
* path /var/run/td-agent/td-agent.sock
|
||||
* </source>
|
||||
*
|
||||
* Monolog setup:
|
||||
*
|
||||
* $logger = new Monolog\Logger('fluent.tag');
|
||||
* $fluentHandler = new Monolog\Handler\SocketHandler('unix:///var/run/td-agent/td-agent.sock');
|
||||
* $fluentHandler->setFormatter(new Monolog\Formatter\FluentdFormatter());
|
||||
* $logger->pushHandler($fluentHandler);
|
||||
*
|
||||
* @author Andrius Putna <fordnox@gmail.com>
|
||||
*/
|
||||
class FluentdFormatter implements FormatterInterface
|
||||
{
|
||||
/**
|
||||
* @var bool $levelTag should message level be a part of the fluentd tag
|
||||
*/
|
||||
protected $levelTag = false;
|
||||
|
||||
public function __construct($levelTag = false)
|
||||
{
|
||||
if (!function_exists('json_encode')) {
|
||||
throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s FluentdUnixFormatter');
|
||||
}
|
||||
|
||||
$this->levelTag = (bool) $levelTag;
|
||||
}
|
||||
|
||||
public function isUsingLevelsInTag()
|
||||
{
|
||||
return $this->levelTag;
|
||||
}
|
||||
|
||||
public function format(array $record)
|
||||
{
|
||||
$tag = $record['channel'];
|
||||
if ($this->levelTag) {
|
||||
$tag .= '.' . strtolower($record['level_name']);
|
||||
}
|
||||
|
||||
$message = array(
|
||||
'message' => $record['message'],
|
||||
'context' => $record['context'],
|
||||
'extra' => $record['extra'],
|
||||
);
|
||||
|
||||
if (!$this->levelTag) {
|
||||
$message['level'] = $record['level'];
|
||||
$message['level_name'] = $record['level_name'];
|
||||
}
|
||||
|
||||
return json_encode(array($tag, $record['datetime']->getTimestamp(), $message));
|
||||
}
|
||||
|
||||
public function formatBatch(array $records)
|
||||
{
|
||||
$message = '';
|
||||
foreach ($records as $record) {
|
||||
$message .= $this->format($record);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
<?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\Formatter;
|
||||
|
||||
/**
|
||||
* Interface for formatters
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
interface FormatterInterface
|
||||
{
|
||||
/**
|
||||
* Formats a log record.
|
||||
*
|
||||
* @param array $record A record to format
|
||||
* @return mixed The formatted record
|
||||
*/
|
||||
public function format(array $record);
|
||||
|
||||
/**
|
||||
* Formats a set of log records.
|
||||
*
|
||||
* @param array $records A set of records to format
|
||||
* @return mixed The formatted set of records
|
||||
*/
|
||||
public function formatBatch(array $records);
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
<?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\Formatter;
|
||||
|
||||
use Monolog\Logger;
|
||||
use Gelf\Message;
|
||||
|
||||
/**
|
||||
* Serializes a log message to GELF
|
||||
* @see http://www.graylog2.org/about/gelf
|
||||
*
|
||||
* @author Matt Lehner <mlehner@gmail.com>
|
||||
*/
|
||||
class GelfMessageFormatter extends NormalizerFormatter
|
||||
{
|
||||
const DEFAULT_MAX_LENGTH = 32766;
|
||||
|
||||
/**
|
||||
* @var string the name of the system for the Gelf log message
|
||||
*/
|
||||
protected $systemName;
|
||||
|
||||
/**
|
||||
* @var string a prefix for 'extra' fields from the Monolog record (optional)
|
||||
*/
|
||||
protected $extraPrefix;
|
||||
|
||||
/**
|
||||
* @var string a prefix for 'context' fields from the Monolog record (optional)
|
||||
*/
|
||||
protected $contextPrefix;
|
||||
|
||||
/**
|
||||
* @var int max length per field
|
||||
*/
|
||||
protected $maxLength;
|
||||
|
||||
/**
|
||||
* Translates Monolog log levels to Graylog2 log priorities.
|
||||
*/
|
||||
private $logLevels = array(
|
||||
Logger::DEBUG => 7,
|
||||
Logger::INFO => 6,
|
||||
Logger::NOTICE => 5,
|
||||
Logger::WARNING => 4,
|
||||
Logger::ERROR => 3,
|
||||
Logger::CRITICAL => 2,
|
||||
Logger::ALERT => 1,
|
||||
Logger::EMERGENCY => 0,
|
||||
);
|
||||
|
||||
public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $maxLength = null)
|
||||
{
|
||||
parent::__construct('U.u');
|
||||
|
||||
$this->systemName = $systemName ?: gethostname();
|
||||
|
||||
$this->extraPrefix = $extraPrefix;
|
||||
$this->contextPrefix = $contextPrefix;
|
||||
$this->maxLength = is_null($maxLength) ? self::DEFAULT_MAX_LENGTH : $maxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
$record = parent::format($record);
|
||||
|
||||
if (!isset($record['datetime'], $record['message'], $record['level'])) {
|
||||
throw new \InvalidArgumentException('The record should at least contain datetime, message and level keys, '.var_export($record, true).' given');
|
||||
}
|
||||
|
||||
$message = new Message();
|
||||
$message
|
||||
->setTimestamp($record['datetime'])
|
||||
->setShortMessage((string) $record['message'])
|
||||
->setHost($this->systemName)
|
||||
->setLevel($this->logLevels[$record['level']]);
|
||||
|
||||
// message length + system name length + 200 for padding / metadata
|
||||
$len = 200 + strlen((string) $record['message']) + strlen($this->systemName);
|
||||
|
||||
if ($len > $this->maxLength) {
|
||||
$message->setShortMessage(substr($record['message'], 0, $this->maxLength));
|
||||
}
|
||||
|
||||
if (isset($record['channel'])) {
|
||||
$message->setFacility($record['channel']);
|
||||
}
|
||||
if (isset($record['extra']['line'])) {
|
||||
$message->setLine($record['extra']['line']);
|
||||
unset($record['extra']['line']);
|
||||
}
|
||||
if (isset($record['extra']['file'])) {
|
||||
$message->setFile($record['extra']['file']);
|
||||
unset($record['extra']['file']);
|
||||
}
|
||||
|
||||
foreach ($record['extra'] as $key => $val) {
|
||||
$val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
|
||||
$len = strlen($this->extraPrefix . $key . $val);
|
||||
if ($len > $this->maxLength) {
|
||||
$message->setAdditional($this->extraPrefix . $key, substr($val, 0, $this->maxLength));
|
||||
break;
|
||||
}
|
||||
$message->setAdditional($this->extraPrefix . $key, $val);
|
||||
}
|
||||
|
||||
foreach ($record['context'] as $key => $val) {
|
||||
$val = is_scalar($val) || null === $val ? $val : $this->toJson($val);
|
||||
$len = strlen($this->contextPrefix . $key . $val);
|
||||
if ($len > $this->maxLength) {
|
||||
$message->setAdditional($this->contextPrefix . $key, substr($val, 0, $this->maxLength));
|
||||
break;
|
||||
}
|
||||
$message->setAdditional($this->contextPrefix . $key, $val);
|
||||
}
|
||||
|
||||
if (null === $message->getFile() && isset($record['context']['exception']['file'])) {
|
||||
if (preg_match("/^(.+):([0-9]+)$/", $record['context']['exception']['file'], $matches)) {
|
||||
$message->setFile($matches[1]);
|
||||
$message->setLine($matches[2]);
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
<?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\Formatter;
|
||||
|
||||
use Monolog\Logger;
|
||||
|
||||
/**
|
||||
* Formats incoming records into an HTML table
|
||||
*
|
||||
* This is especially useful for html email logging
|
||||
*
|
||||
* @author Tiago Brito <tlfbrito@gmail.com>
|
||||
*/
|
||||
class HtmlFormatter extends NormalizerFormatter
|
||||
{
|
||||
/**
|
||||
* Translates Monolog log levels to html color priorities.
|
||||
*/
|
||||
protected $logLevels = array(
|
||||
Logger::DEBUG => '#cccccc',
|
||||
Logger::INFO => '#468847',
|
||||
Logger::NOTICE => '#3a87ad',
|
||||
Logger::WARNING => '#c09853',
|
||||
Logger::ERROR => '#f0ad4e',
|
||||
Logger::CRITICAL => '#FF7708',
|
||||
Logger::ALERT => '#C12A19',
|
||||
Logger::EMERGENCY => '#000000',
|
||||
);
|
||||
|
||||
/**
|
||||
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
|
||||
*/
|
||||
public function __construct($dateFormat = null)
|
||||
{
|
||||
parent::__construct($dateFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an HTML table row
|
||||
*
|
||||
* @param string $th Row header content
|
||||
* @param string $td Row standard cell content
|
||||
* @param bool $escapeTd false if td content must not be html escaped
|
||||
* @return string
|
||||
*/
|
||||
protected function addRow($th, $td = ' ', $escapeTd = true)
|
||||
{
|
||||
$th = htmlspecialchars($th, ENT_NOQUOTES, 'UTF-8');
|
||||
if ($escapeTd) {
|
||||
$td = '<pre>'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'</pre>';
|
||||
}
|
||||
|
||||
return "<tr style=\"padding: 4px;text-align: left;\">\n<th style=\"vertical-align: top;background: #ccc;color: #000\" width=\"100\">$th:</th>\n<td style=\"padding: 4px;text-align: left;vertical-align: top;background: #eee;color: #000\">".$td."</td>\n</tr>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a HTML h1 tag
|
||||
*
|
||||
* @param string $title Text to be in the h1
|
||||
* @param int $level Error level
|
||||
* @return string
|
||||
*/
|
||||
protected function addTitle($title, $level)
|
||||
{
|
||||
$title = htmlspecialchars($title, ENT_NOQUOTES, 'UTF-8');
|
||||
|
||||
return '<h1 style="background: '.$this->logLevels[$level].';color: #ffffff;padding: 5px;" class="monolog-output">'.$title.'</h1>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a log record.
|
||||
*
|
||||
* @param array $record A record to format
|
||||
* @return mixed The formatted record
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
$output = $this->addTitle($record['level_name'], $record['level']);
|
||||
$output .= '<table cellspacing="1" width="100%" class="monolog-output">';
|
||||
|
||||
$output .= $this->addRow('Message', (string) $record['message']);
|
||||
$output .= $this->addRow('Time', $record['datetime']->format($this->dateFormat));
|
||||
$output .= $this->addRow('Channel', $record['channel']);
|
||||
if ($record['context']) {
|
||||
$embeddedTable = '<table cellspacing="1" width="100%">';
|
||||
foreach ($record['context'] as $key => $value) {
|
||||
$embeddedTable .= $this->addRow($key, $this->convertToString($value));
|
||||
}
|
||||
$embeddedTable .= '</table>';
|
||||
$output .= $this->addRow('Context', $embeddedTable, false);
|
||||
}
|
||||
if ($record['extra']) {
|
||||
$embeddedTable = '<table cellspacing="1" width="100%">';
|
||||
foreach ($record['extra'] as $key => $value) {
|
||||
$embeddedTable .= $this->addRow($key, $this->convertToString($value));
|
||||
}
|
||||
$embeddedTable .= '</table>';
|
||||
$output .= $this->addRow('Extra', $embeddedTable, false);
|
||||
}
|
||||
|
||||
return $output.'</table>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a set of log records.
|
||||
*
|
||||
* @param array $records A set of records to format
|
||||
* @return mixed The formatted set of records
|
||||
*/
|
||||
public function formatBatch(array $records)
|
||||
{
|
||||
$message = '';
|
||||
foreach ($records as $record) {
|
||||
$message .= $this->format($record);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
protected function convertToString($data)
|
||||
{
|
||||
if (null === $data || is_scalar($data)) {
|
||||
return (string) $data;
|
||||
}
|
||||
|
||||
$data = $this->normalize($data);
|
||||
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
|
||||
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
return str_replace('\\/', '/', json_encode($data));
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
<?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\Formatter;
|
||||
|
||||
use Exception;
|
||||
use Monolog\Utils;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Encodes whatever record data is passed to it as json
|
||||
*
|
||||
* This can be useful to log to databases or remote APIs
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class JsonFormatter extends NormalizerFormatter
|
||||
{
|
||||
const BATCH_MODE_JSON = 1;
|
||||
const BATCH_MODE_NEWLINES = 2;
|
||||
|
||||
protected $batchMode;
|
||||
protected $appendNewline;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $includeStacktraces = false;
|
||||
|
||||
/**
|
||||
* @param int $batchMode
|
||||
* @param bool $appendNewline
|
||||
*/
|
||||
public function __construct($batchMode = self::BATCH_MODE_JSON, $appendNewline = true)
|
||||
{
|
||||
$this->batchMode = $batchMode;
|
||||
$this->appendNewline = $appendNewline;
|
||||
}
|
||||
|
||||
/**
|
||||
* The batch mode option configures the formatting style for
|
||||
* multiple records. By default, multiple records will be
|
||||
* formatted as a JSON-encoded array. However, for
|
||||
* compatibility with some API endpoints, alternative styles
|
||||
* are available.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getBatchMode()
|
||||
{
|
||||
return $this->batchMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if newlines are appended to every formatted record
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAppendingNewlines()
|
||||
{
|
||||
return $this->appendNewline;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatBatch(array $records)
|
||||
{
|
||||
switch ($this->batchMode) {
|
||||
case static::BATCH_MODE_NEWLINES:
|
||||
return $this->formatBatchNewlines($records);
|
||||
|
||||
case static::BATCH_MODE_JSON:
|
||||
default:
|
||||
return $this->formatBatchJson($records);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $include
|
||||
*/
|
||||
public function includeStacktraces($include = true)
|
||||
{
|
||||
$this->includeStacktraces = $include;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a JSON-encoded array of records.
|
||||
*
|
||||
* @param array $records
|
||||
* @return string
|
||||
*/
|
||||
protected function formatBatchJson(array $records)
|
||||
{
|
||||
return $this->toJson($this->normalize($records), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use new lines to separate records instead of a
|
||||
* JSON-encoded array.
|
||||
*
|
||||
* @param array $records
|
||||
* @return string
|
||||
*/
|
||||
protected function formatBatchNewlines(array $records)
|
||||
{
|
||||
$instance = $this;
|
||||
|
||||
$oldNewline = $this->appendNewline;
|
||||
$this->appendNewline = false;
|
||||
array_walk($records, function (&$value, $key) use ($instance) {
|
||||
$value = $instance->format($value);
|
||||
});
|
||||
$this->appendNewline = $oldNewline;
|
||||
|
||||
return implode("\n", $records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes given $data.
|
||||
*
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function normalize($data, $depth = 0)
|
||||
{
|
||||
if ($depth > 9) {
|
||||
return 'Over 9 levels deep, aborting normalization';
|
||||
}
|
||||
|
||||
if (is_array($data) || $data instanceof \Traversable) {
|
||||
$normalized = array();
|
||||
|
||||
$count = 1;
|
||||
foreach ($data as $key => $value) {
|
||||
if ($count++ > 1000) {
|
||||
$normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
|
||||
break;
|
||||
}
|
||||
|
||||
$normalized[$key] = $this->normalize($value, $depth+1);
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
if ($data instanceof Exception || $data instanceof Throwable) {
|
||||
return $this->normalizeException($data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes given exception with or without its own stack trace based on
|
||||
* `includeStacktraces` property.
|
||||
*
|
||||
* @param Exception|Throwable $e
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function normalizeException($e)
|
||||
{
|
||||
// TODO 2.0 only check for Throwable
|
||||
if (!$e instanceof Exception && !$e instanceof Throwable) {
|
||||
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e));
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'class' => Utils::getClass($e),
|
||||
'message' => $e->getMessage(),
|
||||
'code' => $e->getCode(),
|
||||
'file' => $e->getFile().':'.$e->getLine(),
|
||||
);
|
||||
|
||||
if ($this->includeStacktraces) {
|
||||
$trace = $e->getTrace();
|
||||
foreach ($trace as $frame) {
|
||||
if (isset($frame['file'])) {
|
||||
$data['trace'][] = $frame['file'].':'.$frame['line'];
|
||||
} elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
|
||||
// We should again normalize the frames, because it might contain invalid items
|
||||
$data['trace'][] = $frame['function'];
|
||||
} else {
|
||||
// We should again normalize the frames, because it might contain invalid items
|
||||
$data['trace'][] = $this->normalize($frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($previous = $e->getPrevious()) {
|
||||
$data['previous'] = $this->normalizeException($previous);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
<?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\Formatter;
|
||||
|
||||
use Monolog\Utils;
|
||||
|
||||
/**
|
||||
* Formats incoming records into a one-line string
|
||||
*
|
||||
* This is especially useful for logging to files
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*/
|
||||
class LineFormatter extends NormalizerFormatter
|
||||
{
|
||||
const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
|
||||
|
||||
protected $format;
|
||||
protected $allowInlineLineBreaks;
|
||||
protected $ignoreEmptyContextAndExtra;
|
||||
protected $includeStacktraces;
|
||||
|
||||
/**
|
||||
* @param string $format The format of the message
|
||||
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
|
||||
* @param bool $allowInlineLineBreaks Whether to allow inline line breaks in log entries
|
||||
* @param bool $ignoreEmptyContextAndExtra
|
||||
*/
|
||||
public function __construct($format = null, $dateFormat = null, $allowInlineLineBreaks = false, $ignoreEmptyContextAndExtra = false)
|
||||
{
|
||||
$this->format = $format ?: static::SIMPLE_FORMAT;
|
||||
$this->allowInlineLineBreaks = $allowInlineLineBreaks;
|
||||
$this->ignoreEmptyContextAndExtra = $ignoreEmptyContextAndExtra;
|
||||
parent::__construct($dateFormat);
|
||||
}
|
||||
|
||||
public function includeStacktraces($include = true)
|
||||
{
|
||||
$this->includeStacktraces = $include;
|
||||
if ($this->includeStacktraces) {
|
||||
$this->allowInlineLineBreaks = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function allowInlineLineBreaks($allow = true)
|
||||
{
|
||||
$this->allowInlineLineBreaks = $allow;
|
||||
}
|
||||
|
||||
public function ignoreEmptyContextAndExtra($ignore = true)
|
||||
{
|
||||
$this->ignoreEmptyContextAndExtra = $ignore;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
$vars = parent::format($record);
|
||||
|
||||
$output = $this->format;
|
||||
|
||||
foreach ($vars['extra'] as $var => $val) {
|
||||
if (false !== strpos($output, '%extra.'.$var.'%')) {
|
||||
$output = str_replace('%extra.'.$var.'%', $this->stringify($val), $output);
|
||||
unset($vars['extra'][$var]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
foreach ($vars['context'] as $var => $val) {
|
||||
if (false !== strpos($output, '%context.'.$var.'%')) {
|
||||
$output = str_replace('%context.'.$var.'%', $this->stringify($val), $output);
|
||||
unset($vars['context'][$var]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->ignoreEmptyContextAndExtra) {
|
||||
if (empty($vars['context'])) {
|
||||
unset($vars['context']);
|
||||
$output = str_replace('%context%', '', $output);
|
||||
}
|
||||
|
||||
if (empty($vars['extra'])) {
|
||||
unset($vars['extra']);
|
||||
$output = str_replace('%extra%', '', $output);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($vars as $var => $val) {
|
||||
if (false !== strpos($output, '%'.$var.'%')) {
|
||||
$output = str_replace('%'.$var.'%', $this->stringify($val), $output);
|
||||
}
|
||||
}
|
||||
|
||||
// remove leftover %extra.xxx% and %context.xxx% if any
|
||||
if (false !== strpos($output, '%')) {
|
||||
$output = preg_replace('/%(?:extra|context)\..+?%/', '', $output);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function formatBatch(array $records)
|
||||
{
|
||||
$message = '';
|
||||
foreach ($records as $record) {
|
||||
$message .= $this->format($record);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function stringify($value)
|
||||
{
|
||||
return $this->replaceNewlines($this->convertToString($value));
|
||||
}
|
||||
|
||||
protected function normalizeException($e)
|
||||
{
|
||||
// TODO 2.0 only check for Throwable
|
||||
if (!$e instanceof \Exception && !$e instanceof \Throwable) {
|
||||
throw new \InvalidArgumentException('Exception/Throwable expected, got '.gettype($e).' / '.Utils::getClass($e));
|
||||
}
|
||||
|
||||
$previousText = '';
|
||||
if ($previous = $e->getPrevious()) {
|
||||
do {
|
||||
$previousText .= ', '.Utils::getClass($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine();
|
||||
} while ($previous = $previous->getPrevious());
|
||||
}
|
||||
|
||||
$str = '[object] ('.Utils::getClass($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')';
|
||||
if ($this->includeStacktraces) {
|
||||
$str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n";
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
protected function convertToString($data)
|
||||
{
|
||||
if (null === $data || is_bool($data)) {
|
||||
return var_export($data, true);
|
||||
}
|
||||
|
||||
if (is_scalar($data)) {
|
||||
return (string) $data;
|
||||
}
|
||||
|
||||
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
|
||||
return $this->toJson($data, true);
|
||||
}
|
||||
|
||||
return str_replace('\\/', '/', @json_encode($data));
|
||||
}
|
||||
|
||||
protected function replaceNewlines($str)
|
||||
{
|
||||
if ($this->allowInlineLineBreaks) {
|
||||
if (0 === strpos($str, '{')) {
|
||||
return str_replace(array('\r', '\n'), array("\r", "\n"), $str);
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
return str_replace(array("\r\n", "\r", "\n"), ' ', $str);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
<?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\Formatter;
|
||||
|
||||
/**
|
||||
* Encodes message information into JSON in a format compatible with Loggly.
|
||||
*
|
||||
* @author Adam Pancutt <adam@pancutt.com>
|
||||
*/
|
||||
class LogglyFormatter extends JsonFormatter
|
||||
{
|
||||
/**
|
||||
* Overrides the default batch mode to new lines for compatibility with the
|
||||
* Loggly bulk API.
|
||||
*
|
||||
* @param int $batchMode
|
||||
*/
|
||||
public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false)
|
||||
{
|
||||
parent::__construct($batchMode, $appendNewline);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the 'timestamp' parameter for indexing by Loggly.
|
||||
*
|
||||
* @see https://www.loggly.com/docs/automated-parsing/#json
|
||||
* @see \Monolog\Formatter\JsonFormatter::format()
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
if (isset($record["datetime"]) && ($record["datetime"] instanceof \DateTime)) {
|
||||
$record["timestamp"] = $record["datetime"]->format("Y-m-d\TH:i:s.uO");
|
||||
// TODO 2.0 unset the 'datetime' parameter, retained for BC
|
||||
}
|
||||
|
||||
return parent::format($record);
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
<?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\Formatter;
|
||||
|
||||
/**
|
||||
* Serializes a log message to Logstash Event Format
|
||||
*
|
||||
* @see http://logstash.net/
|
||||
* @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb
|
||||
*
|
||||
* @author Tim Mower <timothy.mower@gmail.com>
|
||||
*/
|
||||
class LogstashFormatter extends NormalizerFormatter
|
||||
{
|
||||
const V0 = 0;
|
||||
const V1 = 1;
|
||||
|
||||
/**
|
||||
* @var string the name of the system for the Logstash log message, used to fill the @source field
|
||||
*/
|
||||
protected $systemName;
|
||||
|
||||
/**
|
||||
* @var string an application name for the Logstash log message, used to fill the @type field
|
||||
*/
|
||||
protected $applicationName;
|
||||
|
||||
/**
|
||||
* @var string a prefix for 'extra' fields from the Monolog record (optional)
|
||||
*/
|
||||
protected $extraPrefix;
|
||||
|
||||
/**
|
||||
* @var string a prefix for 'context' fields from the Monolog record (optional)
|
||||
*/
|
||||
protected $contextPrefix;
|
||||
|
||||
/**
|
||||
* @var int logstash format version to use
|
||||
*/
|
||||
protected $version;
|
||||
|
||||
/**
|
||||
* @param string $applicationName the application that sends the data, used as the "type" field of logstash
|
||||
* @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine
|
||||
* @param string $extraPrefix prefix for extra keys inside logstash "fields"
|
||||
* @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_
|
||||
* @param int $version the logstash format version to use, defaults to 0
|
||||
*/
|
||||
public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_', $version = self::V0)
|
||||
{
|
||||
// logstash requires a ISO 8601 format date with optional millisecond precision.
|
||||
parent::__construct('Y-m-d\TH:i:s.uP');
|
||||
|
||||
$this->systemName = $systemName ?: gethostname();
|
||||
$this->applicationName = $applicationName;
|
||||
$this->extraPrefix = $extraPrefix;
|
||||
$this->contextPrefix = $contextPrefix;
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
$record = parent::format($record);
|
||||
|
||||
if ($this->version === self::V1) {
|
||||
$message = $this->formatV1($record);
|
||||
} else {
|
||||
$message = $this->formatV0($record);
|
||||
}
|
||||
|
||||
return $this->toJson($message) . "\n";
|
||||
}
|
||||
|
||||
protected function formatV0(array $record)
|
||||
{
|
||||
if (empty($record['datetime'])) {
|
||||
$record['datetime'] = gmdate('c');
|
||||
}
|
||||
$message = array(
|
||||
'@timestamp' => $record['datetime'],
|
||||
'@source' => $this->systemName,
|
||||
'@fields' => array(),
|
||||
);
|
||||
if (isset($record['message'])) {
|
||||
$message['@message'] = $record['message'];
|
||||
}
|
||||
if (isset($record['channel'])) {
|
||||
$message['@tags'] = array($record['channel']);
|
||||
$message['@fields']['channel'] = $record['channel'];
|
||||
}
|
||||
if (isset($record['level'])) {
|
||||
$message['@fields']['level'] = $record['level'];
|
||||
}
|
||||
if ($this->applicationName) {
|
||||
$message['@type'] = $this->applicationName;
|
||||
}
|
||||
if (isset($record['extra']['server'])) {
|
||||
$message['@source_host'] = $record['extra']['server'];
|
||||
}
|
||||
if (isset($record['extra']['url'])) {
|
||||
$message['@source_path'] = $record['extra']['url'];
|
||||
}
|
||||
if (!empty($record['extra'])) {
|
||||
foreach ($record['extra'] as $key => $val) {
|
||||
$message['@fields'][$this->extraPrefix . $key] = $val;
|
||||
}
|
||||
}
|
||||
if (!empty($record['context'])) {
|
||||
foreach ($record['context'] as $key => $val) {
|
||||
$message['@fields'][$this->contextPrefix . $key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
protected function formatV1(array $record)
|
||||
{
|
||||
if (empty($record['datetime'])) {
|
||||
$record['datetime'] = gmdate('c');
|
||||
}
|
||||
$message = array(
|
||||
'@timestamp' => $record['datetime'],
|
||||
'@version' => 1,
|
||||
'host' => $this->systemName,
|
||||
);
|
||||
if (isset($record['message'])) {
|
||||
$message['message'] = $record['message'];
|
||||
}
|
||||
if (isset($record['channel'])) {
|
||||
$message['type'] = $record['channel'];
|
||||
$message['channel'] = $record['channel'];
|
||||
}
|
||||
if (isset($record['level_name'])) {
|
||||
$message['level'] = $record['level_name'];
|
||||
}
|
||||
if ($this->applicationName) {
|
||||
$message['type'] = $this->applicationName;
|
||||
}
|
||||
if (!empty($record['extra'])) {
|
||||
foreach ($record['extra'] as $key => $val) {
|
||||
$message[$this->extraPrefix . $key] = $val;
|
||||
}
|
||||
}
|
||||
if (!empty($record['context'])) {
|
||||
foreach ($record['context'] as $key => $val) {
|
||||
$message[$this->contextPrefix . $key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
<?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\Formatter;
|
||||
|
||||
use Monolog\Utils;
|
||||
|
||||
/**
|
||||
* Formats a record for use with the MongoDBHandler.
|
||||
*
|
||||
* @author Florian Plattner <me@florianplattner.de>
|
||||
*/
|
||||
class MongoDBFormatter implements FormatterInterface
|
||||
{
|
||||
private $exceptionTraceAsString;
|
||||
private $maxNestingLevel;
|
||||
|
||||
/**
|
||||
* @param int $maxNestingLevel 0 means infinite nesting, the $record itself is level 1, $record['context'] is 2
|
||||
* @param bool $exceptionTraceAsString set to false to log exception traces as a sub documents instead of strings
|
||||
*/
|
||||
public function __construct($maxNestingLevel = 3, $exceptionTraceAsString = true)
|
||||
{
|
||||
$this->maxNestingLevel = max($maxNestingLevel, 0);
|
||||
$this->exceptionTraceAsString = (bool) $exceptionTraceAsString;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
return $this->formatArray($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function formatBatch(array $records)
|
||||
{
|
||||
foreach ($records as $key => $record) {
|
||||
$records[$key] = $this->format($record);
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
protected function formatArray(array $record, $nestingLevel = 0)
|
||||
{
|
||||
if ($this->maxNestingLevel == 0 || $nestingLevel <= $this->maxNestingLevel) {
|
||||
foreach ($record as $name => $value) {
|
||||
if ($value instanceof \DateTime) {
|
||||
$record[$name] = $this->formatDate($value, $nestingLevel + 1);
|
||||
} elseif ($value instanceof \Exception) {
|
||||
$record[$name] = $this->formatException($value, $nestingLevel + 1);
|
||||
} elseif (is_array($value)) {
|
||||
$record[$name] = $this->formatArray($value, $nestingLevel + 1);
|
||||
} elseif (is_object($value)) {
|
||||
$record[$name] = $this->formatObject($value, $nestingLevel + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$record = '[...]';
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
protected function formatObject($value, $nestingLevel)
|
||||
{
|
||||
$objectVars = get_object_vars($value);
|
||||
$objectVars['class'] = Utils::getClass($value);
|
||||
|
||||
return $this->formatArray($objectVars, $nestingLevel);
|
||||
}
|
||||
|
||||
protected function formatException(\Exception $exception, $nestingLevel)
|
||||
{
|
||||
$formattedException = array(
|
||||
'class' => Utils::getClass($exception),
|
||||
'message' => $exception->getMessage(),
|
||||
'code' => $exception->getCode(),
|
||||
'file' => $exception->getFile() . ':' . $exception->getLine(),
|
||||
);
|
||||
|
||||
if ($this->exceptionTraceAsString === true) {
|
||||
$formattedException['trace'] = $exception->getTraceAsString();
|
||||
} else {
|
||||
$formattedException['trace'] = $exception->getTrace();
|
||||
}
|
||||
|
||||
return $this->formatArray($formattedException, $nestingLevel);
|
||||
}
|
||||
|
||||
protected function formatDate(\DateTime $value, $nestingLevel)
|
||||
{
|
||||
return new \MongoDate($value->getTimestamp());
|
||||
}
|
||||
}
|
@ -0,0 +1,314 @@
|
||||
<?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\Formatter;
|
||||
|
||||
use Exception;
|
||||
use Monolog\Utils;
|
||||
|
||||
/**
|
||||
* Normalizes incoming records to remove objects/resources so it's easier to dump to various targets
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class NormalizerFormatter implements FormatterInterface
|
||||
{
|
||||
const SIMPLE_DATE = "Y-m-d H:i:s";
|
||||
|
||||
protected $dateFormat;
|
||||
|
||||
/**
|
||||
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
|
||||
*/
|
||||
public function __construct($dateFormat = null)
|
||||
{
|
||||
$this->dateFormat = $dateFormat ?: static::SIMPLE_DATE;
|
||||
if (!function_exists('json_encode')) {
|
||||
throw new \RuntimeException('PHP\'s json extension is required to use Monolog\'s NormalizerFormatter');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
return $this->normalize($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatBatch(array $records)
|
||||
{
|
||||
foreach ($records as $key => $record) {
|
||||
$records[$key] = $this->format($record);
|
||||
}
|
||||
|
||||
return $records;
|
||||
}
|
||||
|
||||
protected function normalize($data, $depth = 0)
|
||||
{
|
||||
if ($depth > 9) {
|
||||
return 'Over 9 levels deep, aborting normalization';
|
||||
}
|
||||
|
||||