1
0
mirror of https://github.com/Seldaek/monolog.git synced 2025-10-24 10:06:08 +02:00

Merge branch 'master' into feature/elasticsearch

# Conflicts:
#	composer.json
#	src/Monolog/Handler/ElasticSearchHandler.php
This commit is contained in:
Avtandil Kikabidze
2018-06-24 20:43:55 +04:00
83 changed files with 1210 additions and 336 deletions

View File

@@ -23,29 +23,9 @@ class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable
public function __construct($useMicroseconds, \DateTimeZone $timezone = null)
{
static $needsMicrosecondsHack = PHP_VERSION_ID < 70100;
$this->useMicroseconds = $useMicroseconds;
$date = 'now';
if ($needsMicrosecondsHack && $useMicroseconds) {
$timestamp = microtime(true);
// apply offset of the timezone as microtime() is always UTC
if ($timezone && $timezone->getName() !== 'UTC') {
$timestamp += (new \DateTime('now', $timezone))->getOffset();
}
// Circumvent DateTimeImmutable::createFromFormat() which always returns \DateTimeImmutable instead of `static`
// @link https://bugs.php.net/bug.php?id=60302
//
// So we create a DateTime but then format it so we
// can re-create one using the right class
$dt = self::createFromFormat('U.u', sprintf('%.6F', $timestamp));
$date = $dt->format('Y-m-d H:i:s.u');
}
parent::__construct($date, $timezone);
parent::__construct('now', $timezone);
}
public function jsonSerialize(): string

View File

