Vendor update && Started using DoctrineMigrations

This commit is contained in:
Polonkai Gergely
2012-07-23 17:09:03 +02:00
parent 7c36f93436
commit bf46316347
1102 changed files with 103189 additions and 7 deletions

View File

@@ -0,0 +1,4 @@
vendor
composer.phar
phpunit.xml
composer.lock

View File

@@ -0,0 +1,11 @@
language: php
php:
- 5.3
- 5.4
before_script:
- curl -s http://getcomposer.org/installer | php
- php composer.phar install --dev
script: phpunit

View File

@@ -0,0 +1,38 @@
* 1.1.0 (2012-04-23)
Changes:
* 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)
Changes:
* Fixed bug in IE with large response headers and FirePHPHandler
* 1.0.1 (2011-08-25)
Changes:
* Added MemoryPeakUsageProcessor and MemoryUsageProcessor
* Added Monolog\Logger::getName() to get a logger's channel name
* 1.0.0 (2011-07-06)
Changes:
* Added IntrospectionProcessor to get info from where the logger was called
* Fixed WebProcessor in CLI
* 1.0.0-RC1 (2011-07-01)
* Initial release

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.

View File

@@ -0,0 +1,172 @@
Monolog - Logging for PHP 5.3
=============================
[![Build Status](https://secure.travis-ci.org/Seldaek/monolog.png)](http://travis-ci.org/Seldaek/monolog)
Usage
-----
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 allow for flexible logging setups, for example having a FileHandler 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 bubbling property which define whether they block the
record or not if they handled it. In this example, setting the MailHandler's
$bubble argument to true means that all records will propagate to the
FileHandler, even the errors that are handled by the MailHandler.
You can create many Loggers, 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 six levels (debug, info,
warning, error, critical, alert) 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 exposes 6 log levels. Although it is possible to add more by extending
the classes you need, these are generally enough.
- **DEBUG** (100): Detailed debug information.
- **INFO** (200): Interesting events. Examples: User logs in, SQL logs.
- **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.
Docs
====
**See the doc/ directory for more detailed documentation. The following is only a list of all parts that come with Monolog.**
Handlers
--------
- _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.
- _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.
- _MongoDBHandler_: Handler to write records in MongoDB via a
[Mongo](http://pecl.php.net/package/mongo) extension connection.
- _NativeMailHandler_: Sends emails using PHP's mail() function.
- _SwiftMailerHandler_: Sends emails using a SwiftMailer instance.
- _SyslogHandler_: Logs records to the syslog.
- _GelfHandler_: Logs records to a [Graylog2](http://www.graylog2.org) server.
- _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).
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.
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.
About
=====
Requirements
------------
- Any flavor of PHP 5.3 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)
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.

View File

@@ -0,0 +1,27 @@
{
"name": "monolog/monolog",
"description": "Logging for PHP 5.3",
"keywords": ["log","logging"],
"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"
},
"require-dev": {
"mlehner/gelf-php": "1.0.*"
},
"suggest": {
"mlehner/gelf-php": "Allow sending log messages to a GrayLog2 server"
},
"autoload": {
"psr-0": {"Monolog": "src/"}
}
}

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']``).

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 [] []

View File

@@ -0,0 +1,124 @@
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). You can then easily grep through log files for
example to filter this or that type of log record.
Using different loggers with the same handlers allow to identify the logger
that issued the record (through the channel name) by keeping the same handlers
(for instance to use a single log file).
```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);
```

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,77 @@
<?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::WARNING => 'warn',
Logger::ERROR => 'error',
Logger::CRITICAL => 'error',
Logger::ALERT => '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
*/
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
*/
function formatBatch(array $records);
}

View File

