update v 1.0.7.5

This commit is contained in:
Sujit Prasad
2016-06-13 20:41:55 +05:30
parent aa9786d829
commit 283d97e3ea
5078 changed files with 339851 additions and 175995 deletions

View File

@@ -1,15 +1,59 @@
<?php
$finder = Symfony\CS\Finder\DefaultFinder::create()
$header = <<<EOF
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.
EOF;
$finder = Symfony\CS\Finder::create()
->files()
->name('*.php')
->exclude('Fixtures')
->in(__DIR__.'/src')
->in(__DIR__.'/tests')
;
return Symfony\CS\Config\Config::create()
->fixers(array(
'psr0', 'encoding', 'short_tag', 'braces', 'elseif', 'eof_ending', 'function_declaration', 'indentation', 'line_after_namespace', 'linefeed', 'lowercase_constants', 'lowercase_keywords', 'multiple_use', 'php_closing_tag', 'trailing_spaces', 'visibility', 'duplicate_semicolon', 'extra_empty_lines', 'include', 'namespace_no_leading_whitespace', 'object_operator', 'operators_spaces', 'phpdoc_params', 'return', 'single_array_no_trailing_comma', 'spaces_cast', 'standardize_not_equal', 'ternary_spaces', 'unused_use', 'whitespacy_lines',
return Symfony\CS\Config::create()
->setUsingCache(true)
//->setUsingLinter(false)
->setRiskyAllowed(true)
->setRules(array(
'@PSR2' => true,
'binary_operator_spaces' => true,
'blank_line_before_return' => true,
'header_comment' => array('header' => $header),
'include' => true,
'long_array_syntax' => true,
'method_separation' => true,
'no_blank_lines_after_class_opening' => true,
'no_blank_lines_after_phpdoc' => true,
'no_blank_lines_between_uses' => true,
'no_duplicate_semicolons' => true,
'no_extra_consecutive_blank_lines' => true,
'no_leading_import_slash' => true,
'no_leading_namespace_whitespace' => true,
'no_trailing_comma_in_singleline_array' => true,
'no_unused_imports' => true,
'object_operator_without_whitespace' => true,
'phpdoc_align' => true,
'phpdoc_indent' => true,
'phpdoc_no_access' => true,
'phpdoc_no_package' => true,
'phpdoc_order' => true,
'phpdoc_scalar' => true,
'phpdoc_trim' => true,
'phpdoc_type_to_var' => true,
'psr0' => true,
'single_blank_line_before_namespace' => true,
'spaces_cast' => true,
'standardize_not_equals' => true,
'ternary_operator_spaces' => true,
'trailing_comma_in_multiline_array' => true,
'whitespacy_lines' => true,
))
->finder($finder)
;

View File

@@ -1,3 +1,37 @@
### 1.19.0 (2016-04-12)
* Break: StreamHandler will not close streams automatically that it does not own. If you pass in a stream (not a path/url), then it will not close it for you. You can retrieve those using getStream() if needed
* Added DeduplicationHandler to remove duplicate records from notifications across multiple requests, useful for email or other notifications on errors
* Added ability to use `%message%` and other LineFormatter replacements in the subject line of emails sent with NativeMailHandler and SwiftMailerHandler
* Fixed HipChatHandler handling of long messages
### 1.18.2 (2016-04-02)
* Fixed ElasticaFormatter to use more precise dates
* Fixed GelfMessageFormatter sending too long messages
### 1.18.1 (2016-03-13)
* Fixed SlackHandler bug where slack dropped messages randomly
* Fixed RedisHandler issue when using with the PHPRedis extension
* Fixed AmqpHandler content-type being incorrectly set when using with the AMQP extension
* Fixed BrowserConsoleHandler regression
### 1.18.0 (2016-03-01)
* Added optional reduction of timestamp precision via `Logger->useMicrosecondTimestamps(false)`, disabling it gets you a bit of performance boost but reduces the precision to the second instead of microsecond
* Added possibility to skip some extra stack frames in IntrospectionProcessor if you have some library wrapping Monolog that is always adding frames
* Added `Logger->withName` to clone a logger (keeping all handlers) with a new name
* Added FluentdFormatter for the Fluentd unix socket protocol
* Added HandlerWrapper base class to ease the creation of handler wrappers, just extend it and override as needed
* Added support for replacing context sub-keys using `%context.*%` in LineFormatter
* Added support for `payload` context value in RollbarHandler
* Added setRelease to RavenHandler to describe the application version, sent with every log
* Added support for `fingerprint` context value in RavenHandler
* Fixed JSON encoding errors that would gobble up the whole log record, we now handle those more gracefully by dropping chars as needed
* Fixed write timeouts in SocketHandler and derivatives, set to 10sec by default, lower it with `setWritingTimeout()`
* Fixed PHP7 compatibility with regard to Exception/Throwable handling in a few places
### 1.17.2 (2015-10-14)
* Fixed ErrorHandler compatibility with non-Monolog PSR-3 loggers

View File

@@ -1,4 +1,4 @@
Copyright (c) 2011-2015 Jordi Boggiano
Copyright (c) 2011-2016 Jordi Boggiano
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -23,7 +23,7 @@
"ruflin/elastica": ">=0.90 <3.0",
"doctrine/couchdb": "~1.0@dev",
"aws/aws-sdk-php": "^2.4.9",
"videlalvaro/php-amqplib": "~2.4",
"php-amqplib/php-amqplib": "~2.4",
"swiftmailer/swiftmailer": "~5.3",
"php-console/php-console": "^3.1.3",
"phpunit/phpunit-mock-objects": "2.3.0",
@@ -35,9 +35,10 @@
"raven/raven": "Allow sending log messages to a Sentry server",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
"videlalvaro/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongo": "Allow sending log messages to a MongoDB server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"php-console/php-console": "Allow sending log messages to Google Chrome"
@@ -53,7 +54,7 @@
},
"extra": {
"branch-alias": {
"dev-master": "1.16.x-dev"
"dev-master": "2.0.x-dev"
}
},
"scripts": {

View File

@@ -189,6 +189,9 @@ $logger->pushHandler($firephp);
$securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
$securityLogger->pushHandler($firephp);
// Or clone the first one to only change the channel
$securityLogger = $logger->withName('security');
```
## Customizing the log format

View File

@@ -85,6 +85,16 @@
when it happens you will have the full information, including debug and info
records. This provides you with all the information you need, but only when
you need it.
- _DeduplicationHandler_: Useful if you are sending notifications or emails
when critical errors occur. It takes a logger as parameter and will
accumulate log records of all levels until the end of the request (or
`flush()` is called). At that point it delivers all records to the handler
it wraps, but only if the records are unique over a given time period
(60seconds by default). If the records are duplicates they are simply
discarded. The main use of this is in case of critical failure like if your
database is unreachable for example all your requests will fail and that
can result in a lot of notifications being sent. Adding this handler reduces
the amount of notifications to a manageable level.
- _WhatFailureGroupHandler_: This handler extends the _GroupHandler_ ignoring
exceptions raised by each child handler. This allows you to ignore issues
where a remote tcp connection may have died but you do not want your entire
@@ -105,6 +115,8 @@
- _PsrHandler_: Can be used to forward log records to an existing PSR-3 logger
- _TestHandler_: Used for testing, it records everything that is sent to it and
has accessors to read out the information.
- _HandlerWrapper_: A simple handler wrapper you can inherit from to create
your own wrappers easily.
## Formatters
@@ -124,6 +136,7 @@
## Processors
- _PsrLogMessageProcessor_: Processes a log record's message according to PSR-3 rules, replacing `{foo}` with the value from `$context['foo']`.
- _IntrospectionProcessor_: Adds the line/file/class/method from which the log call originated.
- _WebProcessor_: Adds the current request URI, request method and client IP to a log record.
- _MemoryUsageProcessor_: Adds the current memory usage to a log record.

View File

@@ -41,10 +41,9 @@ class ChromePHPFormatter implements FormatterInterface
{
// Retrieve the line and file if set and remove them from the formatted extra
$backtrace = 'unknown';
if (isset($record['extra']['file']) && isset($record['extra']['line'])) {
if (isset($record['extra']['file'], $record['extra']['line'])) {
$backtrace = $record['extra']['file'].' : '.$record['extra']['line'];
unset($record['extra']['file']);
unset($record['extra']['line']);
unset($record['extra']['file'], $record['extra']['line']);
}
$message = array('message' => $record['message']);

View File

@@ -36,7 +36,9 @@ class ElasticaFormatter extends NormalizerFormatter
*/
public function __construct($index, $type)
{
parent::__construct(\DateTime::ISO8601);
// 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;
}

View File

@@ -0,0 +1,85 @@
<?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'],
'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;
}
}

View File

@@ -22,6 +22,8 @@ use Gelf\Message;
*/
class GelfMessageFormatter extends NormalizerFormatter
{
const MAX_LENGTH = 32766;
/**
* @var string the name of the system for the Gelf log message
*/
@@ -79,24 +81,48 @@ class GelfMessageFormatter extends NormalizerFormatter
->setHost($this->systemName)
->setLevel($this->logLevels[$record['level']]);
// start count with message length + system name length + 200 for padding / metadata
$len = 200 + strlen((string) $record['message']) + strlen($this->systemName);
if ($len > self::MAX_LENGTH) {
$message->setShortMessage(substr($record['message'], 0, self::MAX_LENGTH - 200));
return $message;
}
if (isset($record['channel'])) {
$message->setFacility($record['channel']);
$len += strlen($record['channel']);
}
if (isset($record['extra']['line'])) {
$message->setLine($record['extra']['line']);
$len += 10;
unset($record['extra']['line']);
}
if (isset($record['extra']['file'])) {
$message->setFile($record['extra']['file']);
$len += strlen($record['extra']['file']);
unset($record['extra']['file']);
}
foreach ($record['extra'] as $key => $val) {
$message->setAdditional($this->extraPrefix . $key, is_scalar($val) ? $val : $this->toJson($val));
$val = is_scalar($val) ? $val : $this->toJson($val);
$len += strlen($this->extraPrefix . $key . $val);
if ($len > self::MAX_LENGTH) {
$message->setAdditional($this->extraPrefix . $key, substr($val, 0, self::MAX_LENGTH - $len));
break;
}
$message->setAdditional($this->extraPrefix . $key, $val);
}
foreach ($record['context'] as $key => $val) {
$message->setAdditional($this->contextPrefix . $key, is_scalar($val) ? $val : $this->toJson($val));
$val = is_scalar($val) ? $val : $this->toJson($val);
$len += strlen($this->contextPrefix . $key . $val);
if ($len > self::MAX_LENGTH) {
$message->setAdditional($this->contextPrefix . $key, substr($val, 0, self::MAX_LENGTH - $len));
break;
}
$message->setAdditional($this->contextPrefix . $key, $val);
}
if (null === $message->getFile() && isset($record['context']['exception']['file'])) {

View File

@@ -24,7 +24,7 @@ class HtmlFormatter extends NormalizerFormatter
/**
* Translates Monolog log levels to html color priorities.
*/
private $logLevels = array(
protected $logLevels = array(
Logger::DEBUG => '#cccccc',
Logger::INFO => '#468847',
Logger::NOTICE => '#3a87ad',
@@ -64,8 +64,8 @@ class HtmlFormatter extends NormalizerFormatter
/**
* Create a HTML h1 tag
*
* @param string $title Text to be in the h1
* @param integer $level Error level
* @param string $title Text to be in the h1
* @param int $level Error level
* @return string
*/
private function addTitle($title, $level)
@@ -74,6 +74,7 @@ class HtmlFormatter extends NormalizerFormatter
return '<h1 style="background: '.$this->logLevels[$level].';color: #ffffff;padding: 5px;" class="monolog-output">'.$title.'</h1>';
}
/**
* Formats a log record.
*

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -22,7 +22,7 @@ class LogglyFormatter extends JsonFormatter
* Overrides the default batch mode to new lines for compatibility with the
* Loggly bulk API.
*
* @param integer $batchMode
* @param int $batchMode
*/
public function __construct($batchMode = self::BATCH_MODE_NEWLINES, $appendNewline = false)
{

View File

@@ -45,16 +45,16 @@ class LogstashFormatter extends NormalizerFormatter
protected $contextPrefix;
/**
* @var integer logstash format version to use
* @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 integer $version the logstash format version to use, defaults to 0
* @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)
{
@@ -92,7 +92,7 @@ class LogstashFormatter extends NormalizerFormatter
$message = array(
'@timestamp' => $record['datetime'],
'@source' => $this->systemName,
'@fields' => array()
'@fields' => array(),
);
if (isset($record['message'])) {
$message['@message'] = $record['message'];

View File

@@ -90,13 +90,14 @@ 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);
}
// non-serializable objects that implement __toString stringified
if (method_exists($data, '__toString') && !$data instanceof \JsonSerializable) {
$value = (string) $data;
$value = $data->__toString();
} else {
// the rest is json-serialized in some way
$value = $this->toJson($data, true);
@@ -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(),
@@ -138,23 +144,72 @@ class NormalizerFormatter implements FormatterInterface
return $data;
}
/**
* Return the JSON representation of a value
*
* @param mixed $data
* @param bool $ignoreErrors
* @throws \RuntimeException if encoding fails and errors are not ignored
* @return string
*/
protected function toJson($data, $ignoreErrors = false)
{
// suppress json_encode errors since it's twitchy with some inputs
if ($ignoreErrors) {
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return @json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return @json_encode($data);
return @$this->jsonEncode($data);
}
$json = $this->jsonEncode($data);
if ($json === false) {
$json = $this->handleJsonError(json_last_error(), $data);
}
return $json;
}
/**
* @param mixed $data
* @return string JSON encoded data or null on failure
*/
private function jsonEncode($data)
{
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
$json = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} else {
$json = json_encode($data);
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return json_encode($data);
}
/**
* Handle a json_encode failure.
*
* If the failure is due to invalid string encoding, try to clean the
* input and encode again. If the second encoding iattempt fails, the
* inital error is not encoding related or the input can't be cleaned then
* raise a descriptive exception.
*
* @param int $code return code of json_last_error function
* @param mixed $data data that was meant to be encoded
* @throws \RuntimeException if failure can't be corrected
* @return string JSON encoded data after error correction
*/
private function handleJsonError($code, $data)
{
if ($code !== JSON_ERROR_UTF8) {
$this->throwEncodeError($code, $data);
}
if (is_string($data)) {
$this->detectAndCleanUtf8($data);
} elseif (is_array($data)) {
array_walk_recursive($data, array($this, 'detectAndCleanUtf8'));
} else {
$this->throwEncodeError($code, $data);
}
$json = $this->jsonEncode($data);
if ($json === false) {
$this->throwEncodeError(json_last_error(), $data);
}
@@ -165,8 +220,8 @@ class NormalizerFormatter implements FormatterInterface
/**
* Throws an exception according to a given code with a customized message
*
* @param int $code return code of json_last_error function
* @param mixed $data data that was meant to be encoded
* @param int $code return code of json_last_error function
* @param mixed $data data that was meant to be encoded
* @throws \RuntimeException
*/
private function throwEncodeError($code, $data)
@@ -190,4 +245,36 @@ class NormalizerFormatter implements FormatterInterface
throw new \RuntimeException('JSON encoding failed: '.$msg.'. Encoding: '.var_export($data, true));
}
/**
* Detect invalid UTF-8 string characters and convert to valid UTF-8.
*
* Valid UTF-8 input will be left unmodified, but strings containing
* invalid UTF-8 codepoints will be reencoded as UTF-8 with an assumed
* original encoding of ISO-8859-15. This conversion may result in
* incorrect output if the actual encoding was not ISO-8859-15, but it
* will be clean UTF-8 output and will not rely on expensive and fragile
* detection algorithms.
*
* Function converts the input in place in the passed variable so that it
* can be used as a callback for array_walk_recursive.
*
* @param mixed &$data Input to check and convert if needed
* @private
*/
public function detectAndCleanUtf8(&$data)
{
if (is_string($data) && !preg_match('//u', $data)) {
$data = preg_replace_callback(
'/[\x80-\xFF]+/',
function ($m) { return utf8_encode($m[0]); },
$data
);
$data = str_replace(
array('¤', '¦', '¨', '´', '¸', '¼', '½', '¾'),
array('€', 'Š', 'š', 'Ž', 'ž', 'Œ', 'œ', 'Ÿ'),
$data
);
}
}
}

View File

@@ -32,7 +32,7 @@ abstract class AbstractHandler implements HandlerInterface
protected $processors = array();
/**
* @param integer $level The minimum logging level at which this handler will be triggered
* @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
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
@@ -118,7 +118,7 @@ abstract class AbstractHandler implements HandlerInterface
/**
* Sets minimum logging level at which this handler will be triggered.
*
* @param integer $level
* @param int|string $level Level or level name
* @return self
*/
public function setLevel($level)
@@ -131,7 +131,7 @@ abstract class AbstractHandler implements HandlerInterface
/**
* Gets minimum logging level at which this handler will be triggered.
*
* @return integer
* @return int
*/
public function getLevel()
{

View File

@@ -54,7 +54,7 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler
/**
* @param mixed $facility
* @param integer $level The minimum logging level at which this handler will be triggered
* @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
*/
public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)
@@ -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

View File

@@ -55,7 +55,64 @@ class AmqpHandler extends AbstractProcessingHandler
protected function write(array $record)
{
$data = $record["formatted"];
$routingKey = $this->getRoutingKey($record);
if ($this->exchange instanceof AMQPExchange) {
$this->exchange->publish(
$data,
$routingKey,
0,
array(
'delivery_mode' => 2,
'content_type' => 'application/json',
)
);
} else {
$this->exchange->basic_publish(
$this->createAmqpMessage($data),
$this->exchangeName,
$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
@@ -63,29 +120,22 @@ class AmqpHandler extends AbstractProcessingHandler
$record['channel']
);
if ($this->exchange instanceof AMQPExchange) {
$this->exchange->publish(
$data,
strtolower($routingKey),
0,
array(
'delivery_mode' => 2,
'Content-type' => 'application/json'
)
);
} else {
$this->exchange->basic_publish(
new AMQPMessage(
(string) $data,
array(
'delivery_mode' => 2,
'content_type' => 'application/json'
)
),
$this->exchangeName,
strtolower($routingKey)
);
}
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',
)
);
}
/**

View File

@@ -31,7 +31,6 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
* Example of formatted string:
*
* You can do [[blue text]]{color: blue} or [[green background]]{background-color: green; color: white}
*
*/
protected function getDefaultFormatter()
{
@@ -47,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();
}
}
@@ -59,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();
}
@@ -92,6 +81,55 @@ 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
*
* If Content-Type is set to application/javascript or text/javascript -> js
* If Content-Type is set to text/html, or is unset -> html
* If Content-Type is anything else -> unknown
*
* @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';
}
if (stripos($header, 'text/html') === false) {
return 'unknown';
}
break;
}
}
return 'html';
}
private static function generateScript()
{
$script = array();

View File

@@ -32,8 +32,8 @@ class BufferHandler extends AbstractHandler
/**
* @param HandlerInterface $handler Handler.
* @param integer $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param integer $level The minimum logging level at which this handler will be triggered
* @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
*/

View File

@@ -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
@@ -30,6 +32,11 @@ class ChromePHPHandler extends AbstractProcessingHandler
* Header name
*/
const HEADER_NAME = 'X-ChromeLogger-Data';
/**
* Regular expression to detect supported browsers (matches any Chrome, or Firefox 43+)
*/
const USER_AGENT_REGEX = '{\b(?:Chrome/\d+(?:\.\d+)*|Firefox/(?:4[3-9]|[5-9]\d|\d{3,})(?:\.\d)*)\b}';
protected static $initialized = false;
@@ -51,7 +58,7 @@ class ChromePHPHandler extends AbstractProcessingHandler
protected static $sendHeaders = true;
/**
* @param integer $level The minimum logging level at which this handler will be triggered
* @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
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
@@ -175,7 +182,7 @@ class ChromePHPHandler extends AbstractProcessingHandler
return false;
}
return preg_match('{\bChrome/\d+[\.\d+]*\b}', $_SERVER['HTTP_USER_AGENT']);
return preg_match(self::USER_AGENT_REGEX, $_SERVER['HTTP_USER_AGENT']);
}
/**

View File

@@ -54,7 +54,7 @@ class CouchDBHandler extends AbstractProcessingHandler
'ignore_errors' => true,
'max_redirects' => 0,
'header' => 'Content-type: application/json',
)
),
));
if (false === @file_get_contents($url, null, $context)) {

View File

@@ -21,37 +21,37 @@ use Monolog\Logger;
*/
class CubeHandler extends AbstractProcessingHandler
{
private $udpConnection = null;
private $httpConnection = null;
private $scheme = null;
private $host = null;
private $port = null;
private $udpConnection;
private $httpConnection;
private $scheme;
private $host;
private $port;
private $acceptedSchemes = array('http', 'udp');
/**
* Create a Cube handler
*
* @throws \UnexpectedValueException when given url is not a valid url.
* A valid url must consists of three parts : protocol://host:port
* Only valid protocol used by Cube are http and udp
* A valid url must consist of three parts : protocol://host:port
* Only valid protocols used by Cube are http and udp
*/
public function __construct($url, $level = Logger::DEBUG, $bubble = true)
{
$urlInfos = parse_url($url);
$urlInfo = parse_url($url);
if (!isset($urlInfos['scheme']) || !isset($urlInfos['host']) || !isset($urlInfos['port'])) {
if (!isset($urlInfo['scheme'], $urlInfo['host'], $urlInfo['port'])) {
throw new \UnexpectedValueException('URL "'.$url.'" is not valid');
}
if (!in_array($urlInfos['scheme'], $this->acceptedSchemes)) {
if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) {
throw new \UnexpectedValueException(
'Invalid protocol (' . $urlInfos['scheme'] . ').'
'Invalid protocol (' . $urlInfo['scheme'] . ').'
. ' Valid options are ' . implode(', ', $this->acceptedSchemes));
}
$this->scheme = $urlInfos['scheme'];
$this->host = $urlInfos['host'];
$this->port = $urlInfos['port'];
$this->scheme = $urlInfo['scheme'];
$this->host = $urlInfo['host'];
$this->port = $urlInfo['port'];
parent::__construct($level, $bubble);
}
@@ -59,7 +59,8 @@ class CubeHandler extends AbstractProcessingHandler
/**
* Establish a connection to an UDP socket
*
* @throws \LogicException when unable to connect to the socket
* @throws \LogicException when unable to connect to the socket
* @throws MissingExtensionException when there is no socket extension
*/
protected function connectUdp()
{
@@ -79,6 +80,7 @@ class CubeHandler extends AbstractProcessingHandler
/**
* Establish a connection to a http server
* @throws \LogicException when no curl extension
*/
protected function connectHttp()
{
@@ -140,10 +142,10 @@ class CubeHandler extends AbstractProcessingHandler
curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');
curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen('['.$data.']'))
);
'Content-Type: application/json',
'Content-Length: ' . strlen('['.$data.']'),
));
Curl\Util::execute($ch, 5, false);
Curl\Util::execute($this->httpConnection, 5, false);
}
}

View File

@@ -26,7 +26,7 @@ class Util
/**
* Executes a CURL request with optional retries and exception on failure
*
* @param resource $ch curl handler
* @param resource $ch curl handler
* @throws \RuntimeException
*/
public static function execute($ch, $retries = 5, $closeAfterDone = true)

View File

@@ -0,0 +1,169 @@
<?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\Logger;
/**
* Simple handler wrapper that deduplicates log records across multiple requests
*
* It also includes the BufferHandler functionality and will buffer
* all messages until the end of the request or flush() is called.
*
* This works by storing all log records' messages above $deduplicationLevel
* to the file specified by $deduplicationStore. When further logs come in at the end of the
* request (or when flush() is called), all those above $deduplicationLevel are checked
* against the existing stored logs. If they match and the timestamps in the stored log is
* not older than $time seconds, the new log record is discarded. If no log record is new, the
* whole data set is discarded.
*
* This is mainly useful in combination with Mail handlers or things like Slack or HipChat handlers
* that send messages to people, to avoid spamming with the same message over and over in case of
* a major component failure like a database server being down which makes all requests fail in the
* same way.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class DeduplicationHandler extends BufferHandler
{
/**
* @var string
*/
protected $deduplicationStore;
/**
* @var int
*/
protected $deduplicationLevel;
/**
* @var int
*/
protected $time;
/**
* @var bool
*/
private $gc = false;
/**
* @param HandlerInterface $handler Handler.
* @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
*/
public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true)
{
parent::__construct($handler, 0, Logger::DEBUG, $bubble, false);
$this->deduplicationStore = $deduplicationStore === null ? sys_get_temp_dir() . '/monolog-dedup-' . substr(md5(__FILE__), 0, 20) .'.log' : $deduplicationStore;
$this->deduplicationLevel = Logger::toMonologLevel($deduplicationLevel);
$this->time = $time;
}
public function flush()
{
if ($this->bufferSize === 0) {
return;
}
$passthru = null;
foreach ($this->buffer as $record) {
if ($record['level'] >= $this->deduplicationLevel) {
$passthru = $passthru || !$this->isDuplicate($record);
if ($passthru) {
$this->appendRecord($record);
}
}
}
// default of null is valid as well as if no record matches duplicationLevel we just pass through
if ($passthru === true || $passthru === null) {
$this->handler->handleBatch($this->buffer);
}
$this->clear();
if ($this->gc) {
$this->collectLogs();
}
}
private function isDuplicate(array $record)
{
if (!file_exists($this->deduplicationStore)) {
return false;
}
$store = file($this->deduplicationStore, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (!is_array($store)) {
return false;
}
$yesterday = time() - 86400;
$timestampValidity = $record['datetime']->getTimestamp() - $this->time;
$expectedMessage = preg_replace('{[\r\n].*}', '', $record['message']);
for ($i = count($store) - 1; $i >= 0; $i--) {
list($timestamp, $level, $message) = explode(':', $store[$i], 3);
if ($level === $record['level_name'] && $message === $expectedMessage && $timestamp > $timestampValidity) {
return true;
}
if ($timestamp < $yesterday) {
$this->gc = true;
}
}
return false;
}
private function collectLogs()
{
if (!file_exists($this->deduplicationStore)) {
return false;
}
$handle = fopen($this->deduplicationStore, 'rw+');
flock($handle, LOCK_EX);
$validLogs = array();
$timestampValidity = time() - $this->time;
while (!feof($handle)) {
$log = fgets($handle);
if (substr($log, 0, 10) >= $timestampValidity) {
$validLogs[] = $log;
}
}
ftruncate($handle, 0);
rewind($handle);
foreach ($validLogs as $log) {
fwrite($handle, $log);
}
flock($handle, LOCK_UN);
fclose($handle);
$this->gc = false;
}
private function appendRecord(array $record)
{
file_put_contents($this->deduplicationStore, $record['datetime']->getTimestamp() . ':' . $record['level_name'] . ':' . preg_replace('{[\r\n].*}', '', $record['message']) . "\n", FILE_APPEND);
}
}

View File

@@ -39,8 +39,8 @@ class DynamoDbHandler extends AbstractProcessingHandler
/**
* @param DynamoDbClient $client
* @param string $table
* @param integer $level
* @param boolean $bubble
* @param int $level
* @param bool $bubble
*/
public function __construct(DynamoDbClient $client, $table, $level = Logger::DEBUG, $bubble = true)
{
@@ -64,7 +64,7 @@ class DynamoDbHandler extends AbstractProcessingHandler
$this->client->putItem(array(
'TableName' => $this->table,
'Item' => $formatted
'Item' => $formatted,
));
}

View File

@@ -48,7 +48,7 @@ class ElasticSearchHandler extends AbstractProcessingHandler
/**
* @param Client $client Elastica Client object
* @param array $options Handler configuration
* @param integer $level The minimum logging level at which this handler will be triggered
* @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
*/
public function __construct(Client $client, array $options = array(), $level = Logger::DEBUG, $bubble = true)

View File

@@ -28,8 +28,8 @@ class ErrorLogHandler extends AbstractProcessingHandler
protected $expandNewlines;
/**
* @param integer $messageType Says where the error should go.
* @param integer $level The minimum logging level at which this handler will be triggered
* @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
*/

View File

@@ -31,7 +31,7 @@ class FilterHandler extends AbstractHandler
protected $handler;
/**
* Minimum level for logs that are passes to handler
* Minimum level for logs that are passed to handler
*
* @var int[]
*/
@@ -70,8 +70,8 @@ class FilterHandler extends AbstractHandler
}
/**
* @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 int|string|array $minLevelOrList A list of levels to accept or a minimum level or level name if maxLevel is provided
* @param int|string $maxLevel Maximum level or level name to accept, only used if $minLevelOrList is not an array
*/
public function setAcceptedLevels($minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY)
{

View File

@@ -2,12 +2,12 @@
/*
* 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.
*/
*
* (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\FingersCrossed;

View File

@@ -118,7 +118,7 @@ class FleepHookHandler extends SocketHandler
private function buildContent($record)
{
$dataArray = array(
'message' => $record['formatted']
'message' => $record['formatted'],
);
return http_build_query($dataArray);

View File

@@ -33,15 +33,15 @@ class GelfHandler extends AbstractProcessingHandler
/**
* @param PublisherInterface|IMessagePublisher|Publisher $publisher a publisher object
* @param integer $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($publisher, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
if (!$publisher instanceof Publisher && !$publisher instanceof IMessagePublisher && !$publisher instanceof PublisherInterface) {
throw new InvalidArgumentException("Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance");
throw new InvalidArgumentException('Invalid publisher, expected a Gelf\Publisher, Gelf\IMessagePublisher or Gelf\PublisherInterface instance');
}
$this->publisher = $publisher;

View File

@@ -11,6 +11,8 @@
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
/**
* Forwards records to multiple handlers
*
@@ -77,4 +79,16 @@ class GroupHandler extends AbstractHandler
$handler->handleBatch($records);
}
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
foreach ($this->handlers as $handler) {
$handler->setFormatter($formatter);
}
return $this;
}
}

View File

@@ -0,0 +1,106 @@
<?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();
}
}

View File

@@ -84,16 +84,16 @@ class HipChatHandler extends SocketHandler
private $version;
/**
* @param string $token HipChat API Token
* @param string $room The room that should be alerted of the message (Id or Name)
* @param string $name Name used in the "from" field. Not used for v2
* @param bool $notify Trigger a notification in clients or not
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $useSSL Whether to connect via SSL.
* @param string $format The format of the messages (default to text, can be set to html if you have html in the messages)
* @param string $host The HipChat server hostname.
* @param string $version The HipChat API version (default HipChatHandler::API_V1)
* @param string $token HipChat API Token
* @param string $room The room that should be alerted of the message (Id or Name)
* @param string $name Name used in the "from" field.
* @param bool $notify Trigger a notification in clients or not
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
* @param bool $useSSL Whether to connect via SSL.
* @param string $format The format of the messages (default to text, can be set to html if you have html in the messages)
* @param string $host The HipChat server hostname.
* @param string $version The HipChat API version (default HipChatHandler::API_V1)
*/
public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $format = 'text', $host = 'api.hipchat.com', $version = self::API_V1)
{
@@ -143,10 +143,23 @@ class HipChatHandler extends SocketHandler
'color' => $this->getAlertColor($record['level']),
);
if (!$this->validateStringLength($dataArray['message'], static::MAXIMUM_MESSAGE_LENGTH)) {
if (function_exists('mb_substr')) {
$dataArray['message'] = mb_substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
} else {
$dataArray['message'] = substr($dataArray['message'], 0, static::MAXIMUM_MESSAGE_LENGTH).' [truncated]';
}
}
// if we are using the legacy API then we need to send some additional information
if ($this->version == self::API_V1) {
$dataArray['room_id'] = $this->room;
$dataArray['from'] = $this->name;
}
// append the sender name if it is set
// always append it if we use the v1 api (it is required in v1)
if ($this->version == self::API_V1 || $this->name !== null) {
$dataArray['from'] = (string) $this->name;
}
return http_build_query($dataArray);
@@ -179,7 +192,7 @@ class HipChatHandler extends SocketHandler
/**
* Assigns a color to each level of log records.
*
* @param integer $level
* @param int $level
* @return string
*/
protected function getAlertColor($level)
@@ -303,7 +316,7 @@ class HipChatHandler extends SocketHandler
array(
'level' => $level,
'level_name' => $levelName,
'datetime' => $datetime
'datetime' => $datetime,
)
);
}

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 integer $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 Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true)
{
@@ -51,7 +51,7 @@ class IFTTTHandler extends AbstractProcessingHandler
$postData = array(
"value1" => $record["channel"],
"value2" => $record["level_name"],
"value3" => $record["message"]
"value3" => $record["message"],
);
$postString = json_encode($postData);
@@ -61,7 +61,7 @@ class IFTTTHandler extends AbstractProcessingHandler
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postString);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"Content-Type: application/json"
"Content-Type: application/json",
));
Curl\Util::execute($ch);

View File

@@ -24,10 +24,10 @@ class LogEntriesHandler extends SocketHandler
protected $logToken;
/**
* @param string $token Log token supplied by LogEntries
* @param boolean $useSSL Whether or not SSL encryption should be used.
* @param int $level The minimum logging level to trigger this handler
* @param boolean $bubble Whether or not messages that are handled should bubble up the stack.
* @param string $token Log token supplied by LogEntries
* @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
*/

View File

@@ -52,4 +52,16 @@ abstract class MailHandler extends AbstractProcessingHandler
{
$this->send((string) $record['formatted'], array($record));
}
protected function getHighestRecord(array $records)
{
$highestRecord = null;
foreach ($records as $record) {
if ($highestRecord === null || $highestRecord['level'] < $record['level']) {
$highestRecord = $record;
}
}
return $highestRecord;
}
}

View File

@@ -26,7 +26,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 integer $level The minimum logging level at which this handler will be triggered
* @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
*/
public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true)

View File

@@ -31,8 +31,8 @@ class MongoDBHandler extends AbstractProcessingHandler
public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true)
{
if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
throw new \InvalidArgumentException('MongoClient or Mongo instance required');
if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo || $mongo instanceof \MongoDB\Client)) {
throw new \InvalidArgumentException('MongoClient, Mongo or MongoDB\Client instance required');
}
$this->mongoCollection = $mongo->selectCollection($database, $collection);
@@ -42,7 +42,11 @@ class MongoDBHandler extends AbstractProcessingHandler
protected function write(array $record)
{
$this->mongoCollection->save($record["formatted"]);
if ($this->mongoCollection instanceof \MongoDB\Collection) {
$this->mongoCollection->insertOne($record["formatted"]);
} else {
$this->mongoCollection->save($record["formatted"]);
}
}
/**

View File

@@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
/**
* NativeMailerHandler uses the mail() function to send the emails
@@ -47,7 +48,7 @@ class NativeMailerHandler extends MailHandler
/**
* The wordwrap length for the message
* @var integer
* @var int
*/
protected $maxColumnWidth;
@@ -67,8 +68,8 @@ class NativeMailerHandler extends MailHandler
* @param string|array $to The receiver of the mail
* @param string $subject The subject of the mail
* @param string $from The sender of the mail
* @param integer $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
* @param int $maxColumnWidth The maximum column width that the message lines will have
*/
public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70)
@@ -101,7 +102,7 @@ class NativeMailerHandler extends MailHandler
/**
* Add parameters to the message
*
* @param string|array $parameters Custom added parameters
* @param string|array $parameters Custom added parameters
* @return self
*/
public function addParameter($parameters)
@@ -122,8 +123,16 @@ class NativeMailerHandler extends MailHandler
if ($this->getContentType() == 'text/html' && false === strpos($headers, 'MIME-Version:')) {
$headers .= 'MIME-Version: 1.0' . "\r\n";
}
$subject = $this->subject;
if ($records) {
$subjectFormatter = new LineFormatter($this->subject);
$subject = $subjectFormatter->format($this->getHighestRecord($records));
}
$parameters = implode(' ', $this->parameters);
foreach ($this->to as $to) {
mail($to, $this->subject, $content, $headers, implode(' ', $this->parameters));
mail($to, $subject, $content, $headers, $parameters);
}
}

View File

@@ -41,16 +41,16 @@ class NewRelicHandler extends AbstractProcessingHandler
* Some context and extra data is passed into the handler as arrays of values. Do we send them as is
* (useful if we are using the API), or explode them for display on the NewRelic RPM website?
*
* @var boolean
* @var bool
*/
protected $explodeArrays;
/**
* {@inheritDoc}
*
* @param string $appName
* @param boolean $explodeArrays
* @param string $transactionName
* @param string $appName
* @param bool $explodeArrays
* @param string $transactionName
*/
public function __construct(
$level = Logger::ERROR,

View File

@@ -24,7 +24,7 @@ use Monolog\Logger;
class NullHandler extends AbstractHandler
{
/**
* @param integer $level The minimum logging level at which this handler will be triggered
* @param int $level The minimum logging level at which this handler will be triggered
*/
public function __construct($level = Logger::DEBUG)
{

View File

@@ -66,10 +66,10 @@ class PHPConsoleHandler extends AbstractProcessingHandler
private $connector;
/**
* @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details
* @param Connector|null $connector Instance of \PhpConsole\Connector class (optional)
* @param int $level
* @param bool $bubble
* @param array $options See \Monolog\Handler\PHPConsoleHandler::$options for more details
* @param Connector|null $connector Instance of \PhpConsole\Connector class (optional)
* @param int $level
* @param bool $bubble
* @throws Exception
*/
public function __construct(array $options = array(), Connector $connector = null, $level = Logger::DEBUG, $bubble = true)

View File

@@ -68,16 +68,16 @@ class PushoverHandler extends SocketHandler
* @param string $token Pushover api token
* @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 integer $level The minimum logging level at which this handler will be triggered
* @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
* the pushover.net app owner. OpenSSL is required for this option.
* @param integer $highPriorityLevel The minimum logging level at which this handler will start
* @param int $highPriorityLevel The minimum logging level at which this handler will start
* sending "high priority" requests to the Pushover API
* @param integer $emergencyLevel The minimum logging level at which this handler will start
* @param int $emergencyLevel The minimum logging level at which this handler will start
* sending "emergency" requests to the Pushover API
* @param integer $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user.
* @param integer $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds).
* @param int $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user.
* @param int $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds).
*/
public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200)
{
@@ -115,7 +115,7 @@ class PushoverHandler extends SocketHandler
'user' => $this->user,
'message' => $message,
'title' => $this->title,
'timestamp' => $timestamp
'timestamp' => $timestamp,
);
if (isset($record['level']) && $record['level'] >= $this->emergencyLevel) {
@@ -176,7 +176,7 @@ class PushoverHandler extends SocketHandler
/**
* Use the formatted message?
* @param boolean $value
* @param bool $value
*/
public function useFormattedMessage($value)
{

View File

@@ -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
*/
@@ -50,7 +56,7 @@ class RavenHandler extends AbstractProcessingHandler
/**
* @param Raven_Client $ravenClient
* @param integer $level The minimum logging level at which this handler will be triggered
* @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
*/
public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true)
@@ -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;
}
}

View File

@@ -34,9 +34,9 @@ class RedisHandler extends AbstractProcessingHandler
/**
* @param \Predis\Client|\Redis $redis The redis instance
* @param string $key The key name to push records to
* @param integer $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 integer $capSize Number of entries to limit list size to
* @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 $capSize Number of entries to limit list size to
*/
public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true, $capSize = false)
{
@@ -67,7 +67,7 @@ class RedisHandler extends AbstractProcessingHandler
* Write and cap the collection
* Writes the record to the redis list and caps its
*
* @param array $record associative record array
* @param array $record associative record array
* @return void
*/
protected function writeCapped(array $record)
@@ -76,7 +76,7 @@ class RedisHandler extends AbstractProcessingHandler
$this->redisClient->multi()
->rpush($this->redisKey, $record["formatted"])
->ltrim($this->redisKey, -$this->capSize, -1)
->execute();
->exec();
} else {
$redisKey = $this->redisKey;
$capSize = $this->capSize;

View File

@@ -18,6 +18,9 @@ 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.
*
* @author Paul Statezny <paulstatezny@gmail.com>
*/
class RollbarHandler extends AbstractProcessingHandler
@@ -38,8 +41,8 @@ class RollbarHandler extends AbstractProcessingHandler
/**
* @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token
* @param integer $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(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true)
{
@@ -72,10 +75,18 @@ class RollbarHandler extends AbstractProcessingHandler
'datetime' => $record['datetime']->format('U'),
);
$context = $record['context'];
$payload = array();
if (isset($context['payload'])) {
$payload = $context['payload'];
unset($context['payload']);
}
$this->rollbarNotifier->report_message(
$record['message'],
$record['level_name'],
array_merge($record['context'], $record['extra'], $extraData)
array_merge($record['context'], $record['extra'], $extraData),
$payload
);
}

View File

@@ -33,8 +33,8 @@ class RotatingFileHandler extends StreamHandler
/**
* @param string $filename
* @param integer $maxFiles The maximal amount of files to keep (0 means unlimited)
* @param integer $level The minimum logging level at which this handler will be triggered
* @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 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
@@ -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();
}
}

View File

@@ -70,15 +70,16 @@ class SlackHandler extends SocketHandler
private $lineFormatter;
/**
* @param string $token Slack API token
* @param string $channel Slack channel (encoded ID or name)
* @param string $username Name of a bot
* @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise)
* @param string|null $iconEmoji The emoji name to use (or null)
* @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 $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style
* @param bool $includeContextAndExtra Whether the attachment should include context and extra data
* @param string $token Slack API token
* @param string $channel Slack channel (encoded ID or name)
* @param string $username Name of a bot
* @param bool $useAttachment Whether the message should be added to Slack as attachment (plain text otherwise)
* @param string|null $iconEmoji The emoji name to use (or null)
* @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 $useShortAttachment Whether the the context/extra messages added to Slack as attachments are in a short style
* @param bool $includeContextAndExtra Whether the attachment should include context and extra data
* @throws MissingExtensionException If no OpenSSL PHP extension configured
*/
public function __construct($token, $channel, $username = 'Monolog', $useAttachment = true, $iconEmoji = null, $level = Logger::CRITICAL, $bubble = true, $useShortAttachment = false, $includeContextAndExtra = false)
{
@@ -95,7 +96,8 @@ class SlackHandler extends SocketHandler
$this->useAttachment = $useAttachment;
$this->useShortAttachment = $useShortAttachment;
$this->includeContextAndExtra = $includeContextAndExtra;
if ($this->includeContextAndExtra) {
if ($this->includeContextAndExtra && $this->useShortAttachment) {
$this->lineFormatter = new LineFormatter;
}
}
@@ -139,14 +141,14 @@ class SlackHandler extends SocketHandler
'channel' => $this->channel,
'username' => $this->username,
'text' => '',
'attachments' => array()
'attachments' => array(),
);
if ($this->useAttachment) {
$attachment = array(
'fallback' => $record['message'],
'color' => $this->getAttachmentColor($record['level']),
'fields' => array()
'fields' => array(),
);
if ($this->useShortAttachment) {
@@ -158,7 +160,7 @@ class SlackHandler extends SocketHandler
$attachment['fields'][] = array(
'title' => 'Level',
'value' => $record['level_name'],
'short' => true
'short' => true,
);
}
@@ -168,7 +170,7 @@ class SlackHandler extends SocketHandler
$attachment['fields'][] = array(
'title' => "Extra",
'value' => $this->stringify($record['extra']),
'short' => $this->useShortAttachment
'short' => $this->useShortAttachment,
);
} else {
// Add all extra fields as individual fields in attachment
@@ -176,7 +178,7 @@ class SlackHandler extends SocketHandler
$attachment['fields'][] = array(
'title' => $var,
'value' => $val,
'short' => $this->useShortAttachment
'short' => $this->useShortAttachment,
);
}
}
@@ -187,7 +189,7 @@ class SlackHandler extends SocketHandler
$attachment['fields'][] = array(
'title' => "Context",
'value' => $this->stringify($record['context']),
'short' => $this->useShortAttachment
'short' => $this->useShortAttachment,
);
} else {
// Add all context fields as individual fields in attachment
@@ -195,7 +197,7 @@ class SlackHandler extends SocketHandler
$attachment['fields'][] = array(
'title' => $var,
'value' => $val,
'short' => $this->useShortAttachment
'short' => $this->useShortAttachment,
);
}
}
@@ -239,6 +241,10 @@ class SlackHandler extends SocketHandler
protected function write(array $record)
{
parent::write($record);
$res = $this->getResource();
if (is_resource($res)) {
@fread($res, 2048);
}
$this->closeSocket();
}
@@ -266,8 +272,7 @@ class SlackHandler extends SocketHandler
/**
* Stringifies an array of key/value pairs to be used in attachment fields
*
* @param array $fields
* @access protected
* @param array $fields
* @return string
*/
protected function stringify($fields)

View File

@@ -25,13 +25,16 @@ class SocketHandler extends AbstractProcessingHandler
private $connectionTimeout;
private $resource;
private $timeout = 0;
private $writingTimeout = 10;
private $lastSentBytes = null;
private $persistent = false;
private $errno;
private $errstr;
private $lastWritingAt;
/**
* @param string $connectionString Socket connection string
* @param integer $level The minimum logging level at which this handler will be triggered
* @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
*/
public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true)
@@ -80,7 +83,7 @@ class SocketHandler extends AbstractProcessingHandler
/**
* Set socket connection to nbe persistent. It only has effect before the connection is initiated.
*
* @param boolean $persistent
* @param bool $persistent
*/
public function setPersistent($persistent)
{
@@ -113,6 +116,17 @@ class SocketHandler extends AbstractProcessingHandler
$this->timeout = (float) $seconds;
}
/**
* Set writing timeout. Only has effect during connection in the writing cycle.
*
* @param float $seconds 0 for no timeout
*/
public function setWritingTimeout($seconds)
{
$this->validateTimeout($seconds);
$this->writingTimeout = (float) $seconds;
}
/**
* Get current connection string
*
@@ -126,7 +140,7 @@ class SocketHandler extends AbstractProcessingHandler
/**
* Get persistent setting
*
* @return boolean
* @return bool
*/
public function isPersistent()
{
@@ -153,12 +167,22 @@ class SocketHandler extends AbstractProcessingHandler
return $this->timeout;
}
/**
* Get current local writing timeout
*
* @return float
*/
public function getWritingTimeout()
{
return $this->writingTimeout;
}
/**
* Check to see if the socket is currently available.
*
* UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details.
*
* @return boolean
* @return bool
*/
public function isConnected()
{
@@ -232,6 +256,14 @@ class SocketHandler extends AbstractProcessingHandler
return (string) $record['formatted'];
}
/**
* @return resource|null
*/
protected function getResource()
{
return $this->resource;
}
private function connect()
{
$this->createSocketResource();
@@ -262,6 +294,7 @@ class SocketHandler extends AbstractProcessingHandler
{
$length = strlen($data);
$sent = 0;
$this->lastSentBytes = $sent;
while ($this->isConnected() && $sent < $length) {
if (0 == $sent) {
$chunk = $this->fwrite($data);
@@ -276,9 +309,38 @@ class SocketHandler extends AbstractProcessingHandler
if ($socketInfo['timed_out']) {
throw new \RuntimeException("Write timed-out");
}
if ($this->writingIsTimedOut($sent)) {
throw new \RuntimeException("Write timed-out, no data sent for `{$this->writingTimeout}` seconds, probably we got disconnected (sent $sent of $length)");
}
}
if (!$this->isConnected() && $sent < $length) {
throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)");
}
}
private function writingIsTimedOut($sent)
{
$writingTimeout = (int) floor($this->writingTimeout);
if (0 === $writingTimeout) {
return false;
}
if ($sent !== $this->lastSentBytes) {
$this->lastWritingAt = time();
$this->lastSentBytes = $sent;
return false;
} else {
usleep(100);
}
if ((time() - $this->lastWritingAt) >= $writingTimeout) {
$this->closeSocket();
return true;
}
return false;
}
}

View File

@@ -31,7 +31,7 @@ class StreamHandler extends AbstractProcessingHandler
/**
* @param resource|string $stream
* @param integer $level The minimum logging level at which this handler will be triggered
* @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|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
@@ -59,12 +59,22 @@ class StreamHandler extends AbstractProcessingHandler
*/
public function close()
{
if (is_resource($this->stream)) {
if ($this->url && is_resource($this->stream)) {
fclose($this->stream);
}
$this->stream = null;
}
/**
* Return the currently active stream if it is open
*
* @return resource|null
*/
public function getStream()
{
return $this->stream;
}
/**
* {@inheritdoc}
*/

View File

@@ -12,6 +12,7 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
/**
* SwiftMailerHandler uses Swift_Mailer to send the emails
@@ -26,7 +27,7 @@ class SwiftMailerHandler extends MailHandler
/**
* @param \Swift_Mailer $mailer The mailer to use
* @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
* @param integer $level The minimum logging level at which this handler will be triggered
* @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
*/
public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true)
@@ -48,8 +49,8 @@ class SwiftMailerHandler extends MailHandler
/**
* Creates instance of Swift_Message to be sent
*
* @param string $content formatted email body to be sent
* @param array $records Log records that formed the content
* @param string $content formatted email body to be sent
* @param array $records Log records that formed the content
* @return \Swift_Message
*/
protected function buildMessage($content, array $records)
@@ -66,6 +67,11 @@ class SwiftMailerHandler extends MailHandler
throw new \InvalidArgumentException('Could not resolve message as instance of Swift_Message or a callable returning it');
}
if ($records) {
$subjectFormatter = new LineFormatter($message->getSubject());
$message->setSubject($subjectFormatter->format($this->getHighestRecord($records)));
}
$message->setBody($content);
$message->setDate(time());

View File

@@ -34,7 +34,7 @@ class SyslogHandler extends AbstractSyslogHandler
/**
* @param string $ident
* @param mixed $facility
* @param integer $level The minimum logging level at which this handler will be triggered
* @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
*/

View File

@@ -27,7 +27,7 @@ class SyslogUdpHandler extends AbstractSyslogHandler
* @param string $host
* @param int $port
* @param mixed $facility
* @param integer $level The minimum logging level at which this handler will be triggered
* @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
*/
public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)

View File

@@ -18,50 +18,50 @@ namespace Monolog\Handler;
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*
* @method boolean hasEmergency($record)
* @method boolean hasAlert($record)
* @method boolean hasCritical($record)
* @method boolean hasError($record)
* @method boolean hasWarning($record)
* @method boolean hasNotice($record)
* @method boolean hasInfo($record)
* @method boolean hasDebug($record)
* @method bool hasEmergency($record)
* @method bool hasAlert($record)
* @method bool hasCritical($record)
* @method bool hasError($record)
* @method bool hasWarning($record)
* @method bool hasNotice($record)
* @method bool hasInfo($record)
* @method bool hasDebug($record)
*
* @method boolean hasEmergencyRecords()
* @method boolean hasAlertRecords()
* @method boolean hasCriticalRecords()
* @method boolean hasErrorRecords()
* @method boolean hasWarningRecords()
* @method boolean hasNoticeRecords()
* @method boolean hasInfoRecords()
* @method boolean hasDebugRecords()
* @method bool hasEmergencyRecords()
* @method bool hasAlertRecords()
* @method bool hasCriticalRecords()
* @method bool hasErrorRecords()
* @method bool hasWarningRecords()
* @method bool hasNoticeRecords()
* @method bool hasInfoRecords()
* @method bool hasDebugRecords()
*
* @method boolean hasEmergencyThatContains($message)
* @method boolean hasAlertThatContains($message)
* @method boolean hasCriticalThatContains($message)
* @method boolean hasErrorThatContains($message)
* @method boolean hasWarningThatContains($message)
* @method boolean hasNoticeThatContains($message)
* @method boolean hasInfoThatContains($message)
* @method boolean hasDebugThatContains($message)
* @method bool hasEmergencyThatContains($message)
* @method bool hasAlertThatContains($message)
* @method bool hasCriticalThatContains($message)
* @method bool hasErrorThatContains($message)
* @method bool hasWarningThatContains($message)
* @method bool hasNoticeThatContains($message)
* @method bool hasInfoThatContains($message)
* @method bool hasDebugThatContains($message)
*
* @method boolean hasEmergencyThatMatches($message)
* @method boolean hasAlertThatMatches($message)
* @method boolean hasCriticalThatMatches($message)
* @method boolean hasErrorThatMatches($message)
* @method boolean hasWarningThatMatches($message)
* @method boolean hasNoticeThatMatches($message)
* @method boolean hasInfoThatMatches($message)
* @method boolean hasDebugThatMatches($message)
* @method bool hasEmergencyThatMatches($message)
* @method bool hasAlertThatMatches($message)
* @method bool hasCriticalThatMatches($message)
* @method bool hasErrorThatMatches($message)
* @method bool hasWarningThatMatches($message)
* @method bool hasNoticeThatMatches($message)
* @method bool hasInfoThatMatches($message)
* @method bool hasDebugThatMatches($message)
*
* @method boolean hasEmergencyThatPasses($message)
* @method boolean hasAlertThatPasses($message)
* @method boolean hasCriticalThatPasses($message)
* @method boolean hasErrorThatPasses($message)
* @method boolean hasWarningThatPasses($message)
* @method boolean hasNoticeThatPasses($message)
* @method boolean hasInfoThatPasses($message)
* @method boolean hasDebugThatPasses($message)
* @method bool hasEmergencyThatPasses($message)
* @method bool hasAlertThatPasses($message)
* @method bool hasCriticalThatPasses($message)
* @method bool hasErrorThatPasses($message)
* @method bool hasWarningThatPasses($message)
* @method bool hasNoticeThatPasses($message)
* @method bool hasInfoThatPasses($message)
* @method bool hasDebugThatPasses($message)
*/
class TestHandler extends AbstractProcessingHandler
{
@@ -73,6 +73,12 @@ class TestHandler extends AbstractProcessingHandler
return $this->records;
}
public function clear()
{
$this->records = array();
$this->recordsByLevel = array();
}
protected function hasRecordRecords($level)
{
return isset($this->recordsByLevel[$level]);

View File

@@ -92,14 +92,14 @@ class Logger implements LoggerInterface
* @var array $levels Logging levels
*/
protected static $levels = array(
100 => 'DEBUG',
200 => 'INFO',
250 => 'NOTICE',
300 => 'WARNING',
400 => 'ERROR',
500 => 'CRITICAL',
550 => 'ALERT',
600 => 'EMERGENCY',
self::DEBUG => 'DEBUG',
self::INFO => 'INFO',
self::NOTICE => 'NOTICE',
self::WARNING => 'WARNING',
self::ERROR => 'ERROR',
self::CRITICAL => 'CRITICAL',
self::ALERT => 'ALERT',
self::EMERGENCY => 'EMERGENCY',
);
/**
@@ -128,6 +128,11 @@ class Logger implements LoggerInterface
*/
protected $processors;
/**
* @var bool
*/
protected $microsecondTimestamps = true;
/**
* @param string $name The logging channel
* @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc.
@@ -148,10 +153,23 @@ 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.
*
* @param HandlerInterface $handler
* @param HandlerInterface $handler
* @return $this
*/
public function pushHandler(HandlerInterface $handler)
@@ -180,7 +198,7 @@ class Logger implements LoggerInterface
*
* If a map is passed, keys will be ignored.
*
* @param HandlerInterface[] $handlers
* @param HandlerInterface[] $handlers
* @return $this
*/
public function setHandlers(array $handlers)
@@ -204,7 +222,7 @@ class Logger implements LoggerInterface
/**
* Adds a processor on to the stack.
*
* @param callable $callback
* @param callable $callback
* @return $this
*/
public function pushProcessor($callback)
@@ -239,10 +257,28 @@ class Logger implements LoggerInterface
return $this->processors;
}
/**
* Control the use of microsecond resolution timestamps in the 'datetime'
* member of new records.
*
* Generating microsecond resolution timestamps by calling
* microtime(true), formatting the result via sprintf() and then parsing
* the resulting string via \DateTime::createFromFormat() can incur
* a measurable runtime overhead vs simple usage of DateTime to capture
* a second resolution timestamp in systems which generate a large number
* of log events.
*
* @param bool $micro True to use microtime() to create timestamps
*/
public function useMicrosecondTimestamps($micro)
{
$this->microsecondTimestamps = (bool) $micro;
}
/**
* Adds a log record.
*
* @param integer $level The logging level
* @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
@@ -257,11 +293,14 @@ class Logger implements LoggerInterface
// check if any handler will handle this message so we can return early and save cycles
$handlerKey = null;
foreach ($this->handlers as $key => $handler) {
reset($this->handlers);
while ($handler = current($this->handlers)) {
if ($handler->isHandling(array('level' => $level))) {
$handlerKey = $key;
$handlerKey = key($this->handlers);
break;
}
next($this->handlers);
}
if (null === $handlerKey) {
@@ -272,22 +311,33 @@ class Logger implements LoggerInterface
static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC');
}
if ($this->microsecondTimestamps) {
$ts = \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone);
} else {
$ts = new \DateTime(null, static::$timezone);
}
$ts->setTimezone(static::$timezone);
$record = array(
'message' => (string) $message,
'context' => $context,
'level' => $level,
'level_name' => $levelName,
'channel' => $this->name,
'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone)->setTimezone(static::$timezone),
'datetime' => $ts,
'extra' => array(),
);
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
while (isset($this->handlers[$handlerKey]) &&
false === $this->handlers[$handlerKey]->handle($record)) {
$handlerKey++;
while ($handler = current($this->handlers)) {
if (true === $handler->handle($record)) {
break;
}
next($this->handlers);
}
return true;
@@ -402,7 +452,7 @@ class Logger implements LoggerInterface
/**
* Gets the name of the logging level.
*
* @param integer $level
* @param int $level
* @return string
*/
public static function getLevelName($level)
@@ -432,7 +482,7 @@ class Logger implements LoggerInterface
/**
* Checks whether the Logger has a handler that listens on the given level
*
* @param integer $level
* @param int $level
* @return Boolean
*/
public function isHandling($level)

View File

@@ -30,15 +30,18 @@ class IntrospectionProcessor
private $skipClassesPartials;
private $skipStackFramesCount;
private $skipFunctions = array(
'call_user_func',
'call_user_func_array',
);
public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array())
public function __construct($level = Logger::DEBUG, array $skipClassesPartials = array(), $skipStackFramesCount = 0)
{
$this->level = Logger::toMonologLevel($level);
$this->skipClassesPartials = array_merge(array('Monolog\\'), $skipClassesPartials);
$this->skipStackFramesCount = $skipStackFramesCount;
}
/**
@@ -52,7 +55,7 @@ class IntrospectionProcessor
return $record;
}
$trace = debug_backtrace();
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// skip first since it's always the current method
array_shift($trace);
@@ -77,6 +80,8 @@ class IntrospectionProcessor
break;
}
$i += $this->skipStackFramesCount;
// we should have the call source now
$record['extra'] = array_merge(
$record['extra'],

View File

@@ -19,18 +19,18 @@ namespace Monolog\Processor;
abstract class MemoryProcessor
{
/**
* @var boolean If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported.
* @var bool If true, get the real size of memory allocated from system. Else, only the memory used by emalloc() is reported.
*/
protected $realUsage;
/**
* @var boolean If true, then format memory size to human readable string (MB, KB, B depending on size)
* @var bool If true, then format memory size to human readable string (MB, KB, B depending on size)
*/
protected $useFormatting;
/**
* @param boolean $realUsage Set this to true to get the real size of memory allocated from system.
* @param boolean $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size)
* @param bool $realUsage Set this to true to get the real size of memory allocated from system.
* @param bool $useFormatting If true, then format memory size to human readable string (MB, KB, B depending on size)
*/
public function __construct($realUsage = true, $useFormatting = true)
{

View File

@@ -24,6 +24,10 @@ class WebProcessor
protected $serverData;
/**
* Default fields
*
* Array is structured as [key in record.extra => key in $serverData]
*
* @var array
*/
protected $extraFields = array(
@@ -36,7 +40,7 @@ class WebProcessor
/**
* @param array|\ArrayAccess $serverData Array or object w/ ArrayAccess that provides access to the $_SERVER data
* @param array|null $extraFields Extra field names to be added (all available by default)
* @param array|null $extraFields Field names and the related key inside $serverData to be added. If not provided it defaults to: url, ip, http_method, server, referrer
*/
public function __construct($serverData = null, array $extraFields = null)
{
@@ -49,10 +53,14 @@ class WebProcessor
}
if (null !== $extraFields) {
foreach (array_keys($this->extraFields) as $fieldName) {
if (!in_array($fieldName, $extraFields)) {
unset($this->extraFields[$fieldName]);
if (isset($extraFields[0])) {
foreach (array_keys($this->extraFields) as $fieldName) {
if (!in_array($fieldName, $extraFields)) {
unset($this->extraFields[$fieldName]);
}
}
} else {
$this->extraFields = $extraFields;
}
}
}

View File

@@ -49,7 +49,7 @@ class Registry
*
* @param Logger $logger Instance of the logging channel
* @param string|null $name Name of the logging channel ($logger->getName() by default)
* @param boolean $overwrite Overwrite instance in the registry if the given name already exists?
* @param bool $overwrite Overwrite instance in the registry if the given name already exists?
* @throws \InvalidArgumentException If $overwrite set to false and named Logger instance already exists
*/
public static function addLogger(Logger $logger, $name = null, $overwrite = false)
@@ -107,8 +107,8 @@ class Registry
* Gets Logger instance from the registry
*
* @param string $name Name of the requested Logger instance
* @return Logger Requested instance of Logger
* @throws \InvalidArgumentException If named Logger instance is not in the registry
* @return Logger Requested instance of Logger
*/
public static function getInstance($name)
{
@@ -124,8 +124,8 @@ class Registry
*
* @param string $name Name of the requested Logger instance
* @param array $arguments Arguments passed to static method call
* @return Logger Requested instance of Logger
* @throws \InvalidArgumentException If named Logger instance is not in the registry
* @return Logger Requested instance of Logger
*/
public static function __callStatic($name, $arguments)
{

View File

@@ -42,7 +42,7 @@ class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase
'extra' => array('ip' => '127.0.0.1'),
),
'unknown',
'error'
'error',
),
$message
);
@@ -75,7 +75,7 @@ class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase
'extra' => array('ip' => '127.0.0.1'),
),
'test : 14',
'error'
'error',
),
$message
);
@@ -104,7 +104,7 @@ class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase
'meh',
'log',
'unknown',
'log'
'log',
),
$message
);
@@ -143,13 +143,13 @@ class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase
'meh',
'log',
'unknown',
'info'
'info',
),
array(
'foo',
'log2',
'unknown',
'warn'
'warn',
),
),
$formatter->formatBatch($records)

View File

@@ -42,7 +42,7 @@ class ElasticaFormatterTest extends \PHPUnit_Framework_TestCase
// expected values
$expected = $msg;
$expected['datetime'] = '1970-01-01T00:00:00+0000';
$expected['datetime'] = '1970-01-01T00:00:00.000000+00:00';
$expected['context'] = array(
'class' => '[object] (stdClass: {})',
'foo' => 7,

View File

@@ -0,0 +1,62 @@
<?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 Monolog\TestCase;
class FluentdFormatterTest extends TestCase
{
/**
* @covers Monolog\Formatter\FluentdFormatter::__construct
* @covers Monolog\Formatter\FluentdFormatter::isUsingLevelsInTag
*/
public function testConstruct()
{
$formatter = new FluentdFormatter();
$this->assertEquals(false, $formatter->isUsingLevelsInTag());
$formatter = new FluentdFormatter(false);
$this->assertEquals(false, $formatter->isUsingLevelsInTag());
$formatter = new FluentdFormatter(true);
$this->assertEquals(true, $formatter->isUsingLevelsInTag());
}
/**
* @covers Monolog\Formatter\FluentdFormatter::format
*/
public function testFormat()
{
$record = $this->getRecord(Logger::WARNING);
$record['datetime'] = new \DateTime("@0");
$formatter = new FluentdFormatter();
$this->assertEquals(
'["test",0,{"message":"test","extra":[],"level":300,"level_name":"WARNING"}]',
$formatter->format($record)
);
}
/**
* @covers Monolog\Formatter\FluentdFormatter::format
*/
public function testFormatWithTag()
{
$record = $this->getRecord(Logger::ERROR);
$record['datetime'] = new \DateTime("@0");
$formatter = new FluentdFormatter(true);
$this->assertEquals(
'["test.error",0,{"message":"test","extra":[]}]',
$formatter->format($record)
);
}
}

View File

@@ -108,7 +108,7 @@ class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
'message' => 'log',
);
$message = $formatter->format($record);
@@ -145,11 +145,11 @@ class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase
'context' => array('from' => 'logger', 'exception' => array(
'class' => '\Exception',
'file' => '/some/file/in/dir.php:56',
'trace' => array('/some/file/1.php:23', '/some/file/2.php:3')
'trace' => array('/some/file/1.php:23', '/some/file/2.php:3'),
)),
'datetime' => new \DateTime("@0"),
'extra' => array(),
'message' => 'log'
'message' => 'log',
);
$message = $formatter->format($record);
@@ -173,7 +173,7 @@ class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
'message' => 'log',
);
$message = $formatter->format($record);
@@ -197,6 +197,36 @@ class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('pair', $message_array['_EXTkey']);
}
public function testFormatWithLargeData()
{
$formatter = new GelfMessageFormatter();
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('exception' => str_repeat(' ', 32767)),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => str_repeat(' ', 32767)),
'message' => 'log'
);
$message = $formatter->format($record);
$messageArray = $message->toArray();
// 200 for padding + metadata
$length = 200;
foreach ($messageArray as $key => $value) {
if (!in_array($key, array('level', 'timestamp'))) {
$length += strlen($value);
}
}
// in graylog2/gelf-php before 1.4.1 empty strings are filtered and won't be included in the message
// though it should be sufficient to ensure that the entire message length does not exceed the maximum
// length being allowed
$this->assertLessThanOrEqual(32766, $length, 'The message length is no longer than the maximum allowed length');
}
private function isLegacy()
{
return interface_exists('\Gelf\IMessagePublisher');

View File

@@ -75,4 +75,48 @@ class JsonFormatterTest extends TestCase
});
$this->assertEquals(implode("\n", $expected), $formatter->formatBatch($records));
}
public function testDefFormatWithException()
{
$formatter = new JsonFormatter();
$exception = new \RuntimeException('Foo');
$message = $formatter->format(array(
'level_name' => 'CRITICAL',
'channel' => 'core',
'context' => array('exception' => $exception),
'datetime' => new \DateTime(),
'extra' => array(),
'message' => 'foobar',
));
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
$path = substr(json_encode($exception->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1);
} else {
$path = substr(json_encode($exception->getFile()), 1, -1);
}
$this->assertEquals('{"level_name":"CRITICAL","channel":"core","context":{"exception":{"class":"RuntimeException","message":"'.$exception->getMessage().'","code":'.$exception->getCode().',"file":"'.$path.':'.$exception->getLine().'"}},"datetime":'.json_encode(new \DateTime()).',"extra":[],"message":"foobar"}'."\n", $message);
}
public function testDefFormatWithPreviousException()
{
$formatter = new JsonFormatter();
$exception = new \RuntimeException('Foo', 0, new \LogicException('Wut?'));
$message = $formatter->format(array(
'level_name' => 'CRITICAL',
'channel' => 'core',
'context' => array('exception' => $exception),
'datetime' => new \DateTime(),
'extra' => array(),
'message' => 'foobar',
));
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
$pathPrevious = substr(json_encode($exception->getPrevious()->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1);
$pathException = substr(json_encode($exception->getFile(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 1, -1);
} else {
$pathPrevious = substr(json_encode($exception->getPrevious()->getFile()), 1, -1);
$pathException = substr(json_encode($exception->getFile()), 1, -1);
}
$this->assertEquals('{"level_name":"CRITICAL","channel":"core","context":{"exception":{"class":"RuntimeException","message":"'.$exception->getMessage().'","code":'.$exception->getCode().',"file":"'.$pathException.':'.$exception->getLine().'","previous":{"class":"LogicException","message":"'.$exception->getPrevious()->getMessage().'","code":'.$exception->getPrevious()->getCode().',"file":"'.$pathPrevious.':'.$exception->getPrevious()->getLine().'"}}},"datetime":'.json_encode(new \DateTime()).',"extra":[],"message":"foobar"}'."\n", $message);
}
}

View File

@@ -44,7 +44,7 @@ class LineFormatterTest extends \PHPUnit_Framework_TestCase
'baz' => 'qux',
'bool' => false,
'null' => null,
)
),
));
$this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foo {"foo":"bar","baz":"qux","bool":false,"null":null} []'."\n", $message);
}
@@ -91,6 +91,20 @@ class LineFormatterTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log '."\n", $message);
}
public function testContextAndExtraReplacement()
{
$formatter = new LineFormatter('%context.foo% => %extra.foo%');
$message = $formatter->format(array(
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('foo' => 'bar'),
'datetime' => new \DateTime,
'extra' => array('foo' => 'xbar'),
'message' => 'log',
));
$this->assertEquals('bar => xbar', $message);
}
public function testDefFormatWithObject()
{
$formatter = new LineFormatter(null, 'Y-m-d');

View File

@@ -15,6 +15,13 @@ use Monolog\Logger;
class LogstashFormatterTest extends \PHPUnit_Framework_TestCase
{
public function tearDown()
{
\PHPUnit_Framework_Error_Warning::$enabled = true;
return parent::tearDown();
}
/**
* @covers Monolog\Formatter\LogstashFormatter::format
*/
@@ -83,7 +90,7 @@ class LogstashFormatterTest extends \PHPUnit_Framework_TestCase
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
'message' => 'log',
);
$message = json_decode($formatter->format($record), true);
@@ -116,7 +123,7 @@ class LogstashFormatterTest extends \PHPUnit_Framework_TestCase
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
'message' => 'log',
);
$message = json_decode($formatter->format($record), true);
@@ -146,7 +153,7 @@ class LogstashFormatterTest extends \PHPUnit_Framework_TestCase
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
'message' => 'log',
);
$message = json_decode($formatter->format($record), true);
@@ -223,7 +230,7 @@ class LogstashFormatterTest extends \PHPUnit_Framework_TestCase
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
'message' => 'log',
);
$message = json_decode($formatter->format($record), true);
@@ -252,7 +259,7 @@ class LogstashFormatterTest extends \PHPUnit_Framework_TestCase
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
'message' => 'log',
);
$message = json_decode($formatter->format($record), true);
@@ -278,7 +285,7 @@ class LogstashFormatterTest extends \PHPUnit_Framework_TestCase
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
'message' => 'log',
);
$message = json_decode($formatter->format($record), true);
@@ -286,4 +293,41 @@ class LogstashFormatterTest extends \PHPUnit_Framework_TestCase
$this->assertArrayHasKey('type', $message);
$this->assertEquals('app', $message['type']);
}
public function testFormatWithLatin9Data()
{
if (version_compare(PHP_VERSION, '5.5.0', '<')) {
// Ignore the warning that will be emitted by PHP <5.5.0
\PHPUnit_Framework_Error_Warning::$enabled = false;
}
$formatter = new LogstashFormatter('test', 'hostname');
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => '¯\_(ツ)_/¯',
'context' => array(),
'datetime' => new \DateTime("@0"),
'extra' => array(
'user_agent' => "\xD6WN; FBCR/OrangeEspa\xF1a; Vers\xE3o/4.0; F\xE4rist",
),
'message' => 'log',
);
$message = json_decode($formatter->format($record), true);
$this->assertEquals("1970-01-01T00:00:00.000000+00:00", $message['@timestamp']);
$this->assertEquals('log', $message['@message']);
$this->assertEquals('¯\_(ツ)_/¯', $message['@fields']['channel']);
$this->assertContains('¯\_(ツ)_/¯', $message['@tags']);
$this->assertEquals(Logger::ERROR, $message['@fields']['level']);
$this->assertEquals('test', $message['@type']);
$this->assertEquals('hostname', $message['@source']);
if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
$this->assertEquals('ÖWN; FBCR/OrangeEspaña; Versão/4.0; Färist', $message['@fields']['user_agent']);
} else {
// PHP <5.5 does not return false for an element encoding failure,
// instead it emits a warning (possibly) and nulls the value.
$this->assertEquals(null, $message['@fields']['user_agent']);
}
}
}

View File

@@ -1,5 +1,14 @@
<?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;
@@ -128,9 +137,9 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
'property' => 'anything',
'nest3' => array(
'nest4' => 'value',
'property' => 'nothing'
)
)
'property' => 'nothing',
),
),
),
'level' => Logger::WARNING,
'level_name' => Logger::getLevelName(Logger::WARNING),
@@ -147,7 +156,7 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
'nest2' => array(
'property' => 'anything',
'nest3' => '[...]',
)
),
),
$formattedResult['context']
);
@@ -165,8 +174,8 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
'nest4' => array(
'property' => 'nothing',
),
)
)
),
),
),
'level' => Logger::WARNING,
'level_name' => Logger::getLevelName(Logger::WARNING),
@@ -186,9 +195,9 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
'property' => 'anything',
'nest4' => array(
'property' => 'nothing',
)
),
),
)
),
),
$formattedResult['context']
);
@@ -205,7 +214,7 @@ class MongoDBFormatterTest extends \PHPUnit_Framework_TestCase
$record = array(
'message' => 'some log message',
'context' => array(
'nest2' => $someObject
'nest2' => $someObject,
),
'level' => Logger::WARNING,
'level_name' => Logger::getLevelName(Logger::WARNING),

View File

@@ -16,6 +16,13 @@ namespace Monolog\Formatter;
*/
class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
{
public function tearDown()
{
\PHPUnit_Framework_Error_Warning::$enabled = true;
return parent::tearDown();
}
public function testFormat()
{
$formatter = new NormalizerFormatter('Y-m-d');
@@ -51,7 +58,7 @@ class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
'inf' => 'INF',
'-inf' => '-INF',
'nan' => 'NaN',
)
),
), $formatted);
}
@@ -74,10 +81,19 @@ class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
'message' => $e2->getMessage(),
'code' => $e2->getCode(),
'file' => $e2->getFile().':'.$e2->getLine(),
)
),
), $formatted);
}
public function testFormatToStringExceptionHandle()
{
$formatter = new NormalizerFormatter('Y-m-d');
$this->setExpectedException('RuntimeException', 'Could not convert to string');
$formatter->format(array(
'myObject' => new TestToStringError(),
));
}
public function testBatchFormat()
{
$formatter = new NormalizerFormatter('Y-m-d');
@@ -179,17 +195,101 @@ class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
*/
public function testThrowsOnInvalidEncoding()
{
if (version_compare(PHP_VERSION, '5.5.0', '<')) {
// Ignore the warning that will be emitted by PHP <5.5.0
\PHPUnit_Framework_Error_Warning::$enabled = false;
}
$formatter = new NormalizerFormatter();
$reflMethod = new \ReflectionMethod($formatter, 'toJson');
$reflMethod->setAccessible(true);
// send an invalid unicode sequence
$res = $reflMethod->invoke($formatter, array('message' => "\xB1\x31"));
// send an invalid unicode sequence as a object that can't be cleaned
$record = new \stdClass;
$record->message = "\xB1\x31";
$res = $reflMethod->invoke($formatter, $record);
if (PHP_VERSION_ID < 50500 && $res === '{"message":null}') {
throw new \RuntimeException('PHP 5.3/5.4 throw a warning and null the value instead of returning false entirely');
}
}
public function testConvertsInvalidEncodingAsLatin9()
{
if (version_compare(PHP_VERSION, '5.5.0', '<')) {
// Ignore the warning that will be emitted by PHP <5.5.0
\PHPUnit_Framework_Error_Warning::$enabled = false;
}
$formatter = new NormalizerFormatter();
$reflMethod = new \ReflectionMethod($formatter, 'toJson');
$reflMethod->setAccessible(true);
$res = $reflMethod->invoke($formatter, array('message' => "\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE"));
if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
$this->assertSame('{"message":"€ŠšŽžŒœŸ"}', $res);
} else {
// PHP <5.5 does not return false for an element encoding failure,
// instead it emits a warning (possibly) and nulls the value.
$this->assertSame('{"message":null}', $res);
}
}
/**
* @param mixed $in Input
* @param mixed $expect Expected output
* @covers Monolog\Formatter\NormalizerFormatter::detectAndCleanUtf8
* @dataProvider providesDetectAndCleanUtf8
*/
public function testDetectAndCleanUtf8($in, $expect)
{
$formatter = new NormalizerFormatter();
$formatter->detectAndCleanUtf8($in);
$this->assertSame($expect, $in);
}
public function providesDetectAndCleanUtf8()
{
$obj = new \stdClass;
return array(
'null' => array(null, null),
'int' => array(123, 123),
'float' => array(123.45, 123.45),
'bool false' => array(false, false),
'bool true' => array(true, true),
'ascii string' => array('abcdef', 'abcdef'),
'latin9 string' => array("\xB1\x31\xA4\xA6\xA8\xB4\xB8\xBC\xBD\xBE\xFF", '±1€ŠšŽžŒœŸÿ'),
'unicode string' => array('¤¦¨´¸¼½¾€ŠšŽžŒœŸ', '¤¦¨´¸¼½¾€ŠšŽžŒœŸ'),
'empty array' => array(array(), array()),
'array' => array(array('abcdef'), array('abcdef')),
'object' => array($obj, $obj),
);
}
/**
* @param int $code
* @param string $msg
* @dataProvider providesHandleJsonErrorFailure
*/
public function testHandleJsonErrorFailure($code, $msg)
{
$formatter = new NormalizerFormatter();
$reflMethod = new \ReflectionMethod($formatter, 'handleJsonError');
$reflMethod->setAccessible(true);
$this->setExpectedException('RuntimeException', $msg);
$reflMethod->invoke($formatter, $code, 'faked');
}
public function providesHandleJsonErrorFailure()
{
return array(
'depth' => array(JSON_ERROR_DEPTH, 'Maximum stack depth exceeded'),
'state' => array(JSON_ERROR_STATE_MISMATCH, 'Underflow or the modes mismatch'),
'ctrl' => array(JSON_ERROR_CTRL_CHAR, 'Unexpected control character found'),
'default' => array(-1, 'Unknown error'),
);
}
public function testExceptionTraceWithArgs()
{
if (defined('HHVM_VERSION')) {
@@ -268,3 +368,11 @@ class TestStreamFoo
return $this->foo . ' - ' . (string) stream_get_contents($this->resource);
}
}
class TestToStringError
{
public function __toString()
{
throw new \RuntimeException('Could not convert to string');
}
}

View File

@@ -1,4 +1,14 @@
<?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 ScalarFormatterTest extends \PHPUnit_Framework_TestCase
@@ -44,7 +54,7 @@ class ScalarFormatterTest extends \PHPUnit_Framework_TestCase
'bam' => array(1, 2, 3),
'bat' => array('foo' => 'bar'),
'bap' => \DateTime::createFromFormat(\DateTime::ISO8601, '1970-01-01T00:00:00+0000'),
'ban' => $exception
'ban' => $exception,
));
$this->assertSame(array(
@@ -59,8 +69,8 @@ class ScalarFormatterTest extends \PHPUnit_Framework_TestCase
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile() . ':' . $exception->getLine(),
'trace' => $this->buildTrace($exception)
))
'trace' => $this->buildTrace($exception),
)),
), $formatted);
}
@@ -68,11 +78,11 @@ class ScalarFormatterTest extends \PHPUnit_Framework_TestCase
{
$context = array('file' => 'foo', 'line' => 1);
$formatted = $this->formatter->format(array(
'context' => $context
'context' => $context,
));
$this->assertSame(array(
'context' => $this->encodeJson($context)
'context' => $this->encodeJson($context),
), $formatted);
}
@@ -81,8 +91,8 @@ class ScalarFormatterTest extends \PHPUnit_Framework_TestCase
$exception = new \Exception('foo');
$formatted = $this->formatter->format(array(
'context' => array(
'exception' => $exception
)
'exception' => $exception,
),
));
$this->assertSame(array(
@@ -92,9 +102,9 @@ class ScalarFormatterTest extends \PHPUnit_Framework_TestCase
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => $exception->getFile() . ':' . $exception->getLine(),
'trace' => $this->buildTrace($exception)
)
))
'trace' => $this->buildTrace($exception),
),
)),
), $formatted);
}
}

View File

@@ -65,8 +65,8 @@ class AmqpHandlerTest extends TestCase
0,
array(
'delivery_mode' => 2,
'Content-type' => 'application/json'
)
'content_type' => 'application/json',
),
);
$handler->handle($record);
@@ -117,8 +117,8 @@ class AmqpHandlerTest extends TestCase
null,
array(
'delivery_mode' => 2,
'content_type' => 'application/json'
)
'content_type' => 'application/json',
),
);
$handler->handle($record);

View File

@@ -41,7 +41,7 @@ class ChromePHPHandlerTest extends TestCase
'test',
),
'request_uri' => '',
))))
)))),
);
$this->assertEquals($expected, $handler->getHeaders());
@@ -81,7 +81,7 @@ class ChromePHPHandlerTest extends TestCase
),
),
'request_uri' => '',
))))
)))),
);
$this->assertEquals($expected, $handler->getHeaders());
@@ -110,7 +110,7 @@ class ChromePHPHandlerTest extends TestCase
'test',
),
'request_uri' => '',
))))
)))),
);
$this->assertEquals($expected, $handler2->getHeaders());

View File

@@ -0,0 +1,165 @@
<?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\TestCase;
use Monolog\Logger;
class DeduplicationHandlerTest extends TestCase
{
/**
* @covers Monolog\Handler\DeduplicationHandler::flush
*/
public function testFlushPassthruIfAllRecordsUnderTrigger()
{
$test = new TestHandler();
@unlink(sys_get_temp_dir().'/monolog_dedup.log');
$handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0);
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::INFO));
$handler->flush();
$this->assertTrue($test->hasInfoRecords());
$this->assertTrue($test->hasDebugRecords());
$this->assertFalse($test->hasWarningRecords());
}
/**
* @covers Monolog\Handler\DeduplicationHandler::flush
* @covers Monolog\Handler\DeduplicationHandler::appendRecord
*/
public function testFlushPassthruIfEmptyLog()
{
$test = new TestHandler();
@unlink(sys_get_temp_dir().'/monolog_dedup.log');
$handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0);
$handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar'));
$handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar"));
$handler->flush();
$this->assertTrue($test->hasErrorRecords());
$this->assertTrue($test->hasCriticalRecords());
$this->assertFalse($test->hasWarningRecords());
}
/**
* @covers Monolog\Handler\DeduplicationHandler::flush
* @covers Monolog\Handler\DeduplicationHandler::appendRecord
* @covers Monolog\Handler\DeduplicationHandler::isDuplicate
* @depends testFlushPassthruIfEmptyLog
*/
public function testFlushSkipsIfLogExists()
{
$test = new TestHandler();
$handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0);
$handler->handle($this->getRecord(Logger::ERROR, 'Foo:bar'));
$handler->handle($this->getRecord(Logger::CRITICAL, "Foo\nbar"));
$handler->flush();
$this->assertFalse($test->hasErrorRecords());
$this->assertFalse($test->hasCriticalRecords());
$this->assertFalse($test->hasWarningRecords());
}
/**
* @covers Monolog\Handler\DeduplicationHandler::flush
* @covers Monolog\Handler\DeduplicationHandler::appendRecord
* @covers Monolog\Handler\DeduplicationHandler::isDuplicate
* @depends testFlushPassthruIfEmptyLog
*/
public function testFlushPassthruIfLogTooOld()
{
$test = new TestHandler();
$handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0);
$record = $this->getRecord(Logger::ERROR);
$record['datetime']->modify('+62seconds');
$handler->handle($record);
$record = $this->getRecord(Logger::CRITICAL);
$record['datetime']->modify('+62seconds');
$handler->handle($record);
$handler->flush();
$this->assertTrue($test->hasErrorRecords());
$this->assertTrue($test->hasCriticalRecords());
$this->assertFalse($test->hasWarningRecords());
}
/**
* @covers Monolog\Handler\DeduplicationHandler::flush
* @covers Monolog\Handler\DeduplicationHandler::appendRecord
* @covers Monolog\Handler\DeduplicationHandler::isDuplicate
* @covers Monolog\Handler\DeduplicationHandler::collectLogs
*/
public function testGcOldLogs()
{
$test = new TestHandler();
@unlink(sys_get_temp_dir().'/monolog_dedup.log');
$handler = new DeduplicationHandler($test, sys_get_temp_dir().'/monolog_dedup.log', 0);
// handle two records from yesterday, and one recent
$record = $this->getRecord(Logger::ERROR);
$record['datetime']->modify('-1day -10seconds');
$handler->handle($record);
$record2 = $this->getRecord(Logger::CRITICAL);
$record2['datetime']->modify('-1day -10seconds');
$handler->handle($record2);
$record3 = $this->getRecord(Logger::CRITICAL);
$record3['datetime']->modify('-30seconds');
$handler->handle($record3);
// log is written as none of them are duplicate
$handler->flush();
$this->assertSame(
$record['datetime']->getTimestamp() . ":ERROR:test\n" .
$record2['datetime']->getTimestamp() . ":CRITICAL:test\n" .
$record3['datetime']->getTimestamp() . ":CRITICAL:test\n",
file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log')
);
$this->assertTrue($test->hasErrorRecords());
$this->assertTrue($test->hasCriticalRecords());
$this->assertFalse($test->hasWarningRecords());
// clear test handler
$test->clear();
$this->assertFalse($test->hasErrorRecords());
$this->assertFalse($test->hasCriticalRecords());
// log new records, duplicate log gets GC'd at the end of this flush call
$handler->handle($record = $this->getRecord(Logger::ERROR));
$handler->handle($record2 = $this->getRecord(Logger::CRITICAL));
$handler->flush();
// log should now contain the new errors and the previous one that was recent enough
$this->assertSame(
$record3['datetime']->getTimestamp() . ":CRITICAL:test\n" .
$record['datetime']->getTimestamp() . ":ERROR:test\n" .
$record2['datetime']->getTimestamp() . ":CRITICAL:test\n",
file_get_contents(sys_get_temp_dir() . '/monolog_dedup.log')
);
$this->assertTrue($test->hasErrorRecords());
$this->assertTrue($test->hasCriticalRecords());
$this->assertFalse($test->hasWarningRecords());
}
public static function tearDownAfterClass()
{
@unlink(sys_get_temp_dir().'/monolog_dedup.log');
}
}

View File

@@ -67,7 +67,7 @@ class DynamoDbHandlerTest extends TestCase
->method('__call')
->with('putItem', array(array(
'TableName' => 'foo',
'Item' => $formatted
'Item' => $formatted,
)));
$handler->handle($record);

View File

@@ -0,0 +1,130 @@
<?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\TestCase;
/**
* @author Alexey Karapetov <alexey@karapetov.com>
*/
class HandlerWrapperTest extends TestCase
{
/**
* @var HandlerWrapper
*/
private $wrapper;
private $handler;
public function setUp()
{
parent::setUp();
$this->handler = $this->getMock('Monolog\\Handler\\HandlerInterface');
$this->wrapper = new HandlerWrapper($this->handler);
}
/**
* @return array
*/
public function trueFalseDataProvider()
{
return array(
array(true),
array(false),
);
}
/**
* @param $result
* @dataProvider trueFalseDataProvider
*/
public function testIsHandling($result)
{
$record = $this->getRecord();
$this->handler->expects($this->once())
->method('isHandling')
->with($record)
->willReturn($result);
$this->assertEquals($result, $this->wrapper->isHandling($record));
}
/**
* @param $result
* @dataProvider trueFalseDataProvider
*/
public function testHandle($result)
{
$record = $this->getRecord();
$this->handler->expects($this->once())
->method('handle')
->with($record)
->willReturn($result);
$this->assertEquals($result, $this->wrapper->handle($record));
}
/**
* @param $result
* @dataProvider trueFalseDataProvider
*/
public function testHandleBatch($result)
{
$records = $this->getMultipleRecords();
$this->handler->expects($this->once())
->method('handleBatch')
->with($records)
->willReturn($result);
$this->assertEquals($result, $this->wrapper->handleBatch($records));
}
public function testPushProcessor()
{
$processor = function () {};
$this->handler->expects($this->once())
->method('pushProcessor')
->with($processor);
$this->assertEquals($this->wrapper, $this->wrapper->pushProcessor($processor));
}
public function testPopProcessor()
{
$processor = function () {};
$this->handler->expects($this->once())
->method('popProcessor')
->willReturn($processor);
$this->assertEquals($processor, $this->wrapper->popProcessor());
}
public function testSetFormatter()
{
$formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
$this->handler->expects($this->once())
->method('setFormatter')
->with($formatter);
$this->assertEquals($this->wrapper, $this->wrapper->setFormatter($formatter));
}
public function testGetFormatter()
{
$formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
$this->handler->expects($this->once())
->method('getFormatter')
->willReturn($formatter);
$this->assertEquals($formatter, $this->wrapper->getFormatter());
}
}

View File

@@ -21,6 +21,7 @@ use Monolog\Logger;
class HipChatHandlerTest extends TestCase
{
private $res;
/** @var HipChatHandler */
private $handler;
public function testWriteHeader()
@@ -91,6 +92,18 @@ class HipChatHandlerTest extends TestCase
$this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=Monolog$/', $content);
}
public function testWriteContentV1WithoutName()
{
$this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v1');
$this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$this->assertRegexp('/notify=0&message=test1&message_format=text&color=red&room_id=room1&from=$/', $content);
return $content;
}
/**
* @depends testWriteCustomHostHeader
*/
@@ -104,7 +117,7 @@ class HipChatHandlerTest extends TestCase
*/
public function testWriteContentV2($content)
{
$this->assertRegexp('/notify=false&message=test1&message_format=text&color=red$/', $content);
$this->assertRegexp('/notify=false&message=test1&message_format=text&color=red&from=Monolog$/', $content);
}
/**
@@ -112,7 +125,19 @@ class HipChatHandlerTest extends TestCase
*/
public function testWriteContentV2Notify($content)
{
$this->assertRegexp('/notify=true&message=test1&message_format=text&color=red$/', $content);
$this->assertRegexp('/notify=true&message=test1&message_format=text&color=red&from=Monolog$/', $content);
}
public function testWriteContentV2WithoutName()
{
$this->createHandler('myToken', 'room1', null, false, 'hipchat.foo.bar', 'v2');
$this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$this->assertRegexp('/notify=false&message=test1&message_format=text&color=red$/', $content);
return $content;
}
public function testWriteWithComplexMessage()
@@ -125,6 +150,16 @@ class HipChatHandlerTest extends TestCase
$this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content);
}
public function testWriteTruncatesLongMessage()
{
$this->createHandler();
$this->handler->handle($this->getRecord(Logger::CRITICAL, str_repeat('abcde', 2000)));
fseek($this->res, 0);
$content = fread($this->res, 12000);
$this->assertRegexp('/message='.str_repeat('abcde', 1900).'\+%5Btruncated%5D/', $content);
}
/**
* @dataProvider provideLevelColors
*/
@@ -174,7 +209,7 @@ class HipChatHandlerTest extends TestCase
array(
array('level' => Logger::WARNING, 'message' => 'Oh bugger!', 'level_name' => 'warning', 'datetime' => new \DateTime()),
array('level' => Logger::NOTICE, 'message' => 'Something noticeable happened.', 'level_name' => 'notice', 'datetime' => new \DateTime()),
array('level' => Logger::CRITICAL, 'message' => 'Everything is broken!', 'level_name' => 'critical', 'datetime' => new \DateTime())
array('level' => Logger::CRITICAL, 'message' => 'Everything is broken!', 'level_name' => 'critical', 'datetime' => new \DateTime()),
),
'red',
),

View File

@@ -45,7 +45,7 @@ class LogEntriesHandlerTest extends TestCase
$records = array(
$this->getRecord(),
$this->getRecord(),
$this->getRecord()
$this->getRecord(),
);
$this->createHandler();
$this->handler->handleBatch($records);

View File

@@ -12,9 +12,21 @@
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
use InvalidArgumentException;
function mail($to, $subject, $message, $additional_headers = null, $additional_parameters = null)
{
$GLOBALS['mail'][] = func_get_args();
}
class NativeMailerHandlerTest extends TestCase
{
protected function setUp()
{
$GLOBALS['mail'] = array();
}
/**
* @expectedException InvalidArgumentException
*/
@@ -58,4 +70,42 @@ class NativeMailerHandlerTest extends TestCase
$mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org');
$mailer->setEncoding("utf-8\r\nFrom: faked@attacker.org");
}
public function testSend()
{
$to = 'spammer@example.org';
$subject = 'dear victim';
$from = 'receiver@example.org';
$mailer = new NativeMailerHandler($to, $subject, $from);
$mailer->handleBatch(array());
// batch is empty, nothing sent
$this->assertEmpty($GLOBALS['mail']);
// non-empty batch
$mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz"));
$this->assertNotEmpty($GLOBALS['mail']);
$this->assertInternalType('array', $GLOBALS['mail']);
$this->assertArrayHasKey('0', $GLOBALS['mail']);
$params = $GLOBALS['mail'][0];
$this->assertCount(5, $params);
$this->assertSame($to, $params[0]);
$this->assertSame($subject, $params[1]);
$this->assertStringEndsWith(" test.ERROR: Foo Bar Baz [] []\n", $params[2]);
$this->assertSame("From: $from\r\nContent-type: text/plain; charset=utf-8\r\n", $params[3]);
$this->assertSame('', $params[4]);
}
public function testMessageSubjectFormatting()
{
$mailer = new NativeMailerHandler('to@example.org', 'Alert: %level_name% %message%', 'from@example.org');
$mailer->handle($this->getRecord(Logger::ERROR, "Foo\nBar\r\n\r\nBaz"));
$this->assertNotEmpty($GLOBALS['mail']);
$this->assertInternalType('array', $GLOBALS['mail']);
$this->assertArrayHasKey('0', $GLOBALS['mail']);
$params = $GLOBALS['mail'][0];
$this->assertCount(5, $params);
$this->assertSame('Alert: ERROR Foo Bar Baz', $params[1]);
}
}

View File

@@ -27,7 +27,6 @@ use PHPUnit_Framework_MockObject_MockObject;
*/
class PHPConsoleHandlerTest extends TestCase
{
/** @var Connector|PHPUnit_Framework_MockObject_MockObject */
protected $connector;
/** @var DebugDispatcher|PHPUnit_Framework_MockObject_MockObject */
@@ -103,7 +102,7 @@ class PHPConsoleHandlerTest extends TestCase
protected function initLogger($handlerOptions = array(), $level = Logger::DEBUG)
{
return new Logger('test', array(
new PHPConsoleHandler($handlerOptions, $this->connector, $level)
new PHPConsoleHandler($handlerOptions, $this->connector, $level),
));
}

View File

@@ -48,7 +48,7 @@ class PushoverHandlerTest extends TestCase
public function testWriteWithComplexTitle()
{
$this->createHandler('myToken', 'myUser', 'Backup finished - SQL1', Logger::EMERGENCY);
$this->createHandler('myToken', 'myUser', 'Backup finished - SQL1');
$this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
fseek($this->res, 0);
$content = fread($this->res, 1024);

View File

@@ -99,6 +99,18 @@ class RavenHandlerTest extends TestCase
$this->assertEquals($release, $ravenClient->lastData['release']);
}
public function testFingerprint()
{
$ravenClient = $this->getRavenClient();
$handler = $this->getHandler($ravenClient);
$fingerprint = array('{{ default }}', 'other value');
$record = $this->getRecord(Logger::INFO, 'test', array('fingerprint' => $fingerprint));
$handler->handle($record);
$this->assertEquals($fingerprint, $ravenClient->lastData['fingerprint']);
}
public function testUserContext()
{
$ravenClient = $this->getRavenClient();
@@ -109,7 +121,7 @@ class RavenHandlerTest extends TestCase
$user = array(
'id' => '123',
'email' => 'test@test.com'
'email' => 'test@test.com',
);
$recordWithContext = $this->getRecord(Logger::INFO, 'test', array('user' => $user));
@@ -192,6 +204,22 @@ class RavenHandlerTest extends TestCase
$this->assertSame($formatter, $handler->getBatchFormatter());
}
public function testRelease()
{
$ravenClient = $this->getRavenClient();
$handler = $this->getHandler($ravenClient);
$release = 'v42.42.42';
$handler->setRelease($release);
$record = $this->getRecord(Logger::INFO, 'test');
$handler->handle($record);
$this->assertEquals($release, $ravenClient->lastData['release']);
$localRelease = 'v41.41.41';
$record = $this->getRecord(Logger::INFO, 'test', array('release' => $localRelease));
$handler->handle($record);
$this->assertEquals($localRelease, $ravenClient->lastData['release']);
}
private function methodThatThrowsAnException()
{
throw new \Exception('This is an exception');

View File

@@ -71,7 +71,7 @@ class RedisHandlerTest extends TestCase
public function testRedisHandleCapped()
{
$redis = $this->getMock('Redis', array('multi', 'rpush', 'ltrim', 'execute'));
$redis = $this->getMock('Redis', array('multi', 'rpush', 'ltrim', 'exec'));
// Redis uses multi
$redis->expects($this->once())
@@ -87,7 +87,7 @@ class RedisHandlerTest extends TestCase
->will($this->returnSelf());
$redis->expects($this->once())
->method('execute')
->method('exec')
->will($this->returnSelf());
$record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));

View File

@@ -70,6 +70,13 @@ class SocketHandlerTest extends TestCase
$this->assertEquals(10.25, $this->handler->getTimeout());
}
public function testSetWritingTimeout()
{
$this->createHandler('localhost:1234');
$this->handler->setWritingTimeout(10.25);
$this->assertEquals(10.25, $this->handler->getWritingTimeout());
}
public function testSetConnectionString()
{
$this->createHandler('tcp://localhost:9090');
@@ -235,6 +242,26 @@ class SocketHandlerTest extends TestCase
$this->assertTrue(is_resource($this->res));
}
/**
* @expectedException \RuntimeException
*/
public function testAvoidInfiniteLoopWhenNoDataIsWrittenForAWritingTimeoutSeconds()
{
$this->setMockHandler(array('fwrite', 'streamGetMetadata'));
$this->handler->expects($this->any())
->method('fwrite')
->will($this->returnValue(0));
$this->handler->expects($this->any())
->method('streamGetMetadata')
->will($this->returnValue(array('timed_out' => false)));
$this->handler->setWritingTimeout(1);
$this->writeRecord('Hello world');
}
private function createHandler($connectionString)
{
$this->handler = new SocketHandler($connectionString);

View File

@@ -35,10 +35,26 @@ class StreamHandlerTest extends TestCase
/**
* @covers Monolog\Handler\StreamHandler::close
*/
public function testClose()
public function testCloseKeepsExternalHandlersOpen()
{
$handle = fopen('php://memory', 'a+');
$handler = new StreamHandler($handle);
$this->assertTrue(is_resource($handle));
$handler->close();
$this->assertTrue(is_resource($handle));
}
/**
* @covers Monolog\Handler\StreamHandler::close
*/
public function testClose()
{
$handler = new StreamHandler('php://memory');
$handler->handle($this->getRecord(Logger::WARNING, 'test'));
$streamProp = new \ReflectionProperty('Monolog\Handler\StreamHandler', 'stream');
$streamProp->setAccessible(true);
$handle = $streamProp->getValue($handler);
$this->assertTrue(is_resource($handle));
$handler->close();
$this->assertFalse(is_resource($handle));

View File

@@ -1,5 +1,14 @@
<?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\Logger;
@@ -63,7 +72,32 @@ class SwiftMailerHandlerTest extends TestCase
$handler->handleBatch($records);
}
public function testMessageHaveUniqueId() {
public function testMessageSubjectFormatting()
{
// Wire Mailer to expect a specific Swift_Message with a customized Subject
$messageTemplate = new \Swift_Message();
$messageTemplate->setSubject('Alert: %level_name% %message%');
$receivedMessage = null;
$this->mailer->expects($this->once())
->method('send')
->with($this->callback(function ($value) use (&$receivedMessage) {
$receivedMessage = $value;
return true;
}));
$handler = new SwiftMailerHandler($this->mailer, $messageTemplate);
$records = array(
$this->getRecord(Logger::EMERGENCY),
);
$handler->handleBatch($records);
$this->assertEquals('Alert: EMERGENCY test', $receivedMessage->getSubject());
}
public function testMessageHaveUniqueId()
{
$messageTemplate = \Swift_Message::newInstance();
$handler = new SwiftMailerHandler($this->mailer, $messageTemplate);

View File

@@ -30,7 +30,7 @@ class ZendMonitorHandlerTest extends TestCase
{
$record = $this->getRecord();
$formatterResult = array(
'message' => $record['message']
'message' => $record['message'],
);
$zendMonitor = $this->getMockBuilder('Monolog\Handler\ZendMonitorHandler')

View File

@@ -33,6 +33,19 @@ class LoggerTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR));
}
/**
* @covers Monolog\Logger::withName
*/
public function testWithName()
{
$first = new Logger('first', array($handler = new TestHandler()));
$second = $first->withName('second');
$this->assertSame('first', $first->getName());
$this->assertSame('second', $second->getName());
$this->assertSame($handler, $second->popHandler());
}
/**
* @covers Monolog\Logger::toMonologLevel
*/
@@ -303,6 +316,45 @@ class LoggerTest extends \PHPUnit_Framework_TestCase
$logger->debug('test');
}
/**
* @covers Monolog\Logger::addRecord
*/
public function testHandlersNotCalledBeforeFirstHandlingWithAssocArray()
{
$handler1 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler1->expects($this->never())
->method('isHandling')
->will($this->returnValue(false))
;
$handler1->expects($this->once())
->method('handle')
->will($this->returnValue(false))
;
$handler2 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler2->expects($this->once())
->method('isHandling')
->will($this->returnValue(true))
;
$handler2->expects($this->once())
->method('handle')
->will($this->returnValue(false))
;
$handler3 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler3->expects($this->once())
->method('isHandling')
->will($this->returnValue(false))
;
$handler3->expects($this->never())
->method('handle')
;
$logger = new Logger(__METHOD__, array('last' => $handler3, 'second' => $handler2, 'first' => $handler1));
$logger->debug('test');
}
/**
* @covers Monolog\Logger::addRecord
*/
@@ -468,4 +520,29 @@ class LoggerTest extends \PHPUnit_Framework_TestCase
\DateTimeZone::listIdentifiers()
);
}
/**
* @dataProvider useMicrosecondTimestampsProvider
* @covers Monolog\Logger::useMicrosecondTimestamps
* @covers Monolog\Logger::addRecord
*/
public function testUseMicrosecondTimestamps($micro, $assert)
{
$logger = new Logger('foo');
$logger->useMicrosecondTimestamps($micro);
$handler = new TestHandler;
$logger->pushHandler($handler);
$logger->info('test');
list($record) = $handler->getRecords();
$this->{$assert}('000000', $record['datetime']->format('u'));
}
public function useMicrosecondTimestampsProvider()
{
return array(
// this has a very small chance of a false negative (1/10^6)
'with microseconds' => array(true, 'assertNotSame'),
'without microseconds' => array(false, 'assertSame'),
);
}
}

View File

@@ -22,7 +22,7 @@ class PsrLogMessageProcessorTest extends \PHPUnit_Framework_TestCase
$message = $proc(array(
'message' => '{foo}',
'context' => array('foo' => $val)
'context' => array('foo' => $val),
));
$this->assertEquals($expected, $message['message']);
}

View File

@@ -24,6 +24,7 @@ class UidProcessorTest extends TestCase
$record = $processor($this->getRecord());
$this->assertArrayHasKey('uid', $record['extra']);
}
public function testGetUid()
{
$processor = new UidProcessor(10);

View File

@@ -88,6 +88,21 @@ class WebProcessorTest extends TestCase
$this->assertSame(array('url' => 'A', 'http_method' => 'C'), $record['extra']);
}
public function testProcessorConfiguringOfExtraFields()
{
$server = array(
'REQUEST_URI' => 'A',
'REMOTE_ADDR' => 'B',
'REQUEST_METHOD' => 'C',
'SERVER_NAME' => 'F',
);
$processor = new WebProcessor($server, array('url' => 'REMOTE_ADDR'));
$record = $processor($this->getRecord());
$this->assertSame(array('url' => 'B'), $record['extra']);
}
/**
* @expectedException UnexpectedValueException
*/

View File

@@ -39,7 +39,7 @@ class TestCase extends \PHPUnit_Framework_TestCase
$this->getRecord(Logger::DEBUG, 'debug message 2'),
$this->getRecord(Logger::INFO, 'information'),
$this->getRecord(Logger::WARNING, 'warning'),
$this->getRecord(Logger::ERROR, 'error')
$this->getRecord(Logger::ERROR, 'error'),
);
}