@@ -37,6 +37,7 @@ class ErrorHandler
private $hasFatalErrorHandler;
private $fatalLevel;
private $reservedMemory;
private $lastFatalTrace;
private static $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
public function __construct(LoggerInterface $logger)
@@ -49,10 +50,10 @@ class ErrorHandler
*
* 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 array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling
* @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling
* @param LoggerInterface $logger
* @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
* @param array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling
* @param string|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling
* @return ErrorHandler
*/
public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self
@@ -103,7 +104,11 @@ class ErrorHandler
return $this;
}
public function registerFatalHandler($level = null, $reservedMemorySize = 20): self
/**
* @param string|null $level a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling
* @param int $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done
*/
public function registerFatalHandler($level = null, int $reservedMemorySize = 20): self
{
register_shutdown_function([$this, 'handleFatalError']);
@@ -166,6 +171,10 @@ class ErrorHandler
call_user_func($this->previousExceptionHandler, $e);
}
if (!headers_sent() && ini_get('display_errors') === 0) {
http_response_code(500);
}
exit(255);
}
@@ -182,6 +191,10 @@ class ErrorHandler
if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
$level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL;
$this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]);
} else {
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
array_shift($trace); // Exclude handleError from trace
$this->lastFatalTrace = $trace;
}
if ($this->previousErrorHandler === true) {
@@ -203,7 +216,7 @@ class ErrorHandler
$this->logger->log(
$this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel,
'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line']]
['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace]
);
if ($this->logger instanceof Logger) {

View File

@@ -62,6 +62,7 @@ class FluentdFormatter implements FormatterInterface
$message = [
'message' => $record['message'],
'context' => $record['context'],
'extra' => $record['extra'],
];

View File

@@ -59,7 +59,7 @@ class HtmlFormatter extends NormalizerFormatter
$td = '<pre>'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'</pre>';
}
return "<tr style=\"padding: 4px;spacing: 0;text-align: left;\">\n<th style=\"background: #cccccc\" width=\"100px\">$th:</th>\n<td style=\"padding: 4px;spacing: 0;text-align: left;background: #eeeeee\">".$td."</td>\n</tr>";
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>";
}
/**

View File

@@ -64,7 +64,15 @@ class JsonFormatter extends NormalizerFormatter
*/
public function format(array $record): string
{
return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : '');
$normalized = $this->normalize($record);
if (isset($normalized['context']) && $normalized['context'] === []) {
$normalized['context'] = new \stdClass;
}
if (isset($normalized['extra']) && $normalized['extra'] === []) {
$normalized['extra'] = new \stdClass;
}
return $this->toJson($normalized, true) . ($this->appendNewline ? "\n" : '');
}
/**
@@ -122,8 +130,8 @@ class JsonFormatter extends NormalizerFormatter
*/
protected function normalize($data, int $depth = 0)
{
if ($depth > 9) {
return 'Over 9 levels deep, aborting normalization';
if ($depth > $this->maxNormalizeDepth) {
return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization';
}
if (is_array($data) || $data instanceof \Traversable) {
@@ -131,10 +139,11 @@ class JsonFormatter extends NormalizerFormatter
$count = 1;
foreach ($data as $key => $value) {
if ($count++ >= 1000) {
$normalized['...'] = 'Over 1000 items, aborting normalization';
if ($count++ > $this->maxNormalizeItemCount) {
$normalized['...'] = 'Over '.$this->maxNormalizeItemCount.' items ('.count($data).' total), aborting normalization';
break;
}
$normalized[$key] = $this->normalize($value, $depth + 1);
}

View File

@@ -126,18 +126,14 @@ class LineFormatter extends NormalizerFormatter
protected function normalizeException(\Throwable $e, int $depth = 0): string
{
$previousText = '';
$str = $this->formatException($e);
if ($previous = $e->getPrevious()) {
do {
$previousText .= ', '.get_class($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine();
$str .= "\n[previous exception] " . $this->formatException($previous);
} while ($previous = $previous->getPrevious());
}
$str = '[object] ('.get_class($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')';
if ($this->includeStacktraces) {
$str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n";
}
return $str;
}
@@ -166,4 +162,14 @@ class LineFormatter extends NormalizerFormatter
return str_replace(["\r\n", "\r", "\n"], ' ', $str);
}
private function formatException(\Throwable $e): string
{
$str = '[object] (' . get_class($e) . '(code: ' . $e->getCode() . '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')';
if ($this->includeStacktraces) {
$str .= "\n[stacktrace]\n" . $e->getTraceAsString() . "\n";
}
return $str;
}
}

View File

@@ -14,8 +14,8 @@ namespace Monolog\Formatter;
/**
* Serializes a log message to Logstash Event Format
*
* @see http://logstash.net/
* @see https://github.com/elastic/logstash/blob/master/logstash-core-event/lib/logstash/event.rb
* @see https://www.elastic.co/products/logstash
* @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java
*
* @author Tim Mower <timothy.mower@gmail.com>
*/
@@ -32,30 +32,30 @@ class LogstashFormatter extends NormalizerFormatter
protected $applicationName;
/**
* @var string a prefix for 'extra' fields from the Monolog record (optional)
* @var string the key for 'extra' fields from the Monolog record
*/
protected $extraPrefix;
protected $extraKey;
/**
* @var string a prefix for 'context' fields from the Monolog record (optional)
* @var string the key for 'context' fields from the Monolog record
*/
protected $contextPrefix;
protected $contextKey;
/**
* @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 string $extraKey the key for extra keys inside logstash "fields", defaults to extra
* @param string $contextKey the key for context keys inside logstash "fields", defaults to context
*/
public function __construct(string $applicationName, string $systemName = null, string $extraPrefix = null, string $contextPrefix = 'ctxt_')
public function __construct(string $applicationName, string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context')
{
// 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->extraKey = $extraKey;
$this->contextKey = $contextKey;
}
/**
@@ -90,10 +90,10 @@ class LogstashFormatter extends NormalizerFormatter
$message['type'] = $this->applicationName;
}
if (!empty($record['extra'])) {
$message[$this->extraPrefix.'extra'] = $record['extra'];
$message[$this->extraKey] = $record['extra'];
}
if (!empty($record['context'])) {
$message[$this->contextPrefix.'context'] = $record['context'];
$message[$this->contextKey] = $record['context'];
}
return $this->toJson($message) . "\n";

View File

@@ -24,6 +24,8 @@ class NormalizerFormatter implements FormatterInterface
const SIMPLE_DATE = "Y-m-d\TH:i:sP";
protected $dateFormat;
protected $maxNormalizeDepth = 9;
protected $maxNormalizeItemCount = 1000;
/**
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
@@ -56,14 +58,40 @@ class NormalizerFormatter implements FormatterInterface
return $records;
}
/**
* The maximum number of normalization levels to go through
*/
public function getMaxNormalizeDepth(): int
{
return $this->maxNormalizeDepth;
}
public function setMaxNormalizeDepth(int $maxNormalizeDepth): void
{
$this->maxNormalizeDepth = $maxNormalizeDepth;
}
/**
* The maximum number of items to normalize per level
*/
public function getMaxNormalizeItemCount(): int
{
return $this->maxNormalizeItemCount;
}
public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): void
{
$this->maxNormalizeItemCount = $maxNormalizeItemCount;
}
/**
* @param mixed $data
* @return int|bool|string|null|array
*/
protected function normalize($data, int $depth = 0)
{
if ($depth > 9) {
return 'Over 9 levels deep, aborting normalization';
if ($depth > $this->maxNormalizeDepth) {
return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization';
}
if (null === $data || is_scalar($data)) {
@@ -84,10 +112,11 @@ class NormalizerFormatter implements FormatterInterface
$count = 1;
foreach ($data as $key => $value) {
if ($count++ >= 1000) {
$normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
if ($count++ > $this->maxNormalizeItemCount) {
$normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items ('.count($data).' total), aborting normalization';
break;
}
$normalized[$key] = $this->normalize($value, $depth + 1);
}
@@ -158,9 +187,20 @@ class NormalizerFormatter implements FormatterInterface
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
// Simplify closures handling
$data['trace'][] = $frame['function'];
} else {
if (isset($frame['args'])) {
// Make sure that objects present as arguments are not serialized nicely but rather only
// as a class name to avoid any unexpected leak of sensitive information
$frame['args'] = array_map(function ($arg) {
if (is_object($arg) && !$arg instanceof \DateTimeInterface) {
return sprintf("[object] (%s)", get_class($arg));
}
return $arg;
}, $frame['args']);
}
// We should again normalize the frames, because it might contain invalid items
$data['trace'][] = $this->toJson($this->normalize($frame, $depth + 1), true);
}

View File

@@ -24,8 +24,8 @@ abstract class AbstractHandler extends Handler
protected $bubble = true;
/**
* @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 int|string $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
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
{
@@ -67,8 +67,8 @@ abstract class AbstractHandler extends Handler
/**
* Sets the bubbling behavior.
*
* @param Boolean $bubble true means that this handler allows bubbling.
* false means that bubbling is not permitted.
* @param bool $bubble true means that this handler allows bubbling.
* false means that bubbling is not permitted.
* @return self
*/
public function setBubble(bool $bubble): self
@@ -81,8 +81,8 @@ abstract class AbstractHandler extends Handler
/**
* Gets the bubbling behavior.
*
* @return Boolean true means that this handler allows bubbling.
* false means that bubbling is not permitted.
* @return bool true means that this handler allows bubbling.
* false means that bubbling is not permitted.
*/
public function getBubble(): bool
{

View File

@@ -54,9 +54,9 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler
];
/**
* @param mixed $facility
* @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 mixed $facility
* @param 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
*/
public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)
{

47
src/Monolog/Handler/BrowserConsoleHandler.php Normal file → Executable file
View File

@@ -44,11 +44,11 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
protected function write(array $record)
{
// Accumulate records
self::$records[] = $record;
static::$records[] = $record;
// Register shutdown handler if not already done
if (!self::$initialized) {
self::$initialized = true;
if (!static::$initialized) {
static::$initialized = true;
$this->registerShutdownFunction();
}
}
@@ -59,18 +59,18 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
*/
public static function send()
{
$format = self::getResponseFormat();
$format = static::getResponseFormat();
if ($format === 'unknown') {
return;
}
if (count(self::$records)) {
if (count(static::$records)) {
if ($format === 'html') {
self::writeOutput('<script>' . self::generateScript() . '</script>');
static::writeOutput('<script>' . static::generateScript() . '</script>');
} elseif ($format === 'js') {
self::writeOutput(self::generateScript());
static::writeOutput(static::generateScript());
}
self::reset();
static::reset();
}
}
@@ -79,7 +79,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
*/
public static function reset()
{
self::$records = [];
static::$records = [];
}
/**
@@ -134,18 +134,19 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
private static function generateScript()
{
$script = [];
foreach (self::$records as $record) {
$context = self::dump('Context', $record['context']);
$extra = self::dump('Extra', $record['extra']);
foreach (static::$records as $record) {
$context = static::dump('Context', $record['context']);
$extra = static::dump('Extra', $record['extra']);
if (empty($context) && empty($extra)) {
$script[] = self::call_array('log', self::handleStyles($record['formatted']));
$script[] = static::call_array('log', static::handleStyles($record['formatted']));
} else {
$script = array_merge($script,
[self::call_array('groupCollapsed', self::handleStyles($record['formatted']))],
$script = array_merge(
$script,
[static::call_array('groupCollapsed', static::handleStyles($record['formatted']))],
$context,
$extra,
[self::call('groupEnd')]
[static::call('groupEnd')]
);
}
}
@@ -155,19 +156,19 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
private static function handleStyles($formatted)
{
$args = [self::quote('font-weight: normal')];
$args = [static::quote('font-weight: normal')];
$format = '%c' . $formatted;
preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
foreach (array_reverse($matches) as $match) {
$args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0]));
$args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0]));
$args[] = '"font-weight: normal"';
$pos = $match[0][1];
$format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0]));
}
array_unshift($args, self::quote($format));
array_unshift($args, static::quote($format));
return $args;
}
@@ -199,13 +200,13 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
if (empty($dict)) {
return $script;
}
$script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title));
$script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title));
foreach ($dict as $key => $value) {
$value = json_encode($value);
if (empty($value)) {
$value = self::quote('');
$value = static::quote('');
}
$script[] = self::call('log', self::quote('%s: %o'), self::quote($key), $value);
$script[] = static::call('log', static::quote('%s: %o'), static::quote($key), $value);
}
return $script;
@@ -221,7 +222,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
$args = func_get_args();
$method = array_shift($args);
return self::call_array($method, $args);
return static::call_array($method, $args);
}
private static function call_array($method, array $args)

View File

@@ -36,8 +36,8 @@ class BufferHandler extends AbstractHandler implements ProcessableHandlerInterfa
* @param HandlerInterface $handler Handler.
* @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @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 $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
*/
public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false)
{

View File

@@ -24,6 +24,8 @@ use Monolog\Logger;
*/
class ChromePHPHandler extends AbstractProcessingHandler
{
use WebRequestRecognizerTrait;
/**
* Version of the extension
*/
@@ -46,7 +48,7 @@ class ChromePHPHandler extends AbstractProcessingHandler
*
* Chrome limits the headers to 256KB, so when we sent 240KB we stop sending
*
* @var Boolean
* @var bool
*/
protected static $overflowed = false;
@@ -59,8 +61,8 @@ class ChromePHPHandler extends AbstractProcessingHandler
protected static $sendHeaders = true;
/**
* @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 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
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
{
@@ -75,6 +77,10 @@ class ChromePHPHandler extends AbstractProcessingHandler
*/
public function handleBatch(array $records)
{
if (!$this->isWebRequest()) {
return;
}
$messages = [];
foreach ($records as $record) {
@@ -108,6 +114,10 @@ class ChromePHPHandler extends AbstractProcessingHandler
*/
protected function write(array $record)
{
if (!$this->isWebRequest()) {
return;
}
self::$json['rows'][] = $record['formatted'];
$this->send();

View File

@@ -46,7 +46,8 @@ class CubeHandler extends AbstractProcessingHandler
if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) {
throw new \UnexpectedValueException(
'Invalid protocol (' . $urlInfo['scheme'] . ').'
. ' Valid options are ' . implode(', ', $this->acceptedSchemes));
. ' Valid options are ' . implode(', ', $this->acceptedSchemes)
);
}
$this->scheme = $urlInfo['scheme'];

View File

@@ -60,7 +60,7 @@ class DeduplicationHandler extends BufferHandler
* @param string $deduplicationStore The file/path where the deduplication log should be kept
* @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes
* @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true)
{

View File

@@ -29,17 +29,18 @@ class ErrorLogHandler extends AbstractProcessingHandler
protected $expandNewlines;
/**
* @param int $messageType Says where the error should go.
* @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 $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
* @param int $messageType Says where the error should go.
* @param 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 bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
*/
public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false)
{
parent::__construct($level, $bubble);
if (false === in_array($messageType, self::getAvailableTypes())) {
if (false === in_array($messageType, self::getAvailableTypes(), true)) {
$message = sprintf('The given message type "%s" is not supported', print_r($messageType, true));
throw new \InvalidArgumentException($message);
}
@@ -73,9 +74,10 @@ class ErrorLogHandler extends AbstractProcessingHandler
{
if (!$this->expandNewlines) {
error_log((string) $record['formatted'], $this->messageType);
return;
}
}
$lines = preg_split('{[\r\n]+}', (string) $record['formatted']);
foreach ($lines as $line) {
error_log($line, $this->messageType);

View File

@@ -42,7 +42,7 @@ class FilterHandler extends Handler implements ProcessableHandlerInterface
/**
* Whether the messages that are handled can bubble up the stack or not
*
* @var Boolean
* @var bool
*/
protected $bubble;
@@ -50,7 +50,7 @@ class FilterHandler extends Handler implements ProcessableHandlerInterface
* @param callable|HandlerInterface $handler Handler or factory callable($record, $this).
* @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided
* @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true)
{

View File

@@ -21,8 +21,8 @@ interface ActivationStrategyInterface
/**
* Returns whether the given record activates the handler.
*
* @param array $record
* @return Boolean
* @param array $record
* @return bool
*/
public function isHandlerActivated(array $record);
}

View File

@@ -43,8 +43,8 @@ class FingersCrossedHandler extends Handler implements ProcessableHandlerInterfa
* @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler).
* @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action
* @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true)
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true)
* @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered
*/
public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null)

View File

@@ -21,6 +21,8 @@ use Monolog\Formatter\FormatterInterface;
*/
class FirePHPHandler extends AbstractProcessingHandler
{
use WebRequestRecognizerTrait;
/**
* WildFire JSON header message format
*/
@@ -130,7 +132,7 @@ class FirePHPHandler extends AbstractProcessingHandler
*/
protected function write(array $record)
{
if (!self::$sendHeaders) {
if (!self::$sendHeaders || !$this->isWebRequest()) {
return;
}
@@ -157,7 +159,7 @@ class FirePHPHandler extends AbstractProcessingHandler
/**
* Verifies if the headers are accepted by the current user agent
*
* @return Boolean
* @return bool
*/
protected function headersAccepted()
{

View File

@@ -41,14 +41,6 @@ class GelfHandler extends AbstractProcessingHandler
$this->publisher = $publisher;
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->publisher = null;
}
/**
* {@inheritdoc}
*/

View File

@@ -25,8 +25,8 @@ class GroupHandler extends Handler implements ProcessableHandlerInterface
protected $handlers;
/**
* @param array $handlers Array of Handlers.
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param array $handlers Array of Handlers.
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(array $handlers, $bubble = true)
{

View File

@@ -29,7 +29,7 @@ interface HandlerInterface
*
* @param array $record Partial log record containing only a level key
*
* @return Boolean
* @return bool
*/
public function isHandling(array $record): bool;
@@ -43,9 +43,9 @@ interface HandlerInterface
* Unless the bubbling is interrupted (by returning true), the Logger class will keep on
* calling further handlers in the stack with a given log record.
*
* @param array $record The record to handle
* @return Boolean true means that this handler handled the record, and that bubbling is not permitted.
* false means the record was either not processed or that this handler allows bubbling.
* @param array $record The record to handle
* @return bool true means that this handler handled the record, and that bubbling is not permitted.
* false means the record was either not processed or that this handler allows bubbling.
*/
public function handle(array $record): bool;

View File

@@ -188,6 +188,21 @@ class HipChatHandler extends SocketHandler
protected function write(array $record)
{
parent::write($record);
$this->finalizeWrite();
}
/**
* Finalizes the request by reading some bytes and then closing the socket
*
* If we do not read some but close the socket too early, hipchat sometimes
* drops the request entirely.
*/
protected function finalizeWrite()
{
$res = $this->getResource();
if (is_resource($res)) {
@fread($res, 2048);
}
$this->closeSocket();
}

View File

@@ -30,10 +30,10 @@ class IFTTTHandler extends AbstractProcessingHandler
private $secretKey;
/**
* @param string $eventName The name of the IFTTT Maker event that should be triggered
* @param string $secretKey A valid IFTTT secret key
* @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 string $eventName The name of the IFTTT Maker event that should be triggered
* @param string $secretKey A valid IFTTT secret key
* @param 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
*/
public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true)
{

View File

@@ -0,0 +1,62 @@
<?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;
use Monolog\Logger;
/**
* Inspired on LogEntriesHandler.
*
* @author Robert Kaufmann III <rok3@rok3.me>
* @author Gabriel Machado <gabriel.ms1@hotmail.com>
*/
class InsightOpsHandler extends SocketHandler
{
/**
* @var string
*/
protected $logToken;
/**
* @param string $token Log token supplied by InsightOps
* @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'.
* @param bool $useSSL Whether or not SSL encryption should be used
* @param int $level The minimum logging level to trigger this handler
* @param bool $bubble Whether or not messages that are handled should bubble up the stack.
*
* @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
*/
public function __construct($token, $region = 'us', $useSSL = true, $level = Logger::DEBUG, $bubble = true)
{
if ($useSSL && !extension_loaded('openssl')) {
throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler');
}
$endpoint = $useSSL
? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443'
: $region . '.data.logs.insight.rapid7.com:80';
parent::__construct($endpoint, $level, $bubble);
$this->logToken = $token;
}
/**
* {@inheritdoc}
*
* @param array $record
* @return string
*/
protected function generateDataStream($record)
{
return $this->logToken . ' ' . $record['formatted'];
}
}

View File

@@ -27,7 +27,7 @@ class MandrillHandler extends MailHandler
* @param string $apiKey A valid Mandrill API key
* @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
* @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 bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true)
{

View File

@@ -44,7 +44,7 @@ class MongoDBHandler extends AbstractProcessingHandler
* @param string $database Database name
* @param string $collection Collection name
* @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 bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($mongodb, $database, $collection, $level = Logger::DEBUG, $bubble = true)
{

View File

@@ -19,6 +19,8 @@ use Monolog\Formatter\FormatterInterface;
* Class to record a log on a NewRelic application.
* Enabling New Relic High Security mode may prevent capture of useful information.
*
* This handler requires a NormalizerFormatter to function and expects an array in $record['formatted']
*
* @see https://docs.newrelic.com/docs/agents/php-agent
* @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security
*/
@@ -85,7 +87,7 @@ class NewRelicHandler extends AbstractProcessingHandler
unset($record['formatted']['context']['transaction_name']);
}
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) {
if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) {
newrelic_notice_error($record['message'], $record['context']['exception']);
unset($record['formatted']['context']['exception']);
} else {

View File

@@ -31,7 +31,7 @@ class PsrHandler extends AbstractHandler
/**
* @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied
* @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 bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true)
{

View File

@@ -69,8 +69,8 @@ class PushoverHandler extends SocketHandler
* @param string|array $users Pushover user id or array of ids the message will be sent to
* @param string $title Title sent to the Pushover API
* @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. Required when pushing messages to users that are not
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not
* the pushover.net app owner. OpenSSL is required for this option.
* @param int $highPriorityLevel The minimum logging level at which this handler will start
* sending "high priority" requests to the Pushover API
@@ -180,6 +180,6 @@ class PushoverHandler extends SocketHandler
*/
public function useFormattedMessage($value)
{
$this->useFormattedMessage = (boolean) $value;
$this->useFormattedMessage = (bool) $value;
}
}

View File

@@ -57,7 +57,7 @@ class RavenHandler extends AbstractProcessingHandler
/**
* @param Raven_Client $ravenClient
* @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 bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true)
{
@@ -181,7 +181,7 @@ class RavenHandler extends AbstractProcessingHandler
}
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) {
$options['extra']['message'] = $record['formatted'];
$options['message'] = $record['formatted'];
$this->ravenClient->captureException($record['context']['exception'], $options);
} else {
$this->ravenClient->captureMessage($record['formatted'], [], $options);

View File

@@ -11,7 +11,7 @@
namespace Monolog\Handler;
use RollbarNotifier;
use Rollbar\RollbarLogger;
use Throwable;
use Monolog\Logger;
@@ -19,7 +19,7 @@ use Monolog\Logger;
* Sends errors to Rollbar
*
* If the context data contains a `payload` key, that is used as an array
* of payload options to RollbarNotifier's report_message/report_exception methods.
* of payload options to RollbarLogger's log method.
*
* Rollbar's context info will contain the context + extra keys from the log record
* merged, and then on top of that a few keys:
@@ -34,11 +34,9 @@ use Monolog\Logger;
class RollbarHandler extends AbstractProcessingHandler
{
/**
* Rollbar notifier
*
* @var RollbarNotifier
* @var RollbarLogger
*/
protected $rollbarNotifier;
protected $rollbarLogger;
protected $levelMap = [
Logger::DEBUG => 'debug',
@@ -61,13 +59,13 @@ class RollbarHandler extends AbstractProcessingHandler
protected $initialized = false;
/**
* @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token
* @param 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 RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token
* @param 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
*/
public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true)
public function __construct(RollbarLogger $rollbarLogger, $level = Logger::ERROR, $bubble = true)
{
$this->rollbarNotifier = $rollbarNotifier;
$this->rollbarLogger = $rollbarLogger;
parent::__construct($level, $bubble);
}
@@ -84,11 +82,6 @@ class RollbarHandler extends AbstractProcessingHandler
}
$context = $record['context'];
$payload = [];
if (isset($context['payload'])) {
$payload = $context['payload'];
unset($context['payload']);
}
$context = array_merge($context, $record['extra'], [
'level' => $this->levelMap[$record['level']],
'monolog_level' => $record['level_name'],
@@ -97,27 +90,22 @@ class RollbarHandler extends AbstractProcessingHandler
]);
if (isset($context['exception']) && $context['exception'] instanceof Throwable) {
$payload['level'] = $context['level'];
$exception = $context['exception'];
unset($context['exception']);
$this->rollbarNotifier->report_exception($exception, $context, $payload);
$toLog = $exception;
} else {
$this->rollbarNotifier->report_message(
$record['message'],
$context['level'],
$context,
$payload
);
$toLog = $record['message'];
}
$this->rollbarLogger->log($context['level'], $toLog, $context);
$this->hasRecords = true;
}
public function flush()
{
if ($this->hasRecords) {
$this->rollbarNotifier->flush();
$this->rollbarLogger->flush();
$this->hasRecords = false;
}
}

View File

@@ -40,9 +40,9 @@ class RotatingFileHandler extends StreamHandler
* @param string $filename
* @param int $maxFiles The maximal amount of files to keep (0 means unlimited)
* @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 bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write)
* @param Boolean $useLocking Try to lock log file before doing any writes
* @param bool $useLocking Try to lock log file before doing any writes
*/
public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false)
{
@@ -50,7 +50,7 @@ class RotatingFileHandler extends StreamHandler
$this->maxFiles = (int) $maxFiles;
$this->nextRotation = new \DateTimeImmutable('tomorrow');
$this->filenameFormat = '{filename}-{date}';
$this->dateFormat = 'Y-m-d';
$this->dateFormat = self::FILE_PER_DAY;
parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking);
}
@@ -98,7 +98,7 @@ class RotatingFileHandler extends StreamHandler
$this->mustRotate = !file_exists($this->url);
}
if ($this->nextRotation < $record['datetime']) {
if ($this->nextRotation <= $record['datetime']) {
$this->mustRotate = true;
$this->close();
}
@@ -166,7 +166,7 @@ class RotatingFileHandler extends StreamHandler
$fileInfo = pathinfo($this->filename);
$glob = str_replace(
['{filename}', '{date}'],
[$fileInfo['filename'], '*'],
[$fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'],
$fileInfo['dirname'] . '/' . $this->filenameFormat
);
if (!empty($fileInfo['extension'])) {

12
src/Monolog/Handler/Slack/SlackRecord.php Normal file → Executable file
View File

@@ -145,7 +145,7 @@ class SlackRecord
if ($this->useShortAttachment) {
$attachment['fields'][] = $this->generateAttachmentField(
ucfirst($key),
$key,
$record[$key]
);
} else {
@@ -211,8 +211,8 @@ class SlackRecord
$hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric'));
return $hasSecondDimension || $hasNonNumericKeys
? json_encode($normalized, $prettyPrintFlag)
: json_encode($normalized);
? json_encode($normalized, $prettyPrintFlag|JSON_UNESCAPED_UNICODE)
: json_encode($normalized, JSON_UNESCAPED_UNICODE);
}
/**
@@ -229,7 +229,7 @@ class SlackRecord
* Generates attachment field
*
* @param string $title
* @param string|array $value\
* @param string|array $value
*
* @return array
*/
@@ -240,7 +240,7 @@ class SlackRecord
: $value;
return array(
'title' => $title,
'title' => ucfirst($title),
'value' => $value,
'short' => false,
);
@@ -256,7 +256,7 @@ class SlackRecord
private function generateAttachmentFields(array $data)
{
$fields = array();
foreach ($data as $key => $value) {
foreach ($this->normalizerFormatter->format($data) as $key => $value) {
$fields[] = $this->generateAttachmentField($key, $value);
}

View File

@@ -63,8 +63,7 @@ class SlackHandler extends SocketHandler
$iconEmoji,
$useShortAttachment,
$includeContextAndExtra,
$excludeFields,
$this->formatter
$excludeFields
);
$this->token = $token;
@@ -75,6 +74,11 @@ class SlackHandler extends SocketHandler
return $this->slackRecord;
}
public function getToken()
{
return $this->token;
}
/**
* {@inheritdoc}
*

View File

@@ -60,8 +60,7 @@ class SlackWebhookHandler extends AbstractProcessingHandler
$iconEmoji,
$useShortAttachment,
$includeContextAndExtra,
$excludeFields,
$this->formatter
$excludeFields
);
}
@@ -70,6 +69,11 @@ class SlackWebhookHandler extends AbstractProcessingHandler
return $this->slackRecord;
}
public function getWebhookUrl()
{
return $this->webhookUrl;
}
/**
* {@inheritdoc}
*
@@ -86,7 +90,7 @@ class SlackWebhookHandler extends AbstractProcessingHandler
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => array('Content-type: application/json'),
CURLOPT_POSTFIELDS => $postString
CURLOPT_POSTFIELDS => $postString,
);
if (defined('CURLOPT_SAFE_UPLOAD')) {
$options[CURLOPT_SAFE_UPLOAD] = true;

View File

@@ -30,15 +30,16 @@ class SocketHandler extends AbstractProcessingHandler
/** @var float */
private $writingTimeout = 10;
private $lastSentBytes = null;
private $chunkSize = null;
private $persistent = false;
private $errno;
private $errstr;
private $lastWritingAt;
/**
* @param string $connectionString Socket connection string
* @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 string $connectionString Socket connection string
* @param 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
*/
public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true)
{
@@ -90,7 +91,7 @@ class SocketHandler extends AbstractProcessingHandler
*/
public function setPersistent($persistent)
{
$this->persistent = (boolean) $persistent;
$this->persistent = (bool) $persistent;
}
/**
@@ -130,6 +131,16 @@ class SocketHandler extends AbstractProcessingHandler
$this->writingTimeout = (float) $seconds;
}
/**
* Set chunk size. Only has effect during connection in the writing cycle.
*
* @param float $bytes
*/
public function setChunkSize($bytes)
{
$this->chunkSize = $bytes;
}
/**
* Get current connection string
*
@@ -176,6 +187,16 @@ class SocketHandler extends AbstractProcessingHandler
return $this->writingTimeout;
}
/**
* Get current chunk size
*
* @return float
*/
public function getChunkSize()
{
return $this->chunkSize;
}
/**
* Check to see if the socket is currently available.
*
@@ -218,6 +239,16 @@ class SocketHandler extends AbstractProcessingHandler
return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds);
}
/**
* Wrapper to allow mocking
*
* @see http://php.net/manual/en/function.stream-set-chunk-size.php
*/
protected function streamSetChunkSize()
{
return stream_set_chunk_size($this->resource, $this->chunkSize);
}
/**
* Wrapper to allow mocking
*/
@@ -267,6 +298,7 @@ class SocketHandler extends AbstractProcessingHandler
{
$this->createSocketResource();
$this->setSocketTimeout();
$this->setStreamChunkSize();
}
private function createSocketResource()
@@ -289,6 +321,13 @@ class SocketHandler extends AbstractProcessingHandler
}
}
private function setStreamChunkSize()
{
if ($this->chunkSize && !$this->streamSetChunkSize()) {
throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()");
}
}
private function writeToSocket($data)
{
$length = strlen($data);

View File

@@ -21,6 +21,11 @@ use Monolog\Logger;
*/
class SqsHandler extends AbstractProcessingHandler
{
/** 256 KB in bytes - maximum message size in SQS */
const MAX_MESSAGE_SIZE = 262144;
/** 100 KB in bytes - head message size for new error log */
const HEAD_MESSAGE_SIZE = 102400;
/** @var SqsClient */
private $client;
/** @var string */
@@ -45,9 +50,14 @@ class SqsHandler extends AbstractProcessingHandler
throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string');
}
$messageBody = $record['formatted'];
if (strlen($messageBody) >= self::MAX_MESSAGE_SIZE) {
$messageBody = substr($messageBody, 0, self::HEAD_MESSAGE_SIZE);
}
$this->client->sendMessage([
'QueueUrl' => $this->queueUrl,
'MessageBody' => $record['formatted'],
'MessageBody' => $messageBody,
]);
}
}

View File

@@ -34,9 +34,9 @@ class StreamHandler extends AbstractProcessingHandler
/**
* @param resource|string $stream
* @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 bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write)
* @param Boolean $useLocking Try to lock log file before doing any writes
* @param bool $useLocking Try to lock log file before doing any writes
*
* @throws \Exception If a missing directory is not buildable
* @throws \InvalidArgumentException If stream is not a resource or string
@@ -106,6 +106,7 @@ class StreamHandler extends AbstractProcessingHandler
restore_error_handler();
if (!is_resource($this->stream)) {
$this->stream = null;
throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url));
}
}
@@ -169,7 +170,7 @@ class StreamHandler extends AbstractProcessingHandler
set_error_handler([$this, 'customErrorHandler']);
$status = mkdir($dir, 0777, true);
restore_error_handler();
if (false === $status) {
if (false === $status && !is_dir($dir)) {
throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir));
}
}

View File

@@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Swift_Message;
use Swift;
@@ -48,6 +49,16 @@ class SwiftMailerHandler extends MailHandler
$this->mailer->send($this->buildMessage($content, $records));
}
/**
* Gets the formatter for the Swift_Message subject.
*
* @param string $format The format of the subject
*/
protected function getSubjectFormatter(string $format): FormatterInterface
{
return new LineFormatter($format);
}
/**
* Creates instance of Swift_Message to be sent
*
@@ -70,7 +81,7 @@ class SwiftMailerHandler extends MailHandler
}
if ($records) {
$subjectFormatter = new LineFormatter($message->getSubject());
$subjectFormatter = $this->getSubjectFormatter($message->getSubject());
$message->setSubject($subjectFormatter->format($this->getHighestRecord($records)));
}

View File

@@ -32,11 +32,11 @@ class SyslogHandler extends AbstractSyslogHandler
protected $logopts;
/**
* @param string $ident
* @param mixed $facility
* @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 int $logopts Option flags for the openlog() call, defaults to LOG_PID
* @param string $ident
* @param mixed $facility
* @param 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 int $logopts Option flags for the openlog() call, defaults to LOG_PID
*/
public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID)
{

View File

@@ -25,12 +25,12 @@ class SyslogUdpHandler extends AbstractSyslogHandler
protected $ident;
/**
* @param string $host
* @param int $port
* @param mixed $facility
* @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 string $ident Program name or tag for each log message.
* @param string $host
* @param int $port
* @param mixed $facility
* @param 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 string $ident Program name or tag for each log message.
*/
public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $ident = 'php')
{

View File

@@ -84,14 +84,25 @@ class TestHandler extends AbstractProcessingHandler
return isset($this->recordsByLevel[$level]);
}
/**
* @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records
* @param int $level Logger::LEVEL constant value
*/
public function hasRecord($record, $level)
{
if (is_array($record)) {
$record = $record['message'];
if (is_string($record)) {
$record = array('message' => $record);
}
return $this->hasRecordThatPasses(function ($rec) use ($record) {
return $rec['message'] === $record;
if ($rec['message'] !== $record['message']) {
return false;
}
if (isset($record['context']) && $rec['context'] !== $record['context']) {
return false;
}
return true;
}, $level);
}

View File

@@ -0,0 +1,24 @@
<?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;
trait WebRequestRecognizerTrait
{
/**
* Checks if PHP's serving a web request
* @return bool
*/
protected function isWebRequest(): bool
{
return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI;
}
}

View File

@@ -46,6 +46,16 @@ class WhatFailureGroupHandler extends GroupHandler
*/
public function handleBatch(array $records)
{
if ($this->processors) {
$processed = array();
foreach ($records as $record) {
foreach ($this->processors as $processor) {
$processed[] = call_user_func($processor, $record);
}
}
$records = $processed;
}
foreach ($this->handlers as $handler) {
try {
$handler->handleBatch($records);

View File

@@ -15,6 +15,7 @@ use DateTimeZone;
use Monolog\Handler\HandlerInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\InvalidArgumentException;
use Throwable;
/**
* Monolog log channel
@@ -87,8 +88,6 @@ class Logger implements LoggerInterface
const API = 2;
/**
* Logging levels from syslog protocol defined in RFC 5424
*
* This is a static variable and not a constant to serve as an extension point for custom levels
*
* @var string[] $levels Logging levels with the levels as key
@@ -135,6 +134,11 @@ class Logger implements LoggerInterface
*/
protected $timezone;
/**
* @var callable
*/
protected $exceptionHandler;
/**
* @param string $name The logging channel, a simple descriptive name that is attached to all log records
* @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc.
@@ -144,7 +148,7 @@ class Logger implements LoggerInterface
public function __construct(string $name, array $handlers = [], array $processors = [], DateTimeZone $timezone = null)
{
$this->name = $name;
$this->handlers = $handlers;
$this->setHandlers($handlers);
$this->processors = $processors;
$this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC');
}
@@ -272,10 +276,10 @@ class Logger implements LoggerInterface
/**
* Adds a log record.
*
* @param int $level The logging level
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
* @param int $level The logging level
* @param string $message The log message
* @param array $context The log context
* @return bool Whether the record has been processed
*/
public function addRecord(int $level, string $message, array $context = []): bool
{
@@ -304,22 +308,26 @@ class Logger implements LoggerInterface
'extra' => [],
];
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
// advance the array pointer to the first handler that will handle this record
reset($this->handlers);
while ($handlerKey !== key($this->handlers)) {
next($this->handlers);
}
while ($handler = current($this->handlers)) {
if (true === $handler->handle($record)) {
break;
try {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
next($this->handlers);
// advance the array pointer to the first handler that will handle this record
reset($this->handlers);
while ($handlerKey !== key($this->handlers)) {
next($this->handlers);
}
while ($handler = current($this->handlers)) {
if (true === $handler->handle($record)) {
break;
}
next($this->handlers);
}
} catch (Throwable $e) {
$this->handleException($e, $record);
}
return true;
@@ -338,9 +346,7 @@ class Logger implements LoggerInterface
/**
* Gets the name of the logging level.
*
* @param int $level
* @throws \Psr\Log\InvalidArgumentException If level is not defined
* @return string
*/
public static function getLevelName(int $level): string
{
@@ -356,7 +362,6 @@ class Logger implements LoggerInterface
*
* @param string|int Level number (monolog) or name (PSR-3)
* @throws \Psr\Log\InvalidArgumentException If level is not defined
* @return int
*/
public static function toMonologLevel($level): int
{
@@ -373,9 +378,6 @@ class Logger implements LoggerInterface
/**
* Checks whether the Logger has a handler that listens on the given level
*
* @param int $level
* @return Boolean
*/
public function isHandling(int $level): bool
{
@@ -392,6 +394,23 @@ class Logger implements LoggerInterface
return false;
}
/**
* Set a custom exception handler that will be called if adding a new record fails
*
* The callable will receive an exception object and the record that failed to be logged
*/
public function setExceptionHandler(?callable $callback): self
{
$this->exceptionHandler = $callback;
return $this;
}
public function getExceptionHandler(): ?callable
{
return $this->exceptionHandler;
}
/**
* Adds a log record at an arbitrary level.
*
@@ -513,9 +532,7 @@ class Logger implements LoggerInterface
}
/**
* Set the timezone to be used for the timestamp of log records.
*
* @param DateTimeZone $tz Timezone object
* Sets the timezone to be used for the timestamp of log records.
*/
public function setTimezone(DateTimeZone $tz): self
{
@@ -525,12 +542,23 @@ class Logger implements LoggerInterface
}
/**
* Set the timezone to be used for the timestamp of log records.
*
* @return DateTimeZone
* Returns the timezone to be used for the timestamp of log records.
*/
public function getTimezone(): DateTimeZone
{
return $this->timezone;
}
/**
* Delegates exception management to the custom exception handler,
* or throws the exception if no custom handler is set.
*/
protected function handleException(Throwable $e, array $record)
{
if (!$this->exceptionHandler) {
throw $e;
}
call_user_func($this->exceptionHandler, $e, $record);
}
}

View File

@@ -0,0 +1,32 @@
<?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\Processor;
/**
* Injects value of gethostname in all records
*/
class HostnameProcessor
{
private static $host;
public function __construct()
{
self::$host = (string) gethostname();
}
public function __invoke(array $record): array
{
$record['extra']['hostname'] = self::$host;
return $record;
}
}

View File

@@ -51,12 +51,7 @@ class IntrospectionProcessor
return $record;
}
/*
* 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);
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// skip first since it's always the current method
array_shift($trace);
@@ -70,11 +65,13 @@ class IntrospectionProcessor
foreach ($this->skipClassesPartials as $part) {
if (strpos($trace[$i]['class'], $part) !== false) {
$i++;
continue 2;
}
}
} elseif (in_array($trace[$i]['function'], $this->skipFunctions)) {
$i++;
continue;
}

View File

@@ -20,16 +20,21 @@ namespace Monolog\Processor;
*/
class PsrLogMessageProcessor
{
const SIMPLE_DATE = "Y-m-d\TH:i:sP";
const SIMPLE_DATE = "Y-m-d\TH:i:s.uP";
private $dateFormat;
/** @var bool */
private $removeUsedContextFields;
/**
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
* @param bool $removeUsedContextFields If set to true the fields interpolated into message gets unset
*/
public function __construct(string $dateFormat = null)
public function __construct(string $dateFormat = null, bool $removeUsedContextFields = false)
{
$this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat;
$this->dateFormat = $dateFormat;
$this->removeUsedContextFields = $removeUsedContextFields;
}
/**
@@ -44,14 +49,31 @@ class PsrLogMessageProcessor
$replacements = [];
foreach ($record['context'] as $key => $val) {
$placeholder = '{' . $key . '}';
if (strpos($record['message'], $placeholder) === false) {
continue;
}
if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) {
$replacements['{'.$key.'}'] = $val;
$replacements[$placeholder] = $val;
} elseif ($val instanceof \DateTimeInterface) {
$replacements['{'.$key.'}'] = $val->format($this->dateFormat);
if (!$this->dateFormat && $val instanceof \Monolog\DateTimeImmutable) {
// handle monolog dates using __toString if no specific dateFormat was asked for
// so that it follows the useMicroseconds flag
$replacements[$placeholder] = (string) $val;
} else {
$replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE);
}
} elseif (is_object($val)) {
$replacements['{'.$key.'}'] = '[object '.get_class($val).']';
$replacements[$placeholder] = '[object '.get_class($val).']';
} elseif (is_array($val)) {
$replacements[$placeholder] = 'array'.@json_encode($val);
} else {
$replacements['{'.$key.'}'] = '['.gettype($val).']';
$replacements[$placeholder] = '['.gettype($val).']';
}
if ($this->removeUsedContextFields) {
unset($record['context'][$key]);
}
}