@@ -0,0 +1,91 @@
<?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 according to Wildfire's header requirements
*
* @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 => LOG_DEBUG,
Logger::INFO => LOG_INFO,
Logger::WARNING => LOG_WARNING,
Logger::ERROR => LOG_ERR,
Logger::CRITICAL => LOG_CRIT,
Logger::ALERT => LOG_ALERT,
);
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,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\Formatter;
use Monolog\Logger;
/**
* 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,92 @@
<?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 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);
}
return parent::normalize($data);
}
protected function convertToString($data)
{
if (null === $data || is_scalar($data)) {
return (string) $data;
}
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
return json_encode($this->normalize($data), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
}
return stripslashes(json_encode($this->normalize($data)));
}
}

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\Formatter;
use Monolog\Logger;
/**
* 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();
foreach ($data as $key => $value) {
$normalized[$key] = $this->normalize($value);
}
return $normalized;
}
if ($data instanceof \DateTime) {
return $data->format($this->dateFormat);
}
if (is_resource($data)) {
return '[resource]';
}
return sprintf("[object] (%s: %s)", get_class($data), $this->toJson($data));
}
protected function toJson($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,87 @@
<?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 implements FormatterInterface
{
/**
* Translates Monolog log levels to Wildfire levels.
*/
private $logLevels = array(
Logger::DEBUG => 'LOG',
Logger::INFO => 'INFO',
Logger::WARNING => 'WARN',
Logger::ERROR => 'ERROR',
Logger::CRITICAL => 'ERROR',
Logger::ALERT => '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']);
}
$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);
}
// Create JSON object describing the appearance of the message in the console
$json = json_encode(array(
array(
'Type' => $this->logLevels[$record['level']],
'File' => $file,
'Line' => $line,
'Label' => $record['channel'],
),
$message,
));
// 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');
}
}

View File

