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

Merge pull request #1 from Seldaek/master

Merging update from parent
This commit is contained in:
Wan Qi Chen
2012-10-17 14:34:08 -07:00
20 changed files with 182 additions and 107 deletions

1
.gitignore vendored
View File

@@ -2,5 +2,4 @@ vendor
composer.phar composer.phar
phpunit.xml phpunit.xml
composer.lock composer.lock
.DS_Store .DS_Store

View File

@@ -1,3 +1,25 @@
* 1.2.1 (2012-08-29)
Changes:
* Added new $logopts arg to SyslogHandler to provide custom openlog options
* Fixed fatal error in SyslogHandler
* 1.2.0 (2012-08-18)
Changes:
* Added AmqpHandler (for use with AMQP servers)
* Added CubeHandler
* Added NativeMailerHandler::addHeader() to send custom headers in mails
* Added the possibility to specify more than one recipient in NativeMailerHandler
* Added the possibility to specify float timeouts in SocketHandler
* Added NOTICE and EMERGENCY levels to conform with RFC 5424
* Fixed the log records to use the php default timezone instead of UTC
* Fixed BufferHandler not being flushed properly on PHP fatal errors
* Fixed normalization of exotic resource types
* Fixed the default format of the SyslogHandler to avoid duplicating datetimes in syslog
* 1.1.0 (2012-04-23) * 1.1.0 (2012-04-23)
Changes: Changes:

View File

@@ -113,6 +113,7 @@ Handlers
for UNIX and TCP sockets. See an [example](https://github.com/Seldaek/monolog/blob/master/doc/sockets.md). for UNIX and TCP sockets. See an [example](https://github.com/Seldaek/monolog/blob/master/doc/sockets.md).
- _AmqpHandler_: Logs records to an [amqp](http://www.amqp.org/) compatible - _AmqpHandler_: Logs records to an [amqp](http://www.amqp.org/) compatible
server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+). server. Requires the [php-amqp](http://pecl.php.net/package/amqp) extension (1.0+).
- _CubeHandler_: Logs records to a [Cube](http://square.github.com/cube/) server.
Wrappers / Special Handlers Wrappers / Special Handlers
--------------------------- ---------------------------

View File

@@ -1,7 +1,7 @@
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
"description": "Logging for PHP 5.3", "description": "Logging for PHP 5.3",
"keywords": ["log","logging"], "keywords": ["log", "logging"],
"homepage": "http://github.com/Seldaek/monolog", "homepage": "http://github.com/Seldaek/monolog",
"type": "library", "type": "library",
"license": "MIT", "license": "MIT",
@@ -25,5 +25,10 @@
}, },
"autoload": { "autoload": {
"psr-0": {"Monolog": "src/"} "psr-0": {"Monolog": "src/"}
},
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
} }
} }

View File

@@ -94,12 +94,11 @@ Leveraging channels
Channels are a great way to identify to which part of the application a record 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 is related. This is useful in big applications (and is leveraged by
MonologBundle in Symfony2). You can then easily grep through log files for MonologBundle in Symfony2).
example to filter this or that type of log record.
Using different loggers with the same handlers allow to identify the logger Picture two loggers sharing a handler that writes to a single log file.
that issued the record (through the channel name) by keeping the same handlers Channels would allow you to identify the logger that issued every record.
(for instance to use a single log file). You can easily grep through the log files filtering this or that channel.
```php ```php
<?php <?php
@@ -122,3 +121,38 @@ $securityLogger = new Logger('security');
$securityLogger->pushHandler($stream); $securityLogger->pushHandler($stream);
$securityLogger->pushHandler($firephp); $securityLogger->pushHandler($firephp);
``` ```
Customizing log format
----------------------
In Monolog it's easy to customize the format of the logs written into files,
sockets, mails, databases and other handlers. Most of the handlers use the
```php
$record['formatted']
```
value to be automatically put into the log device. This value depends on the
formatter settings. You can choose between predefined formatter classes or
write your own (e.g. a multiline text file for human-readable output).
To configure a predefined formatter class, just set it as the handler's field:
```php
// the default date format is "Y-m-d H:i:s"
$dateFormat = "Y n j, g:i a";
// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"
$output = "%datetime% > %level_name% > %message% %context% %extra%\n"
// finally, create a formatter
$formatter = new LineFormatter($output, $dateFormat);
// Create a handler
$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG);
$stream->setFormatter($formatter);
// bind it to a logger object
$securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
```
You may also reuse the same formatter between multiple handlers and share those
handlers between multiple loggers.

