diff --git a/app/Config/bootstrap.php b/app/Config/bootstrap.php index 707fd3943..7b5b49810 100644 --- a/app/Config/bootstrap.php +++ b/app/Config/bootstrap.php @@ -163,6 +163,6 @@ CakeLog::config('debug', array( )); CakeLog::config('error', array( 'engine' => 'FileLog', - 'types' => array('error', 'warning'), + 'types' => array('warning', 'error', 'critical', 'alert', 'emergency'), 'file' => 'error', )); diff --git a/app/Config/core.php b/app/Config/core.php index 78eacd805..4bbfabe6e 100644 --- a/app/Config/core.php +++ b/app/Config/core.php @@ -133,7 +133,7 @@ * Defines the default error type when using the log() function. Used for * differentiating error logging and debugging. Currently PHP supports LOG_DEBUG. */ - define('LOG_ERROR', 2); + define('LOG_ERROR', LOG_ERR); /** * Session configuration. diff --git a/lib/Cake/Console/Templates/skel/Config/bootstrap.php b/lib/Cake/Console/Templates/skel/Config/bootstrap.php index 7151dbdd3..786011ee5 100644 --- a/lib/Cake/Console/Templates/skel/Config/bootstrap.php +++ b/lib/Cake/Console/Templates/skel/Config/bootstrap.php @@ -103,6 +103,6 @@ CakeLog::config('debug', array( )); CakeLog::config('error', array( 'engine' => 'FileLog', - 'scopes' => array('error', 'warning'), + 'scopes' => array('warning', 'error', 'critical', 'alert', 'emergency'), 'file' => 'error', )); diff --git a/lib/Cake/Console/Templates/skel/Config/core.php b/lib/Cake/Console/Templates/skel/Config/core.php index bb904bb1b..c53e2fd02 100644 --- a/lib/Cake/Console/Templates/skel/Config/core.php +++ b/lib/Cake/Console/Templates/skel/Config/core.php @@ -133,7 +133,7 @@ * Defines the default error type when using the log() function. Used for * differentiating error logging and debugging. Currently PHP supports LOG_DEBUG. */ - define('LOG_ERROR', 2); + define('LOG_ERROR', LOG_ERR); /** * Session configuration. diff --git a/lib/Cake/Log/CakeLog.php b/lib/Cake/Log/CakeLog.php index 5b56969db..f62c3c3a1 100644 --- a/lib/Cake/Log/CakeLog.php +++ b/lib/Cake/Log/CakeLog.php @@ -24,24 +24,33 @@ * system. * */ -if (!defined('LOG_ERROR')) { - define('LOG_ERROR', 2); +if (!defined('LOG_EMERG')) { + define('LOG_EMERG', 0); +} +if (!defined('LOG_ALERT')) { + define('LOG_ALERT', 1); +} +if (!defined('LOG_CRIT')) { + define('LOG_CRIT', 2); } if (!defined('LOG_ERR')) { - define('LOG_ERR', LOG_ERROR); + define('LOG_ERR', 3); +} +if (!defined('LOG_ERROR')) { + define('LOG_ERROR', LOG_ERR); } if (!defined('LOG_WARNING')) { - define('LOG_WARNING', 3); + define('LOG_WARNING', 4); } if (!defined('LOG_NOTICE')) { - define('LOG_NOTICE', 4); -} -if (!defined('LOG_DEBUG')) { - define('LOG_DEBUG', 5); + define('LOG_NOTICE', 5); } if (!defined('LOG_INFO')) { define('LOG_INFO', 6); } +if (!defined('LOG_DEBUG')) { + define('LOG_DEBUG', 7); +} App::uses('LogEngineCollection', 'Log'); @@ -74,12 +83,38 @@ class CakeLog { */ protected static $_Collection; +/** + * Default log levels as detailed in RFC 5424 + * http://tools.ietf.org/html/rfc5424 + */ + protected static $_defaultLevels = array( + LOG_EMERG => 'emergency', + LOG_ALERT => 'alert', + LOG_CRIT => 'critical', + LOG_ERR => 'error', + LOG_WARNING => 'warning', + LOG_NOTICE => 'notice', + LOG_INFO => 'info', + LOG_DEBUG => 'debug', + ); + +/** + * Active log levels for this instance. + */ + protected static $_levels; + +/** + * Mapped log levels + */ + protected static $_levelMap; + /** * initialize ObjectCollection * * @return void */ protected static function _init() { + self::$_levels = self::defaultLevels(); self::$_Collection = new LogEngineCollection(); } @@ -131,6 +166,70 @@ class CakeLog { return self::$_Collection->attached(); } +/** + * Gets/sets log levels + * + * Call this method without arguments, eg: `CakeLog::levels()` to obtain current + * level configuration. + * + * To append additional level 'user0' and 'user1' to to default log levels: + * + * `CakeLog::levels(array('user0, 'user1'))` or + * `CakeLog::levels(array('user0, 'user1'), true)` + * + * will result in: + * + * array( + * 0 => 'emergency', + * 1 => 'alert', + * ... + * 8 => 'user0', + * 9 => 'user1', + * ); + * + * To set/replace existing configuration, pass an array with the second argument + * set to false. + * + * `CakeLog::levels(array('user0, 'user1'), false); + * + * will result in: + * array( + * 0 => 'user0', + * 1 => 'user1', + * ); + * + * @param mixed $levels array + * @param bool $append true to append, false to replace + * @return array active log levels + */ + public static function levels($levels = array(), $append = true) { + if (empty(self::$_Collection)) { + self::_init(); + } + if (empty($levels)) { + return self::$_levels; + } + $levels = array_values($levels); + if ($append) { + self::$_levels = array_merge(self::$_levels, $levels); + } else { + self::$_levels = $levels; + } + self::$_levelMap = array_flip(self::$_levels); + return self::$_levels; + } + +/** + * Reset log levels to the original value + * + * @return array default log levels + */ + public static function defaultLevels() { + self::$_levels = self::$_defaultLevels; + self::$_levelMap = array_flip(self::$_levels); + return self::$_levels; + } + /** * Removes a stream from the active streams. Once a stream has been removed * it will no longer have messages sent to it. @@ -221,7 +320,7 @@ class CakeLog { protected static function _autoConfig() { self::$_Collection->load('error', array( 'engine' => 'FileLog', - 'types' => array('error', 'warning'), + 'types' => array('warning', 'error', 'critical', 'alert', 'emergency'), 'path' => LOGS, )); } @@ -233,12 +332,14 @@ class CakeLog { * * ### Types: * + * - LOG_EMERG => 'emergency', + * - LOG_ALERT => 'alert', + * - LOG_CRIT => 'critical', + * - `LOG_ERR` => 'error', * - `LOG_WARNING` => 'warning', * - `LOG_NOTICE` => 'notice', * - `LOG_INFO` => 'info', * - `LOG_DEBUG` => 'debug', - * - `LOG_ERR` => 'error', - * - `LOG_ERROR` => 'error' * * ### Usage: * @@ -257,19 +358,11 @@ class CakeLog { if (empty(self::$_Collection)) { self::_init(); } - $levels = array( - LOG_ERROR => 'error', - LOG_ERR => 'error', - LOG_WARNING => 'warning', - LOG_NOTICE => 'notice', - LOG_DEBUG => 'debug', - LOG_INFO => 'info', - ); - if (is_int($type) && isset($levels[$type])) { - $type = $levels[$type]; + if (is_int($type) && isset(self::$_levels[$type])) { + $type = self::$_levels[$type]; } - if (is_string($type) && empty($scope) && !in_array($type, $levels)) { + if (is_string($type) && empty($scope) && !in_array($type, self::$_levels)) { $scope = $type; } if (!self::$_Collection->attached()) { @@ -298,48 +391,91 @@ class CakeLog { } /** - * Convenience method to log error messages + * Convenience method to log emergency messages * + * @param string $message log message + * @param mixed $scope string or array of scopes * @return boolean Success */ - public static function error($message) { - return self::write(LOG_ERROR, $message); + public static function emergency($message, $scope = array()) { + return self::write(self::$_levelMap['emergency'], $message, $scope); + } + +/** + * Convenience method to log alert messages + * + * @param string $message log message + * @param mixed $scope string or array of scopes + * @return boolean Success + */ + public static function alert($message, $scope = array()) { + return self::write(self::$_levelMap['alert'], $message, $scope); + } + +/** + * Convenience method to log critical messages + * + * @param string $message log message + * @param mixed $scope string or array of scopes + * @return boolean Success + */ + public static function critical($message, $scope = array()) { + return self::write(self::$_levelMap['critical'], $message, $scope); + } + +/** + * Convenience method to log error messages + * + * @param string $message log message + * @param mixed $scope string or array of scopes + * @return boolean Success + */ + public static function error($message, $scope = array()) { + return self::write(self::$_levelMap['error'], $message, $scope); } /** * Convenience method to log warning messages * + * @param string $message log message + * @param mixed $scope string or array of scopes * @return boolean Success */ - public static function warning($message) { - return self::write(LOG_WARNING, $message); + public static function warning($message, $scope = array()) { + return self::write(self::$_levelMap['warning'], $message, $scope); } /** * Convenience method to log notice messages * + * @param string $message log message + * @param mixed $scope string or array of scopes * @return boolean Success */ - public static function notice($message) { - return self::write(LOG_NOTICE, $message); + public static function notice($message, $scope = array()) { + return self::write(self::$_levelMap['notice'], $message, $scope); } /** * Convenience method to log debug messages * + * @param string $message log message + * @param mixed $scope string or array of scopes * @return boolean Success */ - public static function debug($message) { - return self::write(LOG_DEBUG, $message); + public static function debug($message, $scope = array()) { + return self::write(self::$_levelMap['debug'], $message, $scope); } /** * Convenience method to log info messages * + * @param string $message log message + * @param mixed $scope string or array of scopes * @return boolean Success */ - public static function info($message) { - return self::write(LOG_INFO, $message); + public static function info($message, $scope = array()) { + return self::write(self::$_levelMap['info'], $message, $scope); } } diff --git a/lib/Cake/Test/Case/Log/CakeLogTest.php b/lib/Cake/Test/Case/Log/CakeLogTest.php index 48af1144b..b552649c9 100644 --- a/lib/Cake/Test/Case/Log/CakeLogTest.php +++ b/lib/Cake/Test/Case/Log/CakeLogTest.php @@ -289,7 +289,7 @@ class CakeLogTest extends CakeTestCase { )); CakeLog::config('error', array( 'engine' => 'FileLog', - 'types' => array('error', 'warning'), + 'types' => array('warning', 'error', 'critical', 'alert', 'emergency'), 'file' => 'error', )); } @@ -430,6 +430,52 @@ class CakeLogTest extends CakeTestCase { CakeLog::drop('shops'); } +/** + * test scoped logging with convenience methods + */ + public function testConvenienceScopedLogging() { + if (file_exists(LOGS . 'shops.log')) { + unlink(LOGS . 'shops.log'); + } + if (file_exists(LOGS . 'error.log')) { + unlink(LOGS . 'error.log'); + } + if (file_exists(LOGS . 'debug.log')) { + unlink(LOGS . 'debug.log'); + } + + $this->_resetLogConfig(); + CakeLog::config('shops', array( + 'engine' => 'FileLog', + 'types' => array('info', 'notice', 'warning'), + 'scopes' => array('transactions', 'orders'), + 'file' => 'shops', + )); + + CakeLog::info('info message', 'transactions'); + $this->assertFalse(file_exists(LOGS . 'error.log')); + $this->assertTrue(file_exists(LOGS . 'shops.log')); + $this->assertTrue(file_exists(LOGS . 'debug.log')); + + $this->_deleteLogs(); + + CakeLog::error('error message', 'orders'); + $this->assertTrue(file_exists(LOGS . 'error.log')); + $this->assertFalse(file_exists(LOGS . 'debug.log')); + $this->assertFalse(file_exists(LOGS . 'shops.log')); + + $this->_deleteLogs(); + + CakeLog::warning('warning message', 'orders'); + $this->assertTrue(file_exists(LOGS . 'error.log')); + $this->assertTrue(file_exists(LOGS . 'shops.log')); + $this->assertFalse(file_exists(LOGS . 'debug.log')); + + $this->_deleteLogs(); + + CakeLog::drop('shops'); + } + /** * test convenience methods */ @@ -443,44 +489,142 @@ class CakeLogTest extends CakeTestCase { )); CakeLog::config('error', array( 'engine' => 'FileLog', - 'types' => array('error', 'warning'), + 'types' => array('emergency', 'alert', 'critical', 'error', 'warning'), 'file' => 'error', )); + $testMessage = 'emergency message'; + CakeLog::emergency($testMessage); + $contents = file_get_contents(LOGS . 'error.log'); + $this->assertContains('Emergency: ' . $testMessage, $contents); + $this->assertFalse(file_exists(LOGS . 'debug.log')); + $this->_deleteLogs(); + + $testMessage = 'alert message'; + CakeLog::alert($testMessage); + $contents = file_get_contents(LOGS . 'error.log'); + $this->assertContains('Alert: ' . $testMessage, $contents); + $this->assertFalse(file_exists(LOGS . 'debug.log')); + $this->_deleteLogs(); + + $testMessage = 'critical message'; + CakeLog::critical($testMessage); + $contents = file_get_contents(LOGS . 'error.log'); + $this->assertContains('Critical: ' . $testMessage, $contents); + $this->assertFalse(file_exists(LOGS . 'debug.log')); + $this->_deleteLogs(); + $testMessage = 'error message'; CakeLog::error($testMessage); $contents = file_get_contents(LOGS . 'error.log'); - $this->assertContains($testMessage, $contents); + $this->assertContains('Error: ' . $testMessage, $contents); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); $testMessage = 'warning message'; CakeLog::warning($testMessage); $contents = file_get_contents(LOGS . 'error.log'); - $this->assertContains($testMessage, $contents); + $this->assertContains('Warning: ' . $testMessage, $contents); $this->assertFalse(file_exists(LOGS . 'debug.log')); $this->_deleteLogs(); + $testMessage = 'notice message'; + CakeLog::notice($testMessage); + $contents = file_get_contents(LOGS . 'debug.log'); + $this->assertContains('Notice: ' . $testMessage, $contents); + $this->assertFalse(file_exists(LOGS . 'error.log')); + $this->_deleteLogs(); + $testMessage = 'info message'; CakeLog::info($testMessage); $contents = file_get_contents(LOGS . 'debug.log'); - $this->assertContains($testMessage, $contents); + $this->assertContains('Info: ' . $testMessage, $contents); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->_deleteLogs(); $testMessage = 'debug message'; CakeLog::debug($testMessage); $contents = file_get_contents(LOGS . 'debug.log'); - $this->assertContains($testMessage, $contents); - $this->assertFalse(file_exists(LOGS . 'error.log')); - $this->_deleteLogs(); - - $testMessage = 'notice message'; - CakeLog::notice($testMessage); - $contents = file_get_contents(LOGS . 'debug.log'); - $this->assertContains($testMessage, $contents); + $this->assertContains('Debug: ' . $testMessage, $contents); $this->assertFalse(file_exists(LOGS . 'error.log')); $this->_deleteLogs(); } +/** + * test levels customization + */ + public function testLevelCustomization() { + $this->skipIf(DIRECTORY_SEPARATOR === '\\', 'Log level tests not supported on Windows.'); + + $levels = CakeLog::defaultLevels(); + $this->assertNotEmpty($levels); + $result = array_keys($levels); + $this->assertEquals(array(0, 1, 2, 3, 4, 5, 6, 7), $result); + + $levels = CakeLog::levels(array('foo', 'bar')); + CakeLog::defaultLevels(); + $this->assertEquals('foo', $levels[8]); + $this->assertEquals('bar', $levels[9]); + + $levels = CakeLog::levels(array(11 => 'spam', 'bar' => 'eggs')); + CakeLog::defaultLevels(); + $this->assertEquals('spam', $levels[8]); + $this->assertEquals('eggs', $levels[9]); + + $levels = CakeLog::levels(array(11 => 'spam', 'bar' => 'eggs'), false); + CakeLog::defaultLevels(); + $this->assertEquals(array('spam', 'eggs'), $levels); + + $levels = CakeLog::levels(array('ham', 9 => 'spam', '12' => 'fam'), false); + CakeLog::defaultLevels(); + $this->assertEquals(array('ham', 'spam', 'fam'), $levels); + } + +/** + * Test writing log files with custom levels + */ + public function testCustomLevelWrites() { + $this->_deleteLogs(); + $this->_resetLogConfig(); + + $levels = CakeLog::levels(array('spam', 'eggs')); + + $testMessage = 'error message'; + CakeLog::write('error', $testMessage); + CakeLog::defaultLevels(); + $this->assertTrue(file_exists(LOGS . 'error.log')); + $contents = file_get_contents(LOGS . 'error.log'); + $this->assertContains('Error: ' . $testMessage, $contents); + + CakeLog::config('spam', array( + 'engine' => 'FileLog', + 'file' => 'spam.log', + 'types' => 'spam', + )); + CakeLog::config('eggs', array( + 'engine' => 'FileLog', + 'file' => 'eggs.log', + 'types' => array('spam', 'eggs'), + )); + + $testMessage = 'spam message'; + CakeLog::write('spam', $testMessage); + CakeLog::defaultLevels(); + $this->assertTrue(file_exists(LOGS . 'spam.log')); + $this->assertTrue(file_exists(LOGS . 'eggs.log')); + $contents = file_get_contents(LOGS . 'spam.log'); + $this->assertContains('Spam: ' . $testMessage, $contents); + + $testMessage = 'egg message'; + CakeLog::write('eggs', $testMessage); + CakeLog::defaultLevels(); + $contents = file_get_contents(LOGS . 'spam.log'); + $this->assertNotContains('Eggs: ' . $testMessage, $contents); + $contents = file_get_contents(LOGS . 'eggs.log'); + $this->assertContains('Eggs: ' . $testMessage, $contents); + + CakeLog::drop('spam'); + CakeLog::drop('eggs'); + } + }