@@ -0,0 +1,173 @@
<?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 = false;
/**
* @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);
}
/**
* {@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;
}
/**
* {@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
*/
public function setLevel($level)
{
$this->level = $level;
}
/**
* 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 bubbling is not permitted.
* False means that this handler allows bubbling.
*/
public function setBubble($bubble)
{
$this->bubble = $bubble;
}
/**
* Gets the bubbling behavior.
*
* @return Boolean True means that bubbling is not permitted.
* False means that this handler allows bubbling.
*/
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,70 @@
<?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
*
* 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,67 @@
<?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;
protected $buffer = array();
/**
* @param HandlerInterface $handler Handler.
* @param integer $bufferSize 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
*/
public function __construct(HandlerInterface $handler, $bufferSize = 0, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct($level, $bubble);
$this->handler = $handler;
$this->bufferSize = $bufferSize;
}
/**
* {@inheritdoc}
*/
public function handle(array $record)
{
if ($record['level'] < $this->level) {
return false;
}
$this->buffer[] = $record;
if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) {
array_shift($this->buffer);
}
return false === $this->bubble;
}
/**
* {@inheritdoc}
*/
public function close()
{
$this->handler->handleBatch($this->buffer);
}
}

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\ChromePHPFormatter;
/**
* 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 = '3.0';
/**
* Header name
*/
const HEADER_NAME = 'X-ChromePhp-Data';
static protected $initialized = false;
static protected $json = array(
'version' => self::VERSION,
'columns' => array('label', 'log', 'backtrace', 'type'),
'rows' => array(),
);
protected $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::$initialized) {
$this->sendHeaders = $this->headersAccepted();
self::$json['request_uri'] = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
self::$initialized = true;
}
$this->sendHeader(self::HEADER_NAME, base64_encode(utf8_encode(json_encode(self::$json))));
}
/**
* Send header string to the client
*
* @param string $header
* @param string $content
*/
protected function sendHeader($header, $content)
{
if (!headers_sent() && $this->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']);
}
}

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
*/
function isHandlerActivated(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.
*/
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,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\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.
*
* @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 callback|HandlerInterface $handler Handler or factory callback($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);
}
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->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) {
$this->handler = call_user_func($this->handler, $record, $this);
}
if (!$this->handler instanceof HandlerInterface) {
throw new \RuntimeException("The factory callback 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,161 @@
<?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\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 $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() && $this->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) {
$this->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']);
}
}

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,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;
/**
* 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)
{
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,77 @@
<?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.
*
* @return Boolean
*/
function isHandling(array $record);
/**
* Handles a record.
*
* The return value of this function controls the bubbling process of the handler stack.
*
* @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.
*/
function handle(array $record);
/**
* Handles a set of records at once.
*
* @param array $records The records to handle (an array of record arrays)
*/
function handleBatch(array $records);
/**
* Adds a processor in the stack.
*
* @param callable $callback
*/
function pushProcessor($callback);
/**
* Removes the processor on top of the stack and returns it.
*
* @return callable
*/
function popProcessor();
/**
* Sets the formatter.
*
* @param FormatterInterface $formatter
*/
function setFormatter(FormatterInterface $formatter);
/**
* Gets the formatter.
*
* @return FormatterInterface
*/
function getFormatter();
}

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,51 @@
<?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 $mongo, $database, $collection, $level = Logger::DEBUG, $bubble = true)
{
$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,49 @@
<?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;
/**
* @param string $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
*/
public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubble = true)
{
parent::__construct($level, $bubble);
$this->to = $to;
$this->subject = $subject;
$this->headers = sprintf("From: %s\r\nContent-type: text/plain; charset=utf-8\r\n", $from);
}
/**
* {@inheritdoc}
*/
protected function send($content, array $records)
{
mail($this->to, $this->subject, wordwrap($content, 70), $this->headers);
}
}

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,109 @@
<?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>
*/
class RotatingFileHandler extends StreamHandler
{
protected $filename;
protected $maxFiles;
protected $mustRotate;
/**
* @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;
$fileInfo = pathinfo($this->filename);
$timedFilename = $fileInfo['dirname'].'/'.$fileInfo['filename'].'-'.date('Y-m-d');
if (!empty($fileInfo['extension'])) {
$timedFilename .= '.'.$fileInfo['extension'];
}
// disable rotation upfront if files are unlimited
if (0 === $this->maxFiles) {
$this->mustRotate = false;
}
parent::__construct($timedFilename, $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);
}
parent::write($record);
}
/**
* Rotates the files.
*/
protected function rotate()
{
$fileInfo = pathinfo($this->filename);
$glob = $fileInfo['dirname'].'/'.$fileInfo['filename'].'-*';
if (!empty($fileInfo['extension'])) {
$glob .= '.'.$fileInfo['extension'];
}
$iterator = new \GlobIterator($glob);
$count = $iterator->count();
if ($this->maxFiles >= $count) {
// no files to remove
return;
}
// Sorting the files by name to remove the older ones
$array = iterator_to_array($iterator);
usort($array, function($a, $b) {
return strcmp($b->getFilename(), $a->getFilename());
});
foreach (array_slice($array, $this->maxFiles) as $file) {
if ($file->isWritable()) {
unlink($file->getRealPath());
}
}
}
}

View File

@@ -0,0 +1,272 @@
<?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();
$this->writeToSocket((string) $record['formatted']);
}
/**
* 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 integer $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 type $seconds
*
* @see http://php.net/manual/en/function.stream-set-timeout.php
*/
public function setTimeout($seconds)
{
$this->validateTimeout($seconds);
$this->timeout = (int) $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
*/
protected function streamSetTimeout()
{
return stream_set_timeout($this->resource, $this->timeout);
}
/**
* 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_INT, array('options' => array(
'min_range' => 0,
)));
if ($ok === false) {
throw new \InvalidArgumentException("Timeout must be 0 or a positive integer (got $value)");
}
}
private function connectIfNotConnected()
{
if ($this->isConnected()) {
return;
}
$this->connect();
}
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) {
$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,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;
/**
* 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().');
}
$this->stream = @fopen($this->url, 'a');
if (!is_resource($this->stream)) {
$this->stream = null;
throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened; it may be invalid or not writable.', $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 callback|\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 callback 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,108 @@
<?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 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
{
/**
* Translates Monolog log levels to syslog log priorities.
*/
private $logLevels = array(
Logger::DEBUG => LOG_DEBUG,
Logger::INFO => LOG_INFO,
Logger::WARNING => LOG_WARNING,
Logger::ERROR => LOG_ERR,
Logger::CRITICAL => LOG_CRIT,
Logger::ALERT => LOG_ALERT,
);
/**
* 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
*/
public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)
{
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)];
} else if (!in_array($facility, array_values($this->facilities), true)) {
throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given');
}
if (!openlog($ident, LOG_PID, $facility)) {
throw new \LogicException('Can\'t open syslog for ident "'.$ident.'" and facility "'.$facility.'"');
}
}
/**
* {@inheritdoc}
*/
public function close()
{
closelog();
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
syslog($this->logLevels[$record['level']], (string) $record['formatted']);
}
}

