diff --git a/.travis.yml b/.travis.yml index eb350496..ea5262a9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: php -php: +php: - 5.3 - 5.4 diff --git a/CHANGELOG.mdown b/CHANGELOG.mdown index 849d3381..b9f93c7d 100644 --- a/CHANGELOG.mdown +++ b/CHANGELOG.mdown @@ -1,3 +1,10 @@ +* 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: diff --git a/composer.json b/composer.json index 7d8964c6..1b7d154c 100644 --- a/composer.json +++ b/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", @@ -27,5 +27,10 @@ }, "autoload": { "psr-0": {"Monolog": "src/"} + }, + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } } } diff --git a/doc/usage.md b/doc/usage.md index 98db29ae..8c5e8e2d 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -121,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. diff --git a/src/Monolog/Formatter/LineFormatter.php b/src/Monolog/Formatter/LineFormatter.php index 1054dbba..dd116967 100644 --- a/src/Monolog/Formatter/LineFormatter.php +++ b/src/Monolog/Formatter/LineFormatter.php @@ -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))); } } diff --git a/src/Monolog/Handler/BufferHandler.php b/src/Monolog/Handler/BufferHandler.php index afbb4a6a..133524ff 100644 --- a/src/Monolog/Handler/BufferHandler.php +++ b/src/Monolog/Handler/BufferHandler.php @@ -24,20 +24,24 @@ use Monolog\Logger; class BufferHandler extends AbstractHandler { protected $handler; - protected $bufferSize; + protected $bufferSize = 0; + protected $bufferLimit; + protected $flushOnOverflow; 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 + * @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 + * @param Boolean $flushOnOverflow If true, the buffer is flushed when the max size has been reached, by default oldest entries are discarded */ - public function __construct(HandlerInterface $handler, $bufferSize = 0, $level = Logger::DEBUG, $bubble = true) + public function __construct(HandlerInterface $handler, $bufferSize = 0, $level = Logger::DEBUG, $bubble = true, $flushOnOverflow = false) { parent::__construct($level, $bubble); $this->handler = $handler; - $this->bufferSize = $bufferSize; + $this->bufferLimit = (int) $bufferSize; + $this->flushOnOverflow = $flushOnOverflow; // __destructor() doesn't get called on Fatal errors register_shutdown_function(array($this, 'close')); @@ -52,22 +56,36 @@ class BufferHandler extends AbstractHandler return false; } - $this->buffer[] = $record; - if ($this->bufferSize > 0 && count($this->buffer) > $this->bufferSize) { - array_shift($this->buffer); + if ($this->bufferLimit > 0 && $this->bufferSize === $this->bufferLimit) { + if ($this->flushOnOverflow) { + $this->flush(); + } else { + array_shift($this->buffer); + $this->bufferSize--; + } } + $this->buffer[] = $record; + $this->bufferSize++; return false === $this->bubble; } + public function flush() + { + if ($this->bufferSize === 0) { + return; + } + + $this->handler->handleBatch($this->buffer); + $this->bufferSize = 0; + $this->buffer = array(); + } + /** * {@inheritdoc} */ public function close() { - if ($this->buffer) { - $this->handler->handleBatch($this->buffer); - $this->buffer = array(); - } + $this->flush(); } } diff --git a/src/Monolog/Handler/CubeHandler.php b/src/Monolog/Handler/CubeHandler.php index c21e0e3e..6ccff26e 100644 --- a/src/Monolog/Handler/CubeHandler.php +++ b/src/Monolog/Handler/CubeHandler.php @@ -103,7 +103,7 @@ class CubeHandler extends AbstractProcessingHandler { $date = $record['datetime']; - $data = array('time' => $date->format('Y-m-d H:i:s')); + $data = array('time' => $date->format('Y-m-d\TH:i:s.u')); unset($record['datetime']); if (isset($record['context']['type'])) { @@ -142,4 +142,4 @@ class CubeHandler extends AbstractProcessingHandler return curl_exec($this->httpConnection); } -} \ No newline at end of file +} diff --git a/src/Monolog/Handler/FingersCrossedHandler.php b/src/Monolog/Handler/FingersCrossedHandler.php index 561ee7cb..c7bddc53 100644 --- a/src/Monolog/Handler/FingersCrossedHandler.php +++ b/src/Monolog/Handler/FingersCrossedHandler.php @@ -34,7 +34,7 @@ class FingersCrossedHandler extends AbstractHandler protected $stopBuffering; /** - * @param callback|HandlerInterface $handler Handler or factory callback($record, $fingersCrossedHandler). + * @param callable|HandlerInterface $handler Handler or factory callable($record, $fingersCrossedHandler). * @param int|ActivationStrategyInterface $activationStrategy Strategy which determines when this handler takes action * @param int $bufferSize How many entries should be buffered at most, beyond that the oldest items are removed from the buffer. * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not @@ -79,10 +79,13 @@ class FingersCrossedHandler extends AbstractHandler $this->buffering = false; } if (!$this->handler instanceof HandlerInterface) { + if (!is_callable($this->handler)) { + throw new \RuntimeException("The given handler (".json_encode($this->handler).") is not a callable nor a Monolog\Handler\HandlerInterface object"); + } $this->handler = call_user_func($this->handler, $record, $this); - } - if (!$this->handler instanceof HandlerInterface) { - throw new \RuntimeException("The factory callback should return a HandlerInterface"); + if (!$this->handler instanceof HandlerInterface) { + throw new \RuntimeException("The factory callable should return a HandlerInterface"); + } } $this->handler->handleBatch($this->buffer); $this->buffer = array(); diff --git a/src/Monolog/Handler/HandlerInterface.php b/src/Monolog/Handler/HandlerInterface.php index d24dc775..9988d9d5 100644 --- a/src/Monolog/Handler/HandlerInterface.php +++ b/src/Monolog/Handler/HandlerInterface.php @@ -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); diff --git a/src/Monolog/Handler/NativeMailerHandler.php b/src/Monolog/Handler/NativeMailerHandler.php index c954de5d..3a0e9356 100644 --- a/src/Monolog/Handler/NativeMailerHandler.php +++ b/src/Monolog/Handler/NativeMailerHandler.php @@ -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) { diff --git a/src/Monolog/Handler/SocketHandler.php b/src/Monolog/Handler/SocketHandler.php index 1aaa22a5..92de217c 100644 --- a/src/Monolog/Handler/SocketHandler.php +++ b/src/Monolog/Handler/SocketHandler.php @@ -190,7 +190,7 @@ class SocketHandler extends AbstractProcessingHandler { $seconds = floor($this->timeout); $microseconds = round(($this->timeout - $seconds)*1e6); - + return stream_set_timeout($this->resource, $seconds, $microseconds); } diff --git a/src/Monolog/Handler/SwiftMailerHandler.php b/src/Monolog/Handler/SwiftMailerHandler.php index 56bf9a29..ca03ccaa 100644 --- a/src/Monolog/Handler/SwiftMailerHandler.php +++ b/src/Monolog/Handler/SwiftMailerHandler.php @@ -25,7 +25,7 @@ class SwiftMailerHandler extends MailHandler /** * @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 callable|\Swift_Message $message An example message for real messages, only the body will be replaced * @param integer $level The minimum logging level at which this handler will be triggered * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not */ @@ -37,7 +37,7 @@ class SwiftMailerHandler extends MailHandler $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'); + throw new \InvalidArgumentException('You must provide either a Swift_Message instance or a callable returning it'); } $this->message = $message; } diff --git a/src/Monolog/Handler/SyslogHandler.php b/src/Monolog/Handler/SyslogHandler.php index 6320fb13..023b8811 100644 --- a/src/Monolog/Handler/SyslogHandler.php +++ b/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 @@ 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.'"'); } } diff --git a/src/Monolog/Logger.php b/src/Monolog/Logger.php index 0ff2aa83..d317e016 100644 --- a/src/Monolog/Logger.php +++ b/src/Monolog/Logger.php @@ -108,8 +108,8 @@ class Logger { $this->name = $name; - if (!self::$timezone) { - self::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); + if (!static::$timezone) { + static::$timezone = new \DateTimeZone(date_default_timezone_get() ?: 'UTC'); } } @@ -183,18 +183,18 @@ class Logger public function addRecord($level, $message, array $context = array()) { if (!$this->handlers) { - $this->pushHandler(new StreamHandler('php://stderr', self::DEBUG)); + $this->pushHandler(new StreamHandler('php://stderr', static::DEBUG)); } $record = array( 'message' => (string) $message, 'context' => $context, 'level' => $level, - 'level_name' => self::getLevelName($level), + 'level_name' => static::getLevelName($level), 'channel' => $this->name, - 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)))->setTimeZone(self::$timezone), + 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true)), static::$timezone)->setTimezone(static::$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)) { @@ -227,7 +227,7 @@ class Logger */ public function addDebug($message, array $context = array()) { - return $this->addRecord(self::DEBUG, $message, $context); + return $this->addRecord(static::DEBUG, $message, $context); } /** @@ -239,7 +239,7 @@ class Logger */ public function addInfo($message, array $context = array()) { - return $this->addRecord(self::INFO, $message, $context); + return $this->addRecord(static::INFO, $message, $context); } /** @@ -251,7 +251,7 @@ class Logger */ public function addNotice($message, array $context = array()) { - return $this->addRecord(self::NOTICE, $message, $context); + return $this->addRecord(static::NOTICE, $message, $context); } /** @@ -263,7 +263,7 @@ class Logger */ public function addWarning($message, array $context = array()) { - return $this->addRecord(self::WARNING, $message, $context); + return $this->addRecord(static::WARNING, $message, $context); } /** @@ -275,7 +275,7 @@ class Logger */ public function addError($message, array $context = array()) { - return $this->addRecord(self::ERROR, $message, $context); + return $this->addRecord(static::ERROR, $message, $context); } /** @@ -287,7 +287,7 @@ class Logger */ public function addCritical($message, array $context = array()) { - return $this->addRecord(self::CRITICAL, $message, $context); + return $this->addRecord(static::CRITICAL, $message, $context); } /** @@ -299,7 +299,7 @@ class Logger */ public function addAlert($message, array $context = array()) { - return $this->addRecord(self::ALERT, $message, $context); + return $this->addRecord(static::ALERT, $message, $context); } /** @@ -311,7 +311,7 @@ class Logger */ public function addEmergency($message, array $context = array()) { - return $this->addRecord(self::EMERGENCY, $message, $context); + return $this->addRecord(static::EMERGENCY, $message, $context); } /** @@ -322,7 +322,11 @@ class Logger */ public static function getLevelName($level) { - return self::$levels[$level]; + if (!isset(static::$levels[$level])) { + throw new \InvalidArgumentException('Level "'.$level.'" is not defined, use one of: '.implode(', ', array_keys(static::$levels))); + } + + return static::$levels[$level]; } /** @@ -337,9 +341,9 @@ class Logger 'message' => '', 'context' => array(), 'level' => $level, - 'level_name' => self::getLevelName($level), + 'level_name' => static::getLevelName($level), 'channel' => $this->name, - 'datetime' => new \DateTime(), + 'datetime' => new \DateTime('now', static::$timezone), 'extra' => array(), ); @@ -363,7 +367,7 @@ class Logger */ public function debug($message, array $context = array()) { - return $this->addRecord(self::DEBUG, $message, $context); + return $this->addRecord(static::DEBUG, $message, $context); } /** @@ -377,7 +381,7 @@ class Logger */ public function info($message, array $context = array()) { - return $this->addRecord(self::INFO, $message, $context); + return $this->addRecord(static::INFO, $message, $context); } /** @@ -391,7 +395,7 @@ class Logger */ public function notice($message, array $context = array()) { - return $this->addRecord(self::NOTICE, $message, $context); + return $this->addRecord(static::NOTICE, $message, $context); } /** @@ -405,7 +409,7 @@ class Logger */ public function warn($message, array $context = array()) { - return $this->addRecord(self::WARNING, $message, $context); + return $this->addRecord(static::WARNING, $message, $context); } /** @@ -419,7 +423,7 @@ class Logger */ public function err($message, array $context = array()) { - return $this->addRecord(self::ERROR, $message, $context); + return $this->addRecord(static::ERROR, $message, $context); } /** @@ -433,7 +437,7 @@ class Logger */ public function crit($message, array $context = array()) { - return $this->addRecord(self::CRITICAL, $message, $context); + return $this->addRecord(static::CRITICAL, $message, $context); } /** @@ -447,7 +451,7 @@ class Logger */ public function alert($message, array $context = array()) { - return $this->addRecord(self::ALERT, $message, $context); + return $this->addRecord(static::ALERT, $message, $context); } /** @@ -461,6 +465,6 @@ class Logger */ public function emerg($message, array $context = array()) { - return $this->addRecord(self::EMERGENCY, $message, $context); + return $this->addRecord(static::EMERGENCY, $message, $context); } } diff --git a/tests/Monolog/Formatter/LineFormatterTest.php b/tests/Monolog/Formatter/LineFormatterTest.php index 9e80917b..e3516f73 100644 --- a/tests/Monolog/Formatter/LineFormatterTest.php +++ b/tests/Monolog/Formatter/LineFormatterTest.php @@ -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() diff --git a/tests/Monolog/Handler/BufferHandlerTest.php b/tests/Monolog/Handler/BufferHandlerTest.php index 5b3c4c8a..9e9795f1 100644 --- a/tests/Monolog/Handler/BufferHandlerTest.php +++ b/tests/Monolog/Handler/BufferHandlerTest.php @@ -36,6 +36,7 @@ class BufferHandlerTest extends TestCase /** * @covers Monolog\Handler\BufferHandler::close + * @covers Monolog\Handler\BufferHandler::flush */ public function testDestructPropagatesRecords() { @@ -65,6 +66,36 @@ class BufferHandlerTest extends TestCase $this->assertFalse($test->hasDebugRecords()); } + /** + * @covers Monolog\Handler\BufferHandler::handle + */ + public function testHandleBufferLimitWithFlushOnOverflow() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 3, Logger::DEBUG, true, true); + + // send two records + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::DEBUG)); + $this->assertFalse($test->hasDebugRecords()); + $this->assertCount(0, $test->getRecords()); + + // overflow + $handler->handle($this->getRecord(Logger::INFO)); + $this->assertTrue($test->hasDebugRecords()); + $this->assertCount(3, $test->getRecords()); + + // should buffer again + $handler->handle($this->getRecord(Logger::WARNING)); + $this->assertCount(3, $test->getRecords()); + + $handler->close(); + $this->assertCount(5, $test->getRecords()); + $this->assertTrue($test->hasWarningRecords()); + $this->assertTrue($test->hasInfoRecords()); + } + /** * @covers Monolog\Handler\BufferHandler::handle */ @@ -81,4 +112,19 @@ class BufferHandlerTest extends TestCase $this->assertTrue($test->hasInfoRecords()); $this->assertFalse($test->hasDebugRecords()); } + + /** + * @covers Monolog\Handler\BufferHandler::flush + */ + public function testFlush() + { + $test = new TestHandler(); + $handler = new BufferHandler($test, 0); + $handler->handle($this->getRecord(Logger::DEBUG)); + $handler->handle($this->getRecord(Logger::INFO)); + $handler->flush(); + $this->assertTrue($test->hasInfoRecords()); + $this->assertTrue($test->hasDebugRecords()); + $this->assertFalse($test->hasWarningRecords()); + } } diff --git a/tests/Monolog/Handler/SyslogHandlerTest.php b/tests/Monolog/Handler/SyslogHandlerTest.php index 88e09b77..98219ac1 100644 --- a/tests/Monolog/Handler/SyslogHandlerTest.php +++ b/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 @@ 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); } /** diff --git a/tests/Monolog/LoggerTest.php b/tests/Monolog/LoggerTest.php index ece04bd5..86e267ad 100644 --- a/tests/Monolog/LoggerTest.php +++ b/tests/Monolog/LoggerTest.php @@ -25,6 +25,23 @@ class LoggerTest extends \PHPUnit_Framework_TestCase $this->assertEquals('foo', $logger->getName()); } + /** + * @covers Monolog\Logger::getLevelName + */ + public function testGetLevelName() + { + $this->assertEquals('ERROR', Logger::getLevelName(Logger::ERROR)); + } + + /** + * @covers Monolog\Logger::getLevelName + * @expectedException InvalidArgumentException + */ + public function testGetLevelNameThrows() + { + Logger::getLevelName(5); + } + /** * @covers Monolog\Logger::__construct */