the whole shebang

This commit is contained in:
2014-11-25 16:42:40 +01:00
parent 7f74c0613e
commit ab1334c0cf
3686 changed files with 496409 additions and 1 deletions

110
vendor/monolog/monolog/CHANGELOG.mdown vendored Normal file
View File

@@ -0,0 +1,110 @@
### 1.6.0 (2013-07-29)
* Added HipChatHandler to send logs to a HipChat chat room
* Added ErrorLogHandler to send logs to PHP's error_log function
* Added NewRelicHandler to send logs to NewRelic's service
* Added Monolog\ErrorHandler helper class to register a Logger as exception/error/fatal handler
* Added ChannelLevelActivationStrategy for the FingersCrossedHandler to customize levels by channel
* Added stack traces output when normalizing exceptions (json output & co)
* Added Monolog\Logger::API constant (currently 1)
* Added support for ChromePHP's v4.0 extension
* Added support for message priorities in PushoverHandler, see $highPriorityLevel and $emergencyLevel
* Added support for sending messages to multiple users at once with the PushoverHandler
* Fixed RavenHandler's support for batch sending of messages (when behind a Buffer or FingersCrossedHandler)
* Fixed normalization of Traversables with very large data sets, only the first 1000 items are shown now
* Fixed issue in RotatingFileHandler when an open_basedir restriction is active
* Fixed minor issues in RavenHandler and bumped the API to Raven 0.5.0
* Fixed SyslogHandler issue when many were used concurrently with different facilities
### 1.5.0 (2013-04-23)
* Added ProcessIdProcessor to inject the PID in log records
* Added UidProcessor to inject a unique identifier to all log records of one request/run
* Added support for previous exceptions in the LineFormatter exception serialization
* Added Monolog\Logger::getLevels() to get all available levels
* Fixed ChromePHPHandler so it avoids sending headers larger than Chrome can handle
### 1.4.1 (2013-04-01)
* Fixed exception formatting in the LineFormatter to be more minimalistic
* Fixed RavenHandler's handling of context/extra data, requires Raven client >0.1.0
* Fixed log rotation in RotatingFileHandler to work with long running scripts spanning multiple days
* Fixed WebProcessor array access so it checks for data presence
* Fixed Buffer, Group and FingersCrossed handlers to make use of their processors
### 1.4.0 (2013-02-13)
* Added RedisHandler to log to Redis via the Predis library or the phpredis extension
* Added ZendMonitorHandler to log to the Zend Server monitor
* Added the possibility to pass arrays of handlers and processors directly in the Logger constructor
* Added `$useSSL` option to the PushoverHandler which is enabled by default
* Fixed ChromePHPHandler and FirePHPHandler issue when multiple instances are used simultaneously
* Fixed header injection capability in the NativeMailHandler
### 1.3.1 (2013-01-11)
* Fixed LogstashFormatter to be usable with stream handlers
* Fixed GelfMessageFormatter levels on Windows
### 1.3.0 (2013-01-08)
* Added PSR-3 compliance, the `Monolog\Logger` class is now an instance of `Psr\Log\LoggerInterface`
* Added PsrLogMessageProcessor that you can selectively enable for full PSR-3 compliance
* Added LogstashFormatter (combine with SocketHandler or StreamHandler to send logs to Logstash)
* Added PushoverHandler to send mobile notifications
* Added CouchDBHandler and DoctrineCouchDBHandler
* Added RavenHandler to send data to Sentry servers
* Added support for the new MongoClient class in MongoDBHandler
* Added microsecond precision to log records' timestamps
* Added `$flushOnOverflow` param to BufferHandler to flush by batches instead of losing
the oldest entries
* Fixed normalization of objects with cyclic references
### 1.2.1 (2012-08-29)
* Added new $logopts arg to SyslogHandler to provide custom openlog options
* Fixed fatal error in SyslogHandler
### 1.2.0 (2012-08-18)
* Added AmqpHandler (for use with AMQP servers)
* Added CubeHandler
* Added NativeMailerHandler::addHeader() to send custom headers in mails
* Added the possibility to specify more than one recipient in NativeMailerHandler
* Added the possibility to specify float timeouts in SocketHandler
* Added NOTICE and EMERGENCY levels to conform with RFC 5424
* Fixed the log records to use the php default timezone instead of UTC
* Fixed BufferHandler not being flushed properly on PHP fatal errors
* Fixed normalization of exotic resource types
* Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog
### 1.1.0 (2012-04-23)
* Added Monolog\Logger::isHandling() to check if a handler will
handle the given log level
* Added ChromePHPHandler
* Added MongoDBHandler
* Added GelfHandler (for use with Graylog2 servers)
* Added SocketHandler (for use with syslog-ng for example)
* Added NormalizerFormatter
* Added the possibility to change the activation strategy of the FingersCrossedHandler
* Added possibility to show microseconds in logs
* Added `server` and `referer` to WebProcessor output
### 1.0.2 (2011-10-24)
* Fixed bug in IE with large response headers and FirePHPHandler
### 1.0.1 (2011-08-25)
* Added MemoryPeakUsageProcessor and MemoryUsageProcessor
* Added Monolog\Logger::getName() to get a logger's channel name
### 1.0.0 (2011-07-06)
* Added IntrospectionProcessor to get info from where the logger was called
* Fixed WebProcessor in CLI
### 1.0.0-RC1 (2011-07-01)
* Initial release

19
vendor/monolog/monolog/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

248
vendor/monolog/monolog/README.mdown vendored Normal file
View File