View File

@@ -0,0 +1,120 @@
<?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 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 hasInfo($record)
{
return $this->hasRecord($record, Logger::INFO);
}
public function hasDebug($record)
{
return $this->hasRecord($record, Logger::DEBUG);
}
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 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,421 @@
<?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;
/**
* 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
{
/**
* Detailed debug information
*/
const DEBUG = 100;
/**
* Interesting events
*
* Examples: User logs in, SQL logs.
*/
const INFO = 200;
/**
* 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;
protected static $levels = array(
100 => 'DEBUG',
200 => 'INFO',
300 => 'WARNING',
400 => 'ERROR',
500 => 'CRITICAL',
550 => 'ALERT',
);
protected $name;
/**
* The handler stack
*
* @var array of Monolog\Handler\HandlerInterface
*/
protected $handlers = array();
protected $processors = array();
/**
* @param string $name The logging channel
*/
public function __construct($name)
{
$this->name = $name;
}
/**
* @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', self::DEBUG));
}
$record = array(
'message' => (string) $message,
'context' => $context,
'level' => $level,
'level_name' => self::getLevelName($level),
'channel' => $this->name,
'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))),
'extra' => array(),
);
// check if any message 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(self::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(self::INFO, $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(self::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(self::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(self::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(self::ALERT, $message, $context);
}
/**
* Gets the name of the logging level.
*
* @param integer $level
* @return string
*/
public static function getLevelName($level)
{
return self::$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(
'message' => '',
'context' => array(),
'level' => $level,
'level_name' => self::getLevelName($level),
'channel' => $this->name,
'datetime' => new \DateTime(),
'extra' => array(),
);
foreach ($this->handlers as $key => $handler) {
if ($handler->isHandling($record)) {
return true;
}
}
return false;
}
// ZF Logger Compat
/**
* Adds a log record at the DEBUG level.
*
* This method allows to have an easy ZF compatibility.
*
* @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(self::DEBUG, $message, $context);
}
/**
* Adds a log record at the INFO level.
*
* This method allows to have an easy ZF compatibility.
*
* @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(self::INFO, $message, $context);
}
/**
* Adds a log record at the INFO level.
*
* This method allows to have an easy ZF compatibility.
*
* @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(self::INFO, $message, $context);
}
/**
* Adds a log record at the WARNING level.
*
* This method allows to have an easy ZF compatibility.
*
* @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(self::WARNING, $message, $context);
}
/**
* Adds a log record at the ERROR level.
*
* This method allows to have an easy ZF compatibility.
*
* @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(self::ERROR, $message, $context);
}
/**
* Adds a log record at the CRITICAL level.
*
* This method allows to have an easy ZF compatibility.
*
* @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(self::CRITICAL, $message, $context);
}
/**
* Adds a log record at the ALERT level.
*
* This method allows to have an easy ZF compatibility.
*
* @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(self::ALERT, $message, $context);
}
/**
* Adds a log record at the ALERT level.
*
* This method allows to have an easy ZF compatibility.
*
* @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(self::ALERT, $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,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\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;
}
if (!isset($this->serverData['HTTP_REFERER'])) {
$this->serverData['HTTP_REFERER'] = null;
}
$record['extra'] = array_merge(
$record['extra'],
array(
'url' => $this->serverData['REQUEST_URI'],
'ip' => $this->serverData['REMOTE_ADDR'],
'http_method' => $this->serverData['REQUEST_METHOD'],
'server' => $this->serverData['SERVER_NAME'],
'referrer' => $this->serverData['HTTP_REFERER'],
)
);
return $record;
}
}

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,150 @@
<?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 GelfMessageFormatterTest extends \PHPUnit_Framework_TestCase
{
/**
* @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(LOG_ERR, $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,134 @@
<?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;
/**
* @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',
));
if (version_compare(PHP_VERSION, '5.4.0', '>=')) {
$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);
} else {
$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 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,107 @@
<?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;
/**
* @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 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);
}
}
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,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;
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' => '',
'REQUEST_URI' => '',
'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,84 @@
<?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
*/
public function testDestructPropagatesRecords()
{
$test = new TestHandler();
$handler = new BufferHandler($test);
$handler->handle($this->getRecord(Logger::WARNING));
$handler->handle($this->getRecord(Logger::DEBUG));
unset($handler);
$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 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());
}
}

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\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-ChromePhp-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 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-ChromePhp-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::$json['rows'] = array();
}
protected function sendHeader($header, $content)
{
$this->headers[$header] = $content;
}
public function getHeaders()
{
return $this->headers;
}
}

