1
0
mirror of https://github.com/Seldaek/monolog.git synced 2025-08-18 19:01:31 +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

3
.gitignore vendored
View File

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

View File

@@ -1,6 +1,6 @@
language: php
php:
php:
- 5.3
- 5.4

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)
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).
- _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+).
- _CubeHandler_: Logs records to a [Cube](http://square.github.com/cube/) server.
Wrappers / Special Handlers
---------------------------

View File

@@ -1,7 +1,7 @@
{
"name": "monolog/monolog",
"description": "Logging for PHP 5.3",
"keywords": ["log","logging"],
"keywords": ["log", "logging"],
"homepage": "http://github.com/Seldaek/monolog",
"type": "library",
"license": "MIT",
@@ -25,5 +25,10 @@
},
"autoload": {
"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
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.
MonologBundle in Symfony2).
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).
Picture two loggers sharing a handler that writes to a single log file.
Channels would allow you to identify the logger that issued every record.
You can easily grep through the log files filtering this or that channel.
```php
<?php
@@ -122,3 +121,38 @@ $securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
$securityLogger->pushHandler($firephp);
```
Customizing log format
----------------------
In Monolog it's easy to customize the format of the logs written into files,
sockets, mails, databases and other handlers. Most of the handlers use the
```php
$record['formatted']
```
value to be automatically put into the log device. This value depends on the
formatter settings. You can choose between predefined formatter classes or
write your own (e.g. a multiline text file for human-readable output).
To configure a predefined formatter class, just set it as the handler's field:
```php
// the default date format is "Y-m-d H:i:s"
$dateFormat = "Y n j, g:i a";
// the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n"
$output = "%datetime% > %level_name% > %message% %context% %extra%\n"
// finally, create a formatter
$formatter = new LineFormatter($output, $dateFormat);
// Create a handler
$stream = new StreamHandler(__DIR__.'/my_app.log', Logger::DEBUG);
$stream->setFormatter($formatter);
// bind it to a logger object
$securityLogger = new Logger('security');
$securityLogger->pushHandler($stream);
```
You may also reuse the same formatter between multiple handlers and share those
handlers between multiple loggers.

View File

@@ -85,6 +85,6 @@ class LineFormatter extends NormalizerFormatter
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 string $exchangeName
* @param string $issuer issuer name
* @param int $level
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/

View File