View File

@@ -85,6 +85,6 @@ class LineFormatter extends NormalizerFormatter
return json_encode($this->normalize($data), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); return json_encode($this->normalize($data), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
} }
return stripslashes(json_encode($this->normalize($data))); return str_replace('\\/', '/', json_encode($this->normalize($data)));
} }
} }

View File

@@ -24,7 +24,6 @@ class AmqpHandler extends AbstractProcessingHandler
/** /**
* @param \AMQPExchange $exchange AMQP exchange, ready for use * @param \AMQPExchange $exchange AMQP exchange, ready for use
* @param string $exchangeName * @param string $exchangeName
* @param string $issuer issuer name
* @param int $level * @param int $level
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not * @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/ */

View File

@@ -15,20 +15,18 @@ use Monolog\Logger;
/** /**
* Logs to Cube. * Logs to Cube.
* @link http://square.github.com/cube/
* *
* @link http://square.github.com/cube/
* @author Wan Chen <kami@kamisama.me> * @author Wan Chen <kami@kamisama.me>
*/ */
class CubeHandler extends AbstractProcessingHandler class CubeHandler extends AbstractProcessingHandler
{ {
private $udpConnection = null; private $udpConnection = null;
private $httpConnection = null; private $httpConnection = null;
private $scheme = null; private $scheme = null;
private $host = null; private $host = null;
private $port = null; private $port = null;
private $acceptedScheme = array('http', 'udp'); private $acceptedSchemes = array('http', 'udp');
/** /**
* Create a Cube handler * Create a Cube handler
@@ -41,36 +39,23 @@ class CubeHandler extends AbstractProcessingHandler
{ {
$urlInfos = parse_url($url); $urlInfos = parse_url($url);
if (!$urlInfos || !isset($urlInfos['scheme']) if (!isset($urlInfos['scheme']) || !isset($urlInfos['host']) || !isset($urlInfos['port'])) {
|| !isset($urlInfos['host']) || !isset($urlInfos['port'])) {
throw new \UnexpectedValueException('URL "'.$url.'" is not valid'); throw new \UnexpectedValueException('URL "'.$url.'" is not valid');
} }
if (!in_array($urlInfos['scheme'], $this->acceptedScheme)) { if (!in_array($urlInfos['scheme'], $this->acceptedSchemes)) {
throw new \UnexpectedValueException( throw new \UnexpectedValueException(
'Invalid ' . $urlInfos['scheme'] . ' protocol.' 'Invalid protocol (' . $urlInfos['scheme'] . ').'
. 'Valid options are ' . implode(', ', $this->acceptedScheme)); . ' Valid options are ' . implode(', ', $this->acceptedSchemes));
} else { }
$this->scheme = $urlInfos['scheme']; $this->scheme = $urlInfos['scheme'];
$this->host = $urlInfos['host']; $this->host = $urlInfos['host'];
$this->port = $urlInfos['port']; $this->port = $urlInfos['port'];
}
parent::__construct($level, $bubble); parent::__construct($level, $bubble);
} }
/**
* Check if a connection resource is available
*
* @return boolean
*/
private function isConnected($scheme)
{
return $this->{$scheme . 'Connection'} !== null;
}
/** /**
* Establish a connection to an UDP socket * Establish a connection to an UDP socket
* *
@@ -79,7 +64,7 @@ class CubeHandler extends AbstractProcessingHandler
protected function connectUdp() protected function connectUdp()
{ {
if (!extension_loaded('sockets')) { if (!extension_loaded('sockets')) {
throw new \LogicException('The sockets extension is not loaded'); throw new \LogicException('The sockets extension is needed to use udp URLs with the CubeHandler');
} }
$this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0); $this->udpConnection = socket_create(AF_INET, SOCK_DGRAM, 0);
@@ -88,31 +73,29 @@ class CubeHandler extends AbstractProcessingHandler
} }
if (!socket_connect($this->udpConnection, $this->host, $this->port)) { if (!socket_connect($this->udpConnection, $this->host, $this->port)) {
throw new \LogicException('Unable to connect to the socket at ' throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port);
. $this->host . ':' . $this->port);
} }
} }
/** /**
* Establish a connection to a http server * Establish a connection to a http server
*/ */
protected function connectHttp() protected function connectHttp()
{ {
$this->httpConnection = if (!extension_loaded('curl')) {
curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put'); throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler');
}
$this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put');
if (!$this->httpConnection) { if (!$this->httpConnection) {
throw new \LogicException('Unable to connect to ' throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port);
. $this->host . ':' . $this->port);
} }
curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST"); curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true);
} }
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@@ -120,47 +103,43 @@ class CubeHandler extends AbstractProcessingHandler
{ {
$date = $record['datetime']; $date = $record['datetime'];
$datas = array('time' => $date->format('Y-m-d H:i:s')); $data = array('time' => $date->format('Y-m-d H:i:s'));
unset($record['datetime']); unset($record['datetime']);
if (isset($record['context']['type'])) { if (isset($record['context']['type'])) {
$datas['type'] = $record['context']['type']; $data['type'] = $record['context']['type'];
unset($record['context']['type']); unset($record['context']['type']);
} else { } else {
$datas['type'] = $record['channel']; $data['type'] = $record['channel'];
} }
$datas['data'] = $record['context']; $data['data'] = $record['context'];
$datas['data']['level'] = $record['level']; $data['data']['level'] = $record['level'];
call_user_func( $this->{'write'.$this->scheme}(json_encode($data));
array($this, 'send'.ucwords($this->scheme)), json_encode($datas));
} }
private function writeUdp($data)
private function sendUdp($datas)
{ {
if (!$this->isConnected($this->scheme)) { if (!$this->udpConnection) {
call_user_func( $this->connectUdp();
array($this, 'connect' . ucwords($this->scheme)));
} }
socket_send($this->udpConnection, $datas, strlen($datas), 0); socket_send($this->udpConnection, $data, strlen($data), 0);
} }
private function writeHttp($data)
private function sendHttp($datas)
{ {
if (!$this->isConnected($this->scheme)) { if (!$this->httpConnection) {
call_user_func( $this->connectHttp();
array($this, 'connect' . ucwords($this->scheme)));
} }
curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$datas.']'); curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');
curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array( curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json', 'Content-Type: application/json',
'Content-Length: ' . strlen('['.$datas.']')) 'Content-Length: ' . strlen('['.$data.']'))
); );
return curl_exec($this->httpConnection); return curl_exec($this->httpConnection);
} }
} }