@@ -0,0 +1,248 @@
Monolog - Logging for PHP 5.3+ [![Build Status](https://secure.travis-ci.org/Seldaek/monolog.png)](http://travis-ci.org/Seldaek/monolog)
==============================
[![Total Downloads](https://poser.pugx.org/monolog/monolog/downloads.png)](https://packagist.org/packages/monolog/monolog)
[![Latest Stable Version](https://poser.pugx.org/monolog/monolog/v/stable.png)](https://packagist.org/packages/monolog/monolog)
Monolog sends your logs to files, sockets, inboxes, databases and various
web services. See the complete list of handlers below. Special handlers
allow you to build advanced logging strategies.
This library implements the [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
interface that you can type-hint against in your own libraries to keep
a maximum of interoperability. You can also use it in your applications to
make sure you can always use another compatible logger at a later time.
Usage
-----
```php
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// create a log channel
$log = new Logger('name');
$log->pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING));
// add records to the log
$log->addWarning('Foo');
$log->addError('Bar');
```
Core Concepts
-------------
Every `Logger` instance has a channel (name) and a stack of handlers. Whenever
you add a record to the logger, it traverses the handler stack. Each handler
decides whether it handled fully the record, and if so, the propagation of the
record ends there.
This allows for flexible logging setups, for example having a `StreamHandler` at
the bottom of the stack that will log anything to disk, and on top of that add
a `MailHandler` that will send emails only when an error message is logged.
Handlers also have a `$bubble` property which defines whether they block the
record or not if they handled it. In this example, setting the `MailHandler`'s
`$bubble` argument to false means that records handled by the `MailHandler` will
not propagate to the `StreamHandler` anymore.
You can create many `Logger`s, each defining a channel (e.g.: db, request,
router, ..) and each of them combining various handlers, which can be shared
or not. The channel is reflected in the logs and allows you to easily see or
filter records.
Each Handler also has a Formatter, a default one with settings that make sense
will be created if you don't set one. The formatters normalize and format
incoming records so that they can be used by the handlers to output useful
information.
Custom severity levels are not available. Only the eight
[RFC 5424](http://tools.ietf.org/html/rfc5424) levels (debug, info, notice,
warning, error, critical, alert, emergency) are present for basic filtering
purposes, but for sorting and other use cases that would require
flexibility, you should add Processors to the Logger that can add extra
information (tags, user ip, ..) to the records before they are handled.
Log Levels
----------
Monolog supports all 8 logging levels defined in
[RFC 5424](http://tools.ietf.org/html/rfc5424), but unless you specifically
need syslog compatibility, it is advised to only use DEBUG, INFO, WARNING,
ERROR, CRITICAL, ALERT.
- **DEBUG** (100): Detailed debug information.
- **INFO** (200): Interesting events. Examples: User logs in, SQL logs.
- NOTICE (250): Normal but significant events.
- **WARNING** (300): Exceptional occurrences that are not errors. Examples:
Use of deprecated APIs, poor use of an API, undesirable things that are not
necessarily wrong.
- **ERROR** (400): Runtime errors that do not require immediate action but
should typically be logged and monitored.
- **CRITICAL** (500): Critical conditions. Example: Application component
unavailable, unexpected exception.
- **ALERT** (550): Action must be taken immediately. Example: Entire website
down, database unavailable, etc. This should trigger the SMS alerts and wake
you up.
- EMERGENCY (600): Emergency: system is unusable.
Docs
====
**See the `doc` directory for more detailed documentation.
The following is only a list of all parts that come with Monolog.**
Handlers
--------
### Log to files and syslog
- _StreamHandler_: Logs records into any PHP stream, use this for log files.
- _RotatingFileHandler_: Logs records to a file and creates one logfile per day.
It will also delete files older than `$maxFiles`. You should use
[logrotate](http://linuxcommand.org/man_pages/logrotate8.html) for high profile
setups though, this is just meant as a quick and dirty solution.
- _SyslogHandler_: Logs records to the syslog.
- _ErrorLogHandler_: Logs records to PHP's
[`error_log()`](http://docs.php.net/manual/en/function.error-log.php) function.
### Send alerts and emails
- _NativeMailerHandler_: Sends emails using PHP's
[`mail()`](http://php.net/manual/en/function.mail.php) function.
- _SwiftMailerHandler_: Sends emails using a [`Swift_Mailer`](http://swiftmailer.org/) instance.
- _PushoverHandler_: Sends mobile notifications via the [Pushover](https://www.pushover.net/) API.
- _HipChatHandler_: Logs records to a [HipChat](http://hipchat.com) chat room using its API.
### Log specific servers and networked logging
- _SocketHandler_: Logs records to [sockets](http://php.net/fsockopen), use this
for UNIX and TCP sockets. See an [example](https://github.com/Seldaek/monolog/blob/master/doc/sockets.md).
- _AmqpHandler_: Logs records to an [amqp](http://www.amqp.org/) compatible
server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+).
- _GelfHandler_: Logs records to a [Graylog2](http://www.graylog2.org) server.
- _CubeHandler_: Logs records to a [Cube](http://square.github.com/cube/) server.
- _RavenHandler_: Logs records to a [Sentry](http://getsentry.com/) server using
[raven](https://packagist.org/packages/raven/raven).
- _ZendMonitorHandler_: Logs records to the Zend Monitor present in Zend Server.
- _NewRelicHandler_: Logs records to a [NewRelic](http://newrelic.com/) application.
### Logging in development
- _FirePHPHandler_: Handler for [FirePHP](http://www.firephp.org/), providing
inline `console` messages within [FireBug](http://getfirebug.com/).
- _ChromePHPHandler_: Handler for [ChromePHP](http://www.chromephp.com/), providing
inline `console` messages within Chrome.
### Log to databases
- _RedisHandler_: Logs records to a [redis](http://redis.io) server.
- _MongoDBHandler_: Handler to write records in MongoDB via a
[Mongo](http://pecl.php.net/package/mongo) extension connection.
- _CouchDBHandler_: Logs records to a CouchDB server.
- _DoctrineCouchDBHandler_: Logs records to a CouchDB server via the Doctrine CouchDB ODM.
### Wrappers / Special Handlers
- _FingersCrossedHandler_: A very interesting wrapper. It takes a logger as
parameter and will accumulate log records of all levels until a record
exceeds the defined severity level. At which point it delivers all records,
including those of lower severity, to the handler it wraps. This means that
until an error actually happens you will not see anything in your logs, but
when it happens you will have the full information, including debug and info
records. This provides you with all the information you need, but only when
you need it.
- _NullHandler_: Any record it can handle will be thrown away. This can be used
to put on top of an existing handler stack to disable it temporarily.
- _BufferHandler_: This handler will buffer all the log records it receives
until `close()` is called at which point it will call `handleBatch()` on the
handler it wraps with all the log messages at once. This is very useful to
send an email with all records at once for example instead of having one mail
for every log record.
- _GroupHandler_: This handler groups other handlers. Every record received is
sent to all the handlers it is configured with.
- _TestHandler_: Used for testing, it records everything that is sent to it and
has accessors to read out the information.
Formatters
----------
- _LineFormatter_: Formats a log record into a one-line string.
- _NormalizerFormatter_: Normalizes objects/resources down to strings so a record can easily be serialized/encoded.
- _JsonFormatter_: Encodes a log record into json.
- _WildfireFormatter_: Used to format log records into the Wildfire/FirePHP protocol, only useful for the FirePHPHandler.
- _ChromePHPFormatter_: Used to format log records into the ChromePHP format, only useful for the ChromePHPHandler.
- _GelfFormatter_: Used to format log records into Gelf message instances, only useful for the GelfHandler.
- _LogstashFormatter_: Used to format log records into [logstash](http://logstash.net/) event json, useful for any handler listed under inputs [here](http://logstash.net/docs/1.1.5/).
Processors
----------
- _IntrospectionProcessor_: Adds the line/file/class/method from which the log call originated.
- _WebProcessor_: Adds the current request URI, request method and client IP to a log record.
- _MemoryUsageProcessor_: Adds the current memory usage to a log record.
- _MemoryPeakUsageProcessor_: Adds the peak memory usage to a log record.
- _ProcessIdProcessor_: Adds the process id to a log record.
- _UidProcessor_: Adds a unique identifier to a log record.
Utilities
---------
- _ErrorHandler_: The `Monolog\ErrorHandler` class allows you to easily register
a Logger instance as an exception handler, error handler or fatal error handler.
- _ErrorLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain log
level is reached.
- _ChannelLevelActivationStrategy_: Activates a FingersCrossedHandler when a certain
log level is reached, depending on which channel received the log record.
About
=====
Requirements
------------
- Any flavor of PHP 5.3 or above should do
- [optional] PHPUnit 3.5+ to execute the test suite (phpunit --version)
Submitting bugs and feature requests
------------------------------------
Bugs and feature request are tracked on [GitHub](https://github.com/Seldaek/monolog/issues)
Frameworks Integration
----------------------
- Frameworks and libraries using [PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
can be used very easily with Monolog since it implements the interface.
- [Symfony2](http://symfony.com) comes out of the box with Monolog.
- [Silex](http://silex.sensiolabs.org/) comes out of the box with Monolog.
- [Laravel4](http://laravel.com/) comes out of the box with Monolog.
- [PPI](http://www.ppi.io/) comes out of the box with Monolog.
- [CakePHP](http://cakephp.org/) is usable with Monolog via the [cakephp-monolog](https://github.com/jadb/cakephp-monolog) plugin.
- [Slim](http://www.slimframework.com/) is usable with Monolog via the [Slim-Monolog](https://github.com/Flynsarmy/Slim-Monolog) log writer.
Author
------
Jordi Boggiano - <j.boggiano@seld.be> - <http://twitter.com/seldaek><br />
See also the list of [contributors](https://github.com/Seldaek/monolog/contributors) which participated in this project.
License
-------
Monolog is licensed under the MIT License - see the `LICENSE` file for details
Acknowledgements
----------------
This library is heavily inspired by Python's [Logbook](http://packages.python.org/Logbook/)
library, although most concepts have been adjusted to fit to the PHP world.

39
vendor/monolog/monolog/composer.json vendored Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "monolog/monolog",
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"keywords": ["log", "logging", "psr-3"],
"homepage": "http://github.com/Seldaek/monolog",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"require": {
"php": ">=5.3.0",
"psr/log": "~1.0"
},
"require-dev": {
"mlehner/gelf-php": "1.0.*",
"raven/raven": "0.5.*",
"doctrine/couchdb": "dev-master"
},
"suggest": {
"mlehner/gelf-php": "Allow sending log messages to a GrayLog2 server",
"raven/raven": "Allow sending log messages to a Sentry server",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongo": "Allow sending log messages to a MongoDB server"
},
"autoload": {
"psr-0": {"Monolog": "src/"}
},
"extra": {
"branch-alias": {
"dev-master": "1.6.x-dev"
}
}
}

76
vendor/monolog/monolog/doc/extending.md vendored Normal file
View File

@@ -0,0 +1,76 @@
Extending Monolog
=================
Monolog is fully extensible, allowing you to adapt your logger to your needs.
Writing your own handler
------------------------
Monolog provides many built-in handlers. But if the one you need does not
exist, you can write it and use it in your logger. The only requirement is
to implement `Monolog\Handler\HandlerInterface`.
Let's write a PDOHandler to log records to a database. We will extend the
abstract class provided by Monolog to keep things DRY.
```php
<?php
use Monolog\Logger;
use Monolog\Handler\AbstractProcessingHandler;
class PDOHandler extends AbstractProcessingHandler
{
private $initialized = false;
private $pdo;
private $statement;
public function __construct(PDO $pdo, $level = Logger::DEBUG, $bubble = true)
{
$this->pdo = $pdo;
parent::__construct($level, $bubble);
}
protected function write(array $record)
{
if (!$this->initialized) {
$this->initialize();
}
$this->statement->execute(array(
'channel' => $record['channel'],
'level' => $record['level'],
'message' => $record['formatted'],
'time' => $record['datetime']->format('U'),
));
}
private function initialize()
{
$this->pdo->exec(
'CREATE TABLE IF NOT EXISTS monolog '
.'(channel VARCHAR(255), level INTEGER, message LONGTEXT, time INTEGER UNSIGNED)'
);
$this->statement = $this->pdo->prepare(
'INSERT INTO monolog (channel, level, message, time) VALUES (:channel, :level, :message, :time)'
);
$this->initialized = true;
}
}
```
You can now use this handler in your logger:
```php
<?php
$logger->pushHandler(new PDOHandler(new PDO('sqlite:logs.sqlite'));
// You can now use your logger
$logger->addInfo('My logger is now ready');
```
The `Monolog\Handler\AbstractProcessingHandler` class provides most of the
logic needed for the handler, including the use of processors and the formatting
of the record (which is why we use ``$record['formatted']`` instead of ``$record['message']``).

37
vendor/monolog/monolog/doc/sockets.md vendored Normal file
View File

@@ -0,0 +1,37 @@
Sockets Handler
===============
This handler allows you to write your logs to sockets using [fsockopen](http://php.net/fsockopen)
or [pfsockopen](http://php.net/pfsockopen).
Persistent sockets are mainly useful in web environments where you gain some performance not closing/opening
the connections between requests.
Basic Example
-------------
```php
<?php
use Monolog\Logger;
use Monolog\Handler\SocketHandler;
// Create the logger
$logger = new Logger('my_logger');
// Create the handler
$handler = new SocketHandler('unix:///var/log/httpd_app_log.socket');
$handler->setPersistent(true);
// Now add the handler
$logger->pushHandler($handler, Logger::DEBUG);
// You can now use your logger
$logger->addInfo('My logger is now ready');
```
In this example, using syslog-ng, you should see the log on the log server:
cweb1 [2012-02-26 00:12:03] my_logger.INFO: My logger is now ready [] []

158
vendor/monolog/monolog/doc/usage.md vendored Normal file
View File

@@ -0,0 +1,158 @@
Using Monolog
=============
Installation
------------
Monolog is available on Packagist ([monolog/monolog](http://packagist.org/packages/monolog/monolog))
and as such installable via [Composer](http://getcomposer.org/).
If you do not use Composer, you can grab the code from GitHub, and use any
PSR-0 compatible autoloader (e.g. the [Symfony2 ClassLoader component](https://github.com/symfony/ClassLoader))
to load Monolog classes.
Configuring a logger
--------------------
Here is a basic setup to log to a file and to firephp on the DEBUG level:
```php
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;
// Create the logger
$logger = new Logger('my_logger');
// Now add some handlers
$logger->pushHandler(new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG));
$logger->pushHandler(new FirePHPHandler());
// You can now use your logger
$logger->addInfo('My logger is now ready');
```
Let's explain it. The first step is to create the logger instance which will
be used in your code. The argument is a channel name, which is useful when
you use several loggers (see below for more details about it).
The logger itself does not know how to handle a record. It delegates it to
some handlers. The code above registers two handlers in the stack to allow
handling records in two different ways.
Note that the FirePHPHandler is called first as it is added on top of the
stack. This allows you to temporarily add a logger with bubbling disabled if
you want to override other configured loggers.
Adding extra data in the records
--------------------------------
Monolog provides two different ways to add extra informations along the simple
textual message.
### Using the logging context
The first way is the context, allowing to pass an array of data along the
record:
```php
<?php
$logger->addInfo('Adding a new user', array('username' => 'Seldaek'));
```
Simple handlers (like the StreamHandler for instance) will simply format
the array to a string but richer handlers can take advantage of the context
(FirePHP is able to display arrays in pretty way for instance).
### Using processors
The second way is to add extra data for all records by using a processor.
Processors can be any callable. They will get the record as parameter and
must return it after having eventually changed the `extra` part of it. Let's
write a processor adding some dummy data in the record:
```php
<?php
$logger->pushProcessor(function ($record) {
$record['extra']['dummy'] = 'Hello world!';
return $record;
});
```
Monolog provides some built-in processors that can be used in your project.
Look at the README file for the list.
> Tip: processors can also be registered on a specific handler instead of
the logger to apply only for this handler.
Leveraging channels
-------------------
Channels are a great way to identify to which part of the application a record
is related. This is useful in big applications (and is leveraged by
MonologBundle in Symfony2).
Picture two loggers sharing a handler that writes to a single log file.
Channels would allow you to identify the logger that issued every record.
You can easily grep through the log files filtering this or that channel.
```php
<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;
// Create some handlers
$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG);
$firephp = new FirePHPHandler();
// Create the main logger of the app
$logger = new Logger('my_logger');
$logger->pushHandler($stream);
$logger->pushHandler($firephp);
// Create a logger for the security-related stuff with a different channel
$securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
$securityLogger->pushHandler($firephp);
```
Customizing log format
----------------------
In Monolog it's easy to customize the format of the logs written into files,
sockets, mails, databases and other handlers. Most of the handlers use the
```php
$record['formatted']
```
value to be automatically put into the log device. This value depends on the
formatter settings. You can choose between predefined formatter classes or
write your own (e.g. a multiline text file for human-readable output).
To configure a predefined formatter class, just set it as the handler's field:
```php
// the default date format is "Y-m-d H:i:s"
$dateFormat = "Y n j, g:i a";
// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"
$output = "%datetime% > %level_name% > %message% %context% %extra%\n";
// finally, create a formatter
$formatter = new LineFormatter($output, $dateFormat);
// Create a handler
$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG);
$stream->setFormatter($formatter);
// bind it to a logger object
$securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
```
You may also reuse the same formatter between multiple handlers and share those
handlers between multiple loggers.

15
vendor/monolog/monolog/phpunit.xml.dist vendored Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php" colors="true">
<testsuites>
<testsuite name="Monolog Test Suite">
<directory>tests/Monolog/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/Monolog/</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,204 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
/**
* Monolog error handler
*
* A facility to enable logging of runtime errors, exceptions and fatal errors.
*
* Quick setup: <code>ErrorHandler::register($logger);</code>
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class ErrorHandler
{
private $logger;
private $previousExceptionHandler;
private $uncaughtExceptionLevel;
private $previousErrorHandler;
private $errorLevelMap;
private $fatalLevel;
private $reservedMemory;
private static $fatalErrors = array(E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR);
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
/**
* Registers a new ErrorHandler for a given Logger
*
* By default it will handle errors, exceptions and fatal errors
*
* @param LoggerInterface $logger
* @param array|false $errorLevelMap an array of E_* constant to LogLevel::* constant mapping, or false to disable error handling
* @param int|false $exceptionLevel a LogLevel::* constant, or false to disable exception handling
* @param int|false $fatalLevel a LogLevel::* constant, or false to disable fatal error handling
* @return ErrorHandler
*/
public static function register(LoggerInterface $logger, $errorLevelMap = array(), $exceptionLevel = null, $fatalLevel = null)
{
$handler = new static($logger);
if ($errorLevelMap !== false) {
$handler->registerErrorHandler($errorLevelMap);
}
if ($exceptionLevel !== false) {
$handler->registerExceptionHandler($exceptionLevel);
}
if ($fatalLevel !== false) {
$handler->registerFatalHandler($fatalLevel);
}
return $handler;
}
public function registerExceptionHandler($level = null, $callPrevious = true)
{
$prev = set_exception_handler(array($this, 'handleException'));
$this->uncaughtExceptionLevel = $level === null ? LogLevel::ERROR : $level;
if ($callPrevious && $prev) {
$this->previousExceptionHandler = $prev;
}
}
public function registerErrorHandler(array $levelMap = array(), $callPrevious = true, $errorTypes = -1)
{
$prev = set_error_handler(array($this, 'handleError'), $errorTypes);
$this->errorLevelMap = array_replace($this->defaultErrorLevelMap(), $levelMap);
if ($callPrevious) {
$this->previousErrorHandler = $prev ?: true;
}
}
public function registerFatalHandler($level = null, $reservedMemorySize = 20)
{
register_shutdown_function(array($this, 'handleFatalError'));
$this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize);
$this->fatalLevel = $level === null ? LogLevel::ALERT : $level;
}
protected function defaultErrorLevelMap()
{
return array(
E_ERROR => LogLevel::CRITICAL,
E_WARNING => LogLevel::WARNING,
E_PARSE => LogLevel::ALERT,
E_NOTICE => LogLevel::NOTICE,
E_CORE_ERROR => LogLevel::CRITICAL,
E_CORE_WARNING => LogLevel::WARNING,
E_COMPILE_ERROR => LogLevel::ALERT,
E_COMPILE_WARNING => LogLevel::WARNING,
E_USER_ERROR => LogLevel::ERROR,
E_USER_WARNING => LogLevel::WARNING,
E_USER_NOTICE => LogLevel::NOTICE,
E_STRICT => LogLevel::NOTICE,
E_RECOVERABLE_ERROR => LogLevel::ERROR,
E_DEPRECATED => LogLevel::NOTICE,
E_USER_DEPRECATED => LogLevel::NOTICE,
);
}
/**
* @private
*/
public function handleException(\Exception $e)
{
$this->logger->log($this->uncaughtExceptionLevel, 'Uncaught exception', array('exception' => $e));
if ($this->previousExceptionHandler) {
call_user_func($this->previousExceptionHandler, $e);
}
}
/**
* @private
*/
public function handleError($code, $message, $file = '', $line = 0, $context = array())
{
if (!(error_reporting() & $code)) {
return;
}
$level = isset($this->errorLevelMap[$code]) ? $this->errorLevelMap[$code] : LogLevel::CRITICAL;
$this->logger->log($level, self::codeToString($code).': '.$message, array('file' => $file, 'line' => $line));
if ($this->previousErrorHandler === true) {
return false;
} elseif ($this->previousErrorHandler) {
return call_user_func($this->previousErrorHandler, $code, $message, $file, $line, $context);
}
}
/**
* @private
*/
public function handleFatalError()
{
$this->reservedMemory = null;
$lastError = error_get_last();
if ($lastError && in_array($lastError['type'], self::$fatalErrors)) {
$this->logger->log(
$this->fatalLevel,
'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'],
array('file' => $lastError['file'], 'line' => $lastError['line'])
);
}
}
private static function codeToString($code)
{
switch ($code) {
case E_ERROR:
return 'E_ERROR';
case E_WARNING:
return 'E_WARNING';
case E_PARSE:
return 'E_PARSE';
case E_NOTICE:
return 'E_NOTICE';
case E_CORE_ERROR:
return 'E_CORE_ERROR';
case E_CORE_WARNING:
return 'E_CORE_WARNING';
case E_COMPILE_ERROR:
return 'E_COMPILE_ERROR';
case E_COMPILE_WARNING:
return 'E_COMPILE_WARNING';
case E_USER_ERROR:
return 'E_USER_ERROR';
case E_USER_WARNING:
return 'E_USER_WARNING';
case E_USER_NOTICE:
return 'E_USER_NOTICE';
case E_STRICT:
return 'E_STRICT';
case E_RECOVERABLE_ERROR:
return 'E_RECOVERABLE_ERROR';
case E_DEPRECATED:
return 'E_DEPRECATED';
case E_USER_DEPRECATED:
return 'E_USER_DEPRECATED';
}
return 'Unknown PHP error';
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
/**
* Formats a log message according to the ChromePHP array format
*
* @author Christophe Coevoet <stof@notk.org>
*/
class ChromePHPFormatter implements FormatterInterface
{
/**
* Translates Monolog log levels to Wildfire levels.
*/
private $logLevels = array(
Logger::DEBUG => 'log',
Logger::INFO => 'info',
Logger::NOTICE => 'info',
Logger::WARNING => 'warn',
Logger::ERROR => 'error',
Logger::CRITICAL => 'error',
Logger::ALERT => 'error',
Logger::EMERGENCY => 'error',
);
/**
* {@inheritdoc}
*/
public function format(array $record)
{
// Retrieve the line and file if set and remove them from the formatted extra
$backtrace = 'unknown';
if (isset($record['extra']['file']) && isset($record['extra']['line'])) {
$backtrace = $record['extra']['file'].' : '.$record['extra']['line'];
unset($record['extra']['file']);
unset($record['extra']['line']);
}
$message = array('message' => $record['message']);
if ($record['context']) {
$message['context'] = $record['context'];
}
if ($record['extra']) {
$message['extra'] = $record['extra'];
}
if (count($message) === 1) {
$message = reset($message);
}
return array(
$record['channel'],
$message,
$backtrace,
$this->logLevels[$record['level']],
);
}
public function formatBatch(array $records)
{
$formatted = array();
foreach ($records as $record) {
$formatted[] = $this->format($record);
}
return $formatted;
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Interface for formatters
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface FormatterInterface
{
/**
* Formats a log record.
*
* @param array $record A record to format
* @return mixed The formatted record
*/
public function format(array $record);
/**
* Formats a set of log records.
*
* @param array $records A set of records to format
* @return mixed The formatted set of records
*/
public function formatBatch(array $records);
}

View File

@@ -0,0 +1,94 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
use Gelf\Message;
/**
* Serializes a log message to GELF
* @see http://www.graylog2.org/about/gelf
*
* @author Matt Lehner <mlehner@gmail.com>
*/
class GelfMessageFormatter extends NormalizerFormatter
{
/**
* @var string the name of the system for the Gelf log message
*/
protected $systemName;
/**
* @var string a prefix for 'extra' fields from the Monolog record (optional)
*/
protected $extraPrefix;
/**
* @var string a prefix for 'context' fields from the Monolog record (optional)
*/
protected $contextPrefix;
/**
* Translates Monolog log levels to Graylog2 log priorities.
*/
private $logLevels = array(
Logger::DEBUG => 7,
Logger::INFO => 6,
Logger::NOTICE => 5,
Logger::WARNING => 4,
Logger::ERROR => 3,
Logger::CRITICAL => 2,
Logger::ALERT => 1,
Logger::EMERGENCY => 0,
);
public function __construct($systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_')
{
parent::__construct('U.u');
$this->systemName = $systemName ?: gethostname();
$this->extraPrefix = $extraPrefix;
$this->contextPrefix = $contextPrefix;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$record = parent::format($record);
$message = new Message();
$message
->setTimestamp($record['datetime'])
->setShortMessage((string) $record['message'])
->setFacility($record['channel'])
->setHost($this->systemName)
->setLine(isset($record['extra']['line']) ? $record['extra']['line'] : null)
->setFile(isset($record['extra']['file']) ? $record['extra']['file'] : null)
->setLevel($this->logLevels[$record['level']]);
// Do not duplicate these values in the additional fields
unset($record['extra']['line']);
unset($record['extra']['file']);
foreach ($record['extra'] as $key => $val) {
$message->setAdditional($this->extraPrefix . $key, is_scalar($val) ? $val : $this->toJson($val));
}
foreach ($record['context'] as $key => $val) {
$message->setAdditional($this->contextPrefix . $key, is_scalar($val) ? $val : $this->toJson($val));
}
return $message;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Encodes whatever record data is passed to it as json
*
* This can be useful to log to databases or remote APIs
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class JsonFormatter implements FormatterInterface
{
/**
* {@inheritdoc}
*/
public function format(array $record)
{
return json_encode($record);
}
/**
* {@inheritdoc}
*/
public function formatBatch(array $records)
{
return json_encode($records);
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Formats incoming records into a one-line string
*
* This is especially useful for logging to files
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Christophe Coevoet <stof@notk.org>
*/
class LineFormatter extends NormalizerFormatter
{
const SIMPLE_FORMAT = "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n";
protected $format;
/**
* @param string $format The format of the message
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
*/
public function __construct($format = null, $dateFormat = null)
{
$this->format = $format ?: static::SIMPLE_FORMAT;
parent::__construct($dateFormat);
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$vars = parent::format($record);
$output = $this->format;
foreach ($vars['extra'] as $var => $val) {
if (false !== strpos($output, '%extra.'.$var.'%')) {
$output = str_replace('%extra.'.$var.'%', $this->convertToString($val), $output);
unset($vars['extra'][$var]);
}
}
foreach ($vars as $var => $val) {
$output = str_replace('%'.$var.'%', $this->convertToString($val), $output);
}
return $output;
}
public function formatBatch(array $records)
{
$message = '';
foreach ($records as $record) {
$message .= $this->format($record);
}
return $message;
}
protected function normalize($data)
{
if (is_bool($data) || is_null($data)) {
return var_export($data, true);
}
if ($data instanceof \Exception) {
$previousText = '';
if ($previous = $data->getPrevious()) {
do {
$previousText .= ', '.get_class($previous).': '.$previous->getMessage().' at '.$previous->getFile().':'.$previous->getLine();
} while ($previous = $previous->getPrevious());
}
return '[object] ('.get_class($data).': '.$data->getMessage().' at '.$data->getFile().':'.$data->getLine().$previousText.')';
}
return parent::normalize($data);
}
protected function convertToString($data)
{
if (null === $data || is_scalar($data)) {
return (string) $data;
}
$data = $this->normalize($data);
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return $this->toJson($data);
}
return str_replace('\\/', '/', json_encode($data));
}
}

View File

@@ -0,0 +1,98 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* Serializes a log message to Logstash Event Format
*
* @see http://logstash.net/
* @see https://github.com/logstash/logstash/blob/master/lib/logstash/event.rb
*
* @author Tim Mower <timothy.mower@gmail.com>
*/
class LogstashFormatter extends NormalizerFormatter
{
/**
* @var string the name of the system for the Logstash log message, used to fill the @source field
*/
protected $systemName;
/**
* @var string an application name for the Logstash log message, used to fill the @type field
*/
protected $applicationName;
/**
* @var string a prefix for 'extra' fields from the Monolog record (optional)
*/
protected $extraPrefix;
/**
* @var string a prefix for 'context' fields from the Monolog record (optional)
*/
protected $contextPrefix;
/**
* @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_
*/
public function __construct($applicationName, $systemName = null, $extraPrefix = null, $contextPrefix = 'ctxt_')
{
//log stash requires a ISO 8601 format date
parent::__construct('c');
$this->systemName = $systemName ?: gethostname();
$this->applicationName = $applicationName;
$this->extraPrefix = $extraPrefix;
$this->contextPrefix = $contextPrefix;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
$record = parent::format($record);
$message = array(
'@timestamp' => $record['datetime'],
'@message' => $record['message'],
'@tags' => array($record['channel']),
'@source' => $this->systemName
);
if ($this->applicationName) {
$message['@type'] = $this->applicationName;
}
$message['@fields'] = array();
$message['@fields']['channel'] = $record['channel'];
$message['@fields']['level'] = $record['level'];
if (isset($record['extra']['server'])) {
$message['@source_host'] = $record['extra']['server'];
}
if (isset($record['extra']['url'])) {
$message['@source_path'] = $record['extra']['url'];
}
foreach ($record['extra'] as $key => $val) {
$message['@fields'][$this->extraPrefix . $key] = $val;
}
foreach ($record['context'] as $key => $val) {
$message['@fields'][$this->contextPrefix . $key] = $val;
}
return json_encode($message) . "\n";
}
}

View File

@@ -0,0 +1,137 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Exception;
/**
* Normalizes incoming records to remove objects/resources so it's easier to dump to various targets
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class NormalizerFormatter implements FormatterInterface
{
const SIMPLE_DATE = "Y-m-d H:i:s";
protected $dateFormat;
/**
* @param string $dateFormat The format of the timestamp: one supported by DateTime::format
*/
public function __construct($dateFormat = null)
{
$this->dateFormat = $dateFormat ?: static::SIMPLE_DATE;
}
/**
* {@inheritdoc}
*/
public function format(array $record)
{
return $this->normalize($record);
}
/**
* {@inheritdoc}
*/
public function formatBatch(array $records)
{
foreach ($records as $key => $record) {
$records[$key] = $this->format($record);
}
return $records;
}
protected function normalize($data)
{
if (null === $data || is_scalar($data)) {
return $data;
}
if (is_array($data) || $data instanceof \Traversable) {
$normalized = array();
$count = 1;
foreach ($data as $key => $value) {
if ($count++ >= 1000) {
$normalized['...'] = 'Over 1000 items, aborting normalization';
break;
}
$normalized[$key] = $this->normalize($value);
}
return $normalized;
}
if ($data instanceof \DateTime) {
return $data->format($this->dateFormat);
}
if (is_object($data)) {
if ($data instanceof Exception) {
return $this->normalizeException($data);
}
return sprintf("[object] (%s: %s)", get_class($data), $this->toJson($data, true));
}
if (is_resource($data)) {
return '[resource]';
}
return '[unknown('.gettype($data).')]';
}
protected function normalizeException(Exception $e)
{
$data = array(
'class' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile().':'.$e->getLine(),
);
$trace = $e->getTrace();
array_shift($trace);
foreach ($trace as $frame) {
if (isset($frame['file'])) {
$data['trace'][] = $frame['file'].':'.$frame['line'];
} else {
$data['trace'][] = json_encode($frame);
}
}
if ($previous = $e->getPrevious()) {
$data['previous'] = $this->normalizeException($previous);
}
return $data;
}
protected function toJson($data, $ignoreErrors = false)
{
// suppress json_encode errors since it's twitchy with some inputs
if ($ignoreErrors) {
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return @json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return @json_encode($data);
}
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return json_encode($data);
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
/**
* Serializes a log message according to Wildfire's header requirements
*
* @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
* @author Christophe Coevoet <stof@notk.org>
* @author Kirill chEbba Chebunin <iam@chebba.org>
*/
class WildfireFormatter extends NormalizerFormatter
{
/**
* Translates Monolog log levels to Wildfire levels.
*/
private $logLevels = array(
Logger::DEBUG => 'LOG',
Logger::INFO => 'INFO',
Logger::NOTICE => 'INFO',
Logger::WARNING => 'WARN',
Logger::ERROR => 'ERROR',
Logger::CRITICAL => 'ERROR',
Logger::ALERT => 'ERROR',
Logger::EMERGENCY => 'ERROR',
);
/**
* {@inheritdoc}
*/
public function format(array $record)
{
// Retrieve the line and file if set and remove them from the formatted extra
$file = $line = '';
if (isset($record['extra']['file'])) {
$file = $record['extra']['file'];
unset($record['extra']['file']);
}
if (isset($record['extra']['line'])) {
$line = $record['extra']['line'];
unset($record['extra']['line']);
}
$record = $this->normalize($record);
$message = array('message' => $record['message']);
$handleError = false;
if ($record['context']) {
$message['context'] = $record['context'];
$handleError = true;
}
if ($record['extra']) {
$message['extra'] = $record['extra'];
$handleError = true;
}
if (count($message) === 1) {
$message = reset($message);
}
// Create JSON object describing the appearance of the message in the console
$json = $this->toJson(array(
array(
'Type' => $this->logLevels[$record['level']],
'File' => $file,
'Line' => $line,
'Label' => $record['channel'],
),
$message,
), $handleError);
// The message itself is a serialization of the above JSON object + it's length
return sprintf(
'%s|%s|',
strlen($json),
$json
);
}
public function formatBatch(array $records)
{
throw new \BadMethodCallException('Batch formatting does not make sense for the WildfireFormatter');
}
protected function normalize($data)
{
if (is_object($data) && !$data instanceof \DateTime) {
return $data;
}
return parent::normalize($data);
}
}

View File

@@ -0,0 +1,184 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
/**
* Base Handler class providing the Handler structure
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
abstract class AbstractHandler implements HandlerInterface
{
protected $level = Logger::DEBUG;
protected $bubble = true;
/**
* @var FormatterInterface
*/
protected $formatter;
protected $processors = array();
/**
* @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
{
$this->level = $level;
$this->bubble = $bubble;
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
return $record['level'] >= $this->level;
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
foreach ($records as $record) {
$this->handle($record);
}
}
/**
* Closes the handler.
*
* This will be called automatically when the object is destroyed
*/
public function close()
{
}
/**
* {@inheritdoc}
*/
public function pushProcessor($callback)
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given');
}
array_unshift($this->processors, $callback);
return $this;
}
/**
* {@inheritdoc}
*/
public function popProcessor()
{
if (!$this->processors) {
throw new \LogicException('You tried to pop from an empty processor stack.');
}
return array_shift($this->processors);
}
/**
* {@inheritdoc}
*/
public function setFormatter(FormatterInterface $formatter)
{
$this->formatter = $formatter;
return $this;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
if (!$this->formatter) {
$this->formatter = $this->getDefaultFormatter();
}
return $this->formatter;
}
/**
* Sets minimum logging level at which this handler will be triggered.
*
* @param integer $level
* @return self
*/
public function setLevel($level)
{
$this->level = $level;
return $this;
}
/**
* Gets minimum logging level at which this handler will be triggered.
*
* @return integer
*/
public function getLevel()
{
return $this->level;
}
/**
* Sets the bubbling behavior.
*
* @param Boolean $bubble true means that this handler allows bubbling.
* false means that bubbling is not permitted.
* @return self
*/
public function setBubble($bubble)
{
$this->bubble = $bubble;
return $this;
}
/**
* Gets the bubbling behavior.
*
* @return Boolean true means that this handler allows bubbling.
* false means that bubbling is not permitted.
*/
public function getBubble()
{
return $this->bubble;
}
public function __destruct()
{
try {
$this->close();
} catch (\Exception $e) {
// do nothing
}
}
/**
* Gets the default formatter.
*
* @return FormatterInterface
*/
protected function getDefaultFormatter()
{
return new LineFormatter();
}
}

View File

@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
/**
* Base Handler class providing the Handler structure
*
* Classes extending it should (in most cases) only implement write($record)
*
* @author Jordi Boggiano <j.boggiano@seld.be>
* @author Christophe Coevoet <stof@notk.org>
*/
abstract class AbstractProcessingHandler extends AbstractHandler
{
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($record['level'] < $this->level) {
return false;
}
$record = $this->processRecord($record);
$record['formatted'] = $this->getFormatter()->format($record);
$this->write($record);
return false === $this->bubble;
}
/**
* Writes the record down to the log of the implementing handler
*
* @param array $record
* @return void
*/
abstract protected function write(array $record);
/**
* Processes a record.
*
* @param array $record
* @return array
*/
protected function processRecord(array $record)
{
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
return $record;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\JsonFormatter;
class AmqpHandler extends AbstractProcessingHandler
{
/**
* @var \AMQPExchange $exchange
*/
protected $exchange;
/**
* @param \AMQPExchange $exchange AMQP exchange, ready for use
* @param string $exchangeName
* @param int $level
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(\AMQPExchange $exchange, $exchangeName = 'log', $level = Logger::DEBUG, $bubble = true)
{
$this->exchange = $exchange;
$this->exchange->setName($exchangeName);
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$data = $record["formatted"];
$routingKey = sprintf(
'%s.%s',
substr($record['level_name'], 0, 4),
$record['channel']
);
$this->exchange->publish(
$data,
strtolower($routingKey),
0,
array(
'delivery_mode' => 2,
'Content-type' => 'application/json'
)
);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new JsonFormatter();
}
}

View File

@@ -0,0 +1,98 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Buffers all records until closing the handler and then pass them as batch.
*
* This is useful for a MailHandler to send only one mail per request instead of
* sending one per log message.
*
* @author Christophe Coevoet <stof@notk.org>
*/
class BufferHandler extends AbstractHandler
{
protected $handler;
protected $bufferSize = 0;
protected $bufferLimit;
protected $flushOnOverflow;
protected $buffer = array();
/**
* @param HandlerInterface $handler Handler.
* @param integer $bufferLimit How many entries should be buffered at most, beyond that the oldest items are removed from the buffer.
* @param integer $level The minimum logging level at which this handler will be triggered
* @param 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
*/
public function __construct(HandlerInterface $handler, $bufferLimit = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false)
{
parent::__construct($level, $bubble);
$this->handler = $handler;
$this->bufferLimit = (int) $bufferLimit;
$this->flushOnOverflow = $flushOnOverflow;
// __destructor() doesn't get called on Fatal errors
register_shutdown_function(array($this, 'close'));
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($record['level'] < $this->level) {
return false;
}
if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) {
if ($this->flushOnOverflow) {
$this->flush();
} else {
array_shift($this->buffer);
$this->bufferSize--;
}
}
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
$this->buffer[] = $record;
$this->bufferSize++;
return false === $this->bubble;
}
public function flush()
{
if ($this->bufferSize === 0) {
return;
}
$this->handler->handleBatch($this->buffer);
$this->bufferSize = 0;
$this->buffer = array();
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->flush();
}
}

View File

@@ -0,0 +1,183 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\ChromePHPFormatter;
use Monolog\Logger;
/**
* Handler sending logs to the ChromePHP extension (http://www.chromephp.com/)
*
* @author Christophe Coevoet <stof@notk.org>
*/
class ChromePHPHandler extends AbstractProcessingHandler
{
/**
* Version of the extension
*/
const VERSION = '4.0';
/**
* Header name
*/
const HEADER_NAME = 'X-ChromeLogger-Data';
protected static $initialized = false;
/**
* Tracks whether we sent too much data
*
* Chrome limits the headers to 256KB, so when we sent 240KB we stop sending
*
* @var Boolean
*/
protected static $overflowed = false;
protected static $json = array(
'version' => self::VERSION,
'columns' => array('label', 'log', 'backtrace', 'type'),
'rows' => array(),
);
protected static $sendHeaders = true;
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$messages = array();
foreach ($records as $record) {
if ($record['level'] < $this->level) {
continue;
}
$messages[] = $this->processRecord($record);
}
if (!empty($messages)) {
$messages = $this->getFormatter()->formatBatch($messages);
self::$json['rows'] = array_merge(self::$json['rows'], $messages);
$this->send();
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new ChromePHPFormatter();
}
/**
* Creates & sends header for a record
*
* @see sendHeader()
* @see send()
* @param array $record
*/
protected function write(array $record)
{
self::$json['rows'][] = $record['formatted'];
$this->send();
}
/**
* Sends the log header
*
* @see sendHeader()
*/
protected function send()
{
if (self::$overflowed) {
return;
}
if (!self::$initialized) {
self::$sendHeaders = $this->headersAccepted();
self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
self::$initialized = true;
}
$json = @json_encode(self::$json);
$data = base64_encode(utf8_encode($json));
if (strlen($data) > 240*1024) {
self::$overflowed = true;
$record = array(
'message' => 'Incomplete logs, chrome header size limit reached',
'context' => array(),
'level' => Logger::WARNING,
'level_name' => Logger::getLevelName(Logger::WARNING),
'channel' => 'monolog',
'datetime' => new \DateTime(),
'extra' => array(),
);
self::$json['rows'][count(self::$json['rows']) - 1] = $this->getFormatter()->format($record);
$json = @json_encode(self::$json);
$data = base64_encode(utf8_encode($json));
}
$this->sendHeader(self::HEADER_NAME, $data);
}
/**
* Send header string to the client
*
* @param string $header
* @param string $content
*/
protected function sendHeader($header, $content)
{
if (!headers_sent() && self::$sendHeaders) {
header(sprintf('%s: %s', $header, $content));
}
}
/**
* Verifies if the headers are accepted by the current user agent
*
* @return Boolean
*/
protected function headersAccepted()
{
return !isset($_SERVER['HTTP_USER_AGENT'])
|| preg_match('{\bChrome/\d+[\.\d+]*\b}', $_SERVER['HTTP_USER_AGENT']);
}
/**
* BC getter for the sendHeaders property that has been made static
*/
public function __get($property)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
return static::$sendHeaders;
}
/**
* BC setter for the sendHeaders property that has been made static
*/
public function __set($property, $value)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
static::$sendHeaders = $value;
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\JsonFormatter;
use Monolog\Logger;
/**
* CouchDB handler
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
class CouchDBHandler extends AbstractProcessingHandler
{
private $options;
public function __construct(array $options = array(), $level = Logger::DEBUG, $bubble = true)
{
$this->options = array_merge(array(
'host' => 'localhost',
'port' => 5984,
'dbname' => 'logger',
'username' => null,
'password' => null,
), $options);
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$basicAuth = null;
if ($this->options['username']) {
$basicAuth = sprintf('%s:%s@', $this->options['username'], $this->options['password']);
}
$url = 'http://'.$basicAuth.$this->options['host'].':'.$this->options['port'].'/'.$this->options['dbname'];
$context = stream_context_create(array(
'http' => array(
'method' => 'POST',
'content' => $record['formatted'],
'ignore_errors' => true,
'max_redirects' => 0,
'header' => 'Content-type: application/json',
)
));
if (false === @file_get_contents($url, null, $context)) {
throw new \RuntimeException(sprintf('Could not connect to %s', $url));
}
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new JsonFormatter();
}
}

View File

@@ -0,0 +1,145 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Logs to Cube.
*
* @link http://square.github.com/cube/
* @author Wan Chen <kami@kamisama.me>
*/
class CubeHandler extends AbstractProcessingHandler
{
private $udpConnection = null;
private $httpConnection = null;
private $scheme = null;
private $host = null;
private $port = null;
private $acceptedSchemes = array('http', 'udp');
/**
* Create a Cube handler
*
* @throws UnexpectedValueException when given url is not a valid url.
* A valid url must consists of three parts : protocol://host:port
* Only valid protocol used by Cube are http and udp
*/
public function __construct($url, $level = Logger::DEBUG, $bubble = true)
{
$urlInfos = parse_url($url);
if (!isset($urlInfos['scheme']) || !isset($urlInfos['host']) || !isset($urlInfos['port'])) {
throw new \UnexpectedValueException('URL "'.$url.'" is not valid');
}
if (!in_array($urlInfos['scheme'], $this->acceptedSchemes)) {
throw new \UnexpectedValueException(
'Invalid protocol (' . $urlInfos['scheme'] . ').'
. ' Valid options are ' . implode(', ', $this->acceptedSchemes));
}
$this->scheme = $urlInfos['scheme'];
$this->host = $urlInfos['host'];
$this->port = $urlInfos['port'];
parent::__construct($level, $bubble);
}
/**
* Establish a connection to an UDP socket
*
* @throws LogicException when unable to connect to the socket
*/
protected function connectUdp()
{
if (!extension_loaded('sockets')) {
throw new MissingExtensionException('The sockets extension is required to use udp URLs with the CubeHandler');
}
$this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0);
if (!$this->udpConnection) {
throw new \LogicException('Unable to create a socket');
}
if (!socket_connect($this->udpConnection, $this->host, $this->port)) {
throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port);
}
}
/**
* Establish a connection to a http server
*/
protected function connectHttp()
{
if (!extension_loaded('curl')) {
throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler');
}
$this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put');
if (!$this->httpConnection) {
throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port);
}
curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true);
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$date = $record['datetime'];
$data = array('time' => $date->format('Y-m-d\TH:i:s.u'));
unset($record['datetime']);
if (isset($record['context']['type'])) {
$data['type'] = $record['context']['type'];
unset($record['context']['type']);
} else {
$data['type'] = $record['channel'];
}
$data['data'] = $record['context'];
$data['data']['level'] = $record['level'];
$this->{'write'.$this->scheme}(json_encode($data));
}
private function writeUdp($data)
{
if (!$this->udpConnection) {
$this->connectUdp();
}
socket_send($this->udpConnection, $data, strlen($data), 0);
}
private function writeHttp($data)
{
if (!$this->httpConnection) {
$this->connectHttp();
}
curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');
curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen('['.$data.']'))
);
return curl_exec($this->httpConnection);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\NormalizerFormatter;
use Doctrine\CouchDB\CouchDBClient;
/**
* CouchDB handler for Doctrine CouchDB ODM
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
*/
class DoctrineCouchDBHandler extends AbstractProcessingHandler
{
private $client;
public function __construct(CouchDBClient $client, $level = Logger::DEBUG, $bubble = true)
{
$this->client = $client;
parent::__construct($level, $bubble);
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
$this->client->postDocument($record['formatted']);
}
protected function getDefaultFormatter()
{
return new NormalizerFormatter;
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Stores to PHP error_log() handler.
*
* @author Elan Ruusamäe <glen@delfi.ee>
*/
class ErrorLogHandler extends AbstractProcessingHandler
{
const OPERATING_SYSTEM = 0;
const SAPI = 4;
protected $messageType;
/**
* @param integer $messageType Says where the error should go.
* @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($messageType = self::OPERATING_SYSTEM, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
if (false === in_array($messageType, self::getAvailableTypes())) {
$message = sprintf('The given message type "%s" is not supported', print_r($messageType, true));
throw new \InvalidArgumentException($message);
}
$this->messageType = $messageType;
}
/**
* @return array With all available types
*/
public static function getAvailableTypes()
{
return array(
self::OPERATING_SYSTEM,
self::SAPI,
);
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
error_log((string) $record['formatted'], $this->messageType);
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\FingersCrossed;
/**
* Interface for activation strategies for the FingersCrossedHandler.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ActivationStrategyInterface
{
/**
* Returns whether the given record activates the handler.
*
* @param array $record
* @return Boolean
*/
public function isHandlerActivated(array $record);
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\FingersCrossed;
/**
* Channel and Error level based monolog activation strategy. Allows to trigger activation
* based on level per channel. e.g. trigger activation on level 'ERROR' by default, except
* for records of the 'sql' channel; those should trigger activation on level 'WARN'.
*
* Example:
*
* <code>
* $activationStrategy = new ChannelLevelActivationStrategy(
* Logger::CRITICAL,
* array(
* 'request' => Logger::ALERT,
* 'sensitive' => Logger::ERROR,
* )
* );
* $handler = new FingersCrossedHandler(new StreamHandler('php://stderr'), $activationStrategy);
* </code>
*
* @author Mike Meessen <netmikey@gmail.com>
*/
class ChannelLevelActivationStrategy implements ActivationStrategyInterface
{
private $defaultActionLevel;
private $channelToActionLevel;
/**
* @param int $defaultActionLevel The default action level to be used if the record's category doesn't match any
* @param array $categoryToActionLevel An array that maps channel names to action levels.
*/
public function __construct($defaultActionLevel, $channelToActionLevel = array())
{
$this->defaultActionLevel = $defaultActionLevel;
$this->channelToActionLevel = $channelToActionLevel;
}
public function isHandlerActivated(array $record)
{
if (isset($this->channelToActionLevel[$record['channel']])) {
return $record['level'] >= $this->channelToActionLevel[$record['channel']];
}
return $record['level'] >= $this->defaultActionLevel;
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler\FingersCrossed;
/**
* Error level based activation strategy.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ErrorLevelActivationStrategy implements ActivationStrategyInterface
{
private $actionLevel;
public function __construct($actionLevel)
{
$this->actionLevel = $actionLevel;
}
public function isHandlerActivated(array $record)
{
return $record['level'] >= $this->actionLevel;
}
}

View File

@@ -0,0 +1,118 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
use Monolog\Handler\FingersCrossed\ActivationStrategyInterface;
use Monolog\Logger;
/**
* Buffers all records until a certain level is reached
*
* The advantage of this approach is that you don't get any clutter in your log files.
* Only requests which actually trigger an error (or whatever your actionLevel is) will be
* in the logs, but they will contain all records, not only those above the level threshold.
*
* You can find the various activation strategies in the
* Monolog\Handler\FingersCrossed\ namespace.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class FingersCrossedHandler extends AbstractHandler
{
protected $handler;
protected $activationStrategy;
protected $buffering = true;
protected $bufferSize;
protected $buffer = array();
protected $stopBuffering;
/**
* @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)
*/
public function __construct($handler, $activationStrategy = null, $bufferSize = 0, $bubble = true, $stopBuffering = true)
{
if (null === $activationStrategy) {
$activationStrategy = new ErrorLevelActivationStrategy(Logger::WARNING);
}
// convert simple int activationStrategy to an object
if (!$activationStrategy instanceof ActivationStrategyInterface) {
$activationStrategy = new ErrorLevelActivationStrategy($activationStrategy);
}
$this->handler = $handler;
$this->activationStrategy = $activationStrategy;
$this->bufferSize = $bufferSize;
$this->bubble = $bubble;
$this->stopBuffering = $stopBuffering;
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
return true;
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
if ($this->buffering) {
$this->buffer[] = $record;
if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) {
array_shift($this->buffer);
}
if ($this->activationStrategy->isHandlerActivated($record)) {
if ($this->stopBuffering) {
$this->buffering = false;
}
if (!$this->handler instanceof HandlerInterface) {
if (!is_callable($this->handler)) {
throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object");
}
$this->handler = call_user_func($this->handler, $record, $this);
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callable should return a HandlerInterface");
}
}
$this->handler->handleBatch($this->buffer);
$this->buffer = array();
}
} else {
$this->handler->handle($record);
}
return false === $this->bubble;
}
/**
* Resets the state of the handler. Stops forwarding records to the wrapped handler.
*/
public function reset()
{
$this->buffering = true;
}
}

View File

@@ -0,0 +1,184 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\WildfireFormatter;
/**
* Simple FirePHP Handler (http://www.firephp.org/), which uses the Wildfire protocol.
*
* @author Eric Clemmons (@ericclemmons) <eric@uxdriven.com>
*/
class FirePHPHandler extends AbstractProcessingHandler
{
/**
* WildFire JSON header message format
*/
const PROTOCOL_URI = 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2';
/**
* FirePHP structure for parsing messages & their presentation
*/
const STRUCTURE_URI = 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1';
/**
* Must reference a "known" plugin, otherwise headers won't display in FirePHP
*/
const PLUGIN_URI = 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3';
/**
* Header prefix for Wildfire to recognize & parse headers
*/
const HEADER_PREFIX = 'X-Wf';
/**
* Whether or not Wildfire vendor-specific headers have been generated & sent yet
*/
protected static $initialized = false;
/**
* Shared static message index between potentially multiple handlers
* @var int
*/
protected static $messageIndex = 1;
protected static $sendHeaders = true;
/**
* Base header creation function used by init headers & record headers
*
* @param array $meta Wildfire Plugin, Protocol & Structure Indexes
* @param string $message Log message
* @return array Complete header string ready for the client as key and message as value
*/
protected function createHeader(array $meta, $message)
{
$header = sprintf('%s-%s', self::HEADER_PREFIX, join('-', $meta));
return array($header => $message);
}
/**
* Creates message header from record
*
* @see createHeader()
* @param array $record
* @return string
*/
protected function createRecordHeader(array $record)
{
// Wildfire is extensible to support multiple protocols & plugins in a single request,
// but we're not taking advantage of that (yet), so we're using "1" for simplicity's sake.
return $this->createHeader(
array(1, 1, 1, self::$messageIndex++),
$record['formatted']
);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new WildfireFormatter();
}
/**
* Wildfire initialization headers to enable message parsing
*
* @see createHeader()
* @see sendHeader()
* @return array
*/
protected function getInitHeaders()
{
// Initial payload consists of required headers for Wildfire
return array_merge(
$this->createHeader(array('Protocol', 1), self::PROTOCOL_URI),
$this->createHeader(array(1, 'Structure', 1), self::STRUCTURE_URI),
$this->createHeader(array(1, 'Plugin', 1), self::PLUGIN_URI)
);
}
/**
* Send header string to the client
*
* @param string $header
* @param string $content
*/
protected function sendHeader($header, $content)
{
if (!headers_sent() && self::$sendHeaders) {
header(sprintf('%s: %s', $header, $content));
}
}
/**
* Creates & sends header for a record, ensuring init headers have been sent prior
*
* @see sendHeader()
* @see sendInitHeaders()
* @param array $record
*/
protected function write(array $record)
{
// WildFire-specific headers must be sent prior to any messages
if (!self::$initialized) {
self::$sendHeaders = $this->headersAccepted();
foreach ($this->getInitHeaders() as $header => $content) {
$this->sendHeader($header, $content);
}
self::$initialized = true;
}
$header = $this->createRecordHeader($record);
$this->sendHeader(key($header), current($header));
}
/**
* Verifies if the headers are accepted by the current user agent
*
* @return Boolean
*/
protected function headersAccepted()
{
return !isset($_SERVER['HTTP_USER_AGENT'])
|| preg_match('{\bFirePHP/\d+\.\d+\b}', $_SERVER['HTTP_USER_AGENT'])
|| isset($_SERVER['HTTP_X_FIREPHP_VERSION']);
}
/**
* BC getter for the sendHeaders property that has been made static
*/
public function __get($property)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
return static::$sendHeaders;
}
/**
* BC setter for the sendHeaders property that has been made static
*/
public function __set($property, $value)
{
if ('sendHeaders' !== $property) {
throw new \InvalidArgumentException('Undefined property '.$property);
}
static::$sendHeaders = $value;
}
}

View File

@@ -0,0 +1,66 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Gelf\IMessagePublisher;
use Monolog\Logger;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Formatter\GelfMessageFormatter;
/**
* Handler to send messages to a Graylog2 (http://www.graylog2.org) server
*
* @author Matt Lehner <mlehner@gmail.com>
*/
class GelfHandler extends AbstractProcessingHandler
{
/**
* @var Gelf\IMessagePublisher the publisher object that sends the message to the server
*/
protected $publisher;
/**
* @param Gelf\IMessagePublisher $publisher a publisher object
* @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(IMessagePublisher $publisher, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
$this->publisher = $publisher;
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->publisher = null;
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$this->publisher->publish($record['formatted']);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new GelfMessageFormatter();
}
}

View File

@@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
/**
* Forwards records to multiple handlers
*
* @author Lenar Lõhmus <lenar@city.ee>
*/
class GroupHandler extends AbstractHandler
{
protected $handlers;
/**
* @param array $handlers Array of Handlers.
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(array $handlers, $bubble = true)
{
foreach ($handlers as $handler) {
if (!$handler instanceof HandlerInterface) {
throw new \InvalidArgumentException('The first argument of the GroupHandler must be an array of HandlerInterface instances.');
}
}
$this->handlers = $handlers;
$this->bubble = $bubble;
}
/**
* {@inheritdoc}
*/
public function isHandling(array $record)
{
foreach ($this->handlers as $handler) {
if ($handler->isHandling($record)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($this->processors) {
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
}
foreach ($this->handlers as $handler) {
$handler->handle($record);
}
return false === $this->bubble;
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
foreach ($this->handlers as $handler) {
$handler->handleBatch($records);
}
}
}

View File

@@ -0,0 +1,90 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\FormatterInterface;
/**
* Interface that all Monolog Handlers must implement
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
interface HandlerInterface
{
/**
* Checks whether the given record will be handled by this handler.
*
* This is mostly done for performance reasons, to avoid calling processors for nothing.
*
* Handlers should still check the record levels within handle(), returning false in isHandling()
* is no guarantee that handle() will not be called, and isHandling() might not be called
* for a given record.
*
* @param array $record
*
* @return Boolean
*/
public function isHandling(array $record);
/**
* Handles a record.
*
* All records may be passed to this method, and the handler should discard
* those that it does not want to handle.
*
* The return value of this function controls the bubbling process of the handler stack.
* Unless the bubbling is interrupted (by returning true), the Logger class will keep on
* calling further handlers in the stack with a given log record.
*
* @param array $record The record to handle
* @return Boolean true means that this handler handled the record, and that bubbling is not permitted.
* false means the record was either not processed or that this handler allows bubbling.
*/
public function handle(array $record);
/**
* Handles a set of records at once.
*
* @param array $records The records to handle (an array of record arrays)
*/
public function handleBatch(array $records);
/**
* Adds a processor in the stack.
*
* @param callable $callback
* @return self
*/
public function pushProcessor($callback);
/**
* Removes the processor on top of the stack and returns it.
*
* @return callable
*/
public function popProcessor();
/**
* Sets the formatter.
*
* @param FormatterInterface $formatter
* @return self
*/
public function setFormatter(FormatterInterface $formatter);
/**
* Gets the formatter.
*
* @return FormatterInterface
*/
public function getFormatter();
}

View File

@@ -0,0 +1,153 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Sends notifications through the hipchat api to a hipchat room
*
* Notes:
* API token - HipChat API token
* Room - HipChat Room Id or name, where messages are sent
* Name - Name used to send the message (from)
* notify - Should the message trigger a notification in the clients
*
* @author Rafael Dohms <rafael@doh.ms>
* @see https://www.hipchat.com/docs/api
*/
class HipChatHandler extends SocketHandler
{
/**
* @var string
*/
private $token;
/**
* @var array
*/
private $room;
/**
* @var string
*/
private $name;
/**
* @var boolean
*/
private $notify;
/**
* @param string $token HipChat API Token
* @param string $room The room that should be alerted of the message (Id or Name)
* @param string $name Name used in the "from" field
* @param bool $notify Trigger a notification in clients or not
* @param int $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param Boolean $useSSL Whether to connect via SSL.
*/
public function __construct($token, $room, $name = 'Monolog', $notify = false, $level = Logger::CRITICAL, $bubble = true, $useSSL = true)
{
$connectionString = $useSSL ? 'ssl://api.hipchat.com:443' : 'api.hipchat.com:80';
parent::__construct($connectionString, $level, $bubble);
$this->token = $token;
$this->name = $name;
$this->notify = $notify;
$this->room = $room;
}
/**
* {@inheritdoc}
*
* @param array $record
* @return string
*/
protected function generateDataStream($record)
{
$content = $this->buildContent($record);
return $this->buildHeader($content) . $content;
}
/**
* Builds the body of API call
*
* @param array $record
* @return string
*/
private function buildContent($record)
{
$dataArray = array(
'from' => $this->name,
'room_id' => $this->room,
'notify' => $this->notify,
'message' => $record['formatted'],
'message_format' => 'text',
'color' => $this->getAlertColor($record['level']),
);
return http_build_query($dataArray);
}
/**
* Builds the header of the API Call
*
* @param string $content
* @return string
*/
private function buildHeader($content)
{
$header = "POST /v1/rooms/message?format=json&auth_token=".$this->token." HTTP/1.1\r\n";
$header .= "Host: api.hipchat.com\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($content) . "\r\n";
$header .= "\r\n";
return $header;
}
/**
* Assigns a color to each level of log records.
*
* @param integer $level
* @return string
*/
protected function getAlertColor($level)
{
switch (true) {
case $level >= Logger::ERROR:
return 'red';
case $level >= Logger::WARNING:
return 'yellow';
case $level >= Logger::INFO:
return 'green';
case $level == Logger::DEBUG:
return 'gray';
default:
return 'yellow';
}
}
/**
* {@inheritdoc}
*
* @param array $record
*/
public function write(array $record)
{
parent::write($record);
$this->closeSocket();
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
/**
* Base class for all mail handlers
*
* @author Gyula Sallai
*/
abstract class MailHandler extends AbstractProcessingHandler
{
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$messages = array();
foreach ($records as $record) {
if ($record['level'] < $this->level) {
continue;
}
$messages[] = $this->processRecord($record);
}
if (!empty($messages)) {
$this->send((string) $this->getFormatter()->formatBatch($messages), $messages);
}
}
/**
* Send a mail with the given content
*
* @param string $content
* @param array $records the array of log records that formed this content
*/
abstract protected function send($content, array $records);
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$this->send((string) $record['formatted'], array($record));
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
/**
* Exception can be thrown if an extension for an handler is missing
*
* @author Christian Bergau <cbergau86@gmail.com>
*/
class MissingExtensionException extends \Exception
{
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Thomas Tourlourat <thomas@tourlourat.com>
*
* 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;
use Monolog\Formatter\NormalizerFormatter;
/**
* Logs to a MongoDB database.
*
* usage example:
*
* $log = new Logger('application');
* $mongodb = new MongoDBHandler(new \Mongo("mongodb://localhost:27017"), "logs", "prod");
* $log->pushHandler($mongodb);
*
* @author Thomas Tourlourat <thomas@tourlourat.com>
*/
class MongoDBHandler extends AbstractProcessingHandler
{
private $mongoCollection;
public function __construct($mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true)
{
if (!($mongo instanceof \MongoClient || $mongo instanceof \Mongo)) {
throw new \InvalidArgumentException('MongoClient or Mongo instance required');
}
$this->mongoCollection = $mongo->selectCollection($database, $collection);
parent::__construct($level, $bubble);
}
protected function write(array $record)
{
$this->mongoCollection->save($record["formatted"]);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new NormalizerFormatter();
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* NativeMailerHandler uses the mail() function to send the emails
*
* @author Christophe Coevoet <stof@notk.org>
*/
class NativeMailerHandler extends MailHandler
{
protected $to;
protected $subject;
protected $headers = array(
'Content-type: text/plain; charset=utf-8'
);
protected $maxColumnWidth;
/**
* @param string|array $to The receiver of the mail
* @param string $subject The subject of the mail
* @param string $from The sender of the mail
* @param integer $level The minimum logging level at which this handler will be triggered
* @param boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param int $maxColumnWidth The maximum column width that the message lines will have
*/
public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true, $maxColumnWidth = 70)
{
parent::__construct($level, $bubble);
$this->to = is_array($to) ? $to : array($to);
$this->subject = $subject;
$this->addHeader(sprintf('From: %s', $from));
$this->maxColumnWidth = $maxColumnWidth;
}
/**
* @param string|array $headers Custom added headers
*/
public function addHeader($headers)
{
foreach ((array) $headers as $header) {
if (strpos($header, "\n") !== false || strpos($header, "\r") !== false) {
throw new \InvalidArgumentException('Headers can not contain newline characters for security reasons');
}
$this->headers[] = $header;
}
}
/**
* {@inheritdoc}
*/
protected function send($content, array $records)
{
$content = wordwrap($content, $this->maxColumnWidth);
$headers = implode("\r\n", $this->headers) . "\r\n";
foreach ($this->to as $to) {
mail($to, $this->subject, $content, $headers);
}
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Class to record a log on a NewRelic application
*
* @see https://newrelic.com/docs/php/new-relic-for-php
*/
class NewRelicHandler extends AbstractProcessingHandler
{
/**
* Name of the New Relic application that will receive logs from this handler.
*
* @var string
*/
protected $appName;
/**
* {@inheritDoc}
*
* @param string $appName
*/
public function __construct($level = Logger::ERROR, $bubble = true, $appName = null)
{
parent::__construct($level, $bubble);
$this->appName = $appName;
}
/**
* {@inheritDoc}
*/
protected function write(array $record)
{
if (!$this->isNewRelicEnabled()) {
throw new MissingExtensionException('The newrelic PHP extension is required to use the NewRelicHandler');
}
if ($appName = $this->getAppName($record['context'])) {
$this->setNewRelicAppName($appName);
}
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) {
newrelic_notice_error($record['message'], $record['context']['exception']);
unset($record['context']['exception']);
} else {
newrelic_notice_error($record['message']);
}
foreach ($record['context'] as $key => $parameter) {
newrelic_add_custom_parameter($key, $parameter);
}
}
/**
* Checks whether the NewRelic extension is enabled in the system.
*
* @return bool
*/
protected function isNewRelicEnabled()
{
return extension_loaded('newrelic');
}
/**
* Returns the appname where this log should be sent. Each log can override the default appname, set in this
* handler's constructor, by providing the appname in its context.
*
* @param array $context
* @return null|string
*/
protected function getAppName(array $context)
{
if (isset($context['appname'])) {
return $context['appname'];
}
return $this->appName;
}
/**
* Sets the NewRelic application that should receive this log.
*
* @param string $appName
*/
protected function setNewRelicAppName($appName)
{
newrelic_set_appname($appName);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Blackhole
*
* Any record it can handle will be thrown away. This can be used
* to put on top of an existing stack to override it temporarily.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class NullHandler extends AbstractHandler
{
/**
* @param integer $level The minimum logging level at which this handler will be triggered
*/
public function __construct($level = Logger::DEBUG)
{
parent::__construct($level, false);
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($record['level'] < $this->level) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,128 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Sends notifications through the pushover api to mobile phones
*
* @author Sebastian Göttschkes <sebastian.goettschkes@googlemail.com>
* @see https://www.pushover.net/api
*/
class PushoverHandler extends SocketHandler
{
private $token;
private $users;
private $title;
private $user;
private $retry;
private $expire;
private $highPriorityLevel;
private $emergencyLevel;
/**
* @param string $token Pushover api token
* @param string|array $users Pushover user id or array of ids the message will be sent to
* @param string $title Title sent to the Pushover API
* @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param Boolean $useSSL Whether to connect via SSL. Required when pushing messages to users that are not
* the pushover.net app owner. OpenSSL is required for this option.
* @param integer $highPriorityLevel The minimum logging level at which this handler will start
* sending "high priority" requests to the Pushover API
* @param integer $emergencyLevel The minimum logging level at which this handler will start
* sending "emergency" requests to the Pushover API
* @param integer $retry The retry parameter specifies how often (in seconds) the Pushover servers will send the same notification to the user.
* @param integer $expire The expire parameter specifies how many seconds your notification will continue to be retried for (every retry seconds).
*/
public function __construct($token, $users, $title = null, $level = Logger::CRITICAL, $bubble = true, $useSSL = true, $highPriorityLevel = Logger::CRITICAL, $emergencyLevel = Logger::EMERGENCY, $retry = 30, $expire = 25200)
{
$connectionString = $useSSL ? 'ssl://api.pushover.net:443' : 'api.pushover.net:80';
parent::__construct($connectionString, $level, $bubble);
$this->token = $token;
$this->users = (array) $users;
$this->title = $title ?: gethostname();
$this->highPriorityLevel = $highPriorityLevel;
$this->emergencyLevel = $emergencyLevel;
$this->retry = $retry;
$this->expire = $expire;
}
protected function generateDataStream($record)
{
$content = $this->buildContent($record);
return $this->buildHeader($content) . $content;
}
private function buildContent($record)
{
// Pushover has a limit of 512 characters on title and message combined.
$maxMessageLength = 512 - strlen($this->title);
$message = substr($record['message'], 0, $maxMessageLength);
$timestamp = $record['datetime']->getTimestamp();
$dataArray = array(
'token' => $this->token,
'user' => $this->user,
'message' => $message,
'title' => $this->title,
'timestamp' => $timestamp
);
if ($record['level'] >= $this->emergencyLevel) {
$dataArray['priority'] = 2;
$dataArray['retry'] = $this->retry;
$dataArray['expire'] = $this->expire;
} elseif ($record['level'] >= $this->highPriorityLevel) {
$dataArray['priority'] = 1;
}
return http_build_query($dataArray);
}
private function buildHeader($content)
{
$header = "POST /1/messages.json HTTP/1.1\r\n";
$header .= "Host: api.pushover.net\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($content) . "\r\n";
$header .= "\r\n";
return $header;
}
public function write(array $record)
{
foreach ($this->users as $user) {
$this->user = $user;
parent::write($record);
$this->closeSocket();
}
$this->user = null;
}
public function setHighPriorityLevel($value)
{
$this->highPriorityLevel = $value;
}
public function setEmergencyLevel($value)
{
$this->emergencyLevel = $value;
}
}

View File

@@ -0,0 +1,167 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\LineFormatter;
use Monolog\Formatter\FormatterInterface;
use Monolog\Logger;
use Monolog\Handler\AbstractProcessingHandler;
use Raven_Client;
/**
* Handler to send messages to a Sentry (https://github.com/dcramer/sentry) server
* using raven-php (https://github.com/getsentry/raven-php)
*
* @author Marc Abramowitz <marc@marc-abramowitz.com>
*/
class RavenHandler extends AbstractProcessingHandler
{
/**
* Translates Monolog log levels to Raven log levels.
*/
private $logLevels = array(
Logger::DEBUG => Raven_Client::DEBUG,
Logger::INFO => Raven_Client::INFO,
Logger::NOTICE => Raven_Client::INFO,
Logger::WARNING => Raven_Client::WARNING,
Logger::ERROR => Raven_Client::ERROR,
Logger::CRITICAL => Raven_Client::FATAL,
Logger::ALERT => Raven_Client::FATAL,
Logger::EMERGENCY => Raven_Client::FATAL,
);
/**
* @var Raven_Client the client object that sends the message to the server
*/
protected $ravenClient;
/**
* @var LineFormatter The formatter to use for the logs generated via handleBatch()
*/
protected $batchFormatter;
/**
* @param Raven_Client $ravenClient
* @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(Raven_Client $ravenClient, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
$this->ravenClient = $ravenClient;
}
/**
* {@inheritdoc}
*/
public function handleBatch(array $records)
{
$level = $this->level;
// filter records based on their level
$records = array_filter($records, function($record) use ($level) {
return $record['level'] >= $level;
});
if (!$records) {
return;
}
// the record with the highest severity is the "main" one
$record = array_reduce($records, function($highest, $record) {
if ($record['level'] >= $highest['level']) {
$highest = $record;
return $highest;
}
});
// the other ones are added as a context item
$logs = array();
foreach ($records as $r) {
$logs[] = $this->processRecord($r);
}
if ($logs) {
$record['context']['logs'] = (string) $this->getBatchFormatter()->formatBatch($logs);
}
$this->handle($record);
}
/**
* Sets the formatter for the logs generated by handleBatch().
*
* @param FormatterInterface $formatter
*/
public function setBatchFormatter(FormatterInterface $formatter)
{
$this->batchFormatter = $formatter;
}
/**
* Gets the formatter for the logs generated by handleBatch().
*
* @return FormatterInterface
*/
public function getBatchFormatter()
{
if (!$this->batchFormatter) {
$this->batchFormatter = $this->getDefaultBatchFormatter();
}
return $this->batchFormatter;
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$options = array();
$options['level'] = $this->logLevels[$record['level']];
if (!empty($record['context'])) {
$options['extra']['context'] = $record['context'];
}
if (!empty($record['extra'])) {
$options['extra']['extra'] = $record['extra'];
}
if (isset($record['context']['exception']) && $record['context']['exception'] instanceof \Exception) {
$options['extra']['message'] = $record['formatted'];
$this->ravenClient->captureException($record['context']['exception'], $options);
return;
}
$this->ravenClient->captureMessage($record['formatted'], array(), $options);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new LineFormatter('[%channel%] %message%');
}
/**
* Gets the default formatter for the logs generated by handleBatch().
*
* @return FormatterInterface
*/
protected function getDefaultBatchFormatter()
{
return new LineFormatter();
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Thomas Tourlourat <thomas@tourlourat.com>
*
* 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;
use Monolog\Formatter\LineFormatter;
/**
* Logs to a Redis key using rpush
*
* usage example:
*
* $log = new Logger('application');
* $redis = new RedisHandler(new Predis\Client("tcp://localhost:6379"), "logs", "prod");
* $log->pushHandler($redis);
*
* @author Thomas Tourlourat <thomas@tourlourat.com>
*/
class RedisHandler extends AbstractProcessingHandler
{
private $redisClient;
private $redisKey;
# redis instance, key to use
public function __construct($redis, $key, $level = Logger::DEBUG, $bubble = true)
{
if (!(($redis instanceof \Predis\Client) || ($redis instanceof \Redis))) {
throw new \InvalidArgumentException('Predis\Client or Redis instance required');
}
$this->redisClient = $redis;
$this->redisKey = $key;
parent::__construct($level, $bubble);
}
protected function write(array $record)
{
$this->redisClient->rpush($this->redisKey, $record["formatted"]);
}
/**
* {@inheritDoc}
*/
protected function getDefaultFormatter()
{
return new LineFormatter();
}
}

View File

@@ -0,0 +1,124 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Stores logs to files that are rotated every day and a limited number of files are kept.
*
* This rotation is only intended to be used as a workaround. Using logrotate to
* handle the rotation is strongly encouraged when you can use it.
*
* @author Christophe Coevoet <stof@notk.org>
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class RotatingFileHandler extends StreamHandler
{
protected $filename;
protected $maxFiles;
protected $mustRotate;
protected $nextRotation;
/**
* @param string $filename
* @param integer $maxFiles The maximal amount of files to keep (0 means unlimited)
* @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($filename, $maxFiles = 0, $level = Logger::DEBUG, $bubble = true)
{
$this->filename = $filename;
$this->maxFiles = (int) $maxFiles;
$this->nextRotation = new \DateTime('tomorrow');
parent::__construct($this->getTimedFilename(), $level, $bubble);
}
/**
* {@inheritdoc}
*/
public function close()
{
parent::close();
if (true === $this->mustRotate) {
$this->rotate();
}
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
// on the first record written, if the log is new, we should rotate (once per day)
if (null === $this->mustRotate) {
$this->mustRotate = !file_exists($this->url);
}
if ($this->nextRotation < $record['datetime']) {
$this->mustRotate = true;
$this->close();
}
parent::write($record);
}
/**
* Rotates the files.
*/
protected function rotate()
{
// update filename
$this->url = $this->getTimedFilename();
$this->nextRotation = new \DateTime('tomorrow');
// skip GC of old logs if files are unlimited
if (0 === $this->maxFiles) {
return;
}
$fileInfo = pathinfo($this->filename);
$glob = $fileInfo['dirname'].'/'.$fileInfo['filename'].'-*';
if (!empty($fileInfo['extension'])) {
$glob .= '.'.$fileInfo['extension'];
}
$logFiles = glob($glob);
if ($this->maxFiles >= count($logFiles)) {
// no files to remove
return;
}
// Sorting the files by name to remove the older ones
usort($logFiles, function($a, $b) {
return strcmp($b, $a);
});
foreach (array_slice($logFiles, $this->maxFiles) as $file) {
if (is_writable($file)) {
unlink($file);
}
}
}
protected function getTimedFilename()
{
$fileInfo = pathinfo($this->filename);
$timedFilename = $fileInfo['dirname'].'/'.$fileInfo['filename'].'-'.date('Y-m-d');
if (!empty($fileInfo['extension'])) {
$timedFilename .= '.'.$fileInfo['extension'];
}
return $timedFilename;
}
}

View File

@@ -0,0 +1,285 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Stores to any socket - uses fsockopen() or pfsockopen().
*
* @author Pablo de Leon Belloc <pablolb@gmail.com>
* @see http://php.net/manual/en/function.fsockopen.php
*/
class SocketHandler extends AbstractProcessingHandler
{
private $connectionString;
private $connectionTimeout;
private $resource;
private $timeout = 0;
private $persistent = false;
private $errno;
private $errstr;
/**
* @param string $connectionString Socket connection string
* @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($connectionString, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
$this->connectionString = $connectionString;
$this->connectionTimeout = (float) ini_get('default_socket_timeout');
}
/**
* Connect (if necessary) and write to the socket
*
* @param array $record
*
* @throws \UnexpectedValueException
* @throws \RuntimeException
*/
public function write(array $record)
{
$this->connectIfNotConnected();
$data = $this->generateDataStream($record);
$this->writeToSocket($data);
}
/**
* We will not close a PersistentSocket instance so it can be reused in other requests.
*/
public function close()
{
if (!$this->isPersistent()) {
$this->closeSocket();
}
}
/**
* Close socket, if open
*/
public function closeSocket()
{
if (is_resource($this->resource)) {
fclose($this->resource);
$this->resource = null;
}
}
/**
* Set socket connection to nbe persistent. It only has effect before the connection is initiated.
*
* @param type $boolean
*/
public function setPersistent($boolean)
{
$this->persistent = (boolean) $boolean;
}
/**
* Set connection timeout. Only has effect before we connect.
*
* @param float $seconds
*
* @see http://php.net/manual/en/function.fsockopen.php
*/
public function setConnectionTimeout($seconds)
{
$this->validateTimeout($seconds);
$this->connectionTimeout = (float) $seconds;
}
/**
* Set write timeout. Only has effect before we connect.
*
* @param float $seconds
*
* @see http://php.net/manual/en/function.stream-set-timeout.php
*/
public function setTimeout($seconds)
{
$this->validateTimeout($seconds);
$this->timeout = (float) $seconds;
}
/**
* Get current connection string
*
* @return string
*/
public function getConnectionString()
{
return $this->connectionString;
}
/**
* Get persistent setting
*
* @return boolean
*/
public function isPersistent()
{
return $this->persistent;
}
/**
* Get current connection timeout setting
*
* @return float
*/
public function getConnectionTimeout()
{
return $this->connectionTimeout;
}
/**
* Get current in-transfer timeout
*
* @return float
*/
public function getTimeout()
{
return $this->timeout;
}
/**
* Check to see if the socket is currently available.
*
* UDP might appear to be connected but might fail when writing. See http://php.net/fsockopen for details.
*
* @return boolean
*/
public function isConnected()
{
return is_resource($this->resource)
&& !feof($this->resource); // on TCP - other party can close connection.
}
/**
* Wrapper to allow mocking
*/
protected function pfsockopen()
{
return @pfsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout);
}
/**
* Wrapper to allow mocking
*/
protected function fsockopen()
{
return @fsockopen($this->connectionString, -1, $this->errno, $this->errstr, $this->connectionTimeout);
}
/**
* Wrapper to allow mocking
*
* @see http://php.net/manual/en/function.stream-set-timeout.php
*/
protected function streamSetTimeout()
{
$seconds = floor($this->timeout);
$microseconds = round(($this->timeout - $seconds)*1e6);
return stream_set_timeout($this->resource, $seconds, $microseconds);
}
/**
* Wrapper to allow mocking
*/
protected function fwrite($data)
{
return @fwrite($this->resource, $data);
}
/**
* Wrapper to allow mocking
*/
protected function streamGetMetadata()
{
return stream_get_meta_data($this->resource);
}
private function validateTimeout($value)
{
$ok = filter_var($value, FILTER_VALIDATE_FLOAT);
if ($ok === false || $value < 0) {
throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)");
}
}
private function connectIfNotConnected()
{
if ($this->isConnected()) {
return;
}
$this->connect();
}
protected function generateDataStream($record)
{
return (string) $record['formatted'];
}
private function connect()
{
$this->createSocketResource();
$this->setSocketTimeout();
}
private function createSocketResource()
{
if ($this->isPersistent()) {
$resource = $this->pfsockopen();
} else {
$resource = $this->fsockopen();
}
if (!$resource) {
throw new \UnexpectedValueException("Failed connecting to $this->connectionString ($this->errno: $this->errstr)");
}
$this->resource = $resource;
}
private function setSocketTimeout()
{
if (!$this->streamSetTimeout()) {
throw new \UnexpectedValueException("Failed setting timeout with stream_set_timeout()");
}
}
private function writeToSocket($data)
{
$length = strlen($data);
$sent = 0;
while ($this->isConnected() && $sent < $length) {
if (0 == $sent) {
$chunk = $this->fwrite($data);
} else {
$chunk = $this->fwrite(substr($data, $sent));
}
if ($chunk === false) {
throw new \RuntimeException("Could not write to socket");
}
$sent += $chunk;
$socketInfo = $this->streamGetMetadata();
if ($socketInfo['timed_out']) {
throw new \RuntimeException("Write timed-out");
}
}
if (!$this->isConnected() && $sent < $length) {
throw new \RuntimeException("End-of-file reached, probably we got disconnected (sent $sent of $length)");
}
}
}

View File

@@ -0,0 +1,76 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Stores to any stream resource
*
* Can be used to store into php://stderr, remote and local files, etc.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class StreamHandler extends AbstractProcessingHandler
{
protected $stream;
protected $url;
/**
* @param string $stream
* @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct($stream, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
if (is_resource($stream)) {
$this->stream = $stream;
} else {
$this->url = $stream;
}
}
/**
* {@inheritdoc}
*/
public function close()
{
if (is_resource($this->stream)) {
fclose($this->stream);
}
$this->stream = null;
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
if (null === $this->stream) {
if (!$this->url) {
throw new \LogicException('Missing stream url, the stream can not be opened. This may be caused by a premature call to close().');
}
$errorMessage = null;
set_error_handler(function ($code, $msg) use (&$errorMessage) {
$errorMessage = preg_replace('{^fopen\(.*?\): }', '', $msg);
});
$this->stream = fopen($this->url, 'a');
restore_error_handler();
if (!is_resource($this->stream)) {
$this->stream = null;
throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$errorMessage, $this->url));
}
}
fwrite($this->stream, (string) $record['formatted']);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* SwiftMailerHandler uses Swift_Mailer to send the emails
*
* @author Gyula Sallai
*/
class SwiftMailerHandler extends MailHandler
{
protected $mailer;
protected $message;
/**
* @param \Swift_Mailer $mailer The mailer to use
* @param callable|\Swift_Message $message An example message for real messages, only the body will be replaced
* @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(\Swift_Mailer $mailer, $message, $level = Logger::ERROR, $bubble = true)
{
parent::__construct($level, $bubble);
$this->mailer = $mailer;
if (!$message instanceof \Swift_Message && is_callable($message)) {
$message = call_user_func($message);
}
if (!$message instanceof \Swift_Message) {
throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it');
}
$this->message = $message;
}
/**
* {@inheritdoc}
*/
protected function send($content, array $records)
{
$message = clone $this->message;
$message->setBody($content);
$this->mailer->send($message);
}
}

View File

@@ -0,0 +1,127 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
/**
* Logs to syslog service.
*
* usage example:
*
* $log = new Logger('application');
* $syslog = new SyslogHandler('myfacility', 'local6');
* $formatter = new LineFormatter("%channel%.%level_name%: %message% %extra%");
* $syslog->setFormatter($formatter);
* $log->pushHandler($syslog);
*
* @author Sven Paulus <sven@karlsruhe.org>
*/
class SyslogHandler extends AbstractProcessingHandler
{
protected $ident;
protected $logopts;
protected $facility;
/**
* Translates Monolog log levels to syslog log priorities.
*/
private $logLevels = array(
Logger::DEBUG => LOG_DEBUG,
Logger::INFO => LOG_INFO,
Logger::NOTICE => LOG_NOTICE,
Logger::WARNING => LOG_WARNING,
Logger::ERROR => LOG_ERR,
Logger::CRITICAL => LOG_CRIT,
Logger::ALERT => LOG_ALERT,
Logger::EMERGENCY => LOG_EMERG,
);
/**
* List of valid log facility names.
*/
private $facilities = array(
'auth' => LOG_AUTH,
'authpriv' => LOG_AUTHPRIV,
'cron' => LOG_CRON,
'daemon' => LOG_DAEMON,
'kern' => LOG_KERN,
'lpr' => LOG_LPR,
'mail' => LOG_MAIL,
'news' => LOG_NEWS,
'syslog' => LOG_SYSLOG,
'user' => LOG_USER,
'uucp' => LOG_UUCP,
);
/**
* @param string $ident
* @param mixed $facility
* @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param int $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)
{
parent::__construct($level, $bubble);
if (!defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->facilities['local0'] = LOG_LOCAL0;
$this->facilities['local1'] = LOG_LOCAL1;
$this->facilities['local2'] = LOG_LOCAL2;
$this->facilities['local3'] = LOG_LOCAL3;
$this->facilities['local4'] = LOG_LOCAL4;
$this->facilities['local5'] = LOG_LOCAL5;
$this->facilities['local6'] = LOG_LOCAL6;
$this->facilities['local7'] = LOG_LOCAL7;
}
// convert textual description of facility to syslog constant
if (array_key_exists(strtolower($facility), $this->facilities)) {
$facility = $this->facilities[strtolower($facility)];
} elseif (!in_array($facility, array_values($this->facilities), true)) {
throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given');
}
$this->ident = $ident;
$this->logopts = $logopts;
$this->facility = $facility;
}
/**
* {@inheritdoc}
*/
public function close()
{
closelog();
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
if (!openlog($this->ident, $this->logopts, $this->facility)) {
throw new \LogicException('Can\'t open syslog for ident "'.$this->ident.'" and facility "'.$this->facility.'"');
}
syslog($this->logLevels[$record['level']], (string) $record['formatted']);
}
/**
* {@inheritdoc}
*/
protected function getDefaultFormatter()
{
return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%');
}
}

View File

@@ -0,0 +1,140 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Used for testing purposes.
*
* It records all records and gives you access to them for verification.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class TestHandler extends AbstractProcessingHandler
{
protected $records = array();
protected $recordsByLevel = array();
public function getRecords()
{
return $this->records;
}
public function hasEmergency($record)
{
return $this->hasRecord($record, Logger::EMERGENCY);
}
public function hasAlert($record)
{
return $this->hasRecord($record, Logger::ALERT);
}
public function hasCritical($record)
{
return $this->hasRecord($record, Logger::CRITICAL);
}
public function hasError($record)
{
return $this->hasRecord($record, Logger::ERROR);
}
public function hasWarning($record)
{
return $this->hasRecord($record, Logger::WARNING);
}
public function hasNotice($record)
{
return $this->hasRecord($record, Logger::NOTICE);
}
public function hasInfo($record)
{
return $this->hasRecord($record, Logger::INFO);
}
public function hasDebug($record)
{
return $this->hasRecord($record, Logger::DEBUG);
}
public function hasEmergencyRecords()
{
return isset($this->recordsByLevel[Logger::EMERGENCY]);
}
public function hasAlertRecords()
{
return isset($this->recordsByLevel[Logger::ALERT]);
}
public function hasCriticalRecords()
{
return isset($this->recordsByLevel[Logger::CRITICAL]);
}
public function hasErrorRecords()
{
return isset($this->recordsByLevel[Logger::ERROR]);
}
public function hasWarningRecords()
{
return isset($this->recordsByLevel[Logger::WARNING]);
}
public function hasNoticeRecords()
{
return isset($this->recordsByLevel[Logger::NOTICE]);
}
public function hasInfoRecords()
{
return isset($this->recordsByLevel[Logger::INFO]);
}
public function hasDebugRecords()
{
return isset($this->recordsByLevel[Logger::DEBUG]);
}
protected function hasRecord($record, $level)
{
if (!isset($this->recordsByLevel[$level])) {
return false;
}
if (is_array($record)) {
$record = $record['message'];
}
foreach ($this->recordsByLevel[$level] as $rec) {
if ($rec['message'] === $record) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$this->recordsByLevel[$record['level']][] = $record;
$this->records[] = $record;
}
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Formatter\NormalizerFormatter;
use Monolog\Logger;
/**
* Handler sending logs to Zend Monitor
*
* @author Christian Bergau <cbergau86@gmail.com>
*/
class ZendMonitorHandler extends AbstractProcessingHandler
{
/**
* Monolog level / ZendMonitor Custom Event priority map
*
* @var array
*/
protected $levelMap = array(
Logger::DEBUG => 1,
Logger::INFO => 2,
Logger::NOTICE => 3,
Logger::WARNING => 4,
Logger::ERROR => 5,
Logger::CRITICAL => 6,
Logger::ALERT => 7,
Logger::EMERGENCY => 0,
);
/**
* Construct
*
* @param int $level
* @param bool $bubble
* @throws MissingExtensionException
*/
public function __construct($level = Logger::DEBUG, $bubble = true)
{
if (!function_exists('zend_monitor_custom_event')) {
throw new MissingExtensionException('You must have Zend Server installed in order to use this handler');
}
parent::__construct($level, $bubble);
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$this->writeZendMonitorCustomEvent(
$this->levelMap[$record['level']],
$record['message'],
$record['formatted']
);
}
/**
* Write a record to Zend Monitor
*
* @param int $level
* @param string $message
* @param array $formatted
*/
protected function writeZendMonitorCustomEvent($level, $message, $formatted)
{
zend_monitor_custom_event($level, $message, $formatted);
}
/**
* {@inheritdoc}
*/
public function getDefaultFormatter()
{
return new NormalizerFormatter();
}
/**
* Get the level map
*
* @return array
*/
public function getLevelMap()
{
return $this->levelMap;
}
}

View File

@@ -0,0 +1,574 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog;
use Monolog\Handler\HandlerInterface;
use Monolog\Handler\StreamHandler;
use Psr\Log\LoggerInterface;
use Psr\Log\InvalidArgumentException;
/**
* Monolog log channel
*
* It contains a stack of Handlers and a stack of Processors,
* and uses them to store records that are added to it.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class Logger implements LoggerInterface
{
/**
* Detailed debug information
*/
const DEBUG = 100;
/**
* Interesting events
*
* Examples: User logs in, SQL logs.
*/
const INFO = 200;
/**
* Uncommon events
*/
const NOTICE = 250;
/**
* Exceptional occurrences that are not errors
*
* Examples: Use of deprecated APIs, poor use of an API,
* undesirable things that are not necessarily wrong.
*/
const WARNING = 300;
/**
* Runtime errors
*/
const ERROR = 400;
/**
* Critical conditions
*
* Example: Application component unavailable, unexpected exception.
*/
const CRITICAL = 500;
/**
* Action must be taken immediately
*
* Example: Entire website down, database unavailable, etc.
* This should trigger the SMS alerts and wake you up.
*/
const ALERT = 550;
/**
* Urgent alert.
*/
const EMERGENCY = 600;
/**
* Monolog API version
*
* This is only bumped when API breaks are done and should
* follow the major version of the library
*
* @var int
*/
const API = 1;
protected static $levels = array(
100 => 'DEBUG',
200 => 'INFO',
250 => 'NOTICE',
300 => 'WARNING',
400 => 'ERROR',
500 => 'CRITICAL',
550 => 'ALERT',
600 => 'EMERGENCY',
);
/**
* @var DateTimeZone
*/
protected static $timezone;
protected $name;
/**
* The handler stack
*
* @var array of Monolog\Handler\HandlerInterface
*/
protected $handlers;
/**
* Processors that will process all log records
*
* To process records of a single handler instead, add the processor on that specific handler
*
* @var array of callables
*/
protected $processors;
/**
* @param string $name The logging channel
* @param array $handlers Optional stack of handlers, the first one in the array is called first, etc.
* @param array $processors Optional array of processors
*/
public function __construct($name, array $handlers = array(), array $processors = array())
{
$this->name = $name;
$this->handlers = $handlers;
$this->processors = $processors;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Pushes a handler on to the stack.
*
* @param HandlerInterface $handler
*/
public function pushHandler(HandlerInterface $handler)
{
array_unshift($this->handlers, $handler);
}
/**
* Pops a handler from the stack
*
* @return HandlerInterface
*/
public function popHandler()
{
if (!$this->handlers) {
throw new \LogicException('You tried to pop from an empty handler stack.');
}
return array_shift($this->handlers);
}
/**
* Adds a processor on to the stack.
*
* @param callable $callback
*/
public function pushProcessor($callback)
{
if (!is_callable($callback)) {
throw new \InvalidArgumentException('Processors must be valid callables (callback or object with an __invoke method), '.var_export($callback, true).' given');
}
array_unshift($this->processors, $callback);
}
/**
* Removes the processor on top of the stack and returns it.
*
* @return callable
*/
public function popProcessor()
{
if (!$this->processors) {
throw new \LogicException('You tried to pop from an empty processor stack.');
}
return array_shift($this->processors);
}
/**
* Adds a log record.
*
* @param integer $level The logging level
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function addRecord($level, $message, array $context = array())
{
if (!$this->handlers) {
$this->pushHandler(new StreamHandler('php://stderr', static::DEBUG));
}
if (!static::$timezone) {
static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC');
}
$record = array(
'message' => (string) $message,
'context' => $context,
'level' => $level,
'level_name' => static::getLevelName($level),
'channel' => $this->name,
'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone)->setTimezone(static::$timezone),
'extra' => array(),
);
// check if any handler will handle this message
$handlerKey = null;
foreach ($this->handlers as $key => $handler) {
if ($handler->isHandling($record)) {
$handlerKey = $key;
break;
}
}
// none found
if (null === $handlerKey) {
return false;
}
// found at least one, process message and dispatch it
foreach ($this->processors as $processor) {
$record = call_user_func($processor, $record);
}
while (isset($this->handlers[$handlerKey]) &&
false === $this->handlers[$handlerKey]->handle($record)) {
$handlerKey++;
}
return true;
}
/**
* Adds a log record at the DEBUG level.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function addDebug($message, array $context = array())
{
return $this->addRecord(static::DEBUG, $message, $context);
}
/**
* Adds a log record at the INFO level.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function addInfo($message, array $context = array())
{
return $this->addRecord(static::INFO, $message, $context);
}
/**
* Adds a log record at the NOTICE level.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function addNotice($message, array $context = array())
{
return $this->addRecord(static::NOTICE, $message, $context);
}
/**
* Adds a log record at the WARNING level.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function addWarning($message, array $context = array())
{
return $this->addRecord(static::WARNING, $message, $context);
}
/**
* Adds a log record at the ERROR level.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function addError($message, array $context = array())
{
return $this->addRecord(static::ERROR, $message, $context);
}
/**
* Adds a log record at the CRITICAL level.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function addCritical($message, array $context = array())
{
return $this->addRecord(static::CRITICAL, $message, $context);
}
/**
* Adds a log record at the ALERT level.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function addAlert($message, array $context = array())
{
return $this->addRecord(static::ALERT, $message, $context);
}
/**
* Adds a log record at the EMERGENCY level.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function addEmergency($message, array $context = array())
{
return $this->addRecord(static::EMERGENCY, $message, $context);
}
/**
* Gets all supported logging levels.
*
* @return array Assoc array with human-readable level names => level codes.
*/
public static function getLevels()
{
return array_flip(static::$levels);
}
/**
* Gets the name of the logging level.
*
* @param integer $level
* @return string
*/
public static function getLevelName($level)
{
if (!isset(static::$levels[$level])) {
throw new InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels)));
}
return static::$levels[$level];
}
/**
* Checks whether the Logger has a handler that listens on the given level
*
* @param integer $level
* @return Boolean
*/
public function isHandling($level)
{
$record = array(
'level' => $level,
);
foreach ($this->handlers as $handler) {
if ($handler->isHandling($record)) {
return true;
}
}
return false;
}
/**
* Adds a log record at an arbitrary level.
*
* This method allows for compatibility with common interfaces.
*
* @param mixed $level The log level
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function log($level, $message, array $context = array())
{
if (is_string($level) && defined(__CLASS__.'::'.strtoupper($level))) {
$level = constant(__CLASS__.'::'.strtoupper($level));
}
return $this->addRecord($level, $message, $context);
}
/**
* Adds a log record at the DEBUG level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function debug($message, array $context = array())
{
return $this->addRecord(static::DEBUG, $message, $context);
}
/**
* Adds a log record at the INFO level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function info($message, array $context = array())
{
return $this->addRecord(static::INFO, $message, $context);
}
/**
* Adds a log record at the INFO level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function notice($message, array $context = array())
{
return $this->addRecord(static::NOTICE, $message, $context);
}
/**
* Adds a log record at the WARNING level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function warn($message, array $context = array())
{
return $this->addRecord(static::WARNING, $message, $context);
}
/**
* Adds a log record at the WARNING level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function warning($message, array $context = array())
{
return $this->addRecord(static::WARNING, $message, $context);
}
/**
* Adds a log record at the ERROR level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function err($message, array $context = array())
{
return $this->addRecord(static::ERROR, $message, $context);
}
/**
* Adds a log record at the ERROR level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function error($message, array $context = array())
{
return $this->addRecord(static::ERROR, $message, $context);
}
/**
* Adds a log record at the CRITICAL level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function crit($message, array $context = array())
{
return $this->addRecord(static::CRITICAL, $message, $context);
}
/**
* Adds a log record at the CRITICAL level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function critical($message, array $context = array())
{
return $this->addRecord(static::CRITICAL, $message, $context);
}
/**
* Adds a log record at the ALERT level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function alert($message, array $context = array())
{
return $this->addRecord(static::ALERT, $message, $context);
}
/**
* Adds a log record at the EMERGENCY level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function emerg($message, array $context = array())
{
return $this->addRecord(static::EMERGENCY, $message, $context);
}
/**
* Adds a log record at the EMERGENCY level.
*
* This method allows for compatibility with common interfaces.
*
* @param string $message The log message
* @param array $context The log context
* @return Boolean Whether the record has been processed
*/
public function emergency($message, array $context = array())
{
return $this->addRecord(static::EMERGENCY, $message, $context);
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Processor;
/**
* Injects line/file:class/function where the log message came from
*
* Warning: This only works if the handler processes the logs directly.
* If you put the processor on a handler that is behind a FingersCrossedHandler
* for example, the processor will only be called once the trigger level is reached,
* and all the log records will have the same file/line/.. data from the call that
* triggered the FingersCrossedHandler.
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class IntrospectionProcessor
{
/**
* @param array $record
* @return array
*/
public function __invoke(array $record)
{
$trace = debug_backtrace();
// skip first since it's always the current method
array_shift($trace);
// the call_user_func call is also skipped
array_shift($trace);
$i = 0;
while (isset($trace[$i]['class']) && false !== strpos($trace[$i]['class'], 'Monolog\\')) {
$i++;
}
// we should have the call source now
$record['extra'] = array_merge(
$record['extra'],
array(
'file' => isset($trace[$i-1]['file']) ? $trace[$i-1]['file'] : null,
'line' => isset($trace[$i-1]['line']) ? $trace[$i-1]['line'] : null,
'class' => isset($trace[$i]['class']) ? $trace[$i]['class'] : null,
'function' => isset($trace[$i]['function']) ? $trace[$i]['function'] : null,
)
);
return $record;
}
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Processor;
/**
* Injects memory_get_peak_usage in all records
*
* @see Monolog\Processor\MemoryProcessor::__construct() for options
* @author Rob Jensen
*/
class MemoryPeakUsageProcessor extends MemoryProcessor
{
/**
* @param array $record
* @return array
*/
public function __invoke(array $record)
{
$bytes = memory_get_peak_usage($this->realUsage);
$formatted = self::formatBytes($bytes);
$record['extra'] = array_merge(
$record['extra'],
array(
'memory_peak_usage' => $formatted,
)
);
return $record;
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Processor;
/**
* Some methods that are common for all memory processors
*
* @author Rob Jensen
*/
abstract class MemoryProcessor
{
protected $realUsage;
/**
* @param boolean $realUsage
*/
public function __construct($realUsage = true)
{
$this->realUsage = (boolean) $realUsage;
}
/**
* Formats bytes into a human readable string
*
* @param int $bytes
* @return string
*/
protected static function formatBytes($bytes)
{
$bytes = (int) $bytes;
if ($bytes > 1024*1024) {
return round($bytes/1024/1024, 2).' MB';
} elseif ($bytes > 1024) {
return round($bytes/1024, 2).' KB';
}
return $bytes . ' B';
}
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Processor;
/**
* Injects memory_get_usage in all records
*
* @see Monolog\Processor\MemoryProcessor::__construct() for options
* @author Rob Jensen
*/
class MemoryUsageProcessor extends MemoryProcessor
{
/**
* @param array $record
* @return array
*/
public function __invoke(array $record)
{
$bytes = memory_get_usage($this->realUsage);
$formatted = self::formatBytes($bytes);
$record['extra'] = array_merge(
$record['extra'],
array(
'memory_usage' => $formatted,
)
);
return $record;
}
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Processor;
/**
* Adds value of getmypid into records
*
* @author Andreas Hörnicke
*/
class ProcessIdProcessor
{
private static $pid;
public function __construct()
{
if (null === self::$pid) {
self::$pid = getmypid();
}
}
/**
* @param array $record
* @return array
*/
public function __invoke(array $record)
{
$record['extra']['process_id'] = self::$pid;
return $record;
}
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Processor;
/**
* Processes a record's message according to PSR-3 rules
*
* It replaces {foo} with the value from $context['foo']
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class PsrLogMessageProcessor
{
/**
* @param array $record
* @return array
*/
public function __invoke(array $record)
{
if (false === strpos($record['message'], '{')) {
return $record;
}
$replacements = array();
foreach ($record['context'] as $key => $val) {
$replacements['{'.$key.'}'] = $val;
}
$record['message'] = strtr($record['message'], $replacements);
return $record;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Processor;
/**
* Adds a unique identifier into records
*
* @author Simon Mönch <sm@webfactory.de>
*/
class UidProcessor
{
private $uid;
public function __construct($length = 7)
{
if (!is_int($length) || $length > 32 || $length < 1) {
throw new \InvalidArgumentException('The uid length must be an integer between 1 and 32');
}
$this->uid = substr(hash('md5', uniqid('', true)), 0, $length);
}
public function __invoke(array $record)
{
$record['extra']['uid'] = $this->uid;
return $record;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Processor;
/**
* Injects url/method and remote IP of the current web request in all records
*
* @author Jordi Boggiano <j.boggiano@seld.be>
*/
class WebProcessor
{
protected $serverData;
/**
* @param mixed $serverData array or object w/ ArrayAccess that provides access to the $_SERVER data
*/
public function __construct($serverData = null)
{
if (null === $serverData) {
$this->serverData =& $_SERVER;
} elseif (is_array($serverData) || $serverData instanceof \ArrayAccess) {
$this->serverData = $serverData;
} else {
throw new \UnexpectedValueException('$serverData must be an array or object implementing ArrayAccess.');
}
}
/**
* @param array $record
* @return array
*/
public function __invoke(array $record)
{
// skip processing if for some reason request data
// is not present (CLI or wonky SAPIs)
if (!isset($this->serverData['REQUEST_URI'])) {
return $record;
}
$record['extra'] = array_merge(
$record['extra'],
array(
'url' => $this->serverData['REQUEST_URI'],
'ip' => isset($this->serverData['REMOTE_ADDR']) ? $this->serverData['REMOTE_ADDR'] : null,
'http_method' => isset($this->serverData['REQUEST_METHOD']) ? $this->serverData['REQUEST_METHOD'] : null,
'server' => isset($this->serverData['SERVER_NAME']) ? $this->serverData['SERVER_NAME'] : null,
'referrer' => isset($this->serverData['HTTP_REFERER']) ? $this->serverData['HTTP_REFERER'] : null,
)
);
return $record;
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog;
use Monolog\Handler\TestHandler;
class ErrorHandlerTest extends \PHPUnit_Framework_TestCase
{
public function testHandleError()
{
$logger = new Logger('test', array($handler = new TestHandler));
$errHandler = new ErrorHandler($logger);
$errHandler->registerErrorHandler(array(E_USER_NOTICE => Logger::EMERGENCY), false);
trigger_error('Foo', E_USER_ERROR);
$this->assertCount(1, $handler->getRecords());
$this->assertTrue($handler->hasErrorRecords());
trigger_error('Foo', E_USER_NOTICE);
$this->assertCount(2, $handler->getRecords());
$this->assertTrue($handler->hasEmergencyRecords());
}
}

View File

@@ -0,0 +1,158 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
class ChromePHPFormatterTest extends \PHPUnit_Framework_TestCase
{
/**
* @covers Monolog\Formatter\ChromePHPFormatter::format
*/
public function testDefaultFormat()
{
$formatter = new ChromePHPFormatter();
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('ip' => '127.0.0.1'),
'message' => 'log',
);
$message = $formatter->format($record);
$this->assertEquals(
array(
'meh',
array(
'message' => 'log',
'context' => array('from' => 'logger'),
'extra' => array('ip' => '127.0.0.1'),
),
'unknown',
'error'
),
$message
);
}
/**
* @covers Monolog\Formatter\ChromePHPFormatter::format
*/
public function testFormatWithFileAndLine()
{
$formatter = new ChromePHPFormatter();
$record = array(
'level' => Logger::CRITICAL,
'level_name' => 'CRITICAL',
'channel' => 'meh',
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14),
'message' => 'log',
);
$message = $formatter->format($record);
$this->assertEquals(
array(
'meh',
array(
'message' => 'log',
'context' => array('from' => 'logger'),
'extra' => array('ip' => '127.0.0.1'),
),
'test : 14',
'error'
),
$message
);
}
/**
* @covers Monolog\Formatter\ChromePHPFormatter::format
*/
public function testFormatWithoutContext()
{
$formatter = new ChromePHPFormatter();
$record = array(
'level' => Logger::DEBUG,
'level_name' => 'DEBUG',
'channel' => 'meh',
'context' => array(),
'datetime' => new \DateTime("@0"),
'extra' => array(),
'message' => 'log',
);
$message = $formatter->format($record);
$this->assertEquals(
array(
'meh',
'log',
'unknown',
'log'
),
$message
);
}
/**
* @covers Monolog\Formatter\ChromePHPFormatter::formatBatch
*/
public function testBatchFormatThrowException()
{
$formatter = new ChromePHPFormatter();
$records = array(
array(
'level' => Logger::INFO,
'level_name' => 'INFO',
'channel' => 'meh',
'context' => array(),
'datetime' => new \DateTime("@0"),
'extra' => array(),
'message' => 'log',
),
array(
'level' => Logger::WARNING,
'level_name' => 'WARNING',
'channel' => 'foo',
'context' => array(),
'datetime' => new \DateTime("@0"),
'extra' => array(),
'message' => 'log2',
),
);
$this->assertEquals(
array(
array(
'meh',
'log',
'unknown',
'info'
),
array(
'foo',
'log2',
'unknown',
'warn'
),
),
$formatter->formatBatch($records)
);
}
}

View File

@@ -0,0 +1,158 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
use Monolog\Formatter\GelfMessageFormatter;
class GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
if (!class_exists("Gelf\Message")) {
$this->markTestSkipped("mlehner/gelf-php not installed");
}
}
/**
* @covers Monolog\Formatter\GelfMessageFormatter::format
*/
public function testDefaultFormatter()
{
$formatter = new GelfMessageFormatter();
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array(),
'datetime' => new \DateTime("@0"),
'extra' => array(),
'message' => 'log',
);
$message = $formatter->format($record);
$this->assertInstanceOf('Gelf\Message', $message);
$this->assertEquals(0, $message->getTimestamp());
$this->assertEquals('log', $message->getShortMessage());
$this->assertEquals('meh', $message->getFacility());
$this->assertEquals(null, $message->getLine());
$this->assertEquals(null, $message->getFile());
$this->assertEquals(3, $message->getLevel());
$this->assertNotEmpty($message->getHost());
$formatter = new GelfMessageFormatter('mysystem');
$message = $formatter->format($record);
$this->assertInstanceOf('Gelf\Message', $message);
$this->assertEquals('mysystem', $message->getHost());
}
/**
* @covers Monolog\Formatter\GelfMessageFormatter::format
*/
public function testFormatWithFileAndLine()
{
$formatter = new GelfMessageFormatter();
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('file' => 'test', 'line' => 14),
'message' => 'log',
);
$message = $formatter->format($record);
$this->assertInstanceOf('Gelf\Message', $message);
$this->assertEquals('test', $message->getFile());
$this->assertEquals(14, $message->getLine());
}
/**
* @covers Monolog\Formatter\GelfMessageFormatter::format
*/
public function testFormatWithContext()
{
$formatter = new GelfMessageFormatter();
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
);
$message = $formatter->format($record);
$this->assertInstanceOf('Gelf\Message', $message);
$message_array = $message->toArray();
$this->assertArrayHasKey('_ctxt_from', $message_array);
$this->assertEquals('logger', $message_array['_ctxt_from']);
// Test with extraPrefix
$formatter = new GelfMessageFormatter(null, null, 'CTX');
$message = $formatter->format($record);
$this->assertInstanceOf('Gelf\Message', $message);
$message_array = $message->toArray();
$this->assertArrayHasKey('_CTXfrom', $message_array);
$this->assertEquals('logger', $message_array['_CTXfrom']);
}
/**
* @covers Monolog\Formatter\GelfMessageFormatter::format
*/
public function testFormatWithExtra()
{
$formatter = new GelfMessageFormatter();
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
);
$message = $formatter->format($record);
$this->assertInstanceOf('Gelf\Message', $message);
$message_array = $message->toArray();
$this->assertArrayHasKey('_key', $message_array);
$this->assertEquals('pair', $message_array['_key']);
// Test with extraPrefix
$formatter = new GelfMessageFormatter(null, 'EXT');
$message = $formatter->format($record);
$this->assertInstanceOf('Gelf\Message', $message);
$message_array = $message->toArray();
$this->assertArrayHasKey('_EXTkey', $message_array);
$this->assertEquals('pair', $message_array['_EXTkey']);
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
use Monolog\TestCase;
class JsonFormatterTest extends TestCase
{
/**
* @covers Monolog\Formatter\JsonFormatter::format
*/
public function testFormat()
{
$formatter = new JsonFormatter();
$record = $this->getRecord();
$this->assertEquals(json_encode($record), $formatter->format($record));
}
/**
* @covers Monolog\Formatter\JsonFormatter::formatBatch
*/
public function testFormatBatch()
{
$formatter = new JsonFormatter();
$records = array(
$this->getRecord(Logger::WARNING),
$this->getRecord(Logger::DEBUG),
);
$this->assertEquals(json_encode($records), $formatter->formatBatch($records));
}
}

View File

@@ -0,0 +1,164 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* @covers Monolog\Formatter\LineFormatter
*/
class LineFormatterTest extends \PHPUnit_Framework_TestCase
{
public function testDefFormatWithString()
{
$formatter = new LineFormatter(null, 'Y-m-d');
$message = $formatter->format(array(
'level_name' => 'WARNING',
'channel' => 'log',
'context' => array(),
'message' => 'foo',
'datetime' => new \DateTime,
'extra' => array(),
));
$this->assertEquals('['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message);
}
public function testDefFormatWithArrayContext()
{
$formatter = new LineFormatter(null, 'Y-m-d');
$message = $formatter->format(array(
'level_name' => 'ERROR',
'channel' => 'meh',
'message' => 'foo',
'datetime' => new \DateTime,
'extra' => array(),
'context' => array(
'foo' => 'bar',
'baz' => 'qux',
)
));
$this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foo {"foo":"bar","baz":"qux"} []'."\n", $message);
}
public function testDefFormatExtras()
{
$formatter = new LineFormatter(null, 'Y-m-d');
$message = $formatter->format(array(
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array(),
'datetime' => new \DateTime,
'extra' => array('ip' => '127.0.0.1'),
'message' => 'log',
));
$this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] {"ip":"127.0.0.1"}'."\n", $message);
}
public function testFormatExtras()
{
$formatter = new LineFormatter("[%datetime%] %channel%.%level_name%: %message% %context% %extra.file% %extra%\n", 'Y-m-d');
$message = $formatter->format(array(
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array(),
'datetime' => new \DateTime,
'extra' => array('ip' => '127.0.0.1', 'file' => 'test'),
'message' => 'log',
));
$this->assertEquals('['.date('Y-m-d').'] meh.ERROR: log [] test {"ip":"127.0.0.1"}'."\n", $message);
}
public function testDefFormatWithObject()
{
$formatter = new LineFormatter(null, 'Y-m-d');
$message = $formatter->format(array(
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array(),
'datetime' => new \DateTime,
'extra' => array('foo' => new TestFoo, 'bar' => new TestBar, 'baz' => array(), 'res' => fopen('php://memory', 'rb')),
'message' => 'foobar',
));
$this->assertEquals('['.date('Y-m-d').'] meh.ERROR: foobar [] {"foo":"[object] (Monolog\\\\Formatter\\\\TestFoo: {\\"foo\\":\\"foo\\"})","bar":"[object] (Monolog\\\\Formatter\\\\TestBar: {})","baz":[],"res":"[resource]"}'."\n", $message);
}
public function testDefFormatWithException()
{
$formatter = new LineFormatter(null, 'Y-m-d');
$message = $formatter->format(array(
'level_name' => 'CRITICAL',
'channel' => 'core',
'context' => array('exception' => new \RuntimeException('Foo')),
'datetime' => new \DateTime,
'extra' => array(),
'message' => 'foobar',
));
$path = str_replace('\\/', '/', json_encode(__FILE__));
$this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException: Foo at '.substr($path, 1, -1).':'.(__LINE__-8).')"} []'."\n", $message);
}
public function testDefFormatWithPreviousException()
{
$formatter = new LineFormatter(null, 'Y-m-d');
$previous = new \LogicException('Wut?');
$message = $formatter->format(array(
'level_name' => 'CRITICAL',
'channel' => 'core',
'context' => array('exception' => new \RuntimeException('Foo', 0, $previous)),
'datetime' => new \DateTime,
'extra' => array(),
'message' => 'foobar',
));
$path = str_replace('\\/', '/', json_encode(__FILE__));
$this->assertEquals('['.date('Y-m-d').'] core.CRITICAL: foobar {"exception":"[object] (RuntimeException: Foo at '.substr($path, 1, -1).':'.(__LINE__-8).', LogicException: Wut? at '.substr($path, 1, -1).':'.(__LINE__-12).')"} []'."\n", $message);
}
public function testBatchFormat()
{
$formatter = new LineFormatter(null, 'Y-m-d');
$message = $formatter->formatBatch(array(
array(
'level_name' => 'CRITICAL',
'channel' => 'test',
'message' => 'bar',
'context' => array(),
'datetime' => new \DateTime,
'extra' => array(),
),
array(
'level_name' => 'WARNING',
'channel' => 'log',
'message' => 'foo',
'context' => array(),
'datetime' => new \DateTime,
'extra' => array(),
),
));
$this->assertEquals('['.date('Y-m-d').'] test.CRITICAL: bar [] []'."\n".'['.date('Y-m-d').'] log.WARNING: foo [] []'."\n", $message);
}
}
class TestFoo
{
public $foo = 'foo';
}
class TestBar
{
public function __toString()
{
return 'bar';
}
}

View File

@@ -0,0 +1,160 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
use Monolog\Formatter\LogstashFormatter;
class LogstashFormatterTest extends \PHPUnit_Framework_TestCase
{
/**
* @covers Monolog\Formatter\LogstashFormatter::format
*/
public function testDefaultFormatter()
{
$formatter = new LogstashFormatter('test', 'hostname');
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array(),
'datetime' => new \DateTime("@0"),
'extra' => array(),
'message' => 'log',
);
$message = json_decode($formatter->format($record), true);
$this->assertEquals("1970-01-01T00:00:00+00:00", $message['@timestamp']);
$this->assertEquals('log', $message['@message']);
$this->assertEquals('meh', $message['@fields']['channel']);
$this->assertContains('meh', $message['@tags']);
$this->assertEquals(Logger::ERROR, $message['@fields']['level']);
$this->assertEquals('test', $message['@type']);
$this->assertEquals('hostname', $message['@source']);
$formatter = new LogstashFormatter('mysystem');
$message = json_decode($formatter->format($record), true);
$this->assertEquals('mysystem', $message['@type']);
}
/**
* @covers Monolog\Formatter\LogstashFormatter::format
*/
public function testFormatWithFileAndLine()
{
$formatter = new LogstashFormatter('test');
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('file' => 'test', 'line' => 14),
'message' => 'log',
);
$message = json_decode($formatter->format($record), true);
$this->assertEquals('test', $message['@fields']['file']);
$this->assertEquals(14, $message['@fields']['line']);
}
/**
* @covers Monolog\Formatter\LogstashFormatter::format
*/
public function testFormatWithContext()
{
$formatter = new LogstashFormatter('test');
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
);
$message = json_decode($formatter->format($record), true);
$message_array = $message['@fields'];
$this->assertArrayHasKey('ctxt_from', $message_array);
$this->assertEquals('logger', $message_array['ctxt_from']);
// Test with extraPrefix
$formatter = new LogstashFormatter('test', null, null, 'CTX');
$message = json_decode($formatter->format($record), true);
$message_array = $message['@fields'];
$this->assertArrayHasKey('CTXfrom', $message_array);
$this->assertEquals('logger', $message_array['CTXfrom']);
}
/**
* @covers Monolog\Formatter\LogstashFormatter::format
*/
public function testFormatWithExtra()
{
$formatter = new LogstashFormatter('test');
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
);
$message = json_decode($formatter->format($record), true);
$message_array = $message['@fields'];
$this->assertArrayHasKey('key', $message_array);
$this->assertEquals('pair', $message_array['key']);
// Test with extraPrefix
$formatter = new LogstashFormatter('test', null, 'EXT');
$message = json_decode($formatter->format($record), true);
$message_array = $message['@fields'];
$this->assertArrayHasKey('EXTkey', $message_array);
$this->assertEquals('pair', $message_array['EXTkey']);
}
public function testFormatWithApplicationName()
{
$formatter = new LogstashFormatter('app', 'test');
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('key' => 'pair'),
'message' => 'log'
);
$message = json_decode($formatter->format($record), true);
$this->assertArrayHasKey('@type', $message);
$this->assertEquals('app', $message['@type']);
}
}

View File

@@ -0,0 +1,182 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
/**
* @covers Monolog\Formatter\NormalizerFormatter
*/
class NormalizerFormatterTest extends \PHPUnit_Framework_TestCase
{
public function testFormat()
{
$formatter = new NormalizerFormatter('Y-m-d');
$formatted = $formatter->format(array(
'level_name' => 'ERROR',
'channel' => 'meh',
'message' => 'foo',
'datetime' => new \DateTime,
'extra' => array('foo' => new TestFooNorm, 'bar' => new TestBarNorm, 'baz' => array(), 'res' => fopen('php://memory', 'rb')),
'context' => array(
'foo' => 'bar',
'baz' => 'qux',
),
));
$this->assertEquals(array(
'level_name' => 'ERROR',
'channel' => 'meh',
'message' => 'foo',
'datetime' => date('Y-m-d'),
'extra' => array(
'foo' => '[object] (Monolog\\Formatter\\TestFooNorm: {"foo":"foo"})',
'bar' => '[object] (Monolog\\Formatter\\TestBarNorm: {})',
'baz' => array(),
'res' => '[resource]',
),
'context' => array(
'foo' => 'bar',
'baz' => 'qux',
)
), $formatted);
}
public function testFormatExceptions()
{
$formatter = new NormalizerFormatter('Y-m-d');
$e = new \LogicException('bar');
$e2 = new \RuntimeException('foo', 0, $e);
$formatted = $formatter->format(array(
'exception' => $e2,
));
$this->assertGreaterThan(5, count($formatted['exception']['trace']));
$this->assertTrue(isset($formatted['exception']['previous']));
unset($formatted['exception']['trace'], $formatted['exception']['previous']);
$this->assertEquals(array(
'exception' => array(
'class' => get_class($e2),
'message' => $e2->getMessage(),
'file' => $e2->getFile().':'.$e2->getLine(),
)
), $formatted);
}
public function testBatchFormat()
{
$formatter = new NormalizerFormatter('Y-m-d');
$formatted = $formatter->formatBatch(array(
array(
'level_name' => 'CRITICAL',
'channel' => 'test',
'message' => 'bar',
'context' => array(),
'datetime' => new \DateTime,
'extra' => array(),
),
array(
'level_name' => 'WARNING',
'channel' => 'log',
'message' => 'foo',
'context' => array(),
'datetime' => new \DateTime,
'extra' => array(),
),
));
$this->assertEquals(array(
array(
'level_name' => 'CRITICAL',
'channel' => 'test',
'message' => 'bar',
'context' => array(),
'datetime' => date('Y-m-d'),
'extra' => array(),
),
array(
'level_name' => 'WARNING',
'channel' => 'log',
'message' => 'foo',
'context' => array(),
'datetime' => date('Y-m-d'),
'extra' => array(),
),
), $formatted);
}
/**
* Test issue #137
*/
public function testIgnoresRecursiveObjectReferences()
{
// set up the recursion
$foo = new \stdClass();
$bar = new \stdClass();
$foo->bar = $bar;
$bar->foo = $foo;
// set an error handler to assert that the error is not raised anymore
$that = $this;
set_error_handler(function ($level, $message, $file, $line, $context) use ($that) {
if (error_reporting() & $level) {
restore_error_handler();
$that->fail("$message should not be raised");
}
});
$formatter = new NormalizerFormatter();
$reflMethod = new \ReflectionMethod($formatter, 'toJson');
$reflMethod->setAccessible(true);
$res = $reflMethod->invoke($formatter, array($foo, $bar), true);
restore_error_handler();
$this->assertEquals(@json_encode(array($foo, $bar)), $res);
}
public function testIgnoresInvalidTypes()
{
// set up the recursion
$resource = fopen(__FILE__, 'r');
// set an error handler to assert that the error is not raised anymore
$that = $this;
set_error_handler(function ($level, $message, $file, $line, $context) use ($that) {
if (error_reporting() & $level) {
restore_error_handler();
$that->fail("$message should not be raised");
}
});
$formatter = new NormalizerFormatter();
$reflMethod = new \ReflectionMethod($formatter, 'toJson');
$reflMethod->setAccessible(true);
$res = $reflMethod->invoke($formatter, array($resource), true);
restore_error_handler();
$this->assertEquals(@json_encode(array($resource)), $res);
}
}
class TestFooNorm
{
public $foo = 'foo';
}
class TestBarNorm
{
public function __toString()
{
return 'bar';
}
}

View File

@@ -0,0 +1,111 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Formatter;
use Monolog\Logger;
class WildfireFormatterTest extends \PHPUnit_Framework_TestCase
{
/**
* @covers Monolog\Formatter\WildfireFormatter::format
*/
public function testDefaultFormat()
{
$wildfire = new WildfireFormatter();
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('ip' => '127.0.0.1'),
'message' => 'log',
);
$message = $wildfire->format($record);
$this->assertEquals(
'125|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},'
.'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|',
$message
);
}
/**
* @covers Monolog\Formatter\WildfireFormatter::format
*/
public function testFormatWithFileAndLine()
{
$wildfire = new WildfireFormatter();
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array('from' => 'logger'),
'datetime' => new \DateTime("@0"),
'extra' => array('ip' => '127.0.0.1', 'file' => 'test', 'line' => 14),
'message' => 'log',
);
$message = $wildfire->format($record);
$this->assertEquals(
'129|[{"Type":"ERROR","File":"test","Line":14,"Label":"meh"},'
.'{"message":"log","context":{"from":"logger"},"extra":{"ip":"127.0.0.1"}}]|',
$message
);
}
/**
* @covers Monolog\Formatter\WildfireFormatter::format
*/
public function testFormatWithoutContext()
{
$wildfire = new WildfireFormatter();
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array(),
'datetime' => new \DateTime("@0"),
'extra' => array(),
'message' => 'log',
);
$message = $wildfire->format($record);
$this->assertEquals(
'58|[{"Type":"ERROR","File":"","Line":"","Label":"meh"},"log"]|',
$message
);
}
/**
* @covers Monolog\Formatter\WildfireFormatter::formatBatch
* @expectedException BadMethodCallException
*/
public function testBatchFormatThrowException()
{
$wildfire = new WildfireFormatter();
$record = array(
'level' => Logger::ERROR,
'level_name' => 'ERROR',
'channel' => 'meh',
'context' => array(),
'datetime' => new \DateTime("@0"),
'extra' => array(),
'message' => 'log',
);
$wildfire->formatBatch(array($record));
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
spl_autoload_register(function($class) {
$file = __DIR__.'/../../../../src/'.strtr($class, '\\', '/').'.php';
if (file_exists($file)) {
require $file;
return true;
}
});
use Monolog\Logger;
use Monolog\Handler\FirePHPHandler;
use Monolog\Handler\ChromePHPHandler;
$logger = new Logger('firephp');
$logger->pushHandler(new FirePHPHandler);
$logger->pushHandler(new ChromePHPHandler());
$logger->addDebug('Debug');
$logger->addInfo('Info');
$logger->addWarning('Warning');
$logger->addError('Error');

View File

@@ -0,0 +1,104 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
use Monolog\Processor\WebProcessor;
class AbstractHandlerTest extends TestCase
{
/**
* @covers Monolog\Handler\AbstractHandler::__construct
* @covers Monolog\Handler\AbstractHandler::getLevel
* @covers Monolog\Handler\AbstractHandler::setLevel
* @covers Monolog\Handler\AbstractHandler::getBubble
* @covers Monolog\Handler\AbstractHandler::setBubble
* @covers Monolog\Handler\AbstractHandler::getFormatter
* @covers Monolog\Handler\AbstractHandler::setFormatter
*/
public function testConstructAndGetSet()
{
$handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false));
$this->assertEquals(Logger::WARNING, $handler->getLevel());
$this->assertEquals(false, $handler->getBubble());
$handler->setLevel(Logger::ERROR);
$handler->setBubble(true);
$handler->setFormatter($formatter = new LineFormatter);
$this->assertEquals(Logger::ERROR, $handler->getLevel());
$this->assertEquals(true, $handler->getBubble());
$this->assertSame($formatter, $handler->getFormatter());
}
/**
* @covers Monolog\Handler\AbstractHandler::handleBatch
*/
public function testHandleBatch()
{
$handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler');
$handler->expects($this->exactly(2))
->method('handle');
$handler->handleBatch(array($this->getRecord(), $this->getRecord()));
}
/**
* @covers Monolog\Handler\AbstractHandler::isHandling
*/
public function testIsHandling()
{
$handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler', array(Logger::WARNING, false));
$this->assertTrue($handler->isHandling($this->getRecord()));
$this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG)));
}
/**
* @covers Monolog\Handler\AbstractHandler::getFormatter
* @covers Monolog\Handler\AbstractHandler::getDefaultFormatter
*/
public function testGetFormatterInitializesDefault()
{
$handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler');
$this->assertInstanceOf('Monolog\Formatter\LineFormatter', $handler->getFormatter());
}
/**
* @covers Monolog\Handler\AbstractHandler::pushProcessor
* @covers Monolog\Handler\AbstractHandler::popProcessor
* @expectedException LogicException
*/
public function testPushPopProcessor()
{
$logger = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler');
$processor1 = new WebProcessor;
$processor2 = new WebProcessor;
$logger->pushProcessor($processor1);
$logger->pushProcessor($processor2);
$this->assertEquals($processor2, $logger->popProcessor());
$this->assertEquals($processor1, $logger->popProcessor());
$logger->popProcessor();
}
/**
* @covers Monolog\Handler\AbstractHandler::pushProcessor
* @expectedException InvalidArgumentException
*/
public function testPushProcessorWithNonCallable()
{
$handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractHandler');
$handler->pushProcessor(new \stdClass());
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
use Monolog\Processor\WebProcessor;
class AbstractProcessingHandlerTest extends TestCase
{
/**
* @covers Monolog\Handler\AbstractProcessingHandler::handle
*/
public function testHandleLowerLevelMessage()
{
$handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, true));
$this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG)));
}
/**
* @covers Monolog\Handler\AbstractProcessingHandler::handle
*/
public function testHandleBubbling()
{
$handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, true));
$this->assertFalse($handler->handle($this->getRecord()));
}
/**
* @covers Monolog\Handler\AbstractProcessingHandler::handle
*/
public function testHandleNotBubbling()
{
$handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::DEBUG, false));
$this->assertTrue($handler->handle($this->getRecord()));
}
/**
* @covers Monolog\Handler\AbstractProcessingHandler::handle
*/
public function testHandleIsFalseWhenNotHandled()
{
$handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler', array(Logger::WARNING, false));
$this->assertTrue($handler->handle($this->getRecord()));
$this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG)));
}
/**
* @covers Monolog\Handler\AbstractProcessingHandler::processRecord
*/
public function testProcessRecord()
{
$handler = $this->getMockForAbstractClass('Monolog\Handler\AbstractProcessingHandler');
$handler->pushProcessor(new WebProcessor(array(
'REQUEST_URI' => '',
'REQUEST_METHOD' => '',
'REMOTE_ADDR' => '',
'SERVER_NAME' => '',
)));
$handledRecord = null;
$handler->expects($this->once())
->method('write')
->will($this->returnCallback(function($record) use (&$handledRecord) {
$handledRecord = $record;
}))
;
$handler->handle($this->getRecord());
$this->assertEquals(5, count($handledRecord['extra']));
}
}

View File

@@ -0,0 +1,38 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
class AmqpExchangeMock extends \AMQPExchange
{
protected $messages = array();
public function __construct()
{
}
public function publish($message, $routing_key, $params = 0, $attributes = array())
{
$this->messages[] = array($message, $routing_key, $params, $attributes);
return true;
}
public function getMessages()
{
return $this->messages;
}
public function setName($name)
{
return true;
}
}

View File

@@ -0,0 +1,74 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
/**
* @covers Monolog\Handler\RotatingFileHandler
*/
class AmqpHandlerTest extends TestCase
{
public function setUp()
{
if (!class_exists('AMQPConnection') || !class_exists('AMQPExchange')) {
$this->markTestSkipped("amqp-php not installed");
}
if (!class_exists('AMQPChannel')) {
$this->markTestSkipped("Please update AMQP to version >= 1.0");
}
}
public function testHandle()
{
$exchange = $this->getExchange();
$handler = new AmqpHandler($exchange, 'log');
$record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
$expected = array(
array(
'message' => 'test',
'context' => array(
'data' => array(),
'foo' => 34,
),
'level' => 300,
'level_name' => 'WARNING',
'channel' => 'test',
'extra' => array(),
),
'warn.test',
0,
array(
'delivery_mode' => 2,
'Content-type' => 'application/json'
)
);
$handler->handle($record);
$messages = $exchange->getMessages();
$this->assertCount(1, $messages);
$messages[0][0] = json_decode($messages[0][0], true);
unset($messages[0][0]['datetime']);
$this->assertEquals($expected, $messages[0]);
}
protected function getExchange()
{
return new AmqpExchangeMock();
}
}

View File

@@ -0,0 +1,149 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
class BufferHandlerTest extends TestCase
{
/**
* @covers Monolog\Handler\BufferHandler::__construct
* @covers Monolog\Handler\BufferHandler::handle
* @covers Monolog\Handler\BufferHandler::close
*/
public function testHandleBuffers()
{
$test = new TestHandler();
$handler = new BufferHandler($test);
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::INFO));
$this->assertFalse($test->hasDebugRecords());
$this->assertFalse($test->hasInfoRecords());
$handler->close();
$this->assertTrue($test->hasInfoRecords());
$this->assertTrue(count($test->getRecords()) === 2);
}
/**
* @covers Monolog\Handler\BufferHandler::close
* @covers Monolog\Handler\BufferHandler::flush
*/
public function testDestructPropagatesRecords()
{
$test = new TestHandler();
$handler = new BufferHandler($test);
$handler->handle($this->getRecord(Logger::WARNING));
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->__destruct();
$this->assertTrue($test->hasWarningRecords());
$this->assertTrue($test->hasDebugRecords());
}
/**
* @covers Monolog\Handler\BufferHandler::handle
*/
public function testHandleBufferLimit()
{
$test = new TestHandler();
$handler = new BufferHandler($test, 2);
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::INFO));
$handler->handle($this->getRecord(Logger::WARNING));
$handler->close();
$this->assertTrue($test->hasWarningRecords());
$this->assertTrue($test->hasInfoRecords());
$this->assertFalse($test->hasDebugRecords());
}
/**
* @covers Monolog\Handler\BufferHandler::handle
*/
public function testHandleBufferLimitWithFlushOnOverflow()
{
$test = new TestHandler();
$handler = new BufferHandler($test, 3, Logger::DEBUG, true, true);
// send two records
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::DEBUG));
$this->assertFalse($test->hasDebugRecords());
$this->assertCount(0, $test->getRecords());
// overflow
$handler->handle($this->getRecord(Logger::INFO));
$this->assertTrue($test->hasDebugRecords());
$this->assertCount(3, $test->getRecords());
// should buffer again
$handler->handle($this->getRecord(Logger::WARNING));
$this->assertCount(3, $test->getRecords());
$handler->close();
$this->assertCount(5, $test->getRecords());
$this->assertTrue($test->hasWarningRecords());
$this->assertTrue($test->hasInfoRecords());
}
/**
* @covers Monolog\Handler\BufferHandler::handle
*/
public function testHandleLevel()
{
$test = new TestHandler();
$handler = new BufferHandler($test, 0, Logger::INFO);
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::INFO));
$handler->handle($this->getRecord(Logger::WARNING));
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->close();
$this->assertTrue($test->hasWarningRecords());
$this->assertTrue($test->hasInfoRecords());
$this->assertFalse($test->hasDebugRecords());
}
/**
* @covers Monolog\Handler\BufferHandler::flush
*/
public function testFlush()
{
$test = new TestHandler();
$handler = new BufferHandler($test, 0);
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::INFO));
$handler->flush();
$this->assertTrue($test->hasInfoRecords());
$this->assertTrue($test->hasDebugRecords());
$this->assertFalse($test->hasWarningRecords());
}
/**
* @covers Monolog\Handler\BufferHandler::handle
*/
public function testHandleUsesProcessors()
{
$test = new TestHandler();
$handler = new BufferHandler($test);
$handler->pushProcessor(function ($record) {
$record['extra']['foo'] = true;
return $record;
});
$handler->handle($this->getRecord(Logger::WARNING));
$handler->flush();
$this->assertTrue($test->hasWarningRecords());
$records = $test->getRecords();
$this->assertTrue($records[0]['extra']['foo']);
}
}

View File

@@ -0,0 +1,139 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
/**
* @covers Monolog\Handler\ChromePHPHandler
*/
class ChromePHPHandlerTest extends TestCase
{
protected function setUp()
{
TestChromePHPHandler::reset();
}
public function testHeaders()
{
$handler = new TestChromePHPHandler();
$handler->setFormatter($this->getIdentityFormatter());
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::WARNING));
$expected = array(
'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array(
'version' => ChromePHPHandler::VERSION,
'columns' => array('label', 'log', 'backtrace', 'type'),
'rows' => array(
'test',
'test',
),
'request_uri' => '',
))))
);
$this->assertEquals($expected, $handler->getHeaders());
}
public function testHeadersOverflow()
{
$handler = new TestChromePHPHandler();
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 150*1024)));
// overflow chrome headers limit
$handler->handle($this->getRecord(Logger::WARNING, str_repeat('a', 100*1024)));
$expected = array(
'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array(
'version' => ChromePHPHandler::VERSION,
'columns' => array('label', 'log', 'backtrace', 'type'),
'rows' => array(
array(
'test',
'test',
'unknown',
'log',
),
array(
'test',
str_repeat('a', 150*1024),
'unknown',
'warn',
),
array(
'monolog',
'Incomplete logs, chrome header size limit reached',
'unknown',
'warn',
),
),
'request_uri' => '',
))))
);
$this->assertEquals($expected, $handler->getHeaders());
}
public function testConcurrentHandlers()
{
$handler = new TestChromePHPHandler();
$handler->setFormatter($this->getIdentityFormatter());
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::WARNING));
$handler2 = new TestChromePHPHandler();
$handler2->setFormatter($this->getIdentityFormatter());
$handler2->handle($this->getRecord(Logger::DEBUG));
$handler2->handle($this->getRecord(Logger::WARNING));
$expected = array(
'X-ChromeLogger-Data' => base64_encode(utf8_encode(json_encode(array(
'version' => ChromePHPHandler::VERSION,
'columns' => array('label', 'log', 'backtrace', 'type'),
'rows' => array(
'test',
'test',
'test',
'test',
),
'request_uri' => '',
))))
);
$this->assertEquals($expected, $handler2->getHeaders());
}
}
class TestChromePHPHandler extends ChromePHPHandler
{
protected $headers = array();
public static function reset()
{
self::$initialized = false;
self::$overflowed = false;
self::$json['rows'] = array();
}
protected function sendHeader($header, $content)
{
$this->headers[$header] = $content;
}
public function getHeaders()
{
return $this->headers;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
class CouchDBHandlerTest extends TestCase
{
public function testHandle()
{
$record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
$expected = array(
'message' => 'test',
'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34),
'level' => Logger::WARNING,
'level_name' => 'WARNING',
'channel' => 'test',
'datetime' => $record['datetime']->format('Y-m-d H:i:s'),
'extra' => array(),
);
$handler = new CouchDBHandler();
try {
$handler->handle($record);
} catch (\RuntimeException $e) {
$this->markTestSkipped('Could not connect to couchdb server on http://localhost:5984');
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
class DoctrineCouchDBHandlerTest extends TestCase
{
protected function setup()
{
if (!class_exists('Doctrine\CouchDB\CouchDBClient')) {
$this->markTestSkipped('The "doctrine/couchdb" package is not installed');
}
}
public function testHandle()
{
$client = $this->getMockBuilder('Doctrine\\CouchDB\\CouchDBClient')
->setMethods(array('postDocument'))
->disableOriginalConstructor()
->getMock();
$record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
$expected = array(
'message' => 'test',
'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34),
'level' => Logger::WARNING,
'level_name' => 'WARNING',
'channel' => 'test',
'datetime' => $record['datetime']->format('Y-m-d H:i:s'),
'extra' => array(),
);
$client->expects($this->once())
->method('postDocument')
->with($expected);
$handler = new DoctrineCouchDBHandler($client);
$handler->handle($record);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
function error_log()
{
$GLOBALS['error_log'] = func_get_args();
}
class ErrorLogHandlerTest extends TestCase
{
protected function setUp()
{
$GLOBALS['error_log'] = array();
}
/**
* @covers Monolog\Handler\ErrorLogHandler::__construct
* @expectedException InvalidArgumentException
* @expectedExceptionMessage The given message type "42" is not supported
*/
public function testShouldNotAcceptAnInvalidTypeOnContructor()
{
new ErrorLogHandler(42);
}
/**
* @covers Monolog\Handler\ErrorLogHandler::write
*/
public function testShouldLogMessagesUsingErrorLogFuncion()
{
$type = ErrorLogHandler::OPERATING_SYSTEM;
$handler = new ErrorLogHandler($type);
$handler->handle($this->getRecord(Logger::ERROR));
$this->assertStringMatchesFormat('[%s] test.ERROR: test [] []', $GLOBALS['error_log'][0]);
$this->assertSame($GLOBALS['error_log'][1], $type);
}
}

View File

@@ -0,0 +1,189 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
use Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy;
use Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy;
class FingersCrossedHandlerTest extends TestCase
{
/**
* @covers Monolog\Handler\FingersCrossedHandler::__construct
* @covers Monolog\Handler\FingersCrossedHandler::handle
*/
public function testHandleBuffers()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test);
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::INFO));
$this->assertFalse($test->hasDebugRecords());
$this->assertFalse($test->hasInfoRecords());
$handler->handle($this->getRecord(Logger::WARNING));
$this->assertTrue($test->hasInfoRecords());
$this->assertTrue(count($test->getRecords()) === 3);
}
/**
* @covers Monolog\Handler\FingersCrossedHandler::handle
*/
public function testHandleStopsBufferingAfterTrigger()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test);
$handler->handle($this->getRecord(Logger::WARNING));
$handler->handle($this->getRecord(Logger::DEBUG));
$this->assertTrue($test->hasWarningRecords());
$this->assertTrue($test->hasDebugRecords());
}
/**
* @covers Monolog\Handler\FingersCrossedHandler::handle
* @covers Monolog\Handler\FingersCrossedHandler::reset
*/
public function testHandleRestartBufferingAfterReset()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test);
$handler->handle($this->getRecord(Logger::WARNING));
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->reset();
$handler->handle($this->getRecord(Logger::INFO));
$this->assertTrue($test->hasWarningRecords());
$this->assertTrue($test->hasDebugRecords());
$this->assertFalse($test->hasInfoRecords());
}
/**
* @covers Monolog\Handler\FingersCrossedHandler::handle
*/
public function testHandleRestartBufferingAfterBeingTriggeredWhenStopBufferingIsDisabled()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test, Logger::WARNING, 0, false, false);
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::WARNING));
$handler->handle($this->getRecord(Logger::INFO));
$this->assertTrue($test->hasWarningRecords());
$this->assertTrue($test->hasDebugRecords());
$this->assertFalse($test->hasInfoRecords());
}
/**
* @covers Monolog\Handler\FingersCrossedHandler::handle
*/
public function testHandleBufferLimit()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test, Logger::WARNING, 2);
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::INFO));
$handler->handle($this->getRecord(Logger::WARNING));
$this->assertTrue($test->hasWarningRecords());
$this->assertTrue($test->hasInfoRecords());
$this->assertFalse($test->hasDebugRecords());
}
/**
* @covers Monolog\Handler\FingersCrossedHandler::handle
*/
public function testHandleWithCallback()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler(function($record, $handler) use ($test) {
return $test;
});
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::INFO));
$this->assertFalse($test->hasDebugRecords());
$this->assertFalse($test->hasInfoRecords());
$handler->handle($this->getRecord(Logger::WARNING));
$this->assertTrue($test->hasInfoRecords());
$this->assertTrue(count($test->getRecords()) === 3);
}
/**
* @covers Monolog\Handler\FingersCrossedHandler::handle
* @expectedException RuntimeException
*/
public function testHandleWithBadCallbackThrowsException()
{
$handler = new FingersCrossedHandler(function($record, $handler) {
return 'foo';
});
$handler->handle($this->getRecord(Logger::WARNING));
}
/**
* @covers Monolog\Handler\FingersCrossedHandler::isHandling
*/
public function testIsHandlingAlways()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test, Logger::ERROR);
$this->assertTrue($handler->isHandling($this->getRecord(Logger::DEBUG)));
}
/**
* @covers Monolog\Handler\FingersCrossedHandler::__construct
* @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::__construct
* @covers Monolog\Handler\FingersCrossed\ErrorLevelActivationStrategy::isHandlerActivated
*/
public function testErrorLevelActivationStrategy()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test, new ErrorLevelActivationStrategy(Logger::WARNING));
$handler->handle($this->getRecord(Logger::DEBUG));
$this->assertFalse($test->hasDebugRecords());
$handler->handle($this->getRecord(Logger::WARNING));
$this->assertTrue($test->hasDebugRecords());
$this->assertTrue($test->hasWarningRecords());
}
/**
* @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::__construct
* @covers Monolog\Handler\FingersCrossed\ChannelLevelActivationStrategy::isHandlerActivated
*/
public function testChannelLevelActivationStrategy()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test, new ChannelLevelActivationStrategy(Logger::ERROR, array('othertest' => Logger::DEBUG)));
$handler->handle($this->getRecord(Logger::WARNING));
$this->assertFalse($test->hasWarningRecords());
$record = $this->getRecord(Logger::DEBUG);
$record['channel'] = 'othertest';
$handler->handle($record);
$this->assertTrue($test->hasDebugRecords());
$this->assertTrue($test->hasWarningRecords());
}
/**
* @covers Monolog\Handler\FingersCrossedHandler::handle
*/
public function testHandleUsesProcessors()
{
$test = new TestHandler();
$handler = new FingersCrossedHandler($test, Logger::INFO);
$handler->pushProcessor(function ($record) {
$record['extra']['foo'] = true;
return $record;
});
$handler->handle($this->getRecord(Logger::WARNING));
$this->assertTrue($test->hasWarningRecords());
$records = $test->getRecords();
$this->assertTrue($records[0]['extra']['foo']);
}
}

