mirror of
https://github.com/Seldaek/monolog.git
synced 2025-08-11 07:34:12 +02:00
Merge branch 'master' into feature/elasticsearch
# Conflicts: # composer.json # src/Monolog/Handler/ElasticSearchHandler.php
This commit is contained in:
11
.php_cs
11
.php_cs
@@ -24,14 +24,13 @@ return PhpCsFixer\Config::create()
|
||||
'@PSR2' => true,
|
||||
// some rules disabled as long as 1.x branch is maintained
|
||||
'binary_operator_spaces' => array(
|
||||
'align_double_arrow' => null,
|
||||
'align_equals' => null,
|
||||
'default' => null,
|
||||
),
|
||||
'blank_line_before_return' => true,
|
||||
'cast_spaces' => true,
|
||||
'header_comment' => array('header' => $header),
|
||||
'blank_line_before_statement' => ['statements' => ['continue', 'declare', 'return', 'throw', 'try']],
|
||||
'cast_spaces' => ['space' => 'single'],
|
||||
'header_comment' => ['header' => $header],
|
||||
'include' => true,
|
||||
'method_separation' => true,
|
||||
'class_attributes_separation' => ['elements' => ['method']],
|
||||
'no_blank_lines_after_class_opening' => true,
|
||||
'no_blank_lines_after_phpdoc' => true,
|
||||
'no_empty_statement' => true,
|
||||
|
@@ -4,8 +4,8 @@ sudo: false
|
||||
dist: trusty
|
||||
|
||||
php:
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- nightly
|
||||
|
||||
cache:
|
||||
@@ -14,7 +14,7 @@ cache:
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- php: 7.0
|
||||
- php: 7.1
|
||||
env: deps=low
|
||||
fast_finish: true
|
||||
|
||||
|
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,3 +1,25 @@
|
||||
### 1.24.0 (2018-06-xx)
|
||||
|
||||
* Added ability to customize error handling at the Logger level using Logger::setExceptionHandler
|
||||
* Added InsightOpsHandler to migrate users of the LogEntriesHandler
|
||||
* Added protection to NormalizerHandler against circular and very deep structures, it now stops normalizing at a depth of 9
|
||||
* Added capture of stack traces to ErrorHandler when logging PHP errors
|
||||
* Added forwarding of context info to FluentdFormatter
|
||||
* Added SocketHandler::setChunkSize to override the default chunk size in case you must send large log lines to rsyslog for example
|
||||
* Added ability to extend/override BrowserConsoleHandler
|
||||
* Added SlackWebhookHandler::getWebhookUrl and SlackHandler::getToken to enable class extensibility
|
||||
* Added SwiftMailerHandler::getSubjectFormatter to enable class extensibility
|
||||
* Dropped official support for HHVM in test builds
|
||||
* Fixed normalization of exception traces when call_user_func is used to avoid serializing objects and the data they contain
|
||||
* Fixed naming of fields in Slack handler, all field names are now capitalized in all cases
|
||||
* Fixed HipChatHandler bug where slack dropped messages randomly
|
||||
* Fixed normalization of objects in Slack handlers
|
||||
* Fixed support for PHP7's Throwable in NewRelicHandler
|
||||
* Fixed race bug when StreamHandler sometimes incorrectly reported it failed to create a directory
|
||||
* Fixed table row styling issues in HtmlFormatter
|
||||
* Fixed RavenHandler dropping the message when logging exception
|
||||
* Fixed WhatFailureGroupHandler skipping processors when using handleBatch
|
||||
|
||||
### 1.23.0 (2017-06-19)
|
||||
|
||||
* Improved SyslogUdpHandler's support for RFC5424 and added optional `$ident` argument
|
||||
|
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2011-2017 Jordi Boggiano
|
||||
Copyright (c) 2011-2018 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
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
[](https://packagist.org/packages/monolog/monolog)
|
||||
[](https://packagist.org/packages/monolog/monolog)
|
||||
[](https://www.versioneye.com/php/monolog:monolog/references)
|
||||
|
||||
|
||||
Monolog sends your logs to files, sockets, inboxes, databases and various
|
||||
@@ -59,7 +58,7 @@ can also add your own there if you publish one.
|
||||
|
||||
### Requirements
|
||||
|
||||
- Monolog works with PHP 7.0 or above, use Monolog `^1.0` for PHP 5.3+ support.
|
||||
- Monolog 2.x works with PHP 7.1 or above, use Monolog `^1.0` for PHP 5.3+ support.
|
||||
|
||||
### Submitting bugs and feature requests
|
||||
|
||||
@@ -82,7 +81,7 @@ Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/mono
|
||||
- [Proton Micro Framework](https://github.com/alexbilbie/Proton) comes out of the box with Monolog.
|
||||
- [FuelPHP](http://fuelphp.com/) comes out of the box with Monolog.
|
||||
- [Equip Framework](https://github.com/equip/framework) comes out of the box with Monolog.
|
||||
- [Yii 2](http://www.yiiframework.com/) is usable with Monolog via the [yii2-monolog](https://github.com/merorafael/yii2-monolog) plugin.
|
||||
- [Yii 2](http://www.yiiframework.com/) is usable with Monolog via the [yii2-monolog](https://github.com/merorafael/yii2-monolog) or [yii2-psr-log-target](https://github.com/samdark/yii2-psr-log-target) plugins.
|
||||
- [Hawkbit Micro Framework](https://github.com/HawkBitPhp/hawkbit) comes out of the box with Monolog.
|
||||
|
||||
### Author
|
||||
|
@@ -13,11 +13,11 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.0",
|
||||
"php": "^7.1",
|
||||
"psr/log": "^1.0.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.7",
|
||||
"phpunit/phpunit": "^6.5",
|
||||
"graylog2/gelf-php": "^1.4.2",
|
||||
"sentry/sentry": "^0.13",
|
||||
"ruflin/elastica": ">=0.90 <3.0",
|
||||
@@ -30,6 +30,8 @@
|
||||
"predis/predis": "^1.1",
|
||||
"phpspec/prophecy": "^1.6.1",
|
||||
"elasticsearch/elasticsearch": "^6.0"
|
||||
"phpspec/prophecy": "^1.6.1",
|
||||
"rollbar/rollbar": "^1.3"
|
||||
},
|
||||
"suggest": {
|
||||
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
|
||||
@@ -56,7 +58,7 @@
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
"dev-master": "2.x-dev"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
|
@@ -45,9 +45,11 @@
|
||||
|
||||
- [_SocketHandler_](../src/Monolog/Handler/SocketHandler.php): Logs records to [sockets](http://php.net/fsockopen), use this
|
||||
for UNIX and TCP sockets. See an [example](sockets.md).
|
||||
- [_AmqpHandler_](../src/Monolog/Handler/AmqpHandler.php): Logs records to an [amqp](http://www.amqp.org/) compatible
|
||||
server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+).
|
||||
- [_AmqpHandler_](../src/Monolog/Handler/AmqpHandler.php): Logs records to an [AMQP](http://www.amqp.org/) compatible
|
||||
server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+) or
|
||||
[php-amqplib](https://github.com/php-amqplib/php-amqplib) library.
|
||||
- [_GelfHandler_](../src/Monolog/Handler/GelfHandler.php): Logs records to a [Graylog2](http://www.graylog2.org) server.
|
||||
Requires package [graylog2/gelf-php](https://github.com/bzikarsky/gelf-php).
|
||||
- [_CubeHandler_](../src/Monolog/Handler/CubeHandler.php): Logs records to a [Cube](http://square.github.com/cube/) server.
|
||||
- [_RavenHandler_](../src/Monolog/Handler/RavenHandler.php): Logs records to a [Sentry](http://getsentry.com/) server using
|
||||
[raven](https://packagist.org/packages/raven/raven).
|
||||
@@ -57,6 +59,7 @@
|
||||
- [_RollbarHandler_](../src/Monolog/Handler/RollbarHandler.php): Logs records to a [Rollbar](https://rollbar.com/) account.
|
||||
- [_SyslogUdpHandler_](../src/Monolog/Handler/SyslogUdpHandler.php): Logs records to a remote [Syslogd](http://www.rsyslog.com/) server.
|
||||
- [_LogEntriesHandler_](../src/Monolog/Handler/LogEntriesHandler.php): Logs records to a [LogEntries](http://logentries.com/) account.
|
||||
- [_InsightOpsHandler_](../src/Monolog/Handler/InsightOpsHandler.php): Logs records to a [InsightOps](https://www.rapid7.com/products/insightops/) account.
|
||||
- [_LogmaticHandler_](../src/Monolog/Handler/LogmaticHandler.php): Logs records to a [Logmatic](http://logmatic.io/) account.
|
||||
- [_SqsHandler_](../src/Monolog/Handler/SqsHandler.php): Logs records to an [AWS SQS](http://docs.aws.amazon.com/aws-sdk-php/v2/guide/service-sqs.html) queue.
|
||||
|
||||
@@ -155,6 +158,7 @@
|
||||
- [_GitProcessor_](../src/Monolog/Processor/GitProcessor.php): Adds the current git branch and commit to a log record.
|
||||
- [_MercurialProcessor_](../src/Monolog/Processor/MercurialProcessor.php): Adds the current hg branch and commit to a log record.
|
||||
- [_TagProcessor_](../src/Monolog/Processor/TagProcessor.php): Adds an array of predefined tags to a log record.
|
||||
- [_HostnameProcessor_](../src/Monolog/Processor/HostnameProcessor.php): Adds the current hostname to a log record.
|
||||
|
||||
## Third Party Packages
|
||||
|
||||
|
@@ -5,7 +5,7 @@ The table below describes which keys are always available for every log message.
|
||||
|
||||
key | type | description
|
||||
-----------|---------------------------|-------------------------------------------------------------------------------
|
||||
message | string | The log message. When the `PsrLogMessageProcessor` is used this string may contain placeholders that will be replaced by variables from the context, e.g., "User %username% logged in" with `['username' => 'John']` as context will be written as "User John logged in".
|
||||
message | string | The log message. When the `PsrLogMessageProcessor` is used this string may contain placeholders that will be replaced by variables from the context, e.g., "User {username} logged in" with `['username' => 'John']` as context will be written as "User John logged in".
|
||||
level | int | Severity of the log message. See log levels described in [01-usage.md](01-usage.md).
|
||||
level_name | string | String representation of log level.
|
||||
context | array | Arbitrary data passed with the construction of the message. For example the username of the current user or their IP address.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit bootstrap="tests/bootstrap.php" colors="true">
|
||||
<phpunit bootstrap="tests/bootstrap.php" colors="true" beStrictAboutTestsThatDoNotTestAnything="false">
|
||||
<testsuites>
|
||||
<testsuite name="Monolog Test Suite">
|
||||
<directory>tests/Monolog/</directory>
|
||||
|
@@ -23,29 +23,9 @@ class DateTimeImmutable extends \DateTimeImmutable implements \JsonSerializable
|
||||
|
||||
public function __construct($useMicroseconds, \DateTimeZone $timezone = null)
|
||||
{
|
||||
static $needsMicrosecondsHack = PHP_VERSION_ID < 70100;
|
||||
|
||||
$this->useMicroseconds = $useMicroseconds;
|
||||
$date = 'now';
|
||||
|
||||
if ($needsMicrosecondsHack && $useMicroseconds) {
|
||||
$timestamp = microtime(true);
|
||||
|
||||
// apply offset of the timezone as microtime() is always UTC
|
||||
if ($timezone && $timezone->getName() !== 'UTC') {
|
||||
$timestamp += (new \DateTime('now', $timezone))->getOffset();
|
||||
}
|
||||
|
||||
// Circumvent DateTimeImmutable::createFromFormat() which always returns \DateTimeImmutable instead of `static`
|
||||
// @link https://bugs.php.net/bug.php?id=60302
|
||||
//
|
||||
// So we create a DateTime but then format it so we
|
||||
// can re-create one using the right class
|
||||
$dt = self::createFromFormat('U.u', sprintf('%.6F', $timestamp));
|
||||
$date = $dt->format('Y-m-d H:i:s.u');
|
||||
}
|
||||
|
||||
parent::__construct($date, $timezone);
|
||||
parent::__construct('now', $timezone);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): string
|
||||
|
@@ -37,6 +37,7 @@ class ErrorHandler
|
||||
private $hasFatalErrorHandler;
|
||||
private $fatalLevel;
|
||||
private $reservedMemory;
|
||||
private $lastFatalTrace;
|
||||
private static $fatalErrors = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR];
|
||||
|
||||
public function __construct(LoggerInterface $logger)
|
||||
@@ -52,7 +53,7 @@ class ErrorHandler
|
||||
* @param LoggerInterface $logger
|
||||
* @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
|
||||
* @param array|false $exceptionLevelMap an array of class name to LogLevel::* constant mapping, or false to disable exception handling
|
||||
* @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling
|
||||
* @param string|null|false $fatalLevel a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling
|
||||
* @return ErrorHandler
|
||||
*/
|
||||
public static function register(LoggerInterface $logger, $errorLevelMap = [], $exceptionLevelMap = [], $fatalLevel = null): self
|
||||
@@ -103,7 +104,11 @@ class ErrorHandler
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function registerFatalHandler($level = null, $reservedMemorySize = 20): self
|
||||
/**
|
||||
* @param string|null $level a LogLevel::* constant, null to use the default LogLevel::ALERT or false to disable fatal error handling
|
||||
* @param int $reservedMemorySize Amount of KBs to reserve in memory so that it can be freed when handling fatal errors giving Monolog some room in memory to get its job done
|
||||
*/
|
||||
public function registerFatalHandler($level = null, int $reservedMemorySize = 20): self
|
||||
{
|
||||
register_shutdown_function([$this, 'handleFatalError']);
|
||||
|
||||
@@ -166,6 +171,10 @@ class ErrorHandler
|
||||
call_user_func($this->previousExceptionHandler, $e);
|
||||
}
|
||||
|
||||
if (!headers_sent() && ini_get('display_errors') === 0) {
|
||||
http_response_code(500);
|
||||
}
|
||||
|
||||
exit(255);
|
||||
}
|
||||
|
||||
@@ -182,6 +191,10 @@ class ErrorHandler
|
||||
if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
|
||||
$level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL;
|
||||
$this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]);
|
||||
} else {
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
array_shift($trace); // Exclude handleError from trace
|
||||
$this->lastFatalTrace = $trace;
|
||||
}
|
||||
|
||||
if ($this->previousErrorHandler === true) {
|
||||
@@ -203,7 +216,7 @@ class ErrorHandler
|
||||
$this->logger->log(
|
||||
$this->fatalLevel === null ? LogLevel::ALERT : $this->fatalLevel,
|
||||
'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
|
||||
['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line']]
|
||||
['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace]
|
||||
);
|
||||
|
||||
if ($this->logger instanceof Logger) {
|
||||
|
@@ -62,6 +62,7 @@ class FluentdFormatter implements FormatterInterface
|
||||
|
||||
$message = [
|
||||
'message' => $record['message'],
|
||||
'context' => $record['context'],
|
||||
'extra' => $record['extra'],
|
||||
];
|
||||
|
||||
|
@@ -59,7 +59,7 @@ class HtmlFormatter extends NormalizerFormatter
|
||||
$td = '<pre>'.htmlspecialchars($td, ENT_NOQUOTES, 'UTF-8').'</pre>';
|
||||
}
|
||||
|
||||
return "<tr style=\"padding: 4px;spacing: 0;text-align: left;\">\n<th style=\"background: #cccccc\" width=\"100px\">$th:</th>\n<td style=\"padding: 4px;spacing: 0;text-align: left;background: #eeeeee\">".$td."</td>\n</tr>";
|
||||
return "<tr style=\"padding: 4px;text-align: left;\">\n<th style=\"vertical-align: top;background: #ccc;color: #000\" width=\"100\">$th:</th>\n<td style=\"padding: 4px;text-align: left;vertical-align: top;background: #eee;color: #000\">".$td."</td>\n</tr>";
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -64,7 +64,15 @@ class JsonFormatter extends NormalizerFormatter
|
||||
*/
|
||||
public function format(array $record): string
|
||||
{
|
||||
return $this->toJson($this->normalize($record), true) . ($this->appendNewline ? "\n" : '');
|
||||
$normalized = $this->normalize($record);
|
||||
if (isset($normalized['context']) && $normalized['context'] === []) {
|
||||
$normalized['context'] = new \stdClass;
|
||||
}
|
||||
if (isset($normalized['extra']) && $normalized['extra'] === []) {
|
||||
$normalized['extra'] = new \stdClass;
|
||||
}
|
||||
|
||||
return $this->toJson($normalized, true) . ($this->appendNewline ? "\n" : '');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,8 +130,8 @@ class JsonFormatter extends NormalizerFormatter
|
||||
*/
|
||||
protected function normalize($data, int $depth = 0)
|
||||
{
|
||||
if ($depth > 9) {
|
||||
return 'Over 9 levels deep, aborting normalization';
|
||||
if ($depth > $this->maxNormalizeDepth) {
|
||||
return 'Over '.$this->maxNormalizeDepth.' levels deep, aborting normalization';
|
||||
}
|
||||
|
||||
if (is_array($data) || $data instanceof \Traversable) {
|
||||
@@ -131,10 +139,11 @@ class JsonFormatter extends NormalizerFormatter
|
||||
|
||||
$count = 1;
|
||||
foreach ($data as $key => $value) {
|
||||
if ($count++ >= 1000) {
|
||||
$normalized['...'] = 'Over 1000 items, aborting normalization';
|
||||
if ($count++ > $this->maxNormalizeItemCount) {
|
||||
$normalized['...'] = 'Over '.$this->maxNormalizeItemCount.' items ('.count($data).' total), aborting normalization';
|
||||
break;
|
||||
}
|
||||
|
||||
$normalized[$key] = $this->normalize($value, $depth + 1);
|
||||
}
|
||||
|
||||
|
@@ -126,18 +126,14 @@ class LineFormatter extends NormalizerFormatter
|
||||
|
||||
protected function normalizeException(\Throwable $e, int $depth = 0): string
|
||||
{
|
||||
$previousText = '';
|
||||
$str = $this->formatException($e);
|
||||
|
||||
if ($previous = $e->getPrevious()) {
|
||||
do {
|
||||
$previousText .= ', '.get_class($previous).'(code: '.$previous->getCode().'): '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine();
|
||||
$str .= "\n[previous exception] " . $this->formatException($previous);
|
||||
} while ($previous = $previous->getPrevious());
|
||||
}
|
||||
|
||||
$str = '[object] ('.get_class($e).'(code: '.$e->getCode().'): '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine().$previousText.')';
|
||||
if ($this->includeStacktraces) {
|
||||
$str .= "\n[stacktrace]\n".$e->getTraceAsString()."\n";
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
@@ -166,4 +162,14 @@ class LineFormatter extends NormalizerFormatter
|
||||
|
||||
return str_replace(["\r\n", "\r", "\n"], ' ', $str);
|
||||
}
|
||||
|
||||
private function formatException(\Throwable $e): string
|
||||
{
|
||||
$str = '[object] (' . get_class($e) . '(code: ' . $e->getCode() . '): ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine() . ')';
|
||||
if ($this->includeStacktraces) {
|
||||
$str .= "\n[stacktrace]\n" . $e->getTraceAsString() . "\n";
|
||||
}
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
|
@@ -14,8 +14,8 @@ namespace Monolog\Formatter;
|
||||
/**
|
||||
* Serializes a log message to Logstash Event Format
|
||||
*
|
||||
* @see http://logstash.net/
|
||||
* @see https://github.com/elastic/logstash/blob/master/logstash-core-event/lib/logstash/event.rb
|
||||
* @see https://www.elastic.co/products/logstash
|
||||
* @see https://github.com/elastic/logstash/blob/master/logstash-core/src/main/java/org/logstash/Event.java
|
||||
*
|
||||
* @author Tim Mower <timothy.mower@gmail.com>
|
||||
*/
|
||||
@@ -32,30 +32,30 @@ class LogstashFormatter extends NormalizerFormatter
|
||||
protected $applicationName;
|
||||
|
||||
/**
|
||||
* @var string a prefix for 'extra' fields from the Monolog record (optional)
|
||||
* @var string the key for 'extra' fields from the Monolog record
|
||||
*/
|
||||
protected $extraPrefix;
|
||||
protected $extraKey;
|
||||
|
||||
/**
|
||||
* @var string a prefix for 'context' fields from the Monolog record (optional)
|
||||
* @var string the key for 'context' fields from the Monolog record
|
||||
*/
|
||||
protected $contextPrefix;
|
||||
protected $contextKey;
|
||||
|
||||
/**
|
||||
* @param string $applicationName the application that sends the data, used as the "type" field of logstash
|
||||
* @param string $systemName the system/machine name, used as the "source" field of logstash, defaults to the hostname of the machine
|
||||
* @param string $extraPrefix prefix for extra keys inside logstash "fields"
|
||||
* @param string $contextPrefix prefix for context keys inside logstash "fields", defaults to ctxt_
|
||||
* @param string $extraKey the key for extra keys inside logstash "fields", defaults to extra
|
||||
* @param string $contextKey the key for context keys inside logstash "fields", defaults to context
|
||||
*/
|
||||
public function __construct(string $applicationName, string $systemName = null, string $extraPrefix = null, string $contextPrefix = 'ctxt_')
|
||||
public function __construct(string $applicationName, string $systemName = null, string $extraKey = 'extra', string $contextKey = 'context')
|
||||
{
|
||||
// logstash requires a ISO 8601 format date with optional millisecond precision.
|
||||
parent::__construct('Y-m-d\TH:i:s.uP');
|
||||
|
||||
$this->systemName = $systemName ?: gethostname();
|
||||
$this->applicationName = $applicationName;
|
||||
$this->extraPrefix = $extraPrefix;
|
||||
$this->contextPrefix = $contextPrefix;
|
||||
$this->extraKey = $extraKey;
|
||||
$this->contextKey = $contextKey;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,10 +90,10 @@ class LogstashFormatter extends NormalizerFormatter
|
||||
$message['type'] = $this->applicationName;
|
||||
}
|
||||
if (!empty($record['extra'])) {
|
||||
$message[$this->extraPrefix.'extra'] = $record['extra'];
|
||||
$message[$this->extraKey] = $record['extra'];
|
||||
}
|
||||
if (!empty($record['context'])) {
|
||||
$message[$this->contextPrefix.'context'] = $record['context'];
|
||||
$message[$this->contextKey] = $record['context'];
|
||||
}
|
||||
|
||||
return $this->toJson($message) . "\n";
|
||||
|
@@ -24,6 +24,8 @@ class NormalizerFormatter implements FormatterInterface
|
||||
const SIMPLE_DATE = "Y-m-d\TH:i:sP";
|
||||
|
||||
protected $dateFormat;
|
||||
protected $maxNormalizeDepth = 9;
|
||||
protected $maxNormalizeItemCount = 1000;
|
||||
|
||||
/**
|
||||
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
|
||||
@@ -56,14 +58,40 @@ class NormalizerFormatter implements FormatterInterface
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of normalization levels to go through
|
||||
*/
|
||||
public function getMaxNormalizeDepth(): int
|
||||
{
|
||||
return $this->maxNormalizeDepth;
|
||||
}
|
||||
|
||||
public function setMaxNormalizeDepth(int $maxNormalizeDepth): void
|
||||
{
|
||||
$this->maxNormalizeDepth = $maxNormalizeDepth;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum number of items to normalize per level
|
||||
*/
|
||||
public function getMaxNormalizeItemCount(): int
|
||||
{
|
||||
return $this->maxNormalizeItemCount;
|
||||
}
|
||||
|
||||
public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): void
|
||||
{
|
||||
$this->maxNormalizeItemCount = $maxNormalizeItemCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $data
|
||||
* @return int|bool|string|null|array
|
||||
*/
|
||||
protected function normalize($data, int $depth = 0)
|
||||
{
|
||||
if ($depth > 9) {
|
||||
return 'Over 9 levels deep, aborting normalization';
|
||||
if ($depth > $this->maxNormalizeDepth) {
|
||||
return 'Over ' . $this->maxNormalizeDepth . ' levels deep, aborting normalization';
|
||||
}
|
||||
|
||||
if (null === $data || is_scalar($data)) {
|
||||
@@ -84,10 +112,11 @@ class NormalizerFormatter implements FormatterInterface
|
||||
|
||||
$count = 1;
|
||||
foreach ($data as $key => $value) {
|
||||
if ($count++ >= 1000) {
|
||||
$normalized['...'] = 'Over 1000 items ('.count($data).' total), aborting normalization';
|
||||
if ($count++ > $this->maxNormalizeItemCount) {
|
||||
$normalized['...'] = 'Over ' . $this->maxNormalizeItemCount . ' items ('.count($data).' total), aborting normalization';
|
||||
break;
|
||||
}
|
||||
|
||||
$normalized[$key] = $this->normalize($value, $depth + 1);
|
||||
}
|
||||
|
||||
@@ -158,9 +187,20 @@ class NormalizerFormatter implements FormatterInterface
|
||||
if (isset($frame['file'])) {
|
||||
$data['trace'][] = $frame['file'].':'.$frame['line'];
|
||||
} elseif (isset($frame['function']) && $frame['function'] === '{closure}') {
|
||||
// We should again normalize the frames, because it might contain invalid items
|
||||
// Simplify closures handling
|
||||
$data['trace'][] = $frame['function'];
|
||||
} else {
|
||||
if (isset($frame['args'])) {
|
||||
// Make sure that objects present as arguments are not serialized nicely but rather only
|
||||
// as a class name to avoid any unexpected leak of sensitive information
|
||||
$frame['args'] = array_map(function ($arg) {
|
||||
if (is_object($arg) && !$arg instanceof \DateTimeInterface) {
|
||||
return sprintf("[object] (%s)", get_class($arg));
|
||||
}
|
||||
|
||||
return $arg;
|
||||
}, $frame['args']);
|
||||
}
|
||||
// We should again normalize the frames, because it might contain invalid items
|
||||
$data['trace'][] = $this->toJson($this->normalize($frame, $depth + 1), true);
|
||||
}
|
||||
|
@@ -24,8 +24,8 @@ abstract class AbstractHandler extends Handler
|
||||
protected $bubble = true;
|
||||
|
||||
/**
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param int|string $level The minimum logging level at which this handler will be triggered
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct($level = Logger::DEBUG, $bubble = true)
|
||||
{
|
||||
@@ -67,7 +67,7 @@ abstract class AbstractHandler extends Handler
|
||||
/**
|
||||
* Sets the bubbling behavior.
|
||||
*
|
||||
* @param Boolean $bubble true means that this handler allows bubbling.
|
||||
* @param bool $bubble true means that this handler allows bubbling.
|
||||
* false means that bubbling is not permitted.
|
||||
* @return self
|
||||
*/
|
||||
@@ -81,7 +81,7 @@ abstract class AbstractHandler extends Handler
|
||||
/**
|
||||
* Gets the bubbling behavior.
|
||||
*
|
||||
* @return Boolean true means that this handler allows bubbling.
|
||||
* @return bool true means that this handler allows bubbling.
|
||||
* false means that bubbling is not permitted.
|
||||
*/
|
||||
public function getBubble(): bool
|
||||
|
@@ -56,7 +56,7 @@ abstract class AbstractSyslogHandler extends AbstractProcessingHandler
|
||||
/**
|
||||
* @param mixed $facility
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct($facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)
|
||||
{
|
||||
|
47
src/Monolog/Handler/BrowserConsoleHandler.php
Normal file → Executable file
47
src/Monolog/Handler/BrowserConsoleHandler.php
Normal file → Executable file
@@ -44,11 +44,11 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
||||
protected function write(array $record)
|
||||
{
|
||||
// Accumulate records
|
||||
self::$records[] = $record;
|
||||
static::$records[] = $record;
|
||||
|
||||
// Register shutdown handler if not already done
|
||||
if (!self::$initialized) {
|
||||
self::$initialized = true;
|
||||
if (!static::$initialized) {
|
||||
static::$initialized = true;
|
||||
$this->registerShutdownFunction();
|
||||
}
|
||||
}
|
||||
@@ -59,18 +59,18 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
||||
*/
|
||||
public static function send()
|
||||
{
|
||||
$format = self::getResponseFormat();
|
||||
$format = static::getResponseFormat();
|
||||
if ($format === 'unknown') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (count(self::$records)) {
|
||||
if (count(static::$records)) {
|
||||
if ($format === 'html') {
|
||||
self::writeOutput('<script>' . self::generateScript() . '</script>');
|
||||
static::writeOutput('<script>' . static::generateScript() . '</script>');
|
||||
} elseif ($format === 'js') {
|
||||
self::writeOutput(self::generateScript());
|
||||
static::writeOutput(static::generateScript());
|
||||
}
|
||||
self::reset();
|
||||
static::reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
||||
*/
|
||||
public static function reset()
|
||||
{
|
||||
self::$records = [];
|
||||
static::$records = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -134,18 +134,19 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
||||
private static function generateScript()
|
||||
{
|
||||
$script = [];
|
||||
foreach (self::$records as $record) {
|
||||
$context = self::dump('Context', $record['context']);
|
||||
$extra = self::dump('Extra', $record['extra']);
|
||||
foreach (static::$records as $record) {
|
||||
$context = static::dump('Context', $record['context']);
|
||||
$extra = static::dump('Extra', $record['extra']);
|
||||
|
||||
if (empty($context) && empty($extra)) {
|
||||
$script[] = self::call_array('log', self::handleStyles($record['formatted']));
|
||||
$script[] = static::call_array('log', static::handleStyles($record['formatted']));
|
||||
} else {
|
||||
$script = array_merge($script,
|
||||
[self::call_array('groupCollapsed', self::handleStyles($record['formatted']))],
|
||||
$script = array_merge(
|
||||
$script,
|
||||
[static::call_array('groupCollapsed', static::handleStyles($record['formatted']))],
|
||||
$context,
|
||||
$extra,
|
||||
[self::call('groupEnd')]
|
||||
[static::call('groupEnd')]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -155,19 +156,19 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
||||
|
||||
private static function handleStyles($formatted)
|
||||
{
|
||||
$args = [self::quote('font-weight: normal')];
|
||||
$args = [static::quote('font-weight: normal')];
|
||||
$format = '%c' . $formatted;
|
||||
preg_match_all('/\[\[(.*?)\]\]\{([^}]*)\}/s', $format, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
|
||||
|
||||
foreach (array_reverse($matches) as $match) {
|
||||
$args[] = self::quote(self::handleCustomStyles($match[2][0], $match[1][0]));
|
||||
$args[] = static::quote(static::handleCustomStyles($match[2][0], $match[1][0]));
|
||||
$args[] = '"font-weight: normal"';
|
||||
|
||||
$pos = $match[0][1];
|
||||
$format = substr($format, 0, $pos) . '%c' . $match[1][0] . '%c' . substr($format, $pos + strlen($match[0][0]));
|
||||
}
|
||||
|
||||
array_unshift($args, self::quote($format));
|
||||
array_unshift($args, static::quote($format));
|
||||
|
||||
return $args;
|
||||
}
|
||||
@@ -199,13 +200,13 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
||||
if (empty($dict)) {
|
||||
return $script;
|
||||
}
|
||||
$script[] = self::call('log', self::quote('%c%s'), self::quote('font-weight: bold'), self::quote($title));
|
||||
$script[] = static::call('log', static::quote('%c%s'), static::quote('font-weight: bold'), static::quote($title));
|
||||
foreach ($dict as $key => $value) {
|
||||
$value = json_encode($value);
|
||||
if (empty($value)) {
|
||||
$value = self::quote('');
|
||||
$value = static::quote('');
|
||||
}
|
||||
$script[] = self::call('log', self::quote('%s: %o'), self::quote($key), $value);
|
||||
$script[] = static::call('log', static::quote('%s: %o'), static::quote($key), $value);
|
||||
}
|
||||
|
||||
return $script;
|
||||
@@ -221,7 +222,7 @@ class BrowserConsoleHandler extends AbstractProcessingHandler
|
||||
$args = func_get_args();
|
||||
$method = array_shift($args);
|
||||
|
||||
return self::call_array($method, $args);
|
||||
return static::call_array($method, $args);
|
||||
}
|
||||
|
||||
private static function call_array($method, array $args)
|
||||
|
@@ -36,8 +36,8 @@ class BufferHandler extends AbstractHandler implements ProcessableHandlerInterfa
|
||||
* @param HandlerInterface $handler Handler.
|
||||
* @param int $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param Boolean $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded
|
||||
*/
|
||||
public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false)
|
||||
{
|
||||
|
@@ -24,6 +24,8 @@ use Monolog\Logger;
|
||||
*/
|
||||
class ChromePHPHandler extends AbstractProcessingHandler
|
||||
{
|
||||
use WebRequestRecognizerTrait;
|
||||
|
||||
/**
|
||||
* Version of the extension
|
||||
*/
|
||||
@@ -46,7 +48,7 @@ class ChromePHPHandler extends AbstractProcessingHandler
|
||||
*
|
||||
* Chrome limits the headers to 256KB, so when we sent 240KB we stop sending
|
||||
*
|
||||
* @var Boolean
|
||||
* @var bool
|
||||
*/
|
||||
protected static $overflowed = false;
|
||||
|
||||
@@ -60,7 +62,7 @@ class ChromePHPHandler extends AbstractProcessingHandler
|
||||
|
||||
/**
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct($level = Logger::DEBUG, $bubble = true)
|
||||
{
|
||||
@@ -75,6 +77,10 @@ class ChromePHPHandler extends AbstractProcessingHandler
|
||||
*/
|
||||
public function handleBatch(array $records)
|
||||
{
|
||||
if (!$this->isWebRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$messages = [];
|
||||
|
||||
foreach ($records as $record) {
|
||||
@@ -108,6 +114,10 @@ class ChromePHPHandler extends AbstractProcessingHandler
|
||||
*/
|
||||
protected function write(array $record)
|
||||
{
|
||||
if (!$this->isWebRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$json['rows'][] = $record['formatted'];
|
||||
|
||||
$this->send();
|
||||
|
@@ -46,7 +46,8 @@ class CubeHandler extends AbstractProcessingHandler
|
||||
if (!in_array($urlInfo['scheme'], $this->acceptedSchemes)) {
|
||||
throw new \UnexpectedValueException(
|
||||
'Invalid protocol (' . $urlInfo['scheme'] . ').'
|
||||
. ' Valid options are ' . implode(', ', $this->acceptedSchemes));
|
||||
. ' Valid options are ' . implode(', ', $this->acceptedSchemes)
|
||||
);
|
||||
}
|
||||
|
||||
$this->scheme = $urlInfo['scheme'];
|
||||
|
@@ -60,7 +60,7 @@ class DeduplicationHandler extends BufferHandler
|
||||
* @param string $deduplicationStore The file/path where the deduplication log should be kept
|
||||
* @param int $deduplicationLevel The minimum logging level for log records to be looked at for deduplication purposes
|
||||
* @param int $time The period (in seconds) during which duplicate entries should be suppressed after a given log is sent through
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct(HandlerInterface $handler, $deduplicationStore = null, $deduplicationLevel = Logger::ERROR, $time = 60, $bubble = true)
|
||||
{
|
||||
|
@@ -31,15 +31,16 @@ class ErrorLogHandler extends AbstractProcessingHandler
|
||||
/**
|
||||
* @param int $messageType Says where the error should go.
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param Boolean $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $expandNewlines If set to true, newlines in the message will be expanded to be take multiple log entries
|
||||
*/
|
||||
public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true, $expandNewlines = false)
|
||||
{
|
||||
parent::__construct($level, $bubble);
|
||||
|
||||
if (false === in_array($messageType, self::getAvailableTypes())) {
|
||||
if (false === in_array($messageType, self::getAvailableTypes(), true)) {
|
||||
$message = sprintf('The given message type "%s" is not supported', print_r($messageType, true));
|
||||
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
|
||||
@@ -73,6 +74,7 @@ class ErrorLogHandler extends AbstractProcessingHandler
|
||||
{
|
||||
if (!$this->expandNewlines) {
|
||||
error_log((string) $record['formatted'], $this->messageType);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -42,7 +42,7 @@ class FilterHandler extends Handler implements ProcessableHandlerInterface
|
||||
/**
|
||||
* Whether the messages that are handled can bubble up the stack or not
|
||||
*
|
||||
* @var Boolean
|
||||
* @var bool
|
||||
*/
|
||||
protected $bubble;
|
||||
|
||||
@@ -50,7 +50,7 @@ class FilterHandler extends Handler implements ProcessableHandlerInterface
|
||||
* @param callable|HandlerInterface $handler Handler or factory callable($record, $this).
|
||||
* @param int|array $minLevelOrList A list of levels to accept or a minimum level if maxLevel is provided
|
||||
* @param int $maxLevel Maximum level to accept, only used if $minLevelOrList is not an array
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct($handler, $minLevelOrList = Logger::DEBUG, $maxLevel = Logger::EMERGENCY, $bubble = true)
|
||||
{
|
||||
|
@@ -22,7 +22,7 @@ interface ActivationStrategyInterface
|
||||
* Returns whether the given record activates the handler.
|
||||
*
|
||||
* @param array $record
|
||||
* @return Boolean
|
||||
* @return bool
|
||||
*/
|
||||
public function isHandlerActivated(array $record);
|
||||
}
|
||||
|
@@ -43,8 +43,8 @@ class FingersCrossedHandler extends Handler implements ProcessableHandlerInterfa
|
||||
* @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler).
|
||||
* @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action
|
||||
* @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param Boolean $stopBuffering Whether the handler should stop buffering after being triggered (default true)
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $stopBuffering Whether the handler should stop buffering after being triggered (default true)
|
||||
* @param int $passthruLevel Minimum level to always flush to handler on close, even if strategy not triggered
|
||||
*/
|
||||
public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true, $passthruLevel = null)
|
||||
|
@@ -21,6 +21,8 @@ use Monolog\Formatter\FormatterInterface;
|
||||
*/
|
||||
class FirePHPHandler extends AbstractProcessingHandler
|
||||
{
|
||||
use WebRequestRecognizerTrait;
|
||||
|
||||
/**
|
||||
* WildFire JSON header message format
|
||||
*/
|
||||
@@ -130,7 +132,7 @@ class FirePHPHandler extends AbstractProcessingHandler
|
||||
*/
|
||||
protected function write(array $record)
|
||||
{
|
||||
if (!self::$sendHeaders) {
|
||||
if (!self::$sendHeaders || !$this->isWebRequest()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -157,7 +159,7 @@ class FirePHPHandler extends AbstractProcessingHandler
|
||||
/**
|
||||
* Verifies if the headers are accepted by the current user agent
|
||||
*
|
||||
* @return Boolean
|
||||
* @return bool
|
||||
*/
|
||||
protected function headersAccepted()
|
||||
{
|
||||
|
@@ -41,14 +41,6 @@ class GelfHandler extends AbstractProcessingHandler
|
||||
$this->publisher = $publisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
$this->publisher = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
@@ -26,7 +26,7 @@ class GroupHandler extends Handler implements ProcessableHandlerInterface
|
||||
|
||||
/**
|
||||
* @param array $handlers Array of Handlers.
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct(array $handlers, $bubble = true)
|
||||
{
|
||||
|
@@ -29,7 +29,7 @@ interface HandlerInterface
|
||||
*
|
||||
* @param array $record Partial log record containing only a level key
|
||||
*
|
||||
* @return Boolean
|
||||
* @return bool
|
||||
*/
|
||||
public function isHandling(array $record): bool;
|
||||
|
||||
@@ -44,7 +44,7 @@ interface HandlerInterface
|
||||
* calling further handlers in the stack with a given log record.
|
||||
*
|
||||
* @param array $record The record to handle
|
||||
* @return Boolean true means that this handler handled the record, and that bubbling is not permitted.
|
||||
* @return bool true means that this handler handled the record, and that bubbling is not permitted.
|
||||
* false means the record was either not processed or that this handler allows bubbling.
|
||||
*/
|
||||
public function handle(array $record): bool;
|
||||
|
@@ -188,6 +188,21 @@ class HipChatHandler extends SocketHandler
|
||||
protected function write(array $record)
|
||||
{
|
||||
parent::write($record);
|
||||
$this->finalizeWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the request by reading some bytes and then closing the socket
|
||||
*
|
||||
* If we do not read some but close the socket too early, hipchat sometimes
|
||||
* drops the request entirely.
|
||||
*/
|
||||
protected function finalizeWrite()
|
||||
{
|
||||
$res = $this->getResource();
|
||||
if (is_resource($res)) {
|
||||
@fread($res, 2048);
|
||||
}
|
||||
$this->closeSocket();
|
||||
}
|
||||
|
||||
|
@@ -33,7 +33,7 @@ class IFTTTHandler extends AbstractProcessingHandler
|
||||
* @param string $eventName The name of the IFTTT Maker event that should be triggered
|
||||
* @param string $secretKey A valid IFTTT secret key
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct($eventName, $secretKey, $level = Logger::ERROR, $bubble = true)
|
||||
{
|
||||
|
62
src/Monolog/Handler/InsightOpsHandler.php
Normal file
62
src/Monolog/Handler/InsightOpsHandler.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Monolog package.
|
||||
*
|
||||
* (c) Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Monolog\Handler;
|
||||
|
||||
use Monolog\Logger;
|
||||
|
||||
/**
|
||||
* Inspired on LogEntriesHandler.
|
||||
*
|
||||
* @author Robert Kaufmann III <rok3@rok3.me>
|
||||
* @author Gabriel Machado <gabriel.ms1@hotmail.com>
|
||||
*/
|
||||
class InsightOpsHandler extends SocketHandler
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $logToken;
|
||||
|
||||
/**
|
||||
* @param string $token Log token supplied by InsightOps
|
||||
* @param string $region Region where InsightOps account is hosted. Could be 'us' or 'eu'.
|
||||
* @param bool $useSSL Whether or not SSL encryption should be used
|
||||
* @param int $level The minimum logging level to trigger this handler
|
||||
* @param bool $bubble Whether or not messages that are handled should bubble up the stack.
|
||||
*
|
||||
* @throws MissingExtensionException If SSL encryption is set to true and OpenSSL is missing
|
||||
*/
|
||||
public function __construct($token, $region = 'us', $useSSL = true, $level = Logger::DEBUG, $bubble = true)
|
||||
{
|
||||
if ($useSSL && !extension_loaded('openssl')) {
|
||||
throw new MissingExtensionException('The OpenSSL PHP plugin is required to use SSL encrypted connection for LogEntriesHandler');
|
||||
}
|
||||
|
||||
$endpoint = $useSSL
|
||||
? 'ssl://' . $region . '.data.logs.insight.rapid7.com:443'
|
||||
: $region . '.data.logs.insight.rapid7.com:80';
|
||||
|
||||
parent::__construct($endpoint, $level, $bubble);
|
||||
$this->logToken = $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array $record
|
||||
* @return string
|
||||
*/
|
||||
protected function generateDataStream($record)
|
||||
{
|
||||
return $this->logToken . ' ' . $record['formatted'];
|
||||
}
|
||||
}
|
@@ -27,7 +27,7 @@ class MandrillHandler extends MailHandler
|
||||
* @param string $apiKey A valid Mandrill API key
|
||||
* @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct($apiKey, $message, $level = Logger::ERROR, $bubble = true)
|
||||
{
|
||||
|
@@ -44,7 +44,7 @@ class MongoDBHandler extends AbstractProcessingHandler
|
||||
* @param string $database Database name
|
||||
* @param string $collection Collection name
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct($mongodb, $database, $collection, $level = Logger::DEBUG, $bubble = true)
|
||||
{
|
||||
|
@@ -19,6 +19,8 @@ use Monolog\Formatter\FormatterInterface;
|
||||
* Class to record a log on a NewRelic application.
|
||||
* Enabling New Relic High Security mode may prevent capture of useful information.
|
||||
*
|
||||
* This handler requires a NormalizerFormatter to function and expects an array in $record['formatted']
|
||||
*
|
||||
* @see https://docs.newrelic.com/docs/agents/php-agent
|
||||
* @see https://docs.newrelic.com/docs/accounts-partnerships/accounts/security/high-security
|
||||
*/
|
||||
@@ -85,7 +87,7 @@ class NewRelicHandler extends AbstractProcessingHandler
|
||||
unset($record['formatted']['context']['transaction_name']);
|
||||
}
|
||||
|
||||
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) {
|
||||
if (isset($record['context']['exception']) && ($record['context']['exception'] instanceof \Exception || (PHP_VERSION_ID >= 70000 && $record['context']['exception'] instanceof \Throwable))) {
|
||||
newrelic_notice_error($record['message'], $record['context']['exception']);
|
||||
unset($record['formatted']['context']['exception']);
|
||||
} else {
|
||||
|
@@ -31,7 +31,7 @@ class PsrHandler extends AbstractHandler
|
||||
/**
|
||||
* @param LoggerInterface $logger The underlying PSR-3 compliant logger to which messages will be proxied
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct(LoggerInterface $logger, $level = Logger::DEBUG, $bubble = true)
|
||||
{
|
||||
|
@@ -69,8 +69,8 @@ class PushoverHandler extends SocketHandler
|
||||
* @param string|array $users Pushover user id or array of ids the message will be sent to
|
||||
* @param string $title Title sent to the Pushover API
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param Boolean $useSSL Whether to connect via SSL. Required when pushing messages to users that are not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $useSSL Whether to connect via SSL. Required when pushing messages to users that are not
|
||||
* the pushover.net app owner. OpenSSL is required for this option.
|
||||
* @param int $highPriorityLevel The minimum logging level at which this handler will start
|
||||
* sending "high priority" requests to the Pushover API
|
||||
@@ -180,6 +180,6 @@ class PushoverHandler extends SocketHandler
|
||||
*/
|
||||
public function useFormattedMessage($value)
|
||||
{
|
||||
$this->useFormattedMessage = (boolean) $value;
|
||||
$this->useFormattedMessage = (bool) $value;
|
||||
}
|
||||
}
|
||||
|
@@ -57,7 +57,7 @@ class RavenHandler extends AbstractProcessingHandler
|
||||
/**
|
||||
* @param Raven_Client $ravenClient
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true)
|
||||
{
|
||||
@@ -181,7 +181,7 @@ class RavenHandler extends AbstractProcessingHandler
|
||||
}
|
||||
|
||||
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Throwable) {
|
||||
$options['extra']['message'] = $record['formatted'];
|
||||
$options['message'] = $record['formatted'];
|
||||
$this->ravenClient->captureException($record['context']['exception'], $options);
|
||||
} else {
|
||||
$this->ravenClient->captureMessage($record['formatted'], [], $options);
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
namespace Monolog\Handler;
|
||||
|
||||
use RollbarNotifier;
|
||||
use Rollbar\RollbarLogger;
|
||||
use Throwable;
|
||||
use Monolog\Logger;
|
||||
|
||||
@@ -19,7 +19,7 @@ use Monolog\Logger;
|
||||
* Sends errors to Rollbar
|
||||
*
|
||||
* If the context data contains a `payload` key, that is used as an array
|
||||
* of payload options to RollbarNotifier's report_message/report_exception methods.
|
||||
* of payload options to RollbarLogger's log method.
|
||||
*
|
||||
* Rollbar's context info will contain the context + extra keys from the log record
|
||||
* merged, and then on top of that a few keys:
|
||||
@@ -34,11 +34,9 @@ use Monolog\Logger;
|
||||
class RollbarHandler extends AbstractProcessingHandler
|
||||
{
|
||||
/**
|
||||
* Rollbar notifier
|
||||
*
|
||||
* @var RollbarNotifier
|
||||
* @var RollbarLogger
|
||||
*/
|
||||
protected $rollbarNotifier;
|
||||
protected $rollbarLogger;
|
||||
|
||||
protected $levelMap = [
|
||||
Logger::DEBUG => 'debug',
|
||||
@@ -61,13 +59,13 @@ class RollbarHandler extends AbstractProcessingHandler
|
||||
protected $initialized = false;
|
||||
|
||||
/**
|
||||
* @param RollbarNotifier $rollbarNotifier RollbarNotifier object constructed with valid token
|
||||
* @param RollbarLogger $rollbarLogger RollbarLogger object constructed with valid token
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct(RollbarNotifier $rollbarNotifier, $level = Logger::ERROR, $bubble = true)
|
||||
public function __construct(RollbarLogger $rollbarLogger, $level = Logger::ERROR, $bubble = true)
|
||||
{
|
||||
$this->rollbarNotifier = $rollbarNotifier;
|
||||
$this->rollbarLogger = $rollbarLogger;
|
||||
|
||||
parent::__construct($level, $bubble);
|
||||
}
|
||||
@@ -84,11 +82,6 @@ class RollbarHandler extends AbstractProcessingHandler
|
||||
}
|
||||
|
||||
$context = $record['context'];
|
||||
$payload = [];
|
||||
if (isset($context['payload'])) {
|
||||
$payload = $context['payload'];
|
||||
unset($context['payload']);
|
||||
}
|
||||
$context = array_merge($context, $record['extra'], [
|
||||
'level' => $this->levelMap[$record['level']],
|
||||
'monolog_level' => $record['level_name'],
|
||||
@@ -97,27 +90,22 @@ class RollbarHandler extends AbstractProcessingHandler
|
||||
]);
|
||||
|
||||
if (isset($context['exception']) && $context['exception'] instanceof Throwable) {
|
||||
$payload['level'] = $context['level'];
|
||||
$exception = $context['exception'];
|
||||
unset($context['exception']);
|
||||
|
||||
$this->rollbarNotifier->report_exception($exception, $context, $payload);
|
||||
$toLog = $exception;
|
||||
} else {
|
||||
$this->rollbarNotifier->report_message(
|
||||
$record['message'],
|
||||
$context['level'],
|
||||
$context,
|
||||
$payload
|
||||
);
|
||||
$toLog = $record['message'];
|
||||
}
|
||||
|
||||
$this->rollbarLogger->log($context['level'], $toLog, $context);
|
||||
|
||||
$this->hasRecords = true;
|
||||
}
|
||||
|
||||
public function flush()
|
||||
{
|
||||
if ($this->hasRecords) {
|
||||
$this->rollbarNotifier->flush();
|
||||
$this->rollbarLogger->flush();
|
||||
$this->hasRecords = false;
|
||||
}
|
||||
}
|
||||
|
@@ -40,9 +40,9 @@ class RotatingFileHandler extends StreamHandler
|
||||
* @param string $filename
|
||||
* @param int $maxFiles The maximal amount of files to keep (0 means unlimited)
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write)
|
||||
* @param Boolean $useLocking Try to lock log file before doing any writes
|
||||
* @param bool $useLocking Try to lock log file before doing any writes
|
||||
*/
|
||||
public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true, $filePermission = null, $useLocking = false)
|
||||
{
|
||||
@@ -50,7 +50,7 @@ class RotatingFileHandler extends StreamHandler
|
||||
$this->maxFiles = (int) $maxFiles;
|
||||
$this->nextRotation = new \DateTimeImmutable('tomorrow');
|
||||
$this->filenameFormat = '{filename}-{date}';
|
||||
$this->dateFormat = 'Y-m-d';
|
||||
$this->dateFormat = self::FILE_PER_DAY;
|
||||
|
||||
parent::__construct($this->getTimedFilename(), $level, $bubble, $filePermission, $useLocking);
|
||||
}
|
||||
@@ -98,7 +98,7 @@ class RotatingFileHandler extends StreamHandler
|
||||
$this->mustRotate = !file_exists($this->url);
|
||||
}
|
||||
|
||||
if ($this->nextRotation < $record['datetime']) {
|
||||
if ($this->nextRotation <= $record['datetime']) {
|
||||
$this->mustRotate = true;
|
||||
$this->close();
|
||||
}
|
||||
@@ -166,7 +166,7 @@ class RotatingFileHandler extends StreamHandler
|
||||
$fileInfo = pathinfo($this->filename);
|
||||
$glob = str_replace(
|
||||
['{filename}', '{date}'],
|
||||
[$fileInfo['filename'], '*'],
|
||||
[$fileInfo['filename'], '[0-9][0-9][0-9][0-9]*'],
|
||||
$fileInfo['dirname'] . '/' . $this->filenameFormat
|
||||
);
|
||||
if (!empty($fileInfo['extension'])) {
|
||||
|
12
src/Monolog/Handler/Slack/SlackRecord.php
Normal file → Executable file
12
src/Monolog/Handler/Slack/SlackRecord.php
Normal file → Executable file
@@ -145,7 +145,7 @@ class SlackRecord
|
||||
|
||||
if ($this->useShortAttachment) {
|
||||
$attachment['fields'][] = $this->generateAttachmentField(
|
||||
ucfirst($key),
|
||||
$key,
|
||||
$record[$key]
|
||||
);
|
||||
} else {
|
||||
@@ -211,8 +211,8 @@ class SlackRecord
|
||||
$hasNonNumericKeys = !count(array_filter(array_keys($normalized), 'is_numeric'));
|
||||
|
||||
return $hasSecondDimension || $hasNonNumericKeys
|
||||
? json_encode($normalized, $prettyPrintFlag)
|
||||
: json_encode($normalized);
|
||||
? json_encode($normalized, $prettyPrintFlag|JSON_UNESCAPED_UNICODE)
|
||||
: json_encode($normalized, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,7 +229,7 @@ class SlackRecord
|
||||
* Generates attachment field
|
||||
*
|
||||
* @param string $title
|
||||
* @param string|array $value\
|
||||
* @param string|array $value
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@@ -240,7 +240,7 @@ class SlackRecord
|
||||
: $value;
|
||||
|
||||
return array(
|
||||
'title' => $title,
|
||||
'title' => ucfirst($title),
|
||||
'value' => $value,
|
||||
'short' => false,
|
||||
);
|
||||
@@ -256,7 +256,7 @@ class SlackRecord
|
||||
private function generateAttachmentFields(array $data)
|
||||
{
|
||||
$fields = array();
|
||||
foreach ($data as $key => $value) {
|
||||
foreach ($this->normalizerFormatter->format($data) as $key => $value) {
|
||||
$fields[] = $this->generateAttachmentField($key, $value);
|
||||
}
|
||||
|
||||
|
@@ -63,8 +63,7 @@ class SlackHandler extends SocketHandler
|
||||
$iconEmoji,
|
||||
$useShortAttachment,
|
||||
$includeContextAndExtra,
|
||||
$excludeFields,
|
||||
$this->formatter
|
||||
$excludeFields
|
||||
);
|
||||
|
||||
$this->token = $token;
|
||||
@@ -75,6 +74,11 @@ class SlackHandler extends SocketHandler
|
||||
return $this->slackRecord;
|
||||
}
|
||||
|
||||
public function getToken()
|
||||
{
|
||||
return $this->token;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
|
@@ -60,8 +60,7 @@ class SlackWebhookHandler extends AbstractProcessingHandler
|
||||
$iconEmoji,
|
||||
$useShortAttachment,
|
||||
$includeContextAndExtra,
|
||||
$excludeFields,
|
||||
$this->formatter
|
||||
$excludeFields
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,6 +69,11 @@ class SlackWebhookHandler extends AbstractProcessingHandler
|
||||
return $this->slackRecord;
|
||||
}
|
||||
|
||||
public function getWebhookUrl()
|
||||
{
|
||||
return $this->webhookUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
@@ -86,7 +90,7 @@ class SlackWebhookHandler extends AbstractProcessingHandler
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => array('Content-type: application/json'),
|
||||
CURLOPT_POSTFIELDS => $postString
|
||||
CURLOPT_POSTFIELDS => $postString,
|
||||
);
|
||||
if (defined('CURLOPT_SAFE_UPLOAD')) {
|
||||
$options[CURLOPT_SAFE_UPLOAD] = true;
|
||||
|
@@ -30,6 +30,7 @@ class SocketHandler extends AbstractProcessingHandler
|
||||
/** @var float */
|
||||
private $writingTimeout = 10;
|
||||
private $lastSentBytes = null;
|
||||
private $chunkSize = null;
|
||||
private $persistent = false;
|
||||
private $errno;
|
||||
private $errstr;
|
||||
@@ -38,7 +39,7 @@ class SocketHandler extends AbstractProcessingHandler
|
||||
/**
|
||||
* @param string $connectionString Socket connection string
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
*/
|
||||
public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true)
|
||||
{
|
||||
@@ -90,7 +91,7 @@ class SocketHandler extends AbstractProcessingHandler
|
||||
*/
|
||||
public function setPersistent($persistent)
|
||||
{
|
||||
$this->persistent = (boolean) $persistent;
|
||||
$this->persistent = (bool) $persistent;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,6 +131,16 @@ class SocketHandler extends AbstractProcessingHandler
|
||||
$this->writingTimeout = (float) $seconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set chunk size. Only has effect during connection in the writing cycle.
|
||||
*
|
||||
* @param float $bytes
|
||||
*/
|
||||
public function setChunkSize($bytes)
|
||||
{
|
||||
$this->chunkSize = $bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current connection string
|
||||
*
|
||||
@@ -176,6 +187,16 @@ class SocketHandler extends AbstractProcessingHandler
|
||||
return $this->writingTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current chunk size
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getChunkSize()
|
||||
{
|
||||
return $this->chunkSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if the socket is currently available.
|
||||
*
|
||||
@@ -218,6 +239,16 @@ class SocketHandler extends AbstractProcessingHandler
|
||||
return stream_set_timeout($this->resource, (int) $seconds, (int) $microseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to allow mocking
|
||||
*
|
||||
* @see http://php.net/manual/en/function.stream-set-chunk-size.php
|
||||
*/
|
||||
protected function streamSetChunkSize()
|
||||
{
|
||||
return stream_set_chunk_size($this->resource, $this->chunkSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to allow mocking
|
||||
*/
|
||||
@@ -267,6 +298,7 @@ class SocketHandler extends AbstractProcessingHandler
|
||||
{
|
||||
$this->createSocketResource();
|
||||
$this->setSocketTimeout();
|
||||
$this->setStreamChunkSize();
|
||||
}
|
||||
|
||||
private function createSocketResource()
|
||||
@@ -289,6 +321,13 @@ class SocketHandler extends AbstractProcessingHandler
|
||||
}
|
||||
}
|
||||
|
||||
private function setStreamChunkSize()
|
||||
{
|
||||
if ($this->chunkSize && !$this->streamSetChunkSize()) {
|
||||
throw new \UnexpectedValueException("Failed setting chunk size with stream_set_chunk_size()");
|
||||
}
|
||||
}
|
||||
|
||||
private function writeToSocket($data)
|
||||
{
|
||||
$length = strlen($data);
|
||||
|
@@ -21,6 +21,11 @@ use Monolog\Logger;
|
||||
*/
|
||||
class SqsHandler extends AbstractProcessingHandler
|
||||
{
|
||||
/** 256 KB in bytes - maximum message size in SQS */
|
||||
const MAX_MESSAGE_SIZE = 262144;
|
||||
/** 100 KB in bytes - head message size for new error log */
|
||||
const HEAD_MESSAGE_SIZE = 102400;
|
||||
|
||||
/** @var SqsClient */
|
||||
private $client;
|
||||
/** @var string */
|
||||
@@ -45,9 +50,14 @@ class SqsHandler extends AbstractProcessingHandler
|
||||
throw new \InvalidArgumentException('SqsHandler accepts only formatted records as a string');
|
||||
}
|
||||
|
||||
$messageBody = $record['formatted'];
|
||||
if (strlen($messageBody) >= self::MAX_MESSAGE_SIZE) {
|
||||
$messageBody = substr($messageBody, 0, self::HEAD_MESSAGE_SIZE);
|
||||
}
|
||||
|
||||
$this->client->sendMessage([
|
||||
'QueueUrl' => $this->queueUrl,
|
||||
'MessageBody' => $record['formatted'],
|
||||
'MessageBody' => $messageBody,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@@ -34,9 +34,9 @@ class StreamHandler extends AbstractProcessingHandler
|
||||
/**
|
||||
* @param resource|string $stream
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param int|null $filePermission Optional file permissions (default (0644) are only for owner read/write)
|
||||
* @param Boolean $useLocking Try to lock log file before doing any writes
|
||||
* @param bool $useLocking Try to lock log file before doing any writes
|
||||
*
|
||||
* @throws \Exception If a missing directory is not buildable
|
||||
* @throws \InvalidArgumentException If stream is not a resource or string
|
||||
@@ -106,6 +106,7 @@ class StreamHandler extends AbstractProcessingHandler
|
||||
restore_error_handler();
|
||||
if (!is_resource($this->stream)) {
|
||||
$this->stream = null;
|
||||
|
||||
throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$this->errorMessage, $this->url));
|
||||
}
|
||||
}
|
||||
@@ -169,7 +170,7 @@ class StreamHandler extends AbstractProcessingHandler
|
||||
set_error_handler([$this, 'customErrorHandler']);
|
||||
$status = mkdir($dir, 0777, true);
|
||||
restore_error_handler();
|
||||
if (false === $status) {
|
||||
if (false === $status && !is_dir($dir)) {
|
||||
throw new \UnexpectedValueException(sprintf('There is no existing directory at "%s" and its not buildable: '.$this->errorMessage, $dir));
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@
|
||||
namespace Monolog\Handler;
|
||||
|
||||
use Monolog\Logger;
|
||||
use Monolog\Formatter\FormatterInterface;
|
||||
use Monolog\Formatter\LineFormatter;
|
||||
use Swift_Message;
|
||||
use Swift;
|
||||
@@ -48,6 +49,16 @@ class SwiftMailerHandler extends MailHandler
|
||||
$this->mailer->send($this->buildMessage($content, $records));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the formatter for the Swift_Message subject.
|
||||
*
|
||||
* @param string $format The format of the subject
|
||||
*/
|
||||
protected function getSubjectFormatter(string $format): FormatterInterface
|
||||
{
|
||||
return new LineFormatter($format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates instance of Swift_Message to be sent
|
||||
*
|
||||
@@ -70,7 +81,7 @@ class SwiftMailerHandler extends MailHandler
|
||||
}
|
||||
|
||||
if ($records) {
|
||||
$subjectFormatter = new LineFormatter($message->getSubject());
|
||||
$subjectFormatter = $this->getSubjectFormatter($message->getSubject());
|
||||
$message->setSubject($subjectFormatter->format($this->getHighestRecord($records)));
|
||||
}
|
||||
|
||||
|
@@ -35,7 +35,7 @@ class SyslogHandler extends AbstractSyslogHandler
|
||||
* @param string $ident
|
||||
* @param mixed $facility
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param int $logopts Option flags for the openlog() call, defaults to LOG_PID
|
||||
*/
|
||||
public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID)
|
||||
|
@@ -29,7 +29,7 @@ class SyslogUdpHandler extends AbstractSyslogHandler
|
||||
* @param int $port
|
||||
* @param mixed $facility
|
||||
* @param int $level The minimum logging level at which this handler will be triggered
|
||||
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
|
||||
* @param string $ident Program name or tag for each log message.
|
||||
*/
|
||||
public function __construct($host, $port = 514, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $ident = 'php')
|
||||
|
@@ -84,14 +84,25 @@ class TestHandler extends AbstractProcessingHandler
|
||||
return isset($this->recordsByLevel[$level]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array $record Either a message string or an array containing message and optionally context keys that will be checked against all records
|
||||
* @param int $level Logger::LEVEL constant value
|
||||
*/
|
||||
public function hasRecord($record, $level)
|
||||
{
|
||||
if (is_array($record)) {
|
||||
$record = $record['message'];
|
||||
if (is_string($record)) {
|
||||
$record = array('message' => $record);
|
||||
}
|
||||
|
||||
return $this->hasRecordThatPasses(function ($rec) use ($record) {
|
||||
return $rec['message'] === $record;
|
||||
if ($rec['message'] !== $record['message']) {
|
||||
return false;
|
||||
}
|
||||
if (isset($record['context']) && $rec['context'] !== $record['context']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, $level);
|
||||
}
|
||||
|
||||
|
24
src/Monolog/Handler/WebRequestRecognizerTrait.php
Normal file
24
src/Monolog/Handler/WebRequestRecognizerTrait.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Monolog package.
|
||||
*
|
||||
* (c) Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Monolog\Handler;
|
||||
|
||||
trait WebRequestRecognizerTrait
|
||||
{
|
||||
/**
|
||||
* Checks if PHP's serving a web request
|
||||
* @return bool
|
||||
*/
|
||||
protected function isWebRequest(): bool
|
||||
{
|
||||
return 'cli' !== \PHP_SAPI && 'phpdbg' !== \PHP_SAPI;
|
||||
}
|
||||
}
|
@@ -46,6 +46,16 @@ class WhatFailureGroupHandler extends GroupHandler
|
||||
*/
|
||||
public function handleBatch(array $records)
|
||||
{
|
||||
if ($this->processors) {
|
||||
$processed = array();
|
||||
foreach ($records as $record) {
|
||||
foreach ($this->processors as $processor) {
|
||||
$processed[] = call_user_func($processor, $record);
|
||||
}
|
||||
}
|
||||
$records = $processed;
|
||||
}
|
||||
|
||||
foreach ($this->handlers as $handler) {
|
||||
try {
|
||||
$handler->handleBatch($records);
|
||||
|
@@ -15,6 +15,7 @@ use DateTimeZone;
|
||||
use Monolog\Handler\HandlerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\InvalidArgumentException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Monolog log channel
|
||||
@@ -87,8 +88,6 @@ class Logger implements LoggerInterface
|
||||
const API = 2;
|
||||
|
||||
/**
|
||||
* Logging levels from syslog protocol defined in RFC 5424
|
||||
*
|
||||
* This is a static variable and not a constant to serve as an extension point for custom levels
|
||||
*
|
||||
* @var string[] $levels Logging levels with the levels as key
|
||||
@@ -135,6 +134,11 @@ class Logger implements LoggerInterface
|
||||
*/
|
||||
protected $timezone;
|
||||
|
||||
/**
|
||||
* @var callable
|
||||
*/
|
||||
protected $exceptionHandler;
|
||||
|
||||
/**
|
||||
* @param string $name The logging channel, a simple descriptive name that is attached to all log records
|
||||
* @param HandlerInterface[] $handlers Optional stack of handlers, the first one in the array is called first, etc.
|
||||
@@ -144,7 +148,7 @@ class Logger implements LoggerInterface
|
||||
public function __construct(string $name, array $handlers = [], array $processors = [], DateTimeZone $timezone = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->handlers = $handlers;
|
||||
$this->setHandlers($handlers);
|
||||
$this->processors = $processors;
|
||||
$this->timezone = $timezone ?: new DateTimeZone(date_default_timezone_get() ?: 'UTC');
|
||||
}
|
||||
@@ -275,7 +279,7 @@ class Logger implements LoggerInterface
|
||||
* @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
|
||||
* @return bool Whether the record has been processed
|
||||
*/
|
||||
public function addRecord(int $level, string $message, array $context = []): bool
|
||||
{
|
||||
@@ -304,6 +308,7 @@ class Logger implements LoggerInterface
|
||||
'extra' => [],
|
||||
];
|
||||
|
||||
try {
|
||||
foreach ($this->processors as $processor) {
|
||||
$record = call_user_func($processor, $record);
|
||||
}
|
||||
@@ -321,6 +326,9 @@ class Logger implements LoggerInterface
|
||||
|
||||
next($this->handlers);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$this->handleException($e, $record);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -338,9 +346,7 @@ class Logger implements LoggerInterface
|
||||
/**
|
||||
* Gets the name of the logging level.
|
||||
*
|
||||
* @param int $level
|
||||
* @throws \Psr\Log\InvalidArgumentException If level is not defined
|
||||
* @return string
|
||||
*/
|
||||
public static function getLevelName(int $level): string
|
||||
{
|
||||
@@ -356,7 +362,6 @@ class Logger implements LoggerInterface
|
||||
*
|
||||
* @param string|int Level number (monolog) or name (PSR-3)
|
||||
* @throws \Psr\Log\InvalidArgumentException If level is not defined
|
||||
* @return int
|
||||
*/
|
||||
public static function toMonologLevel($level): int
|
||||
{
|
||||
@@ -373,9 +378,6 @@ class Logger implements LoggerInterface
|
||||
|
||||
/**
|
||||
* Checks whether the Logger has a handler that listens on the given level
|
||||
*
|
||||
* @param int $level
|
||||
* @return Boolean
|
||||
*/
|
||||
public function isHandling(int $level): bool
|
||||
{
|
||||
@@ -392,6 +394,23 @@ class Logger implements LoggerInterface
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom exception handler that will be called if adding a new record fails
|
||||
*
|
||||
* The callable will receive an exception object and the record that failed to be logged
|
||||
*/
|
||||
public function setExceptionHandler(?callable $callback): self
|
||||
{
|
||||
$this->exceptionHandler = $callback;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getExceptionHandler(): ?callable
|
||||
{
|
||||
return $this->exceptionHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a log record at an arbitrary level.
|
||||
*
|
||||
@@ -513,9 +532,7 @@ class Logger implements LoggerInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timezone to be used for the timestamp of log records.
|
||||
*
|
||||
* @param DateTimeZone $tz Timezone object
|
||||
* Sets the timezone to be used for the timestamp of log records.
|
||||
*/
|
||||
public function setTimezone(DateTimeZone $tz): self
|
||||
{
|
||||
@@ -525,12 +542,23 @@ class Logger implements LoggerInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the timezone to be used for the timestamp of log records.
|
||||
*
|
||||
* @return DateTimeZone
|
||||
* Returns the timezone to be used for the timestamp of log records.
|
||||
*/
|
||||
public function getTimezone(): DateTimeZone
|
||||
{
|
||||
return $this->timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegates exception management to the custom exception handler,
|
||||
* or throws the exception if no custom handler is set.
|
||||
*/
|
||||
protected function handleException(Throwable $e, array $record)
|
||||
{
|
||||
if (!$this->exceptionHandler) {
|
||||
throw $e;
|
||||
}
|
||||
|
||||
call_user_func($this->exceptionHandler, $e, $record);
|
||||
}
|
||||
}
|
||||
|
32
src/Monolog/Processor/HostnameProcessor.php
Normal file
32
src/Monolog/Processor/HostnameProcessor.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Monolog package.
|
||||
*
|
||||
* (c) Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Monolog\Processor;
|
||||
|
||||
/**
|
||||
* Injects value of gethostname in all records
|
||||
*/
|
||||
class HostnameProcessor
|
||||
{
|
||||
private static $host;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
self::$host = (string) gethostname();
|
||||
}
|
||||
|
||||
public function __invoke(array $record): array
|
||||
{
|
||||
$record['extra']['hostname'] = self::$host;
|
||||
|
||||
return $record;
|
||||
}
|
||||
}
|
@@ -51,12 +51,7 @@ class IntrospectionProcessor
|
||||
return $record;
|
||||
}
|
||||
|
||||
/*
|
||||
* http://php.net/manual/en/function.debug-backtrace.php
|
||||
* As of 5.3.6, DEBUG_BACKTRACE_IGNORE_ARGS option was added.
|
||||
* Any version less than 5.3.6 must use the DEBUG_BACKTRACE_IGNORE_ARGS constant value '2'.
|
||||
*/
|
||||
$trace = debug_backtrace((PHP_VERSION_ID < 50306) ? 2 : DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
|
||||
// skip first since it's always the current method
|
||||
array_shift($trace);
|
||||
@@ -70,11 +65,13 @@ class IntrospectionProcessor
|
||||
foreach ($this->skipClassesPartials as $part) {
|
||||
if (strpos($trace[$i]['class'], $part) !== false) {
|
||||
$i++;
|
||||
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
} elseif (in_array($trace[$i]['function'], $this->skipFunctions)) {
|
||||
$i++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@@ -20,16 +20,21 @@ namespace Monolog\Processor;
|
||||
*/
|
||||
class PsrLogMessageProcessor
|
||||
{
|
||||
const SIMPLE_DATE = "Y-m-d\TH:i:sP";
|
||||
const SIMPLE_DATE = "Y-m-d\TH:i:s.uP";
|
||||
|
||||
private $dateFormat;
|
||||
|
||||
/** @var bool */
|
||||
private $removeUsedContextFields;
|
||||
|
||||
/**
|
||||
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
|
||||
* @param bool $removeUsedContextFields If set to true the fields interpolated into message gets unset
|
||||
*/
|
||||
public function __construct(string $dateFormat = null)
|
||||
public function __construct(string $dateFormat = null, bool $removeUsedContextFields = false)
|
||||
{
|
||||
$this->dateFormat = null === $dateFormat ? static::SIMPLE_DATE : $dateFormat;
|
||||
$this->dateFormat = $dateFormat;
|
||||
$this->removeUsedContextFields = $removeUsedContextFields;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,14 +49,31 @@ class PsrLogMessageProcessor
|
||||
|
||||
$replacements = [];
|
||||
foreach ($record['context'] as $key => $val) {
|
||||
$placeholder = '{' . $key . '}';
|
||||
if (strpos($record['message'], $placeholder) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_null($val) || is_scalar($val) || (is_object($val) && method_exists($val, "__toString"))) {
|
||||
$replacements['{'.$key.'}'] = $val;
|
||||
$replacements[$placeholder] = $val;
|
||||
} elseif ($val instanceof \DateTimeInterface) {
|
||||
$replacements['{'.$key.'}'] = $val->format($this->dateFormat);
|
||||
} elseif (is_object($val)) {
|
||||
$replacements['{'.$key.'}'] = '[object '.get_class($val).']';
|
||||
if (!$this->dateFormat && $val instanceof \Monolog\DateTimeImmutable) {
|
||||
// handle monolog dates using __toString if no specific dateFormat was asked for
|
||||
// so that it follows the useMicroseconds flag
|
||||
$replacements[$placeholder] = (string) $val;
|
||||
} else {
|
||||
$replacements['{'.$key.'}'] = '['.gettype($val).']';
|
||||
$replacements[$placeholder] = $val->format($this->dateFormat ?: static::SIMPLE_DATE);
|
||||
}
|
||||
} elseif (is_object($val)) {
|
||||
$replacements[$placeholder] = '[object '.get_class($val).']';
|
||||
} elseif (is_array($val)) {
|
||||
$replacements[$placeholder] = 'array'.@json_encode($val);
|
||||
} else {
|
||||
$replacements[$placeholder] = '['.gettype($val).']';
|
||||
}
|
||||
|
||||
if ($this->removeUsedContextFields) {
|
||||
unset($record['context'][$key]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -40,7 +40,7 @@ class FluentdFormatterTest extends TestCase
|
||||
|
||||
$formatter = new FluentdFormatter();
|
||||
$this->assertEquals(
|
||||
'["test",0,{"message":"test","extra":[],"level":300,"level_name":"WARNING"}]',
|
||||
'["test",0,{"message":"test","context":[],"extra":[],"level":300,"level_name":"WARNING"}]',
|
||||
$formatter->format($record)
|
||||
);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ class FluentdFormatterTest extends TestCase
|
||||
|
||||
$formatter = new FluentdFormatter(true);
|
||||
$this->assertEquals(
|
||||
'["test.error",0,{"message":"test","extra":[]}]',
|
||||
'["test.error",0,{"message":"test","context":[],"extra":[]}]',
|
||||
$formatter->format($record)
|
||||
);
|
||||
}
|
||||
|
@@ -234,7 +234,7 @@ class GelfMessageFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
'context' => array('exception' => str_repeat(' ', 32767 * 2)),
|
||||
'datetime' => new \DateTime("@0"),
|
||||
'extra' => array('key' => str_repeat(' ', 32767 * 2)),
|
||||
'message' => 'log'
|
||||
'message' => 'log',
|
||||
);
|
||||
$message = $formatter->format($record);
|
||||
$messageArray = $message->toArray();
|
||||
|
@@ -38,11 +38,12 @@ class JsonFormatterTest extends TestCase
|
||||
{
|
||||
$formatter = new JsonFormatter();
|
||||
$record = $this->getRecord();
|
||||
$record['context'] = $record['extra'] = new \stdClass;
|
||||
$this->assertEquals(json_encode($record)."\n", $formatter->format($record));
|
||||
|
||||
$formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, false);
|
||||
$record = $this->getRecord();
|
||||
$this->assertEquals('{"message":"test","context":[],"level":300,"level_name":"WARNING","channel":"test","datetime":"'.$record['datetime']->format('Y-m-d\TH:i:s.uP').'","extra":[]}', $formatter->format($record));
|
||||
$this->assertEquals('{"message":"test","context":{},"level":300,"level_name":"WARNING","channel":"test","datetime":"'.$record['datetime']->format('Y-m-d\TH:i:s.uP').'","extra":{}}', $formatter->format($record));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,6 +72,7 @@ class JsonFormatterTest extends TestCase
|
||||
$this->getRecord(Logger::DEBUG),
|
||||
];
|
||||
array_walk($expected, function (&$value, $key) {
|
||||
$value['context'] = $value['extra'] = new \stdClass;
|
||||
$value = json_encode($value);
|
||||
});
|
||||
$this->assertEquals(implode("\n", $expected), $formatter->formatBatch($records));
|
||||
@@ -110,6 +112,47 @@ class JsonFormatterTest extends TestCase
|
||||
$this->assertContextContainsFormattedException($formattedThrowable, $message);
|
||||
}
|
||||
|
||||
public function testMaxNormalizeDepth()
|
||||
{
|
||||
$formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, true);
|
||||
$formatter->setMaxNormalizeDepth(1);
|
||||
$throwable = new \Error('Foo');
|
||||
|
||||
$message = $this->formatRecordWithExceptionInContext($formatter, $throwable);
|
||||
|
||||
$this->assertContextContainsFormattedException('"Over 1 levels deep, aborting normalization"', $message);
|
||||
}
|
||||
|
||||
public function testMaxNormalizeItemCountWith0ItemsMax()
|
||||
{
|
||||
$formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, true);
|
||||
$formatter->setMaxNormalizeDepth(9);
|
||||
$formatter->setMaxNormalizeItemCount(0);
|
||||
$throwable = new \Error('Foo');
|
||||
|
||||
$message = $this->formatRecordWithExceptionInContext($formatter, $throwable);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"...":"Over 0 items (6 total), aborting normalization"}'."\n",
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
public function testMaxNormalizeItemCountWith2ItemsMax()
|
||||
{
|
||||
$formatter = new JsonFormatter(JsonFormatter::BATCH_MODE_JSON, true);
|
||||
$formatter->setMaxNormalizeDepth(9);
|
||||
$formatter->setMaxNormalizeItemCount(2);
|
||||
$throwable = new \Error('Foo');
|
||||
|
||||
$message = $this->formatRecordWithExceptionInContext($formatter, $throwable);
|
||||
|
||||
$this->assertEquals(
|
||||
'{"level_name":"CRITICAL","channel":"core","...":"Over 2 items (6 total), aborting normalization"}'."\n",
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $expected
|
||||
* @param string $actual
|
||||
@@ -119,18 +162,18 @@ class JsonFormatterTest extends TestCase
|
||||
private function assertContextContainsFormattedException($expected, $actual)
|
||||
{
|
||||
$this->assertEquals(
|
||||
'{"level_name":"CRITICAL","channel":"core","context":{"exception":'.$expected.'},"datetime":null,"extra":[],"message":"foobar"}'."\n",
|
||||
'{"level_name":"CRITICAL","channel":"core","context":{"exception":'.$expected.'},"datetime":null,"extra":{},"message":"foobar"}'."\n",
|
||||
$actual
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JsonFormatter $formatter
|
||||
* @param \Exception|\Throwable $exception
|
||||
* @param \Throwable $exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function formatRecordWithExceptionInContext(JsonFormatter $formatter, $exception)
|
||||
private function formatRecordWithExceptionInContext(JsonFormatter $formatter, \Throwable $exception)
|
||||
{
|
||||
$message = $formatter->format([
|
||||
'level_name' => 'CRITICAL',
|
||||
@@ -176,4 +219,40 @@ class JsonFormatterTest extends TestCase
|
||||
|
||||
return $formattedException;
|
||||
}
|
||||
|
||||
public function testNormalizeHandleLargeArraysWithExactly1000Items()
|
||||
{
|
||||
$formatter = new NormalizerFormatter();
|
||||
$largeArray = range(1, 1000);
|
||||
|
||||
$res = $formatter->format(array(
|
||||
'level_name' => 'CRITICAL',
|
||||
'channel' => 'test',
|
||||
'message' => 'bar',
|
||||
'context' => array($largeArray),
|
||||
'datetime' => new \DateTime,
|
||||
'extra' => array(),
|
||||
));
|
||||
|
||||
$this->assertCount(1000, $res['context'][0]);
|
||||
$this->assertArrayNotHasKey('...', $res['context'][0]);
|
||||
}
|
||||
|
||||
public function testNormalizeHandleLargeArrays()
|
||||
{
|
||||
$formatter = new NormalizerFormatter();
|
||||
$largeArray = range(1, 2000);
|
||||
|
||||
$res = $formatter->format(array(
|
||||
'level_name' => 'CRITICAL',
|
||||
'channel' => 'test',
|
||||
'message' => 'bar',
|
||||
'context' => array($largeArray),
|
||||
'datetime' => new \DateTime,
|
||||
'extra' => array(),
|
||||
));
|
||||
|
||||
$this->assertCount(1001, $res['context'][0]);
|
||||
$this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']);
|
||||
}
|
||||
}
|
||||
|
@@ -170,7 +170,7 @@ class LineFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
$path = str_replace('\\/', '/', json_encode(__FILE__));
|
||||
|
||||
$this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).', LogicException(code: 0): Wut? at '.substr($path, 1, -1).':'.(__LINE__ - 12).')"} []'."\n", $message);
|
||||
$this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException(code: 0): Foo at '.substr($path, 1, -1).':'.(__LINE__ - 8).')\n[previous exception] [object] (LogicException(code: 0): Wut? at '.substr($path, 1, -1).':'.(__LINE__ - 12).')"} []'."\n", $message);
|
||||
}
|
||||
|
||||
public function testBatchFormat()
|
||||
|
@@ -17,7 +17,7 @@ class LogstashFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function tearDown()
|
||||
{
|
||||
\PHPUnit_Framework_Error_Warning::$enabled = true;
|
||||
\PHPUnit\Framework\Error\Warning::$enabled = true;
|
||||
|
||||
return parent::tearDown();
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class LogstashFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
*/
|
||||
public function testDefaultFormatterV1()
|
||||
{
|
||||
$formatter = new LogstashFormatter('test', 'hostname', null, 'ctxt_');
|
||||
$formatter = new LogstashFormatter('test', 'hostname');
|
||||
$record = [
|
||||
'level' => Logger::ERROR,
|
||||
'level_name' => 'ERROR',
|
||||
@@ -49,7 +49,7 @@ class LogstashFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertEquals('test', $message['type']);
|
||||
$this->assertEquals('hostname', $message['host']);
|
||||
|
||||
$formatter = new LogstashFormatter('mysystem', null, null, 'ctxt_');
|
||||
$formatter = new LogstashFormatter('mysystem');
|
||||
|
||||
$message = json_decode($formatter->format($record), true);
|
||||
|
||||
@@ -61,7 +61,7 @@ class LogstashFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
*/
|
||||
public function testFormatWithFileAndLineV1()
|
||||
{
|
||||
$formatter = new LogstashFormatter('test', null, null, 'ctxt_');
|
||||
$formatter = new LogstashFormatter('test');
|
||||
$record = [
|
||||
'level' => Logger::ERROR,
|
||||
'level_name' => 'ERROR',
|
||||
@@ -83,7 +83,7 @@ class LogstashFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
*/
|
||||
public function testFormatWithContextV1()
|
||||
{
|
||||
$formatter = new LogstashFormatter('test', null, null, 'ctxt_');
|
||||
$formatter = new LogstashFormatter('test');
|
||||
$record = [
|
||||
'level' => Logger::ERROR,
|
||||
'level_name' => 'ERROR',
|
||||
@@ -96,17 +96,17 @@ class LogstashFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
$message = json_decode($formatter->format($record), true);
|
||||
|
||||
$this->assertArrayHasKey('ctxt_context', $message);
|
||||
$this->assertArrayHasKey('from', $message['ctxt_context']);
|
||||
$this->assertEquals('logger', $message['ctxt_context']['from']);
|
||||
$this->assertArrayHasKey('context', $message);
|
||||
$this->assertArrayHasKey('from', $message['context']);
|
||||
$this->assertEquals('logger', $message['context']['from']);
|
||||
|
||||
// Test with extraPrefix
|
||||
$formatter = new LogstashFormatter('test', null, null, 'CTX');
|
||||
$formatter = new LogstashFormatter('test', null, 'extra', 'CTX');
|
||||
$message = json_decode($formatter->format($record), true);
|
||||
|
||||
$this->assertArrayHasKey('CTXcontext', $message);
|
||||
$this->assertArrayHasKey('from', $message['CTXcontext']);
|
||||
$this->assertEquals('logger', $message['CTXcontext']['from']);
|
||||
$this->assertArrayHasKey('CTX', $message);
|
||||
$this->assertArrayHasKey('from', $message['CTX']);
|
||||
$this->assertEquals('logger', $message['CTX']['from']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,7 +114,7 @@ class LogstashFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
*/
|
||||
public function testFormatWithExtraV1()
|
||||
{
|
||||
$formatter = new LogstashFormatter('test', null, null, 'ctxt_');
|
||||
$formatter = new LogstashFormatter('test');
|
||||
$record = [
|
||||
'level' => Logger::ERROR,
|
||||
'level_name' => 'ERROR',
|
||||
@@ -132,17 +132,17 @@ class LogstashFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertEquals('pair', $message['extra']['key']);
|
||||
|
||||
// Test with extraPrefix
|
||||
$formatter = new LogstashFormatter('test', null, 'EXT', 'ctxt_');
|
||||
$formatter = new LogstashFormatter('test', null, 'EXTRA');
|
||||
$message = json_decode($formatter->format($record), true);
|
||||
|
||||
$this->assertArrayHasKey('EXTextra', $message);
|
||||
$this->assertArrayHasKey('key', $message['EXTextra']);
|
||||
$this->assertEquals('pair', $message['EXTextra']['key']);
|
||||
$this->assertArrayHasKey('EXTRA', $message);
|
||||
$this->assertArrayHasKey('key', $message['EXTRA']);
|
||||
$this->assertEquals('pair', $message['EXTRA']['key']);
|
||||
}
|
||||
|
||||
public function testFormatWithApplicationNameV1()
|
||||
{
|
||||
$formatter = new LogstashFormatter('app', 'test', null, 'ctxt_');
|
||||
$formatter = new LogstashFormatter('app', 'test');
|
||||
$record = [
|
||||
'level' => Logger::ERROR,
|
||||
'level_name' => 'ERROR',
|
||||
|
@@ -18,7 +18,7 @@ class NormalizerFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function tearDown()
|
||||
{
|
||||
\PHPUnit_Framework_Error_Warning::$enabled = true;
|
||||
\PHPUnit\Framework\Error\Warning::$enabled = true;
|
||||
|
||||
return parent::tearDown();
|
||||
}
|
||||
@@ -227,6 +227,24 @@ class NormalizerFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertEquals(@json_encode([$resource]), $res);
|
||||
}
|
||||
|
||||
public function testNormalizeHandleLargeArraysWithExactly1000Items()
|
||||
{
|
||||
$formatter = new NormalizerFormatter();
|
||||
$largeArray = range(1, 1000);
|
||||
|
||||
$res = $formatter->format(array(
|
||||
'level_name' => 'CRITICAL',
|
||||
'channel' => 'test',
|
||||
'message' => 'bar',
|
||||
'context' => array($largeArray),
|
||||
'datetime' => new \DateTime,
|
||||
'extra' => array(),
|
||||
));
|
||||
|
||||
$this->assertCount(1000, $res['context'][0]);
|
||||
$this->assertArrayNotHasKey('...', $res['context'][0]);
|
||||
}
|
||||
|
||||
public function testNormalizeHandleLargeArrays()
|
||||
{
|
||||
$formatter = new NormalizerFormatter();
|
||||
@@ -241,7 +259,7 @@ class NormalizerFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
'extra' => array(),
|
||||
));
|
||||
|
||||
$this->assertCount(1000, $res['context'][0]);
|
||||
$this->assertCount(1001, $res['context'][0]);
|
||||
$this->assertEquals('Over 1000 items (2000 total), aborting normalization', $res['context'][0]['...']);
|
||||
}
|
||||
|
||||
@@ -271,6 +289,48 @@ class NormalizerFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertSame('{"message":"€ŠšŽžŒœŸ"}', $res);
|
||||
}
|
||||
|
||||
public function testMaxNormalizeDepth()
|
||||
{
|
||||
$formatter = new NormalizerFormatter();
|
||||
$formatter->setMaxNormalizeDepth(1);
|
||||
$throwable = new \Error('Foo');
|
||||
|
||||
$message = $this->formatRecordWithExceptionInContext($formatter, $throwable);
|
||||
$this->assertEquals(
|
||||
'Over 1 levels deep, aborting normalization',
|
||||
$message['context']['exception']
|
||||
);
|
||||
}
|
||||
|
||||
public function testMaxNormalizeItemCountWith0ItemsMax()
|
||||
{
|
||||
$formatter = new NormalizerFormatter();
|
||||
$formatter->setMaxNormalizeDepth(9);
|
||||
$formatter->setMaxNormalizeItemCount(0);
|
||||
$throwable = new \Error('Foo');
|
||||
|
||||
$message = $this->formatRecordWithExceptionInContext($formatter, $throwable);
|
||||
$this->assertEquals(
|
||||
["..." => "Over 0 items (6 total), aborting normalization"],
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
public function testMaxNormalizeItemCountWith3ItemsMax()
|
||||
{
|
||||
$formatter = new NormalizerFormatter();
|
||||
$formatter->setMaxNormalizeDepth(9);
|
||||
$formatter->setMaxNormalizeItemCount(2);
|
||||
$throwable = new \Error('Foo');
|
||||
|
||||
$message = $this->formatRecordWithExceptionInContext($formatter, $throwable);
|
||||
|
||||
$this->assertEquals(
|
||||
["level_name" => "CRITICAL", "channel" => "core", "..." => "Over 2 items (6 total), aborting normalization"],
|
||||
$message
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $in Input
|
||||
* @param mixed $expect Expected output
|
||||
@@ -333,10 +393,6 @@ class NormalizerFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
// and no file or line are included in the trace because it's treated as internal function
|
||||
public function testExceptionTraceWithArgs()
|
||||
{
|
||||
if (defined('HHVM_VERSION')) {
|
||||
$this->markTestSkipped('Not supported in HHVM since it detects errors differently');
|
||||
}
|
||||
|
||||
try {
|
||||
// This will contain $resource and $wrappedResource as arguments in the trace item
|
||||
$resource = fopen('php://memory', 'rw+');
|
||||
@@ -356,19 +412,54 @@ class NormalizerFormatterTest extends \PHPUnit\Framework\TestCase
|
||||
$record = ['context' => ['exception' => $e]];
|
||||
$result = $formatter->format($record);
|
||||
|
||||
$this->assertRegExp(
|
||||
'%\[resource\(stream\)\]%',
|
||||
$this->assertSame(
|
||||
'{"function":"Monolog\\\\Formatter\\\\{closure}","class":"Monolog\\\\Formatter\\\\NormalizerFormatterTest","type":"->","args":["[object] (Monolog\\\\Formatter\\\\TestFooNorm)","[resource(stream)]"]}',
|
||||
$result['context']['exception']['trace'][0]
|
||||
);
|
||||
}
|
||||
|
||||
$pattern = '%\[\{"Monolog\\\\\\\\Formatter\\\\\\\\TestFooNorm":"JSON_ERROR"\}%';
|
||||
/**
|
||||
* @param NormalizerFormatter $formatter
|
||||
* @param \Throwable $exception
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function formatRecordWithExceptionInContext(NormalizerFormatter $formatter, \Throwable $exception)
|
||||
{
|
||||
$message = $formatter->format([
|
||||
'level_name' => 'CRITICAL',
|
||||
'channel' => 'core',
|
||||
'context' => ['exception' => $exception],
|
||||
'datetime' => null,
|
||||
'extra' => [],
|
||||
'message' => 'foobar',
|
||||
]);
|
||||
|
||||
// Tests that the wrapped resource is ignored while encoding, only works for PHP <= 5.4
|
||||
$this->assertRegExp(
|
||||
$pattern,
|
||||
return $message;
|
||||
}
|
||||
|
||||
public function testExceptionTraceDoesNotLeakCallUserFuncArgs()
|
||||
{
|
||||
try {
|
||||
$arg = new TestInfoLeak;
|
||||
call_user_func(array($this, 'throwHelper'), $arg, $dt = new \DateTime());
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
$formatter = new NormalizerFormatter();
|
||||
$record = array('context' => array('exception' => $e));
|
||||
$result = $formatter->format($record);
|
||||
|
||||
$this->assertSame(
|
||||
'{"function":"throwHelper","class":"Monolog\\\\Formatter\\\\NormalizerFormatterTest","type":"->","args":["[object] (Monolog\\\\Formatter\\\\TestInfoLeak)","'.$dt->format('Y-m-d\TH:i:sP').'"]}',
|
||||
$result['context']['exception']['trace'][0]
|
||||
);
|
||||
}
|
||||
|
||||
private function throwHelper($arg)
|
||||
{
|
||||
throw new \RuntimeException('Thrown');
|
||||
}
|
||||
}
|
||||
|
||||
class TestFooNorm
|
||||
@@ -410,3 +501,11 @@ class TestToStringError
|
||||
throw new \RuntimeException('Could not convert to string');
|
||||
}
|
||||
}
|
||||
|
||||
class TestInfoLeak
|
||||
{
|
||||
public function __toString()
|
||||
{
|
||||
return 'Sensitive information';
|
||||
}
|
||||
}
|
||||
|
@@ -153,4 +153,9 @@ class TestChromePHPHandler extends ChromePHPHandler
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
protected function isWebRequest(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -201,6 +201,7 @@ class ElasticSearchHandlerTest extends TestCase
|
||||
->setHosts($hosts)
|
||||
->build();
|
||||
$handler = new ElasticSearchHandler($client, $this->options);
|
||||
|
||||
try {
|
||||
$handler->handleBatch([$msg]);
|
||||
} catch (\RuntimeException $e) {
|
||||
|
@@ -32,7 +32,7 @@ class ErrorLogHandlerTest extends TestCase
|
||||
* @expectedException InvalidArgumentException
|
||||
* @expectedExceptionMessage The given message type "42" is not supported
|
||||
*/
|
||||
public function testShouldNotAcceptAnInvalidTypeOnContructor()
|
||||
public function testShouldNotAcceptAnInvalidTypeOnConstructor()
|
||||
{
|
||||
new ErrorLogHandler(42);
|
||||
}
|
||||
|
@@ -146,7 +146,10 @@ class FilterHandlerTest extends TestCase
|
||||
$handler = new FilterHandler(
|
||||
function ($record, $handler) use ($test) {
|
||||
return $test;
|
||||
}, Logger::INFO, Logger::NOTICE, false
|
||||
},
|
||||
Logger::INFO,
|
||||
Logger::NOTICE,
|
||||
false
|
||||
);
|
||||
$handler->handle($this->getRecord(Logger::DEBUG));
|
||||
$handler->handle($this->getRecord(Logger::INFO));
|
||||
|
@@ -93,4 +93,9 @@ class TestFirePHPHandler extends FirePHPHandler
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
protected function isWebRequest(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
79
tests/Monolog/Handler/InsightOpsHandlerTest.php
Normal file
79
tests/Monolog/Handler/InsightOpsHandlerTest.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Monolog package.
|
||||
*
|
||||
* (c) Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Monolog\Handler;
|
||||
|
||||
use Monolog\Test\TestCase;
|
||||
use Monolog\Logger;
|
||||
|
||||
/**
|
||||
* @author Robert Kaufmann III <rok3@rok3.me>
|
||||
* @author Gabriel Machado <gabriel.ms1@hotmail.com>
|
||||
*/
|
||||
class InsightOpsHandlerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $resource;
|
||||
|
||||
/**
|
||||
* @var LogEntriesHandler
|
||||
*/
|
||||
private $handler;
|
||||
|
||||
public function testWriteContent()
|
||||
{
|
||||
$this->createHandler();
|
||||
$this->handler->handle($this->getRecord(Logger::CRITICAL, 'Critical write test'));
|
||||
|
||||
fseek($this->resource, 0);
|
||||
$content = fread($this->resource, 1024);
|
||||
|
||||
$this->assertRegexp('/testToken \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}\+00:00\] test.CRITICAL: Critical write test/', $content);
|
||||
}
|
||||
|
||||
public function testWriteBatchContent()
|
||||
{
|
||||
$this->createHandler();
|
||||
$this->handler->handleBatch($this->getMultipleRecords());
|
||||
|
||||
fseek($this->resource, 0);
|
||||
$content = fread($this->resource, 1024);
|
||||
|
||||
$this->assertRegexp('/(testToken \[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}\+00:00\] .* \[\] \[\]\n){3}/', $content);
|
||||
}
|
||||
|
||||
private function createHandler()
|
||||
{
|
||||
$useSSL = extension_loaded('openssl');
|
||||
$args = array('testToken', 'us', $useSSL, Logger::DEBUG, true);
|
||||
$this->resource = fopen('php://memory', 'a');
|
||||
$this->handler = $this->getMockBuilder(InsightOpsHandler::class)
|
||||
->setMethods(array('fsockopen', 'streamSetTimeout', 'closeSocket'))
|
||||
->setConstructorArgs($args)
|
||||
->getMock();
|
||||
|
||||
$reflectionProperty = new \ReflectionProperty('\Monolog\Handler\SocketHandler', 'connectionString');
|
||||
$reflectionProperty->setAccessible(true);
|
||||
$reflectionProperty->setValue($this->handler, 'localhost:1234');
|
||||
|
||||
$this->handler->expects($this->any())
|
||||
->method('fsockopen')
|
||||
->will($this->returnValue($this->resource));
|
||||
$this->handler->expects($this->any())
|
||||
->method('streamSetTimeout')
|
||||
->will($this->returnValue(true));
|
||||
$this->handler->expects($this->any())
|
||||
->method('closeSocket')
|
||||
->will($this->returnValue(true));
|
||||
}
|
||||
}
|
@@ -37,7 +37,7 @@ class LogmaticHandlerTest extends TestCase
|
||||
fseek($this->res, 0);
|
||||
$content = fread($this->res, 1024);
|
||||
|
||||
$this->assertRegexp('/testToken {"message":"Critical write test","context":\[\],"level":500,"level_name":"CRITICAL","channel":"test","datetime":"(.*)","extra":\[\],"hostname":"testHostname","appname":"testAppname","@marker":\["sourcecode","php"\]}/', $content);
|
||||
$this->assertRegexp('/testToken {"message":"Critical write test","context":{},"level":500,"level_name":"CRITICAL","channel":"test","datetime":"(.*)","extra":{},"hostname":"testHostname","appname":"testAppname","@marker":\["sourcecode","php"\]}/', $content);
|
||||
}
|
||||
|
||||
public function testWriteBatchContent()
|
||||
@@ -53,7 +53,7 @@ class LogmaticHandlerTest extends TestCase
|
||||
fseek($this->res, 0);
|
||||
$content = fread($this->res, 1024);
|
||||
|
||||
$this->assertRegexp('/testToken {"message":"test","context":\[\],"level":300,"level_name":"WARNING","channel":"test","datetime":"(.*)","extra":\[\],"hostname":"testHostname","appname":"testAppname","@marker":\["sourcecode","php"\]}/', $content);
|
||||
$this->assertRegexp('/testToken {"message":"test","context":{},"level":300,"level_name":"WARNING","channel":"test","datetime":"(.*)","extra":{},"hostname":"testHostname","appname":"testAppname","@marker":\["sourcecode","php"\]}/', $content);
|
||||
}
|
||||
|
||||
private function createHandler()
|
||||
|
@@ -77,7 +77,7 @@ class ProcessHandlerTest extends TestCase
|
||||
*/
|
||||
public function testConstructWithInvalidCommandThrowsInvalidArgumentException($invalidCommand, $expectedExcep)
|
||||
{
|
||||
$this->setExpectedException($expectedExcep);
|
||||
$this->expectException($expectedExcep);
|
||||
new ProcessHandler($invalidCommand, Logger::DEBUG);
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class ProcessHandlerTest extends TestCase
|
||||
*/
|
||||
public function testConstructWithInvalidCwdThrowsInvalidArgumentException($invalidCwd, $expectedExcep)
|
||||
{
|
||||
$this->setExpectedException($expectedExcep);
|
||||
$this->expectException($expectedExcep);
|
||||
new ProcessHandler(self::DUMMY_COMMAND, Logger::DEBUG, true, $invalidCwd);
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class ProcessHandlerTest extends TestCase
|
||||
->method('selectErrorStream')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->setExpectedException('\UnexpectedValueException');
|
||||
$this->expectException('\UnexpectedValueException');
|
||||
/** @var ProcessHandler $handler */
|
||||
$handler->handle($this->getRecord(Logger::WARNING, 'stream failing, whoops'));
|
||||
}
|
||||
@@ -147,7 +147,7 @@ class ProcessHandlerTest extends TestCase
|
||||
public function testStartupWithErrorsThrowsUnexpectedValueException()
|
||||
{
|
||||
$handler = new ProcessHandler('>&2 echo "some fake error message"');
|
||||
$this->setExpectedException('\UnexpectedValueException');
|
||||
$this->expectException('\UnexpectedValueException');
|
||||
$handler->handle($this->getRecord(Logger::WARNING, 'some warning in the house'));
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ class ProcessHandlerTest extends TestCase
|
||||
->method('readProcessErrors')
|
||||
->willReturnOnConsecutiveCalls('', $this->returnValue('some fake error message here'));
|
||||
|
||||
$this->setExpectedException('\UnexpectedValueException');
|
||||
$this->expectException('\UnexpectedValueException');
|
||||
/** @var ProcessHandler $handler */
|
||||
$handler->handle($this->getRecord(Logger::WARNING, 'some test stuff'));
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@ use Exception;
|
||||
use Monolog\Test\TestCase;
|
||||
use Monolog\Logger;
|
||||
use PHPUnit_Framework_MockObject_MockObject as MockObject;
|
||||
use Rollbar\RollbarLogger;
|
||||
|
||||
/**
|
||||
* @author Erik Johansson <erik.pm.johansson@gmail.com>
|
||||
@@ -27,7 +28,7 @@ class RollbarHandlerTest extends TestCase
|
||||
/**
|
||||
* @var MockObject
|
||||
*/
|
||||
private $rollbarNotifier;
|
||||
private $rollbarLogger;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
@@ -38,7 +39,7 @@ class RollbarHandlerTest extends TestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setupRollbarNotifierMock();
|
||||
$this->setupRollbarLoggerMock();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,15 +55,21 @@ class RollbarHandlerTest extends TestCase
|
||||
$this->assertEquals('debug', $this->reportedExceptionArguments['payload']['level']);
|
||||
}
|
||||
|
||||
private function setupRollbarNotifierMock()
|
||||
private function setupRollbarLoggerMock()
|
||||
{
|
||||
$this->rollbarNotifier = $this->getMockBuilder('RollbarNotifier')
|
||||
->setMethods(array('report_message', 'report_exception', 'flush'))
|
||||
$config = array(
|
||||
'access_token' => 'ad865e76e7fb496fab096ac07b1dbabb',
|
||||
'environment' => 'test',
|
||||
);
|
||||
|
||||
$this->rollbarLogger = $this->getMockBuilder(RollbarLogger::class)
|
||||
->setConstructorArgs(array($config))
|
||||
->setMethods(array('log'))
|
||||
->getMock();
|
||||
|
||||
$this->rollbarNotifier
|
||||
$this->rollbarLogger
|
||||
->expects($this->any())
|
||||
->method('report_exception')
|
||||
->method('log')
|
||||
->willReturnCallback(function ($exception, $context, $payload) {
|
||||
$this->reportedExceptionArguments = compact('exception', 'context', 'payload');
|
||||
});
|
||||
@@ -70,7 +77,7 @@ class RollbarHandlerTest extends TestCase
|
||||
|
||||
private function createHandler(): RollbarHandler
|
||||
{
|
||||
return new RollbarHandler($this->rollbarNotifier, Logger::DEBUG);
|
||||
return new RollbarHandler($this->rollbarLogger, Logger::DEBUG);
|
||||
}
|
||||
|
||||
private function createExceptionRecord($level = Logger::DEBUG, $message = 'test', $exception = null): array
|
||||
|
@@ -102,10 +102,10 @@ class RotatingFileHandlerTest extends TestCase
|
||||
$dayCallback = function ($ago) use ($now) {
|
||||
return $now + 86400 * $ago;
|
||||
};
|
||||
$monthCallback = function($ago) {
|
||||
$monthCallback = function ($ago) {
|
||||
return gmmktime(0, 0, 0, (int) (date('n') + $ago), 1, (int) date('Y'));
|
||||
};
|
||||
$yearCallback = function($ago) {
|
||||
$yearCallback = function ($ago) {
|
||||
return gmmktime(0, 0, 0, 1, 1, (int) (date('Y') + $ago));
|
||||
};
|
||||
|
||||
@@ -134,7 +134,8 @@ class RotatingFileHandlerTest extends TestCase
|
||||
{
|
||||
$handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);
|
||||
if (!$valid) {
|
||||
$this->setExpectedExceptionRegExp(InvalidArgumentException::class, '~^Invalid date format~');
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessageRegExp('~^Invalid date format~');
|
||||
}
|
||||
$handler->setFilenameFormat('{filename}-{date}', $dateFormat);
|
||||
$this->assertTrue(true);
|
||||
@@ -174,7 +175,8 @@ class RotatingFileHandlerTest extends TestCase
|
||||
{
|
||||
$handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);
|
||||
if (!$valid) {
|
||||
$this->setExpectedExceptionRegExp(InvalidArgumentException::class, '~^Invalid filename format~');
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
$this->expectExceptionMessageRegExp('~^Invalid filename format~');
|
||||
}
|
||||
|
||||
$handler->setFilenameFormat($filenameFormat, RotatingFileHandler::FILE_PER_DAY);
|
||||
@@ -193,6 +195,39 @@ class RotatingFileHandlerTest extends TestCase
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider rotationWhenSimilarFilesExistTests
|
||||
*/
|
||||
public function testRotationWhenSimilarFileNamesExist($dateFormat)
|
||||
{
|
||||
touch($old1 = __DIR__.'/Fixtures/foo-foo-'.date($dateFormat).'.rot');
|
||||
touch($old2 = __DIR__.'/Fixtures/foo-bar-'.date($dateFormat).'.rot');
|
||||
|
||||
$log = __DIR__.'/Fixtures/foo-'.date($dateFormat).'.rot';
|
||||
|
||||
$handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);
|
||||
$handler->setFormatter($this->getIdentityFormatter());
|
||||
$handler->setFilenameFormat('{filename}-{date}', $dateFormat);
|
||||
$handler->handle($this->getRecord());
|
||||
$handler->close();
|
||||
|
||||
$this->assertTrue(file_exists($log));
|
||||
}
|
||||
|
||||
public function rotationWhenSimilarFilesExistTests()
|
||||
{
|
||||
return array(
|
||||
'Rotation is triggered when the file of the current day is not present but similar exists'
|
||||
=> array(RotatingFileHandler::FILE_PER_DAY),
|
||||
|
||||
'Rotation is triggered when the file of the current month is not present but similar exists'
|
||||
=> array(RotatingFileHandler::FILE_PER_MONTH),
|
||||
|
||||
'Rotation is triggered when the file of the current year is not present but similar exists'
|
||||
=> array(RotatingFileHandler::FILE_PER_YEAR),
|
||||
);
|
||||
}
|
||||
|
||||
public function testReuseCurrentFile()
|
||||
{
|
||||
$log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot';
|
||||
|
@@ -324,12 +324,12 @@ class SlackRecordTest extends TestCase
|
||||
'short' => false,
|
||||
),
|
||||
array(
|
||||
'title' => 'tags',
|
||||
'title' => 'Tags',
|
||||
'value' => sprintf('```%s```', json_encode($extra['tags'])),
|
||||
'short' => false,
|
||||
),
|
||||
array(
|
||||
'title' => 'test',
|
||||
'title' => 'Test',
|
||||
'value' => $context['test'],
|
||||
'short' => false,
|
||||
),
|
||||
@@ -357,6 +357,14 @@ class SlackRecordTest extends TestCase
|
||||
$this->assertSame($record['datetime']->getTimestamp(), $attachment['ts']);
|
||||
}
|
||||
|
||||
public function testContextHasException()
|
||||
{
|
||||
$record = $this->getRecord(Logger::CRITICAL, 'This is a critical message.', array('exception' => new \Exception()));
|
||||
$slackRecord = new SlackRecord(null, null, true, null, false, true);
|
||||
$data = $slackRecord->getSlackData($record);
|
||||
$this->assertInternalType('string', $data['attachments'][0]['fields'][1]['value']);
|
||||
}
|
||||
|
||||
public function testExcludeExtraAndContextFields()
|
||||
{
|
||||
$record = $this->getRecord(
|
||||
@@ -372,12 +380,12 @@ class SlackRecordTest extends TestCase
|
||||
|
||||
$expected = array(
|
||||
array(
|
||||
'title' => 'info',
|
||||
'title' => 'Info',
|
||||
'value' => sprintf('```%s```', json_encode(array('author' => 'Jordi'), $this->jsonPrettyPrintFlag)),
|
||||
'short' => false,
|
||||
),
|
||||
array(
|
||||
'title' => 'tags',
|
||||
'title' => 'Tags',
|
||||
'value' => sprintf('```%s```', json_encode(array('web'))),
|
||||
'short' => false,
|
||||
),
|
||||
|
@@ -77,6 +77,13 @@ class SocketHandlerTest extends TestCase
|
||||
$this->assertEquals(10.25, $this->handler->getWritingTimeout());
|
||||
}
|
||||
|
||||
public function testSetChunkSize()
|
||||
{
|
||||
$this->createHandler('localhost:1234');
|
||||
$this->handler->setChunkSize(1025);
|
||||
$this->assertEquals(1025, $this->handler->getChunkSize());
|
||||
}
|
||||
|
||||
public function testSetConnectionString()
|
||||
{
|
||||
$this->createHandler('tcp://localhost:9090');
|
||||
@@ -120,6 +127,19 @@ class SocketHandlerTest extends TestCase
|
||||
$this->writeRecord('Hello world');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException UnexpectedValueException
|
||||
*/
|
||||
public function testExceptionIsThrownIfCannotSetChunkSize()
|
||||
{
|
||||
$this->setMockHandler(array('streamSetChunkSize'));
|
||||
$this->handler->setChunkSize(8192);
|
||||
$this->handler->expects($this->once())
|
||||
->method('streamSetChunkSize')
|
||||
->will($this->returnValue(false));
|
||||
$this->writeRecord('Hello world');
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException RuntimeException
|
||||
*/
|
||||
@@ -277,7 +297,7 @@ class SocketHandlerTest extends TestCase
|
||||
{
|
||||
$this->res = fopen('php://memory', 'a');
|
||||
|
||||
$defaultMethods = ['fsockopen', 'pfsockopen', 'streamSetTimeout'];
|
||||
$defaultMethods = ['fsockopen', 'pfsockopen', 'streamSetTimeout', 'streamSetChunkSize'];
|
||||
$newMethods = array_diff($methods, $defaultMethods);
|
||||
|
||||
$finalMethods = array_merge($defaultMethods, $newMethods);
|
||||
@@ -305,6 +325,12 @@ class SocketHandlerTest extends TestCase
|
||||
->will($this->returnValue(true));
|
||||
}
|
||||
|
||||
if (!in_array('streamSetChunkSize', $methods)) {
|
||||
$this->handler->expects($this->any())
|
||||
->method('streamSetChunkSize')
|
||||
->will($this->returnValue(8192));
|
||||
}
|
||||
|
||||
$this->handler->setFormatter($this->getIdentityFormatter());
|
||||
}
|
||||
}
|
||||
|
@@ -54,6 +54,54 @@ class TestHandlerTest extends TestCase
|
||||
$this->assertEquals([$record], $records);
|
||||
}
|
||||
|
||||
public function testHandlerAssertEmptyContext()
|
||||
{
|
||||
$handler = new TestHandler;
|
||||
$record = $this->getRecord(Logger::WARNING, 'test', []);
|
||||
$this->assertFalse($handler->hasWarning([
|
||||
'message' => 'test',
|
||||
'context' => [],
|
||||
]));
|
||||
|
||||
$handler->handle($record);
|
||||
|
||||
$this->assertTrue($handler->hasWarning([
|
||||
'message' => 'test',
|
||||
'context' => [],
|
||||
]));
|
||||
$this->assertFalse($handler->hasWarning([
|
||||
'message' => 'test',
|
||||
'context' => [
|
||||
'foo' => 'bar',
|
||||
],
|
||||
]));
|
||||
}
|
||||
|
||||
public function testHandlerAssertNonEmptyContext()
|
||||
{
|
||||
$handler = new TestHandler;
|
||||
$record = $this->getRecord(Logger::WARNING, 'test', ['foo' => 'bar']);
|
||||
$this->assertFalse($handler->hasWarning([
|
||||
'message' => 'test',
|
||||
'context' => [
|
||||
'foo' => 'bar',
|
||||
],
|
||||
]));
|
||||
|
||||
$handler->handle($record);
|
||||
|
||||
$this->assertTrue($handler->hasWarning([
|
||||
'message' => 'test',
|
||||
'context' => [
|
||||
'foo' => 'bar',
|
||||
],
|
||||
]));
|
||||
$this->assertFalse($handler->hasWarning([
|
||||
'message' => 'test',
|
||||
'context' => [],
|
||||
]));
|
||||
}
|
||||
|
||||
public function methodProvider()
|
||||
{
|
||||
return [
|
||||
|
@@ -87,6 +87,29 @@ class WhatFailureGroupHandlerTest extends TestCase
|
||||
$this->assertTrue($records[0]['extra']['foo']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Monolog\Handler\WhatFailureGroupHandler::handleBatch
|
||||
*/
|
||||
public function testHandleBatchUsesProcessors()
|
||||
{
|
||||
$testHandlers = array(new TestHandler(), new TestHandler());
|
||||
$handler = new WhatFailureGroupHandler($testHandlers);
|
||||
$handler->pushProcessor(function ($record) {
|
||||
$record['extra']['foo'] = true;
|
||||
|
||||
return $record;
|
||||
});
|
||||
$handler->handleBatch(array($this->getRecord(Logger::DEBUG), $this->getRecord(Logger::INFO)));
|
||||
foreach ($testHandlers as $test) {
|
||||
$this->assertTrue($test->hasDebugRecords());
|
||||
$this->assertTrue($test->hasInfoRecords());
|
||||
$this->assertTrue(count($test->getRecords()) === 2);
|
||||
$records = $test->getRecords();
|
||||
$this->assertTrue($records[0]['extra']['foo']);
|
||||
$this->assertTrue($records[1]['extra']['foo']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Monolog\Handler\WhatFailureGroupHandler::handle
|
||||
*/
|
||||
|
@@ -576,4 +576,63 @@ class LoggerTest extends \PHPUnit\Framework\TestCase
|
||||
'without microseconds' => [false, PHP_VERSION_ID >= 70100 ? 'assertNotSame' : 'assertSame', 'Y-m-d\TH:i:sP'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Monolog\Logger::setExceptionHandler
|
||||
*/
|
||||
public function testSetExceptionHandler()
|
||||
{
|
||||
$logger = new Logger(__METHOD__);
|
||||
$this->assertNull($logger->getExceptionHandler());
|
||||
$callback = function ($ex) {
|
||||
};
|
||||
$logger->setExceptionHandler($callback);
|
||||
$this->assertEquals($callback, $logger->getExceptionHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Monolog\Logger::handleException
|
||||
* @expectedException Exception
|
||||
*/
|
||||
public function testDefaultHandleException()
|
||||
{
|
||||
$logger = new Logger(__METHOD__);
|
||||
$handler = $this->getMockBuilder('Monolog\Handler\HandlerInterface')->getMock();
|
||||
$handler->expects($this->any())
|
||||
->method('isHandling')
|
||||
->will($this->returnValue(true))
|
||||
;
|
||||
$handler->expects($this->any())
|
||||
->method('handle')
|
||||
->will($this->throwException(new \Exception('Some handler exception')))
|
||||
;
|
||||
$logger->pushHandler($handler);
|
||||
$logger->info('test');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers Monolog\Logger::handleException
|
||||
* @covers Monolog\Logger::addRecord
|
||||
*/
|
||||
public function testCustomHandleException()
|
||||
{
|
||||
$logger = new Logger(__METHOD__);
|
||||
$that = $this;
|
||||
$logger->setExceptionHandler(function ($e, $record) use ($that) {
|
||||
$that->assertEquals($e->getMessage(), 'Some handler exception');
|
||||
$that->assertTrue(is_array($record));
|
||||
$that->assertEquals($record['message'], 'test');
|
||||
});
|
||||
$handler = $this->getMockBuilder('Monolog\Handler\HandlerInterface')->getMock();
|
||||
$handler->expects($this->any())
|
||||
->method('isHandling')
|
||||
->will($this->returnValue(true))
|
||||
;
|
||||
$handler->expects($this->any())
|
||||
->method('handle')
|
||||
->will($this->throwException(new \Exception('Some handler exception')))
|
||||
;
|
||||
$logger->pushHandler($handler);
|
||||
$logger->info('test');
|
||||
}
|
||||
}
|
||||
|
30
tests/Monolog/Processor/HostnameProcessorTest.php
Normal file
30
tests/Monolog/Processor/HostnameProcessorTest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Monolog package.
|
||||
*
|
||||
* (c) Jordi Boggiano <j.boggiano@seld.be>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Monolog\Processor;
|
||||
|
||||
use Monolog\Test\TestCase;
|
||||
|
||||
class HostnameProcessorTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @covers Monolog\Processor\HostnameProcessor::__invoke
|
||||
*/
|
||||
public function testProcessor()
|
||||
{
|
||||
$processor = new HostnameProcessor();
|
||||
$record = $processor($this->getRecord());
|
||||
$this->assertArrayHasKey('hostname', $record['extra']);
|
||||
$this->assertInternalType('string', $record['extra']['hostname']);
|
||||
$this->assertNotEmpty($record['extra']['hostname']);
|
||||
$this->assertEquals(gethostname(), $record['extra']['hostname']);
|
||||
}
|
||||
}
|
@@ -25,6 +25,19 @@ class PsrLogMessageProcessorTest extends \PHPUnit\Framework\TestCase
|
||||
'context' => ['foo' => $val],
|
||||
]);
|
||||
$this->assertEquals($expected, $message['message']);
|
||||
$this->assertSame(['foo' => $val], $message['context']);
|
||||
}
|
||||
|
||||
public function testReplacementWithContextRemoval()
|
||||
{
|
||||
$proc = new PsrLogMessageProcessor($dateFormat = null, $removeUsedContextFields = true);
|
||||
|
||||
$message = $proc([
|
||||
'message' => '{foo}',
|
||||
'context' => ['foo' => 'bar', 'lorem' => 'ipsum'],
|
||||
]);
|
||||
$this->assertSame('bar', $message['message']);
|
||||
$this->assertSame(['lorem' => 'ipsum'], $message['context']);
|
||||
}
|
||||
|
||||
public function testCustomDateFormat()
|
||||
@@ -39,6 +52,7 @@ class PsrLogMessageProcessorTest extends \PHPUnit\Framework\TestCase
|
||||
'context' => ['foo' => $date],
|
||||
]);
|
||||
$this->assertEquals($date->format($format), $message['message']);
|
||||
$this->assertSame(['foo' => $date], $message['context']);
|
||||
}
|
||||
|
||||
public function getPairs()
|
||||
@@ -54,7 +68,11 @@ class PsrLogMessageProcessorTest extends \PHPUnit\Framework\TestCase
|
||||
[false, ''],
|
||||
[$date, $date->format(PsrLogMessageProcessor::SIMPLE_DATE)],
|
||||
[new \stdClass, '[object stdClass]'],
|
||||
[[], '[array]'],
|
||||
[[], 'array[]'],
|
||||
[[], 'array[]'],
|
||||
[[1, 2, 3], 'array[1,2,3]'],
|
||||
[['foo' => 'bar'], 'array{"foo":"bar"}'],
|
||||
[stream_context_create(), '[resource]'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -12,3 +12,9 @@
|
||||
date_default_timezone_set('UTC');
|
||||
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
// B.C. for PSR Log's old inheritance
|
||||
// see https://github.com/php-fig/log/pull/52
|
||||
if (!class_exists('\\PHPUnit_Framework_TestCase', true)) {
|
||||
class_alias('\\PHPUnit\\Framework\\TestCase', '\\PHPUnit_Framework_TestCase');
|
||||
}
|
||||
|
Reference in New Issue
Block a user