View File

@@ -25,6 +25,8 @@ interface HandlerInterface
* *
* This is mostly done for performance reasons, to avoid calling processors for nothing. * This is mostly done for performance reasons, to avoid calling processors for nothing.
* *
* @param array $record
*
* @return Boolean * @return Boolean
*/ */
public function isHandling(array $record); public function isHandling(array $record);

View File

@@ -42,7 +42,7 @@ class NativeMailerHandler extends MailHandler
} }
/** /**
* @param string|array $header Custom added headers * @param string|array $headers Custom added headers
*/ */
public function addHeader($headers) public function addHeader($headers)
{ {

View File

@@ -89,7 +89,7 @@ class SocketHandler extends AbstractProcessingHandler
/** /**
* Set connection timeout. Only has effect before we connect. * Set connection timeout. Only has effect before we connect.
* *
* @param integer $seconds * @param float $seconds
* *
* @see http://php.net/manual/en/function.fsockopen.php * @see http://php.net/manual/en/function.fsockopen.php
*/ */
@@ -102,14 +102,14 @@ class SocketHandler extends AbstractProcessingHandler
/** /**
* Set write timeout. Only has effect before we connect. * Set write timeout. Only has effect before we connect.
* *
* @param type $seconds * @param float $seconds
* *
* @see http://php.net/manual/en/function.stream-set-timeout.php * @see http://php.net/manual/en/function.stream-set-timeout.php
*/ */
public function setTimeout($seconds) public function setTimeout($seconds)
{ {
$this->validateTimeout($seconds); $this->validateTimeout($seconds);
$this->timeout = (int) $seconds; $this->timeout = (float) $seconds;
} }
/** /**
@@ -183,10 +183,15 @@ class SocketHandler extends AbstractProcessingHandler
/** /**
* Wrapper to allow mocking * Wrapper to allow mocking
*
* @see http://php.net/manual/en/function.stream-set-timeout.php
*/ */
protected function streamSetTimeout() protected function streamSetTimeout()
{ {
return stream_set_timeout($this->resource, $this->timeout); $seconds = floor($this->timeout);
$microseconds = round(($this->timeout - $seconds)*1e6);
return stream_set_timeout($this->resource, $seconds, $microseconds);
} }
/** /**
@@ -207,11 +212,9 @@ class SocketHandler extends AbstractProcessingHandler
private function validateTimeout($value) private function validateTimeout($value)
{ {
$ok = filter_var($value, FILTER_VALIDATE_INT, array('options' => array( $ok = filter_var($value, FILTER_VALIDATE_FLOAT);
'min_range' => 0, if ($ok === false || $value < 0) {
))); throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)");
if ($ok === false) {
throw new \InvalidArgumentException("Timeout must be 0 or a positive integer (got $value)");
} }
} }
@@ -254,7 +257,11 @@ class SocketHandler extends AbstractProcessingHandler
$length = strlen($data); $length = strlen($data);
$sent = 0; $sent = 0;
while ($this->isConnected() && $sent < $length) { while ($this->isConnected() && $sent < $length) {
if (0 == $sent) {
$chunk = $this->fwrite($data);
} else {
$chunk = $this->fwrite(substr($data, $sent)); $chunk = $this->fwrite(substr($data, $sent));
}
if ($chunk === false) { if ($chunk === false) {
throw new \RuntimeException("Could not write to socket"); throw new \RuntimeException("Could not write to socket");
} }

View File

@@ -60,10 +60,15 @@ class StreamHandler extends AbstractProcessingHandler
if (!$this->url) { 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().'); 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'); $errorMessage = null;
set_error_handler(function ($code, $msg) use (&$errorMessage) {
$errorMessage = preg_replace('{^fopen\(.*?\): }', '', $msg);
});
$this->stream = fopen($this->url, 'a');
restore_error_handler();
if (!is_resource($this->stream)) { if (!is_resource($this->stream)) {
$this->stream = null; $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)); throw new \UnexpectedValueException(sprintf('The stream or file "%s" could not be opened: '.$errorMessage, $this->url));
} }
} }
fwrite($this->stream, (string) $record['formatted']); fwrite($this->stream, (string) $record['formatted']);

View File

@@ -12,6 +12,8 @@
namespace Monolog\Handler; namespace Monolog\Handler;
use Monolog\Logger; use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
/** /**
* Logs to syslog service. * Logs to syslog service.
@@ -64,8 +66,9 @@ class SyslogHandler extends AbstractProcessingHandler
* @param mixed $facility * @param mixed $facility
* @param integer $level The minimum logging level at which this handler will be triggered * @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param int $logopts Option flags for the openlog() call, defaults to LOG_PID
*/ */
public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true) public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID)
{ {
parent::__construct($level, $bubble); parent::__construct($level, $bubble);
@@ -87,7 +90,7 @@ class SyslogHandler extends AbstractProcessingHandler
throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given'); throw new \UnexpectedValueException('Unknown facility value "'.$facility.'" given');
} }
if (!openlog($ident, LOG_PID, $facility)) { if (!openlog($ident, $logopts, $facility)) {
throw new \LogicException('Can\'t open syslog for ident "'.$ident.'" and facility "'.$facility.'"'); throw new \LogicException('Can\'t open syslog for ident "'.$ident.'" and facility "'.$facility.'"');
} }
} }
@@ -107,4 +110,12 @@ class SyslogHandler extends AbstractProcessingHandler
{ {
syslog($this->logLevels[$record['level']], (string) $record['formatted']); syslog($this->logLevels[$record['level']], (string) $record['formatted']);
} }
/**
* {@inheritdoc}
*/
protected function getDefaultFormatter()
{
return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%\n');
}
} }