View File

@@ -0,0 +1,94 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
/**
* @covers Monolog\Handler\FirePHPHandler
*/
class FirePHPHandlerTest extends TestCase
{
public function setUp()
{
TestFirePHPHandler::reset();
}
public function testHeaders()
{
$handler = new TestFirePHPHandler;
$handler->setFormatter($this->getIdentityFormatter());
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::WARNING));
$expected = array(
'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2',
'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1',
'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3',
'X-Wf-1-1-1-1' => 'test',
'X-Wf-1-1-1-2' => 'test',
);
$this->assertEquals($expected, $handler->getHeaders());
}
public function testConcurrentHandlers()
{
$handler = new TestFirePHPHandler;
$handler->setFormatter($this->getIdentityFormatter());
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::WARNING));
$handler2 = new TestFirePHPHandler;
$handler2->setFormatter($this->getIdentityFormatter());
$handler2->handle($this->getRecord(Logger::DEBUG));
$handler2->handle($this->getRecord(Logger::WARNING));
$expected = array(
'X-Wf-Protocol-1' => 'http://meta.wildfirehq.org/Protocol/JsonStream/0.2',
'X-Wf-1-Structure-1' => 'http://meta.firephp.org/Wildfire/Structure/FirePHP/FirebugConsole/0.1',
'X-Wf-1-Plugin-1' => 'http://meta.firephp.org/Wildfire/Plugin/FirePHP/Library-FirePHPCore/0.3',
'X-Wf-1-1-1-1' => 'test',
'X-Wf-1-1-1-2' => 'test',
);
$expected2 = array(
'X-Wf-1-1-1-3' => 'test',
'X-Wf-1-1-1-4' => 'test',
);
$this->assertEquals($expected, $handler->getHeaders());
$this->assertEquals($expected2, $handler2->getHeaders());
}
}
class TestFirePHPHandler extends FirePHPHandler
{
protected $headers = array();
public static function reset()
{
self::$initialized = false;
self::$messageIndex = 1;
}
protected function sendHeader($header, $content)
{
$this->headers[$header] = $content;
}
public function getHeaders()
{
return $this->headers;
}
}

