mirror of
https://github.com/Seldaek/monolog.git
synced 2025-10-23 17:46:09 +02:00
Merge branch '1.x'
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
|
||||
namespace Monolog\Formatter;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Encodes whatever record data is passed to it as json
|
||||
*
|
||||
@@ -18,13 +20,17 @@ namespace Monolog\Formatter;
|
||||
*
|
||||
* @author Jordi Boggiano <j.boggiano@seld.be>
|
||||
*/
|
||||
class JsonFormatter implements FormatterInterface
|
||||
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
|
||||
@@ -64,7 +70,7 @@ class JsonFormatter implements FormatterInterface
|
||||
*/
|
||||
public function format(array $record)
|
||||
{
|
||||
return json_encode($record) . ($this->appendNewline ? "\n" : '');
|
||||
return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,6 +88,14 @@ class JsonFormatter implements FormatterInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $include
|
||||
*/
|
||||
public function includeStacktraces($include = true)
|
||||
{
|
||||
$this->includeStacktraces = $include;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a JSON-encoded array of records.
|
||||
*
|
||||
@@ -90,7 +104,7 @@ class JsonFormatter implements FormatterInterface
|
||||
*/
|
||||
protected function formatBatchJson(array $records)
|
||||
{
|
||||
return json_encode($records);
|
||||
return $this->toJson($this->normalize($records), true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,4 +127,76 @@ class JsonFormatter implements FormatterInterface
|
||||
|
||||
return implode("\n", $records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes given $data.
|
||||
*
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function normalize($data)
|
||||
{
|
||||
if (is_array($data) || $data instanceof \Traversable) {
|
||||
$normalized = array();
|
||||
|
||||
$count = 1;
|
||||
foreach ($data as $key => $value) {
|
||||
if ($count++ >= 1000) {
|
||||
$normalized['...'] = 'Over 1000 items, aborting normalization';
|
||||
break;
|
||||
}
|
||||
$normalized[$key] = $this->normalize($value);
|
||||
}
|
||||
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
if ($data instanceof Exception) {
|
||||
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).' / '.get_class($e));
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'class' => get_class($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'];
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
@@ -11,8 +11,6 @@
|
||||
|
||||
namespace Monolog\Formatter;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Formats incoming records into a one-line string
|
||||
*
|
||||
@@ -78,6 +76,13 @@ class LineFormatter extends NormalizerFormatter
|
||||
}
|
||||
}
|
||||
|
||||
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']);
|
||||
@@ -114,8 +119,13 @@ class LineFormatter extends NormalizerFormatter
|
||||
return $this->replaceNewlines($this->convertToString($value));
|
||||
}
|
||||
|
||||
protected function normalizeException(Exception $e)
|
||||
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).' / '.get_class($e));
|
||||
}
|
||||
|
||||
$previousText = '';
|
||||
if ($previous = $e->getPrevious()) {
|
||||
do {
|
||||
|
@@ -90,7 +90,8 @@ class NormalizerFormatter implements FormatterInterface
|
||||
}
|
||||
|
||||
if (is_object($data)) {
|
||||
if ($data instanceof Exception) {
|
||||
// TODO 2.0 only check for Throwable
|
||||
if ($data instanceof Exception || (PHP_VERSION_ID > 70000 && $data instanceof \Throwable)) {
|
||||
return $this->normalizeException($data);
|
||||
}
|
||||
|
||||
@@ -112,8 +113,13 @@ class NormalizerFormatter implements FormatterInterface
|
||||
return '[unknown('.gettype($data).')]';
|
||||
}
|
||||
|
||||
protected function normalizeException(Exception $e)
|
||||
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).' / '.get_class($e));
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'class' => get_class($e),
|
||||
'message' => $e->getMessage(),
|
||||
|
@@ -70,6 +70,15 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler
|
||||
$this->facilities['local5'] = LOG_LOCAL5;
|
||||
$this->facilities['local6'] = LOG_LOCAL6;
|
||||
$this->facilities['local7'] = LOG_LOCAL7;
|
||||
} else {
|
||||
$this->facilities['local0'] = 128; // LOG_LOCAL0
|
||||
$this->facilities['local1'] = 136; // LOG_LOCAL1
|
||||
$this->facilities['local2'] = 144; // LOG_LOCAL2
|
||||
$this->facilities['local3'] = 152; // LOG_LOCAL3
|
||||
$this->facilities['local4'] = 160; // LOG_LOCAL4
|
||||
$this->facilities['local5'] = 168; // LOG_LOCAL5
|
||||
$this->facilities['local6'] = 176; // LOG_LOCAL6
|
||||
$this->facilities['local7'] = 184; // LOG_LOCAL7
|
||||
}
|
||||
|
||||
// convert textual description of facility to syslog constant
|
||||
|
@@ -55,18 +55,12 @@ class AmqpHandler extends AbstractProcessingHandler
|
||||
protected function write(array $record)
|
||||
{
|
||||
$data = $record["formatted"];
|
||||
|
||||
$routingKey = sprintf(
|
||||
'%s.%s',
|
||||
// TODO 2.0 remove substr call
|
||||
substr($record['level_name'], 0, 4),
|
||||
$record['channel']
|
||||
);
|
||||
$routingKey = $this->getRoutingKey($record);
|
||||
|
||||
if ($this->exchange instanceof AMQPExchange) {
|
||||
$this->exchange->publish(
|
||||
$data,
|
||||
strtolower($routingKey),
|
||||
$routingKey,
|
||||
0,
|
||||
array(
|
||||
'delivery_mode' => 2,
|
||||
@@ -75,19 +69,74 @@ class AmqpHandler extends AbstractProcessingHandler
|
||||
);
|
||||
} else {
|
||||
$this->exchange->basic_publish(
|
||||
new AMQPMessage(
|
||||
(string) $data,
|
||||
array(
|
||||
'delivery_mode' => 2,
|
||||
'content_type' => 'application/json',
|
||||
)
|
||||
),
|
||||
$this->createAmqpMessage($data),
|
||||
$this->exchangeName,
|
||||
strtolower($routingKey)
|
||||
$routingKey
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function handleBatch(array $records)
|
||||
{
|
||||
if ($this->exchange instanceof AMQPExchange) {
|
||||
parent::handleBatch($records);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($records as $record) {
|
||||
if (!$this->isHandling($record)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$record = $this->processRecord($record);
|
||||
$data = $this->getFormatter()->format($record);
|
||||
|
||||
$this->exchange->batch_basic_publish(
|
||||
$this->createAmqpMessage($data),
|
||||
$this->exchangeName,
|
||||
$this->getRoutingKey($record)
|
||||
);
|
||||
}
|
||||
|
||||
$this->exchange->publish_batch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the routing key for the AMQP exchange
|
||||
*
|
||||
* @param array $record
|
||||
* @return string
|
||||
*/
|
||||
private function getRoutingKey(array $record)
|
||||
{
|
||||
$routingKey = sprintf(
|
||||
'%s.%s',
|
||||
// TODO 2.0 remove substr call
|
||||
substr($record['level_name'], 0, 4),
|
||||
$record['channel']
|
||||
);
|
||||
|
||||
return strtolower($routingKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @return AMQPMessage
|
||||
*/
|
||||
private function createAmqpMessage($data)
|
||||
{
|
||||
return new AMQPMessage(
|
||||
(string) $data,
|
||||
array(
|
||||
'delivery_mode' => 2,
|
||||
'content_type' => 'application/json',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
@@ -46,9 +46,9 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
||||
self::$records[] = $record;
|
||||
|
||||
// Register shutdown handler if not already done
|
||||
if (PHP_SAPI !== 'cli' && !self::$initialized) {
|
||||
if (!self::$initialized) {
|
||||
self::$initialized = true;
|
||||
register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send'));
|
||||
$this->registerShutdownFunction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,26 +58,16 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
||||
*/
|
||||
public static function send()
|
||||
{
|
||||
$htmlTags = true;
|
||||
// Check content type
|
||||
foreach (headers_list() as $header) {
|
||||
if (stripos($header, 'content-type:') === 0) {
|
||||
// This handler only works with HTML and javascript outputs
|
||||
// text/javascript is obsolete in favour of application/javascript, but still used
|
||||
if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) {
|
||||
$htmlTags = false;
|
||||
} elseif (stripos($header, 'text/html') === false) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
$format = self::getResponseFormat();
|
||||
if ($format === 'unknown') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count(self::$records)) {
|
||||
if ($htmlTags) {
|
||||
echo '<script>' , self::generateScript() , '</script>';
|
||||
} else {
|
||||
echo self::generateScript();
|
||||
if ($format === 'html') {
|
||||
self::writeOutput('<script>' , self::generateScript() , '</script>');
|
||||
} elseif ($format === 'js') {
|
||||
self::writeOutput(self::generateScript());
|
||||
}
|
||||
self::reset();
|
||||
}
|
||||
@@ -91,6 +81,50 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
||||
self::$records = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for register_shutdown_function to allow overriding
|
||||
*/
|
||||
protected function registerShutdownFunction()
|
||||
{
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
register_shutdown_function(array('Monolog\Handler\BrowserConsoleHandler', 'send'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for echo to allow overriding
|
||||
*
|
||||
* @param string $str
|
||||
*/
|
||||
protected static function writeOutput($str)
|
||||
{
|
||||
echo $str;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the format of the response
|
||||
*
|
||||
* @return string One of 'js', 'html' or 'unknown'
|
||||
*/
|
||||
protected static function getResponseFormat()
|
||||
{
|
||||
// Check content type
|
||||
foreach (headers_list() as $header) {
|
||||
if (stripos($header, 'content-type:') === 0) {
|
||||
// This handler only works with HTML and javascript outputs
|
||||
// text/javascript is obsolete in favour of application/javascript, but still used
|
||||
if (stripos($header, 'application/javascript') !== false || stripos($header, 'text/javascript') !== false) {
|
||||
return 'js';
|
||||
} elseif (stripos($header, 'text/html') !== false) {
|
||||
return 'html';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
private static function generateScript()
|
||||
{
|
||||
$script = array();
|
||||
|
@@ -17,6 +17,8 @@ use Monolog\Logger;
|
||||
/**
|
||||
* Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)
|
||||
*
|
||||
* This also works out of the box with Firefox 43+
|
||||
*
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*/
|
||||
class ChromePHPHandler extends AbstractProcessingHandler
|
||||
@@ -175,7 +177,8 @@ class ChromePHPHandler extends AbstractProcessingHandler
|
||||
return false;
|
||||
}
|
||||
|
||||
return preg_match('{\bChrome/\d+[\.\d+]*\b}', $_SERVER['HTTP_USER_AGENT']);
|
||||
// matches any Chrome, or Firefox 43+
|
||||
return preg_match('{\b(?:Chrome/\d+(?:\.\d+)*|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}', $_SERVER['HTTP_USER_AGENT']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -11,6 +11,8 @@
|
||||
|
||||
namespace Monolog\Handler;
|
||||
|
||||
use Monolog\Formatter\FormatterInterface;
|
||||
|
||||
/**
|
||||
* Forwards records to multiple handlers
|
||||
*
|
||||
@@ -77,4 +79,16 @@ class GroupHandler extends Handler implements ProcessableHandlerInterface
|
||||
$handler->handleBatch($records);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFormatter(FormatterInterface $formatter)
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
$handler->setFormatter($formatter);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
104
src/Monolog/Handler/HandlerWrapper.php
Normal file
104
src/Monolog/Handler/HandlerWrapper.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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\FormatterInterface;
|
||||
|
||||
/**
|
||||
* This simple wrapper class can be used to extend handlers functionality.
|
||||
*
|
||||
* Example: A filtering handle. Inherit from this class, override isHandling() like this
|
||||
*
|
||||
* public function isHandling(array $record)
|
||||
* {
|
||||
* if ($record meets certain conditions) {
|
||||
* return false;
|
||||
* }
|
||||
* return $this->handler->isHandling($record);
|
||||
* }
|
||||
*
|
||||
* @author Alexey Karapetov <alexey@karapetov.com>
|
||||
*/
|
||||
class HandlerWrapper implements HandlerInterface
|
||||
{
|
||||
/**
|
||||
* @var HandlerInterface
|
||||
*/
|
||||
private $handler;
|
||||
|
||||
/**
|
||||
* HandlerWrapper constructor.
|
||||
* @param HandlerInterface $handler
|
||||
*/
|
||||
public function __construct(HandlerInterface $handler)
|
||||
{
|
||||
$this->handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isHandling(array $record)
|
||||
{
|
||||
return $this->handler->isHandling($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(array $record)
|
||||
{
|
||||
return $this->handler->handle($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handleBatch(array $records)
|
||||
{
|
||||
return $this->handler->handleBatch($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function pushProcessor($callback)
|
||||
{
|
||||
$this->handler->pushProcessor($callback);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function popProcessor()
|
||||
{
|
||||
return $this->handler->popProcessor();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setFormatter(FormatterInterface $formatter)
|
||||
{
|
||||
$this->handler->setFormatter($formatter);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormatter()
|
||||
{
|
||||
return $this->handler->getFormatter();
|
||||
}
|
||||
}
|
@@ -38,6 +38,12 @@ class RavenHandler extends AbstractProcessingHandler
|
||||
Logger::EMERGENCY => Raven_Client::FATAL,
|
||||
);
|
||||
|
||||
/**
|
||||
* @var string should represent the current version of the calling
|
||||
* software. Can be any string (git commit, version number)
|
||||
*/
|
||||
private $release;
|
||||
|
||||
/**
|
||||
* @var Raven_Client the client object that sends the message to the server
|
||||
*/
|
||||
@@ -139,6 +145,10 @@ class RavenHandler extends AbstractProcessingHandler
|
||||
$options['tags'] = array_merge($options['tags'], $record['context']['tags']);
|
||||
unset($record['context']['tags']);
|
||||
}
|
||||
if (!empty($record['context']['fingerprint'])) {
|
||||
$options['fingerprint'] = $record['context']['fingerprint'];
|
||||
unset($record['context']['fingerprint']);
|
||||
}
|
||||
if (!empty($record['context']['logger'])) {
|
||||
$options['logger'] = $record['context']['logger'];
|
||||
unset($record['context']['logger']);
|
||||
@@ -165,6 +175,10 @@ class RavenHandler extends AbstractProcessingHandler
|
||||
$options['extra']['extra'] = $record['extra'];
|
||||
}
|
||||
|
||||
if (!empty($this->release) && !isset($options['release'])) {
|
||||
$options['release'] = $this->release;
|
||||
}
|
||||
|
||||
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) {
|
||||
$options['extra']['message'] = $record['formatted'];
|
||||
$this->ravenClient->captureException($record['context']['exception'], $options);
|
||||
@@ -204,4 +218,14 @@ class RavenHandler extends AbstractProcessingHandler
|
||||
{
|
||||
return array('checksum', 'release');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value
|
||||
*/
|
||||
public function setRelease($value)
|
||||
{
|
||||
$this->release = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@@ -115,7 +115,11 @@ class RotatingFileHandler extends StreamHandler
|
||||
|
||||
foreach (array_slice($logFiles, $this->maxFiles) as $file) {
|
||||
if (is_writable($file)) {
|
||||
// suppress errors here as unlink() might fail if two processes
|
||||
// are cleaning up/rotating at the same time
|
||||
set_error_handler(function ($errno, $errstr, $errfile, $errline) {});
|
||||
unlink($file);
|
||||
restore_error_handler();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -157,6 +157,19 @@ class Logger implements LoggerInterface
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new cloned instance with the name changed
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function withName($name)
|
||||
{
|
||||
$new = clone $this;
|
||||
$new->name = $name;
|
||||
|
||||
return $new;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes a handler on to the stack.
|
||||
*
|
||||
|
Reference in New Issue
Block a user