View File

@@ -194,7 +194,7 @@ class Logger
'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)))->setTimeZone(self::$timezone), 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)))->setTimeZone(self::$timezone),
'extra' => array(), 'extra' => array(),
); );
// check if any message will handle this message // check if any handler will handle this message
$handlerKey = null; $handlerKey = null;
foreach ($this->handlers as $key => $handler) { foreach ($this->handlers as $key => $handler) {
if ($handler->isHandling($record)) { if ($handler->isHandling($record)) {

View File

@@ -86,11 +86,8 @@ class LineFormatterTest extends \PHPUnit_Framework_TestCase
'extra' => array('foo' => new TestFoo, 'bar' => new TestBar, 'baz' => array(), 'res' => fopen('php://memory', 'rb')), 'extra' => array('foo' => new TestFoo, 'bar' => new TestBar, 'baz' => array(), 'res' => fopen('php://memory', 'rb')),
'message' => 'foobar', '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); $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() public function testBatchFormat()

View File

@@ -64,7 +64,6 @@ class AbstractProcessingHandlerTest extends TestCase
'REQUEST_URI' => '', 'REQUEST_URI' => '',
'REQUEST_METHOD' => '', 'REQUEST_METHOD' => '',
'REMOTE_ADDR' => '', 'REMOTE_ADDR' => '',
'REQUEST_URI' => '',
'SERVER_NAME' => '', 'SERVER_NAME' => '',
))); )));
$handledRecord = null; $handledRecord = null;