View File

@@ -0,0 +1,94 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
use Monolog\Formatter\GelfMessageFormatter;
class GelfHandlerTest extends TestCase
{
public function setUp()
{
if (!class_exists("Gelf\MessagePublisher") || !class_exists("Gelf\Message")) {
$this->markTestSkipped("mlehner/gelf-php not installed");
}
require_once __DIR__ . '/GelfMocks.php';
}
/**
* @covers Monolog\Handler\GelfHandler::__construct
*/
public function testConstruct()
{
$handler = new GelfHandler($this->getMessagePublisher());
$this->assertInstanceOf('Monolog\Handler\GelfHandler', $handler);
}
protected function getHandler($messagePublisher)
{
$handler = new GelfHandler($messagePublisher);
return $handler;
}
protected function getMessagePublisher()
{
return new MockMessagePublisher('localhost');
}
public function testDebug()
{
$messagePublisher = $this->getMessagePublisher();
$handler = $this->getHandler($messagePublisher);
$record = $this->getRecord(Logger::DEBUG, "A test debug message");
$handler->handle($record);
$this->assertEquals(7, $messagePublisher->lastMessage->getLevel());
$this->assertEquals('test', $messagePublisher->lastMessage->getFacility());
$this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage());
$this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage());
}
public function testWarning()
{
$messagePublisher = $this->getMessagePublisher();
$handler = $this->getHandler($messagePublisher);
$record = $this->getRecord(Logger::WARNING, "A test warning message");
$handler->handle($record);
$this->assertEquals(4, $messagePublisher->lastMessage->getLevel());
$this->assertEquals('test', $messagePublisher->lastMessage->getFacility());
$this->assertEquals($record['message'], $messagePublisher->lastMessage->getShortMessage());
$this->assertEquals(null, $messagePublisher->lastMessage->getFullMessage());
}
public function testInjectedGelfMessageFormatter()
{
$messagePublisher = $this->getMessagePublisher();
$handler = $this->getHandler($messagePublisher);
$handler->setFormatter(new GelfMessageFormatter('mysystem', 'EXT', 'CTX'));
$record = $this->getRecord(Logger::WARNING, "A test warning message");
$record['extra']['blarg'] = 'yep';
$record['context']['from'] = 'logger';
$handler->handle($record);
$this->assertEquals('mysystem', $messagePublisher->lastMessage->getHost());
$this->assertArrayHasKey('_EXTblarg', $messagePublisher->lastMessage->toArray());
$this->assertArrayHasKey('_CTXfrom', $messagePublisher->lastMessage->toArray());
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Gelf\MessagePublisher;
use Gelf\Message;
class MockMessagePublisher extends MessagePublisher
{
public function publish(Message $message)
{
$this->lastMessage = $message;
}
public $lastMessage = null;
}

View File

@@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
class GroupHandlerTest extends TestCase
{
/**
* @covers Monolog\Handler\GroupHandler::__construct
* @expectedException InvalidArgumentException
*/
public function testConstructorOnlyTakesHandler()
{
new GroupHandler(array(new TestHandler(), "foo"));
}
/**
* @covers Monolog\Handler\GroupHandler::__construct
* @covers Monolog\Handler\GroupHandler::handle
*/
public function testHandle()
{
$testHandlers = array(new TestHandler(), new TestHandler());
$handler = new GroupHandler($testHandlers);
$handler->handle($this->getRecord(Logger::DEBUG));
$handler->handle($this->getRecord(Logger::INFO));
foreach ($testHandlers as $test) {
$this->assertTrue($test->hasDebugRecords());
$this->assertTrue($test->hasInfoRecords());
$this->assertTrue(count($test->getRecords()) === 2);
}
}
/**
* @covers Monolog\Handler\GroupHandler::handleBatch
*/
public function testHandleBatch()
{
$testHandlers = array(new TestHandler(), new TestHandler());
$handler = new GroupHandler($testHandlers);
$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);
}
}
/**
* @covers Monolog\Handler\GroupHandler::isHandling
*/
public function testIsHandling()
{
$testHandlers = array(new TestHandler(Logger::ERROR), new TestHandler(Logger::WARNING));
$handler = new GroupHandler($testHandlers);
$this->assertTrue($handler->isHandling($this->getRecord(Logger::ERROR)));
$this->assertTrue($handler->isHandling($this->getRecord(Logger::WARNING)));
$this->assertFalse($handler->isHandling($this->getRecord(Logger::DEBUG)));
}
/**
* @covers Monolog\Handler\GroupHandler::handle
*/
public function testHandleUsesProcessors()
{
$test = new TestHandler();
$handler = new GroupHandler(array($test));
$handler->pushProcessor(function ($record) {
$record['extra']['foo'] = true;
return $record;
});
$handler->handle($this->getRecord(Logger::WARNING));
$this->assertTrue($test->hasWarningRecords());
$records = $test->getRecords();
$this->assertTrue($records[0]['extra']['foo']);
}
}

