Skip to content
Browse files

Merge pull request #1 from Seldaek/master

Merging update from parent
  • Loading branch information...
2 parents 6255ae5 + 4b11ac0 commit 9b3524a92d77c39842b05453dd4536bd4bffbf97 @wa0x6e wa0x6e committed Oct 17, 2012
View
3 .gitignore
@@ -2,5 +2,4 @@ vendor
composer.phar
phpunit.xml
composer.lock
-
-.DS_Store
+.DS_Store
View
2 .travis.yml
@@ -1,6 +1,6 @@
language: php
-php:
+php:
- 5.3
- 5.4
View
22 CHANGELOG.mdown
@@ -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
1 README.mdown
@@ -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
7 composer.json
@@ -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
44 doc/usage.md
@@ -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
2 src/Monolog/Formatter/LineFormatter.php
@@ -85,6 +85,6 @@ protected function convertToString($data)
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
1 src/Monolog/Handler/AmqpHandler.php
@@ -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
119 src/Monolog/Handler/CubeHandler.php
@@ -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 @@ private function isConnected($scheme)
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
2 src/Monolog/Handler/HandlerInterface.php
@@ -25,6 +25,8 @@
*
* This is mostly done for performance reasons, to avoid calling processors for nothing.
*
+ * @param array $record
+ *
* @return Boolean
*/
public function isHandling(array $record);
View
2 src/Monolog/Handler/NativeMailerHandler.php
@@ -42,7 +42,7 @@ public function __construct($to, $subject, $from, $level = Logger::ERROR, $bubbl
}
/**
- * @param string|array $header Custom added headers
+ * @param string|array $headers Custom added headers
*/
public function addHeader($headers)
{
View
27 src/Monolog/Handler/SocketHandler.php
@@ -89,7 +89,7 @@ public function setPersistent($boolean)
/**
* 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 @@ public function setConnectionTimeout($seconds)
/**
* 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 @@ protected function fsockopen()
/**
* 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 @@ protected function streamGetMetadata()
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 @@ private function writeToSocket($data)
$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
9 src/Monolog/Handler/StreamHandler.php
@@ -60,10 +60,15 @@ protected function write(array $record)
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
15 src/Monolog/Handler/SyslogHandler.php
@@ -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 @@ public function __construct($ident, $facility = LOG_USER, $level = Logger::DEBUG
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 @@ protected function write(array $record)
{
syslog($this->logLevels[$record['level']], (string) $record['formatted']);
}
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultFormatter()
+ {
+ return new LineFormatter('%channel%.%level_name%: %message% %context% %extra%\n');
+ }
}
View
2 src/Monolog/Logger.php
@@ -194,7 +194,7 @@ public function addRecord($level, $message, array $context = array())
'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
7 tests/Monolog/Formatter/LineFormatterTest.php
@@ -86,11 +86,8 @@ public function testDefFormatWithObject()
'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
1 tests/Monolog/Handler/AbstractProcessingHandlerTest.php
@@ -64,7 +64,6 @@ public function testProcessRecord()
'REQUEST_URI' => '',
'REQUEST_METHOD' => '',
'REMOTE_ADDR' => '',
- 'REQUEST_URI' => '',
'SERVER_NAME' => '',
)));
$handledRecord = null;
View
8 tests/Monolog/Handler/SocketHandlerTest.php
@@ -50,8 +50,8 @@ public function testBadConnectionTimeout()
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 @@ public function testBadTimeout()
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
11 tests/Monolog/Handler/StreamHandlerTest.php
@@ -74,4 +74,15 @@ public function testWriteInvalidResource()
$handler = new StreamHandler('bogus://url');
$handler->handle($this->getRecord());
}
+
+ /**
+ * @expectedException UnexpectedValueException
+ * @covers Monolog\Handler\StreamHandler::__construct
+ * @covers Monolog\Handler\StreamHandler::write
+ */
+ public function testWriteNonExistingResource()
+ {
+ $handler = new StreamHandler('/foo/bar/baz/'.rand(0, 10000));
+ $handler->handle($this->getRecord());
+ }
}
View
4 tests/Monolog/Handler/SyslogHandlerTest.php
@@ -10,6 +10,7 @@
*/
namespace Monolog\Handler;
+use Monolog\Logger;
class SyslogHandlerTest extends \PHPUnit_Framework_TestCase
{
@@ -26,6 +27,9 @@ public function testConstruct()
$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);
}
/**

0 comments on commit 9b3524a

Please sign in to comment.
Something went wrong with that request. Please try again.