View File

@@ -50,8 +50,8 @@ class SocketHandlerTest extends TestCase
public function testSetConnectionTimeout() public function testSetConnectionTimeout()
{ {
$this->createHandler('localhost:1234'); $this->createHandler('localhost:1234');
$this->handler->setConnectionTimeout(10); $this->handler->setConnectionTimeout(10.1);
$this->assertEquals(10, $this->handler->getConnectionTimeout()); $this->assertEquals(10.1, $this->handler->getConnectionTimeout());
} }
/** /**
@@ -66,8 +66,8 @@ class SocketHandlerTest extends TestCase
public function testSetTimeout() public function testSetTimeout()
{ {
$this->createHandler('localhost:1234'); $this->createHandler('localhost:1234');
$this->handler->setTimeout(10); $this->handler->setTimeout(10.25);
$this->assertEquals(10, $this->handler->getTimeout()); $this->assertEquals(10.25, $this->handler->getTimeout());
} }
public function testSetConnectionString() public function testSetConnectionString()

View File

@@ -74,4 +74,15 @@ class StreamHandlerTest extends TestCase
$handler = new StreamHandler('bogus://url'); $handler = new StreamHandler('bogus://url');
$handler->handle($this->getRecord()); $handler->handle($this->getRecord());
} }
/**
* @expectedException UnexpectedValueException
* @covers Monolog\Handler\StreamHandler::__construct
* @covers Monolog\Handler\StreamHandler::write
*/
public function testWriteNonExistingResource()
{
$handler = new StreamHandler('/foo/bar/baz/'.rand(0, 10000));
$handler->handle($this->getRecord());
}
} }

View File

@@ -10,6 +10,7 @@
*/ */
namespace Monolog\Handler; namespace Monolog\Handler;
use Monolog\Logger;
class SyslogHandlerTest extends \PHPUnit_Framework_TestCase class SyslogHandlerTest extends \PHPUnit_Framework_TestCase
{ {
@@ -26,6 +27,9 @@ class SyslogHandlerTest extends \PHPUnit_Framework_TestCase
$handler = new SyslogHandler('test', 'user'); $handler = new SyslogHandler('test', 'user');
$this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler); $this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler);
$handler = new SyslogHandler('test', LOG_USER, Logger::DEBUG, true, LOG_PERROR);
$this->assertInstanceOf('Monolog\Handler\SyslogHandler', $handler);
} }
/** /**