View File

@@ -0,0 +1,110 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
/**
* @author Rafael Dohms <rafael@doh.ms>
* @see https://www.hipchat.com/docs/api
*/
class HipChatHandlerTest extends TestCase
{
private $res;
private $handler;
public function testWriteHeader()
{
$this->createHandler();
$this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$this->assertRegexp('/POST \/v1\/rooms\/message\?format=json&auth_token=.* HTTP\/1.1\\r\\nHost: api.hipchat.com\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content);
return $content;
}
/**
* @depends testWriteHeader
*/
public function testWriteContent($content)
{
$this->assertRegexp('/from=Monolog&room_id=room1&notify=0&message=test1&message_format=text&color=red$/', $content);
}
public function testWriteWithComplexMessage()
{
$this->createHandler();
$this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.'));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content);
}
/**
* @dataProvider provideLevelColors
*/
public function testWriteWithErrorLevelsAndColors($level, $expectedColor)
{
$this->createHandler();
$this->handler->handle($this->getRecord($level, 'Backup of database "example" finished in 16 minutes.'));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$this->assertRegexp('/color='.$expectedColor.'/', $content);
}
public function provideLevelColors()
{
return array(
array(Logger::DEBUG, 'gray'),
array(Logger::INFO, 'green'),
array(Logger::WARNING, 'yellow'),
array(Logger::ERROR, 'red'),
array(Logger::CRITICAL, 'red'),
array(Logger::ALERT, 'red'),
array(Logger::EMERGENCY,'red'),
array(Logger::NOTICE, 'green'),
);
}
private function createHandler($token = 'myToken', $room = 'room1', $name = 'Monolog', $notify = false)
{
$constructorArgs = array($token, $room, $name, $notify, Logger::DEBUG);
$this->res = fopen('php://memory', 'a');
$this->handler = $this->getMock(
'\Monolog\Handler\HipChatHandler',
array('fsockopen', 'streamSetTimeout', 'closeSocket'),
$constructorArgs
);
$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->res));
$this->handler->expects($this->any())
->method('streamSetTimeout')
->will($this->returnValue(true));
$this->handler->expects($this->any())
->method('closeSocket')
->will($this->returnValue(true));
$this->handler->setFormatter($this->getIdentityFormatter());
}
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\TestCase;
class MailHandlerTest extends TestCase
{
/**
* @covers Monolog\Handler\MailHandler::handleBatch
*/
public function testHandleBatch()
{
$formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
$formatter->expects($this->once())
->method('formatBatch'); // Each record is formatted
$handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler');
$handler->expects($this->once())
->method('send');
$handler->expects($this->never())
->method('write'); // write is for individual records
$handler->setFormatter($formatter);
$handler->handleBatch($this->getMultipleRecords());
}
/**
* @covers Monolog\Handler\MailHandler::handleBatch
*/
public function testHandleBatchNotSendsMailIfMessagesAreBelowLevel()
{
$records = array(
$this->getRecord(Logger::DEBUG, 'debug message 1'),
$this->getRecord(Logger::DEBUG, 'debug message 2'),
$this->getRecord(Logger::INFO, 'information'),
);
$handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler');
$handler->expects($this->never())
->method('send');
$handler->setLevel(Logger::ERROR);
$handler->handleBatch($records);
}
/**
* @covers Monolog\Handler\MailHandler::write
*/
public function testHandle()
{
$handler = $this->getMockForAbstractClass('Monolog\\Handler\\MailHandler');
$record = $this->getRecord();
$records = array($record);
$records[0]['formatted'] = '['.$record['datetime']->format('Y-m-d H:i:s').'] test.WARNING: test [] []'."\n";
$handler->expects($this->once())
->method('send')
->with($records[0]['formatted'], $records);
$handler->handle($record);
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Raven_Client;
class MockRavenClient extends Raven_Client
{
public function capture($data, $stack, $vars = null)
{
$this->lastData = $data;
$this->lastStack = $stack;
}
public $lastData;
public $lastStack;
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
class MongoDBHandlerTest extends TestCase
{
/**
* @expectedException InvalidArgumentException
*/
public function testConstructorShouldThrowExceptionForInvalidMongo()
{
new MongoDBHandler(new \stdClass(), 'DB', 'Collection');
}
public function testHandle()
{
$mongo = $this->getMock('Mongo', array('selectCollection'));
$collection = $this->getMock('stdClass', array('save'));
$mongo->expects($this->once())
->method('selectCollection')
->with('DB', 'Collection')
->will($this->returnValue($collection));
$record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
$expected = array(
'message' => 'test',
'context' => array('data' => '[object] (stdClass: {})', 'foo' => 34),
'level' => Logger::WARNING,
'level_name' => 'WARNING',
'channel' => 'test',
'datetime' => $record['datetime']->format('Y-m-d H:i:s'),
'extra' => array(),
);
$collection->expects($this->once())
->method('save')
->with($expected);
$handler = new MongoDBHandler($mongo, 'DB', 'Collection');
$handler->handle($record);
}
}
if (!class_exists('Mongo')) {
class Mongo
{
public function selectCollection() {}
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
class NativeMailerHandlerTest extends TestCase
{
/**
* @expectedException InvalidArgumentException
*/
public function testConstructorHeaderInjection()
{
$mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', "receiver@example.org\r\nFrom: faked@attacker.org");
}
/**
* @expectedException InvalidArgumentException
*/
public function testSetterHeaderInjection()
{
$mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org');
$mailer->addHeader("Content-Type: text/html\r\nFrom: faked@attacker.org");
}
/**
* @expectedException InvalidArgumentException
*/
public function testSetterArrayHeaderInjection()
{
$mailer = new NativeMailerHandler('spammer@example.org', 'dear victim', 'receiver@example.org');
$mailer->addHeader(array("Content-Type: text/html\r\nFrom: faked@attacker.org"));
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
use Psr\Log\LogLevel;
class NewRelicHandlerTest extends TestCase
{
public static $appname;
public function setUp()
{
$this::$appname = null;
}
/**
* @expectedException Monolog\Handler\MissingExtensionException
*/
public function testThehandlerThrowsAnExceptionIfTheNRExtensionIsNotLoaded()
{
$handler = new StubNewRelicHandlerWithoutExtension();
$handler->handle($this->getRecord(Logger::ERROR));
}
public function testThehandlerCanHandleTheRecord()
{
$handler = new StubNewRelicHandler();
$handler->handle($this->getRecord(Logger::ERROR));
}
public function testThehandlerCanAddParamsToTheNewRelicTrace()
{
$handler = new StubNewRelicHandler();
$handler->handle($this->getRecord(Logger::ERROR, 'log message', array('a' => 'b')));
}
public function testTheAppNameIsNullByDefault()
{
$handler = new StubNewRelicHandler();
$handler->handle($this->getRecord(Logger::ERROR, 'log message'));
$this->assertEquals(null, $this::$appname);
}
public function testTheAppNameCanBeInjectedFromtheConstructor()
{
$handler = new StubNewRelicHandler(LogLevel::ALERT, false, 'myAppName');
$handler->handle($this->getRecord(Logger::ERROR, 'log message'));
$this->assertEquals('myAppName', $this::$appname);
}
public function testTheAppNameCanBeOverriddenFromEachLog()
{
$handler = new StubNewRelicHandler(LogLevel::ALERT, false, 'myAppName');
$handler->handle($this->getRecord(Logger::ERROR, 'log message', array('appname' => 'logAppName')));
$this->assertEquals('logAppName', $this::$appname);
}
}
class StubNewRelicHandlerWithoutExtension extends NewRelicHandler
{
protected function isNewRelicEnabled()
{
return false;
}
}
class StubNewRelicHandler extends NewRelicHandler
{
protected function isNewRelicEnabled()
{
return true;
}
}
function newrelic_notice_error()
{
return true;
}
function newrelic_set_appname($appname)
{
return NewRelicHandlerTest::$appname = $appname;
}
function newrelic_add_custom_parameter()
{
return true;
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
/**
* @covers Monolog\Handler\NullHandler::handle
*/
class NullHandlerTest extends TestCase
{
public function testHandle()
{
$handler = new NullHandler();
$this->assertTrue($handler->handle($this->getRecord()));
}
public function testHandleLowerLevelRecord()
{
$handler = new NullHandler(Logger::WARNING);
$this->assertFalse($handler->handle($this->getRecord(Logger::DEBUG)));
}
}

View File

@@ -0,0 +1,142 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
/**
* Almost all examples (expected header, titles, messages) taken from
* https://www.pushover.net/api
* @author Sebastian Göttschkes <sebastian.goettschkes@googlemail.com>
* @see https://www.pushover.net/api
*/
class PushoverHandlerTest extends TestCase
{
private $res;
private $handler;
public function testWriteHeader()
{
$this->createHandler();
$this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications
$this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$this->assertRegexp('/POST \/1\/messages.json HTTP\/1.1\\r\\nHost: api.pushover.net\\r\\nContent-Type: application\/x-www-form-urlencoded\\r\\nContent-Length: \d{2,4}\\r\\n\\r\\n/', $content);
return $content;
}
/**
* @depends testWriteHeader
*/
public function testWriteContent($content)
{
$this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog&timestamp=\d{10}$/', $content);
}
public function testWriteWithComplexTitle()
{
$this->createHandler('myToken', 'myUser', 'Backup finished - SQL1', Logger::EMERGENCY);
$this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$this->assertRegexp('/title=Backup\+finished\+-\+SQL1/', $content);
}
public function testWriteWithComplexMessage()
{
$this->createHandler();
$this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications
$this->handler->handle($this->getRecord(Logger::CRITICAL, 'Backup of database "example" finished in 16 minutes.'));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$this->assertRegexp('/message=Backup\+of\+database\+%22example%22\+finished\+in\+16\+minutes\./', $content);
}
public function testWriteWithTooLongMessage()
{
$message = str_pad('test', 520, 'a');
$this->createHandler();
$this->handler->setHighPriorityLevel(Logger::EMERGENCY); // skip priority notifications
$this->handler->handle($this->getRecord(Logger::CRITICAL, $message));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$expectedMessage = substr($message, 0, 505);
$this->assertRegexp('/message=' . $expectedMessage . '&title/', $content);
}
public function testWriteWithHighPriority()
{
$this->createHandler();
$this->handler->handle($this->getRecord(Logger::CRITICAL, 'test1'));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog&timestamp=\d{10}&priority=1$/', $content);
}
public function testWriteWithEmergencyPriority()
{
$this->createHandler();
$this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1'));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$this->assertRegexp('/token=myToken&user=myUser&message=test1&title=Monolog&timestamp=\d{10}&priority=2&retry=30&expire=25200$/', $content);
}
public function testWriteToMultipleUsers()
{
$this->createHandler('myToken', array('userA', 'userB'));
$this->handler->handle($this->getRecord(Logger::EMERGENCY, 'test1'));
fseek($this->res, 0);
$content = fread($this->res, 1024);
$this->assertRegexp('/token=myToken&user=userA&message=test1&title=Monolog&timestamp=\d{10}&priority=2&retry=30&expire=25200POST/', $content);
$this->assertRegexp('/token=myToken&user=userB&message=test1&title=Monolog&timestamp=\d{10}&priority=2&retry=30&expire=25200$/', $content);
}
private function createHandler($token = 'myToken', $user = 'myUser', $title = 'Monolog')
{
$constructorArgs = array($token, $user, $title);
$this->res = fopen('php://memory', 'a');
$this->handler = $this->getMock(
'\Monolog\Handler\PushoverHandler',
array('fsockopen', 'streamSetTimeout', 'closeSocket'),
$constructorArgs
);
$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->res));
$this->handler->expects($this->any())
->method('streamSetTimeout')
->will($this->returnValue(true));
$this->handler->expects($this->any())
->method('closeSocket')
->will($this->returnValue(true));
$this->handler->setFormatter($this->getIdentityFormatter());
}
}

View File

@@ -0,0 +1,135 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RavenHandler;
class RavenHandlerTest extends TestCase
{
public function setUp()
{
if (!class_exists("Raven_Client")) {
$this->markTestSkipped("raven/raven not installed");
}
require_once __DIR__ . '/MockRavenClient.php';
}
/**
* @covers Monolog\Handler\RavenHandler::__construct
*/
public function testConstruct()
{
$handler = new RavenHandler($this->getRavenClient());
$this->assertInstanceOf('Monolog\Handler\RavenHandler', $handler);
}
protected function getHandler($ravenClient)
{
$handler = new RavenHandler($ravenClient);
return $handler;
}
protected function getRavenClient()
{
$dsn = 'http://43f6017361224d098402974103bfc53d:a6a0538fc2934ba2bed32e08741b2cd3@marca.python.live.cheggnet.com:9000/1';
return new MockRavenClient($dsn);
}
public function testDebug()
{
$ravenClient = $this->getRavenClient();
$handler = $this->getHandler($ravenClient);
$record = $this->getRecord(Logger::DEBUG, "A test debug message");
$handler->handle($record);
$this->assertEquals($ravenClient::DEBUG, $ravenClient->lastData['level']);
$this->assertContains($record['message'], $ravenClient->lastData['message']);
}
public function testWarning()
{
$ravenClient = $this->getRavenClient();
$handler = $this->getHandler($ravenClient);
$record = $this->getRecord(Logger::WARNING, "A test warning message");
$handler->handle($record);
$this->assertEquals($ravenClient::WARNING, $ravenClient->lastData['level']);
$this->assertContains($record['message'], $ravenClient->lastData['message']);
}
public function testException()
{
$ravenClient = $this->getRavenClient();
$handler = $this->getHandler($ravenClient);
try {
$this->methodThatThrowsAnException();
} catch (\Exception $e) {
$record = $this->getRecord(Logger::ERROR, $e->getMessage(), array('exception' => $e));
$handler->handle($record);
}
$this->assertEquals($record['message'], $ravenClient->lastData['message']);
}
public function testHandleBatch()
{
$records = $this->getMultipleRecords();
$logFormatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
$logFormatter->expects($this->once())->method('formatBatch');
$formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
$formatter->expects($this->once())->method('format');
$handler = $this->getHandler($this->getRavenClient());
$handler->setBatchFormatter($logFormatter);
$handler->setFormatter($formatter);
$handler->handleBatch($records);
}
public function testHandleBatchDoNothingIfRecordsAreBelowLevel()
{
$records = array(
$this->getRecord(Logger::DEBUG, 'debug message 1'),
$this->getRecord(Logger::DEBUG, 'debug message 2'),
$this->getRecord(Logger::INFO, 'information'),
);
$handler = $this->getMock('Monolog\Handler\RavenHandler', null, array($this->getRavenClient()));
$handler->expects($this->never())->method('handle');
$handler->setLevel(Logger::ERROR);
$handler->handleBatch($records);
}
public function testGetSetBatchFormatter()
{
$ravenClient = $this->getRavenClient();
$handler = $this->getHandler($ravenClient);
$handler->setBatchFormatter($formatter = new LineFormatter());
$this->assertSame($formatter, $handler->getBatchFormatter());
}
private function methodThatThrowsAnException()
{
throw new \Exception('This is an exception');
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
class RedisHandlerTest extends TestCase
{
/**
* @expectedException InvalidArgumentException
*/
public function testConstructorShouldThrowExceptionForInvalidRedis()
{
new RedisHandler(new \stdClass(), 'key');
}
public function testConstructorShouldWorkWithPredis()
{
$redis = $this->getMock('Predis\Client');
$this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key'));
}
public function testConstructorShouldWorkWithRedis()
{
$redis = $this->getMock('Redis');
$this->assertInstanceof('Monolog\Handler\RedisHandler', new RedisHandler($redis, 'key'));
}
public function testPredisHandle()
{
$redis = $this->getMock('Predis\Client', array('rpush'));
// Predis\Client uses rpush
$redis->expects($this->once())
->method('rpush')
->with('key', 'test');
$record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
$handler = new RedisHandler($redis, 'key');
$handler->setFormatter(new LineFormatter("%message%"));
$handler->handle($record);
}
public function testRedisHandle()
{
$redis = $this->getMock('Redis', array('rpush'));
// Redis uses rPush
$redis->expects($this->once())
->method('rPush')
->with('key', 'test');
$record = $this->getRecord(Logger::WARNING, 'test', array('data' => new \stdClass, 'foo' => 34));
$handler = new RedisHandler($redis, 'key');
$handler->setFormatter(new LineFormatter("%message%"));
$handler->handle($record);
}
}

View File

@@ -0,0 +1,99 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
/**
* @covers Monolog\Handler\RotatingFileHandler
*/
class RotatingFileHandlerTest extends TestCase
{
public function setUp()
{
$dir = __DIR__.'/Fixtures';
chmod($dir, 0777);
if (!is_writable($dir)) {
$this->markTestSkipped($dir.' must be writeable to test the RotatingFileHandler.');
}
}
public function testRotationCreatesNewFile()
{
touch(__DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot');
$handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot');
$handler->setFormatter($this->getIdentityFormatter());
$handler->handle($this->getRecord());
$log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot';
$this->assertTrue(file_exists($log));
$this->assertEquals('test', file_get_contents($log));
}
/**
* @dataProvider rotationTests
*/
public function testRotation($createFile)
{
touch($old1 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400).'.rot');
touch($old2 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400 * 2).'.rot');
touch($old3 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400 * 3).'.rot');
touch($old4 = __DIR__.'/Fixtures/foo-'.date('Y-m-d', time() - 86400 * 4).'.rot');
$log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot';
if ($createFile) {
touch($log);
}
$handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot', 2);
$handler->setFormatter($this->getIdentityFormatter());
$handler->handle($this->getRecord());
$handler->close();
$this->assertTrue(file_exists($log));
$this->assertTrue(file_exists($old1));
$this->assertEquals($createFile, file_exists($old2));
$this->assertEquals($createFile, file_exists($old3));
$this->assertEquals($createFile, file_exists($old4));
$this->assertEquals('test', file_get_contents($log));
}
public function rotationTests()
{
return array(
'Rotation is triggered when the file of the current day is not present'
=> array(true),
'Rotation is not triggered when the file is already present'
=> array(false),
);
}
public function testReuseCurrentFile()
{
$log = __DIR__.'/Fixtures/foo-'.date('Y-m-d').'.rot';
file_put_contents($log, "foo");
$handler = new RotatingFileHandler(__DIR__.'/Fixtures/foo.rot');
$handler->setFormatter($this->getIdentityFormatter());
$handler->handle($this->getRecord());
$this->assertEquals('footest', file_get_contents($log));
}
public function tearDown()
{
foreach (glob(__DIR__.'/Fixtures/*.rot') as $file) {
unlink($file);
}
}
}

View File

@@ -0,0 +1,283 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
/**
* @author Pablo de Leon Belloc <pablolb@gmail.com>
*/
class SocketHandlerTest extends TestCase
{
/**
* @var Monolog\Handler\SocketHandler
*/
private $handler;
/**
* @var resource
*/
private $res;
/**
* @expectedException UnexpectedValueException
*/
public function testInvalidHostname()
{
$this->createHandler('garbage://here');
$this->writeRecord('data');
}
/**
* @expectedException \InvalidArgumentException
*/
public function testBadConnectionTimeout()
{
$this->createHandler('localhost:1234');
$this->handler->setConnectionTimeout(-1);
}
public function testSetConnectionTimeout()
{
$this->createHandler('localhost:1234');
$this->handler->setConnectionTimeout(10.1);
$this->assertEquals(10.1, $this->handler->getConnectionTimeout());
}
/**
* @expectedException \InvalidArgumentException
*/
public function testBadTimeout()
{
$this->createHandler('localhost:1234');
$this->handler->setTimeout(-1);
}
public function testSetTimeout()
{
$this->createHandler('localhost:1234');
$this->handler->setTimeout(10.25);
$this->assertEquals(10.25, $this->handler->getTimeout());
}
public function testSetConnectionString()
{
$this->createHandler('tcp://localhost:9090');
$this->assertEquals('tcp://localhost:9090', $this->handler->getConnectionString());
}
/**
* @expectedException UnexpectedValueException
*/
public function testExceptionIsThrownOnFsockopenError()
{
$this->setMockHandler(array('fsockopen'));
$this->handler->expects($this->once())
->method('fsockopen')
->will($this->returnValue(false));
$this->writeRecord('Hello world');
}
/**
* @expectedException UnexpectedValueException
*/
public function testExceptionIsThrownOnPfsockopenError()
{
$this->setMockHandler(array('pfsockopen'));
$this->handler->expects($this->once())
->method('pfsockopen')
->will($this->returnValue(false));
$this->handler->setPersistent(true);
$this->writeRecord('Hello world');
}
/**
* @expectedException UnexpectedValueException
*/
public function testExceptionIsThrownIfCannotSetTimeout()
{
$this->setMockHandler(array('streamSetTimeout'));
$this->handler->expects($this->once())
->method('streamSetTimeout')
->will($this->returnValue(false));
$this->writeRecord('Hello world');
}
/**
* @expectedException RuntimeException
*/
public function testWriteFailsOnIfFwriteReturnsFalse()
{
$this->setMockHandler(array('fwrite'));
$callback = function($arg) {
$map = array(
'Hello world' => 6,
'world' => false,
);
return $map[$arg];
};
$this->handler->expects($this->exactly(2))
->method('fwrite')
->will($this->returnCallback($callback));
$this->writeRecord('Hello world');
}
/**
* @expectedException RuntimeException
*/
public function testWriteFailsIfStreamTimesOut()
{
$this->setMockHandler(array('fwrite', 'streamGetMetadata'));
$callback = function($arg) {
$map = array(
'Hello world' => 6,
'world' => 5,
);
return $map[$arg];
};
$this->handler->expects($this->exactly(1))
->method('fwrite')
->will($this->returnCallback($callback));
$this->handler->expects($this->exactly(1))
->method('streamGetMetadata')
->will($this->returnValue(array('timed_out' => true)));
$this->writeRecord('Hello world');
}
/**
* @expectedException RuntimeException
*/
public function testWriteFailsOnIncompleteWrite()
{
$this->setMockHandler(array('fwrite', 'streamGetMetadata'));
$res = $this->res;
$callback = function($string) use ($res) {
fclose($res);
return strlen('Hello');
};
$this->handler->expects($this->exactly(1))
->method('fwrite')
->will($this->returnCallback($callback));
$this->handler->expects($this->exactly(1))
->method('streamGetMetadata')
->will($this->returnValue(array('timed_out' => false)));
$this->writeRecord('Hello world');
}
public function testWriteWithMemoryFile()
{
$this->setMockHandler();
$this->writeRecord('test1');
$this->writeRecord('test2');
$this->writeRecord('test3');
fseek($this->res, 0);
$this->assertEquals('test1test2test3', fread($this->res, 1024));
}
public function testWriteWithMock()
{
$this->setMockHandler(array('fwrite'));
$callback = function($arg) {
$map = array(
'Hello world' => 6,
'world' => 5,
);
return $map[$arg];
};
$this->handler->expects($this->exactly(2))
->method('fwrite')
->will($this->returnCallback($callback));
$this->writeRecord('Hello world');
}
public function testClose()
{
$this->setMockHandler();
$this->writeRecord('Hello world');
$this->assertInternalType('resource', $this->res);
$this->handler->close();
$this->assertFalse(is_resource($this->res), "Expected resource to be closed after closing handler");
}
public function testCloseDoesNotClosePersistentSocket()
{
$this->setMockHandler();
$this->handler->setPersistent(true);
$this->writeRecord('Hello world');
$this->assertTrue(is_resource($this->res));
$this->handler->close();
$this->assertTrue(is_resource($this->res));
}
private function createHandler($connectionString)
{
$this->handler = new SocketHandler($connectionString);
$this->handler->setFormatter($this->getIdentityFormatter());
}
private function writeRecord($string)
{
$this->handler->handle($this->getRecord(Logger::WARNING, $string));
}
private function setMockHandler(array $methods = array())
{
$this->res = fopen('php://memory', 'a');
$defaultMethods = array('fsockopen', 'pfsockopen', 'streamSetTimeout');
$newMethods = array_diff($methods, $defaultMethods);
$finalMethods = array_merge($defaultMethods, $newMethods);
$this->handler = $this->getMock(
'\Monolog\Handler\SocketHandler', $finalMethods, array('localhost:1234')
);
if (!in_array('fsockopen', $methods)) {
$this->handler->expects($this->any())
->method('fsockopen')
->will($this->returnValue($this->res));
}
if (!in_array('pfsockopen', $methods)) {
$this->handler->expects($this->any())
->method('pfsockopen')
->will($this->returnValue($this->res));
}
if (!in_array('streamSetTimeout', $methods)) {
$this->handler->expects($this->any())
->method('streamSetTimeout')
->will($this->returnValue(true));
}
$this->handler->setFormatter($this->getIdentityFormatter());
}
}

View File

@@ -0,0 +1,88 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
class StreamHandlerTest extends TestCase
{
/**
* @covers Monolog\Handler\StreamHandler::__construct
* @covers Monolog\Handler\StreamHandler::write
*/
public function testWrite()
{
$handle = fopen('php://memory', 'a+');
$handler = new StreamHandler($handle);
$handler->setFormatter($this->getIdentityFormatter());
$handler->handle($this->getRecord(Logger::WARNING, 'test'));
$handler->handle($this->getRecord(Logger::WARNING, 'test2'));
$handler->handle($this->getRecord(Logger::WARNING, 'test3'));
fseek($handle, 0);
$this->assertEquals('testtest2test3', fread($handle, 100));
}
/**
* @covers Monolog\Handler\StreamHandler::close
*/
public function testClose()
{
$handle = fopen('php://memory', 'a+');
$handler = new StreamHandler($handle);
$this->assertTrue(is_resource($handle));
$handler->close();
$this->assertFalse(is_resource($handle));
}
/**
* @covers Monolog\Handler\StreamHandler::write
*/
public function testWriteCreatesTheStreamResource()
{
$handler = new StreamHandler('php://memory');
$handler->handle($this->getRecord());
}
/**
* @expectedException LogicException
* @covers Monolog\Handler\StreamHandler::__construct
* @covers Monolog\Handler\StreamHandler::write
*/
public function testWriteMissingResource()
{
$handler = new StreamHandler(null);
$handler->handle($this->getRecord());
}
/**
* @expectedException UnexpectedValueException
* @covers Monolog\Handler\StreamHandler::__construct
* @covers Monolog\Handler\StreamHandler::write
*/
public function testWriteInvalidResource()
{
$handler = new StreamHandler('bogus://url');
$handler->handle($this->getRecord());
}
/**
* @expectedException UnexpectedValueException
* @covers Monolog\Handler\StreamHandler::__construct
* @covers Monolog\Handler\StreamHandler::write
*/
public function testWriteNonExistingResource()
{
$handler = new StreamHandler('/foo/bar/baz/'.rand(0, 10000));
$handler->handle($this->getRecord());
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
class SyslogHandlerTest extends \PHPUnit_Framework_TestCase
{
/**
* @covers Monolog\Handler\SyslogHandler::__construct
*/
public function testConstruct()
{
$handler = new SyslogHandler('test');
$this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler);
$handler = new SyslogHandler('test', LOG_USER);
$this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler);
$handler = new SyslogHandler('test', 'user');
$this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler);
$handler = new SyslogHandler('test', LOG_USER, Logger::DEBUG, true, LOG_PERROR);
$this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler);
}
/**
* @covers Monolog\Handler\SyslogHandler::__construct
*/
public function testConstructInvalidFacility()
{
$this->setExpectedException('UnexpectedValueException');
$handler = new SyslogHandler('test', 'unknown');
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
use Monolog\Logger;
/**
* @covers Monolog\Handler\TestHandler
*/
class TestHandlerTest extends TestCase
{
/**
* @dataProvider methodProvider
*/
public function testHandler($method, $level)
{
$handler = new TestHandler;
$record = $this->getRecord($level, 'test'.$method);
$this->assertFalse($handler->{'has'.$method}($record));
$this->assertFalse($handler->{'has'.$method.'Records'}());
$handler->handle($record);
$this->assertFalse($handler->{'has'.$method}('bar'));
$this->assertTrue($handler->{'has'.$method}($record));
$this->assertTrue($handler->{'has'.$method}('test'.$method));
$this->assertTrue($handler->{'has'.$method.'Records'}());
$records = $handler->getRecords();
unset($records[0]['formatted']);
$this->assertEquals(array($record), $records);
}
public function methodProvider()
{
return array(
array('Emergency', Logger::EMERGENCY),
array('Alert' , Logger::ALERT),
array('Critical' , Logger::CRITICAL),
array('Error' , Logger::ERROR),
array('Warning' , Logger::WARNING),
array('Info' , Logger::INFO),
array('Notice' , Logger::NOTICE),
array('Debug' , Logger::DEBUG),
);
}
}

View File

@@ -0,0 +1,69 @@
<?php
/*
* This file is part of the Monolog package.
*
* (c) Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\TestCase;
class ZendMonitorHandlerTest extends TestCase
{
protected $zendMonitorHandler;
public function setUp()
{
if (!function_exists('zend_monitor_custom_event')) {
$this->markTestSkipped('ZendServer is not installed');
}
}
/**
* @covers Monolog\Handler\ZendMonitorHandler::write
*/
public function testWrite()
{
$record = $this->getRecord();
$formatterResult = array(
'message' => $record['message']
);
$zendMonitor = $this->getMockBuilder('Monolog\Handler\ZendMonitorHandler')
->setMethods(array('writeZendMonitorCustomEvent', 'getDefaultFormatter'))
->getMock();
$formatterMock = $this->getMockBuilder('Monolog\Formatter\NormalizerFormatter')
->disableOriginalConstructor()
->getMock();
$formatterMock->expects($this->once())
->method('format')
->will($this->returnValue($formatterResult));
$zendMonitor->expects($this->once())
->method('getDefaultFormatter')
->will($this->returnValue($formatterMock));
$levelMap = $zendMonitor->getLevelMap();
$zendMonitor->expects($this->once())
->method('writeZendMonitorCustomEvent')
->with($levelMap[$record['level']], $record['message'], $formatterResult);
$zendMonitor->handle($record);
}
/**
* @covers Monolog\Handler\ZendMonitorHandler::getDefaultFormatter
*/
public function testGetDefaultFormatterReturnsNormalizerFormatter()
{
$zendMonitor = new ZendMonitorHandler();
$this->assertInstanceOf('Monolog\Formatter\NormalizerFormatter', $zendMonitor->getDefaultFormatter());
}
}

Some files were not shown because too many files have changed in this diff Show More