@@ -1,5 +1,5 @@
<?php
/*
* This file is part of the Monolog package.
*
@@ -8,28 +8,26 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Monolog\Handler;
use Monolog\Logger;
/**
* Logs to Cube.
* @link http://square.github.com/cube/
*
* @link http://square.github.com/cube/
* @author Wan Chen <kami@kamisama.me>
*/
class CubeHandler extends AbstractProcessingHandler
{
private $udpConnection = null;
private $httpConnection = null;
private $scheme = null;
private $host = null;
private $port = null;
private $acceptedScheme = array('http', 'udp');
private $acceptedSchemes = array('http', 'udp');
/**
* Create a Cube handler
*
@@ -40,36 +38,23 @@ class CubeHandler extends AbstractProcessingHandler
public function __construct($url, $level = Logger::DEBUG, $bubble = true)
{
$urlInfos = parse_url($url);
if (!$urlInfos || !isset($urlInfos['scheme'])
|| !isset($urlInfos['host']) || !isset($urlInfos['port'])) {
if (!isset($urlInfos['scheme']) || !isset($urlInfos['host']) || !isset($urlInfos['port'])) {
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(
'Invalid ' . $urlInfos['scheme'] . ' protocol.'
. 'Valid options are ' . implode(', ', $this->acceptedScheme));
} else {
$this->scheme = $urlInfos['scheme'];
$this->host = $urlInfos['host'];
$this->port = $urlInfos['port'];
'Invalid protocol (' . $urlInfos['scheme'] . ').'
. ' Valid options are ' . implode(', ', $this->acceptedSchemes));
}
$this->scheme = $urlInfos['scheme'];
$this->host = $urlInfos['host'];
$this->port = $urlInfos['port'];
parent::__construct($level, $bubble);
}
/**
* 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
@@ -79,88 +64,82 @@ class CubeHandler extends AbstractProcessingHandler
protected function connectUdp()
{
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);
if (!$this->udpConnection) {
throw new \LogicException('Unable to create a socket');
}
if (!socket_connect($this->udpConnection, $this->host, $this->port)) {
throw new \LogicException('Unable to connect to the socket at '
. $this->host . ':' . $this->port);
throw new \LogicException('Unable to connect to the socket at ' . $this->host . ':' . $this->port);
}
}
/**
* Establish a connection to a http server
*/
protected function connectHttp()
{
$this->httpConnection =
curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put');
if (!extension_loaded('curl')) {
throw new \LogicException('The curl extension is needed to use http URLs with the CubeHandler');
}
$this->httpConnection = curl_init('http://'.$this->host.':'.$this->port.'/1.0/event/put');
if (!$this->httpConnection) {
throw new \LogicException('Unable to connect to '
. $this->host . ':' . $this->port);
throw new \LogicException('Unable to connect to ' . $this->host . ':' . $this->port);
}
curl_setopt($this->httpConnection, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($this->httpConnection, CURLOPT_RETURNTRANSFER, true);
}
/**
* {@inheritdoc}
*/
protected function write(array $record)
{
$date = $record['datetime'];
$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']);
if (isset($record['context']['type'])) {
$datas['type'] = $record['context']['type'];
$data['type'] = $record['context']['type'];
unset($record['context']['type']);
} else {
$datas['type'] = $record['channel'];
$data['type'] = $record['channel'];
}
$datas['data'] = $record['context'];
$datas['data']['level'] = $record['level'];
call_user_func(
array($this, 'send'.ucwords($this->scheme)), json_encode($datas));
$data['data'] = $record['context'];
$data['data']['level'] = $record['level'];
$this->{'write'.$this->scheme}(json_encode($data));
}
private function sendUdp($datas)
private function writeUdp($data)
{
if (!$this->isConnected($this->scheme)) {
call_user_func(
array($this, 'connect' . ucwords($this->scheme)));
if (!$this->udpConnection) {
$this->connectUdp();
}
socket_send($this->udpConnection, $datas, strlen($datas), 0);
socket_send($this->udpConnection, $data, strlen($data), 0);
}
private function sendHttp($datas)
private function writeHttp($data)
{
if (!$this->isConnected($this->scheme)) {
call_user_func(
array($this, 'connect' . ucwords($this->scheme)));
if (!$this->httpConnection) {
$this->connectHttp();
}
curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$datas.']');
curl_setopt($this->httpConnection, CURLOPT_POSTFIELDS, '['.$data.']');
curl_setopt($this->httpConnection, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen('['.$datas.']'))
'Content-Length: ' . strlen('['.$data.']'))
);
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.
*
* @param array $record
*
* @return Boolean
*/
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)
{

View File

@@ -89,7 +89,7 @@ class SocketHandler extends AbstractProcessingHandler
/**
* Set connection timeout. Only has effect before we connect.
*
* @param integer $seconds
* @param float $seconds
*
* @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.
*
* @param type $seconds
* @param float $seconds
*
* @see http://php.net/manual/en/function.stream-set-timeout.php
*/
public function setTimeout($seconds)
{
$this->validateTimeout($seconds);
$this->timeout = (int) $seconds;
$this->timeout = (float) $seconds;
}
/**
@@ -183,10 +183,15 @@ class SocketHandler extends AbstractProcessingHandler
/**
* Wrapper to allow mocking
*
* @see http://php.net/manual/en/function.stream-set-timeout.php
*/
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)
{
$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)");
$ok = filter_var($value, FILTER_VALIDATE_FLOAT);
if ($ok === false || $value < 0) {
throw new \InvalidArgumentException("Timeout must be 0 or a positive float (got $value)");
}
}
@@ -254,7 +257,11 @@ class SocketHandler extends AbstractProcessingHandler
$length = strlen($data);
$sent = 0;
while ($this->isConnected() && $sent < $length) {
$chunk = $this->fwrite(substr($data, $sent));
if (0 == $sent) {
$chunk = $this->fwrite($data);
} else {
$chunk = $this->fwrite(substr($data, $sent));
}
if ($chunk === false) {
throw new \RuntimeException("Could not write to socket");
}

View File

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

View File

@@ -12,6 +12,8 @@
namespace Monolog\Handler;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
/**
* Logs to syslog service.
@@ -64,8 +66,9 @@ class SyslogHandler extends AbstractProcessingHandler
* @param mixed $facility
* @param integer $level The minimum logging level at which this handler will be triggered
* @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
* @param int $logopts Option flags for the openlog() call, defaults to LOG_PID
*/
public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true)
public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG, $bubble = true, $logopts = LOG_PID)
{
parent::__construct($level, $bubble);
@@ -87,7 +90,7 @@ class SyslogHandler extends AbstractProcessingHandler
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.'"');
}
}
@@ -107,4 +110,12 @@ class SyslogHandler extends AbstractProcessingHandler
{
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),
'extra' => array(),
);
// check if any message will handle this message
// check if any handler will handle this message
$handlerKey = null;
foreach ($this->handlers as $key => $handler) {
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')),
'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);
}
$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()

View File

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

View File

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

View File

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

View File

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