View File

@@ -0,0 +1,151 @@
<?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;
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
*/
public function testActivationStrategy()
{
$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());
}
}

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,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 Monolog\Formatter\GelfMessageFormatter;
use Gelf\MessagePublisher;
use Gelf\Message;
class MockMessagePublisher extends MessagePublisher
{
public function publish(Message $message) {
$this->lastMessage = $message;
}
public $lastMessage = null;
}
class GelfHandlerTest extends TestCase
{
public function setUp()
{
if (!class_exists("Gelf\MessagePublisher")) {
$this->markTestSkipped("mlehner/gelf-php not installed");
}
}
/**
* @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(LOG_DEBUG, $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(LOG_WARNING, $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,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;
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)));
}
}

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,54 @@
<?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
{
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,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,100 @@
<?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 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,284 @@
<?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);
$this->assertEquals(10, $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);
$this->assertEquals(10, $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,77 @@
<?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());
}
}

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\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);
}
/**
* @covers Monolog\Handler\SyslogHandler::__construct
*/
public function testConstructInvalidFacility()
{
$this->setExpectedException('UnexpectedValueException');
$handler = new SyslogHandler('test', 'unknown');
}
}

View File

@@ -0,0 +1,54 @@
<?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('Alert' , Logger::ALERT),
array('Critical', Logger::CRITICAL),
array('Error' , Logger::ERROR),
array('Warning' , Logger::WARNING),
array('Info' , Logger::INFO),
array('Debug' , Logger::DEBUG),
);
}
}

View File

@@ -0,0 +1,367 @@
<?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\Processor\WebProcessor;
use Monolog\Handler\TestHandler;
class LoggerTest extends \PHPUnit_Framework_TestCase
{
/**
* @covers Monolog\Logger::getName
*/
public function testGetName()
{
$logger = new Logger('foo');
$this->assertEquals('foo', $logger->getName());
}
/**
* @covers Monolog\Logger::__construct
*/
public function testChannel()
{
$logger = new Logger('foo');
$handler = new TestHandler;
$logger->pushHandler($handler);
$logger->addWarning('test');
list($record) = $handler->getRecords();
$this->assertEquals('foo', $record['channel']);
}
/**
* @covers Monolog\Logger::addRecord
*/
public function testLog()
{
$logger = new Logger(__METHOD__);
$handler = $this->getMock('Monolog\Handler\NullHandler', array('handle'));
$handler->expects($this->once())
->method('handle');
$logger->pushHandler($handler);
$this->assertTrue($logger->addWarning('test'));
}
/**
* @covers Monolog\Logger::addRecord
*/
public function testLogNotHandled()
{
$logger = new Logger(__METHOD__);
$handler = $this->getMock('Monolog\Handler\NullHandler', array('handle'), array(Logger::ERROR));
$handler->expects($this->never())
->method('handle');
$logger->pushHandler($handler);
$this->assertFalse($logger->addWarning('test'));
}
/**
* @covers Monolog\Logger::pushHandler
* @covers Monolog\Logger::popHandler
* @expectedException LogicException
*/
public function testPushPopHandler()
{
$logger = new Logger(__METHOD__);
$handler1 = new TestHandler;
$handler2 = new TestHandler;
$logger->pushHandler($handler1);
$logger->pushHandler($handler2);
$this->assertEquals($handler2, $logger->popHandler());
$this->assertEquals($handler1, $logger->popHandler());
$logger->popHandler();
}
/**
* @covers Monolog\Logger::pushProcessor
* @covers Monolog\Logger::popProcessor
* @expectedException LogicException
*/
public function testPushPopProcessor()
{
$logger = new Logger(__METHOD__);
$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\Logger::pushProcessor
* @expectedException InvalidArgumentException
*/
public function testPushProcessorWithNonCallable()
{
$logger = new Logger(__METHOD__);
$logger->pushProcessor(new \stdClass());
}
/**
* @covers Monolog\Logger::addRecord
*/
public function testProcessorsAreExecuted()
{
$logger = new Logger(__METHOD__);
$handler = new TestHandler;
$logger->pushHandler($handler);
$logger->pushProcessor(function($record) {
$record['extra']['win'] = true;
return $record;
});
$logger->addError('test');
list($record) = $handler->getRecords();
$this->assertTrue($record['extra']['win']);
}
/**
* @covers Monolog\Logger::addRecord
*/
public function testProcessorsAreCalledOnlyOnce()
{
$logger = new Logger(__METHOD__);
$handler = $this->getMock('Monolog\Handler\HandlerInterface');
$handler->expects($this->any())
->method('isHandling')
->will($this->returnValue(true))
;
$handler->expects($this->any())
->method('handle')
->will($this->returnValue(true))
;
$logger->pushHandler($handler);
$processor = $this->getMockBuilder('Monolog\Processor\WebProcessor')
->disableOriginalConstructor()
->setMethods(array('__invoke'))
->getMock()
;
$processor->expects($this->once())
->method('__invoke')
->will($this->returnArgument(0))
;
$logger->pushProcessor($processor);
$logger->addError('test');
}
/**
* @covers Monolog\Logger::addRecord
*/
public function testProcessorsNotCalledWhenNotHandled()
{
$logger = new Logger(__METHOD__);
$handler = $this->getMock('Monolog\Handler\HandlerInterface');
$handler->expects($this->once())
->method('isHandling')
->will($this->returnValue(false))
;
$logger->pushHandler($handler);
$that = $this;
$logger->pushProcessor(function($record) use ($that){
$that->fail('The processor should not be called');
});
$logger->addAlert('test');
}
/**
* @covers Monolog\Logger::addRecord
*/
public function testHandlersNotCalledBeforeFirstHandling()
{
$logger = new Logger(__METHOD__);
$handler1 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler1->expects($this->never())
->method('isHandling')
->will($this->returnValue(false))
;
$handler1->expects($this->once())
->method('handle')
->will($this->returnValue(false))
;
$logger->pushHandler($handler1);
$handler2 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler2->expects($this->once())
->method('isHandling')
->will($this->returnValue(true))
;
$handler2->expects($this->once())
->method('handle')
->will($this->returnValue(false))
;
$logger->pushHandler($handler2);
$handler3 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler3->expects($this->once())
->method('isHandling')
->will($this->returnValue(false))
;
$handler3->expects($this->never())
->method('handle')
;
$logger->pushHandler($handler3);
$logger->debug('test');
}
/**
* @covers Monolog\Logger::addRecord
*/
public function testBubblingWhenTheHandlerReturnsFalse()
{
$logger = new Logger(__METHOD__);
$handler1 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler1->expects($this->any())
->method('isHandling')
->will($this->returnValue(true))
;
$handler1->expects($this->once())
->method('handle')
->will($this->returnValue(false))
;
$logger->pushHandler($handler1);
$handler2 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler2->expects($this->any())
->method('isHandling')
->will($this->returnValue(true))
;
$handler2->expects($this->once())
->method('handle')
->will($this->returnValue(false))
;
$logger->pushHandler($handler2);
$logger->debug('test');
}
/**
* @covers Monolog\Logger::addRecord
*/
public function testNotBubblingWhenTheHandlerReturnsTrue()
{
$logger = new Logger(__METHOD__);
$handler1 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler1->expects($this->any())
->method('isHandling')
->will($this->returnValue(true))
;
$handler1->expects($this->never())
->method('handle')
;
$logger->pushHandler($handler1);
$handler2 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler2->expects($this->any())
->method('isHandling')
->will($this->returnValue(true))
;
$handler2->expects($this->once())
->method('handle')
->will($this->returnValue(true))
;
$logger->pushHandler($handler2);
$logger->debug('test');
}
/**
* @covers Monolog\Logger::isHandling
*/
public function testIsHandling()
{
$logger = new Logger(__METHOD__);
$handler1 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler1->expects($this->any())
->method('isHandling')
->will($this->returnValue(false))
;
$logger->pushHandler($handler1);
$this->assertFalse($logger->isHandling(Logger::DEBUG));
$handler2 = $this->getMock('Monolog\Handler\HandlerInterface');
$handler2->expects($this->any())
->method('isHandling')
->will($this->returnValue(true))
;
$logger->pushHandler($handler2);
$this->assertTrue($logger->isHandling(Logger::DEBUG));
}
/**
* @dataProvider logMethodProvider
* @covers Monolog\Logger::addDebug
* @covers Monolog\Logger::addInfo
* @covers Monolog\Logger::addWarning
* @covers Monolog\Logger::addError
* @covers Monolog\Logger::addCritical
* @covers Monolog\Logger::addAlert
* @covers Monolog\Logger::debug
* @covers Monolog\Logger::info
* @covers Monolog\Logger::notice
* @covers Monolog\Logger::warn
* @covers Monolog\Logger::err
* @covers Monolog\Logger::crit
* @covers Monolog\Logger::alert
* @covers Monolog\Logger::emerg
*/
public function testLogMethods($method, $expectedLevel)
{
$logger = new Logger('foo');
$handler = new TestHandler;
$logger->pushHandler($handler);
$logger->{$method}('test');
list($record) = $handler->getRecords();
$this->assertEquals($expectedLevel, $record['level']);
}
public function logMethodProvider()
{
return array(
// monolog methods
array('addDebug', Logger::DEBUG),
array('addInfo', Logger::INFO),
array('addWarning', Logger::WARNING),
array('addError', Logger::ERROR),
array('addCritical', Logger::CRITICAL),
array('addAlert', Logger::ALERT),
// ZF/Sf2 compat methods
array('debug', Logger::DEBUG),
array('info', Logger::INFO),
array('notice', Logger::INFO),
array('warn', Logger::WARNING),
array('err', Logger::ERROR),
array('crit', Logger::CRITICAL),
array('alert', Logger::ALERT),
array('emerg', Logger::ALERT),
);
}
}

View File

@@ -0,0 +1,64 @@
<?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;
use Monolog\TestCase;
use Monolog\Handler\TestHandler;
class IntrospectionProcessorTest extends TestCase
{
public function getHandler()
{
$processor = new IntrospectionProcessor();
$handler = new TestHandler();
$handler->pushProcessor($processor);
return $handler;
}
public function testProcessorFromClass()
{
$handler = $this->getHandler();
$tester = new \Acme\Tester;
$tester->test($handler, $this->getRecord());
list($record) = $handler->getRecords();
$this->assertEquals(__FILE__, $record['extra']['file']);
$this->assertEquals(57, $record['extra']['line']);
$this->assertEquals('Acme\Tester', $record['extra']['class']);
$this->assertEquals('test', $record['extra']['function']);
}
public function testProcessorFromFunc()
{
$handler = $this->getHandler();
\Acme\tester($handler, $this->getRecord());
list($record) = $handler->getRecords();
$this->assertEquals(__FILE__, $record['extra']['file']);
$this->assertEquals(63, $record['extra']['line']);
$this->assertEquals(null, $record['extra']['class']);
$this->assertEquals('Acme\tester', $record['extra']['function']);
}
}
namespace Acme;
class Tester
{
function test($handler, $record)
{
$handler->handle($record);
}
}
function tester($handler, $record)
{
$handler->handle($record);
}

View File

@@ -0,0 +1,29 @@
<?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;
use Monolog\TestCase;
class MemoryPeakUsageProcessorTest extends TestCase
{
/**
* @covers Monolog\Processor\MemoryPeakUsageProcessor::__invoke
* @covers Monolog\Processor\MemoryProcessor::formatBytes
*/
public function testProcessor()
{
$processor = new MemoryPeakUsageProcessor();
$record = $processor($this->getRecord());
$this->assertArrayHasKey('memory_peak_usage', $record['extra']);
$this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_peak_usage']);
}
}

View File

@@ -0,0 +1,29 @@
<?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;
use Monolog\TestCase;
class MemoryUsageProcessorTest extends TestCase
{
/**
* @covers Monolog\Processor\MemoryUsageProcessor::__invoke
* @covers Monolog\Processor\MemoryProcessor::formatBytes
*/
public function testProcessor()
{
$processor = new MemoryUsageProcessor();
$record = $processor($this->getRecord());
$this->assertArrayHasKey('memory_usage', $record['extra']);
$this->assertRegExp('#[0-9.]+ (M|K)?B$#', $record['extra']['memory_usage']);
}
}

View File

@@ -0,0 +1,68 @@
<?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;
use Monolog\TestCase;
class WebProcessorTest extends TestCase
{
public function testProcessor()
{
$server = array(
'REQUEST_URI' => 'A',
'REMOTE_ADDR' => 'B',
'REQUEST_METHOD' => 'C',
'HTTP_REFERER' => 'D',
'SERVER_NAME' => 'F',
);
$processor = new WebProcessor($server);
$record = $processor($this->getRecord());
$this->assertEquals($server['REQUEST_URI'], $record['extra']['url']);
$this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']);
$this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']);
$this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']);
$this->assertEquals($server['SERVER_NAME'], $record['extra']['server']);
}
public function testProcessorDoNothingIfNoRequestUri()
{
$server = array(
'REMOTE_ADDR' => 'B',
'REQUEST_METHOD' => 'C',
);
$processor = new WebProcessor($server);
$record = $processor($this->getRecord());
$this->assertEmpty($record['extra']);
}
public function testProcessorReturnNullIfNoHttpReferer()
{
$server = array(
'REQUEST_URI' => 'A',
'REMOTE_ADDR' => 'B',
'REQUEST_METHOD' => 'C',
'SERVER_NAME' => 'F',
);
$processor = new WebProcessor($server);
$record = $processor($this->getRecord());
$this->assertNull($record['extra']['referrer']);
}
/**
* @expectedException UnexpectedValueException
*/
public function testInvalidData()
{
new WebProcessor(new \stdClass);
}
}

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;
class TestCase extends \PHPUnit_Framework_TestCase
{
/**
* @return array Record
*/
protected function getRecord($level = Logger::WARNING, $message = 'test', $context = array())
{
return array(
'message' => $message,
'context' => $context,
'level' => $level,
'level_name' => Logger::getLevelName($level),
'channel' => 'test',
'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))),
'extra' => array(),
);
}
/**
* @return array
*/
protected function getMultipleRecords()
{
return array(
$this->getRecord(Logger::DEBUG, 'debug message 1'),
$this->getRecord(Logger::DEBUG, 'debug message 2'),
$this->getRecord(Logger::INFO, 'information'),
$this->getRecord(Logger::WARNING, 'warning'),
$this->getRecord(Logger::ERROR, 'error')
);
}
/**
* @return Monolog\Formatter\FormatterInterface
*/
protected function getIdentityFormatter()
{
$formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
$formatter->expects($this->any())
->method('format')
->will($this->returnCallback(function($record) { return $record['message']; }));
return $formatter;
}
}

View File

@@ -0,0 +1,13 @@
<?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.
*/
require_once __DIR__ . "/../vendor/autoload.php";
require_once __DIR__.'/Monolog/TestCase.php';