diff --git a/app/Config/bootstrap.php b/app/Config/bootstrap.php index 287fc9469..4e7723e86 100644 --- a/app/Config/bootstrap.php +++ b/app/Config/bootstrap.php @@ -151,3 +151,17 @@ Configure::write('Dispatcher.filters', array( 'AssetDispatcher', 'CacheDispatcher' )); + +/** + * Configures default file logging options + */ +CakeLog::config('debug', array( + 'engine' => 'FileLog', + 'types' => array('notice', 'info', 'debug'), + 'file' => 'debug', +)); +CakeLog::config('error', array( + 'engine' => 'FileLog', + 'types' => array('error', 'warning'), + 'file' => 'error', +)); diff --git a/lib/Cake/Console/Shell.php b/lib/Cake/Console/Shell.php index 788bb8b39..a16dafe0d 100644 --- a/lib/Cake/Console/Shell.php +++ b/lib/Cake/Console/Shell.php @@ -165,9 +165,19 @@ class Shell extends Object { if ($this->stdout == null) { $this->stdout = new ConsoleOutput('php://stdout'); } + CakeLog::config('stdout', array( + 'engine' => 'ConsoleLog', + 'types' => array('notice', 'info'), + 'stream' => $this->stdout, + )); if ($this->stderr == null) { $this->stderr = new ConsoleOutput('php://stderr'); } + CakeLog::config('stderr', array( + 'engine' => 'ConsoleLog', + 'types' => array('error', 'warning'), + 'stream' => $this->stderr, + )); if ($this->stdin == null) { $this->stdin = new ConsoleInput('php://stdin'); } diff --git a/lib/Cake/Console/Templates/skel/Config/bootstrap.php b/lib/Cake/Console/Templates/skel/Config/bootstrap.php index 4b1ed7de0..fefb7d47a 100644 --- a/lib/Cake/Console/Templates/skel/Config/bootstrap.php +++ b/lib/Cake/Console/Templates/skel/Config/bootstrap.php @@ -91,3 +91,17 @@ Configure::write('Dispatcher.filters', array( 'AssetDispatcher', 'CacheDispatcher' )); + +/** + * Configures default file logging options + */ +CakeLog::config('debug', array( + 'engine' => 'FileLog', + 'scopes' => array('notice', 'info', 'debug'), + 'file' => 'debug', +)); +CakeLog::config('error', array( + 'engine' => 'FileLog', + 'scopes' => array('error', 'warning'), + 'file' => 'error', +)); diff --git a/lib/Cake/Log/CakeLog.php b/lib/Cake/Log/CakeLog.php index 5b718ebcb..450781c69 100644 --- a/lib/Cake/Log/CakeLog.php +++ b/lib/Cake/Log/CakeLog.php @@ -24,6 +24,12 @@ * system. * */ +if (!defined('LOG_ERROR')) { + define('LOG_ERROR', 2); +} +if (!defined('LOG_ERR')) { + define('LOG_ERR', LOG_ERROR); +} if (!defined('LOG_WARNING')) { define('LOG_WARNING', 3); } @@ -37,6 +43,8 @@ if (!defined('LOG_INFO')) { define('LOG_INFO', 6); } +App::uses('LogEngineCollection', 'Log'); + /** * Logs messages to configured Log adapters. One or more adapters can be configured * using CakeLogs's methods. If you don't configure any adapters, and write to the logs @@ -60,12 +68,20 @@ if (!defined('LOG_INFO')) { class CakeLog { /** - * An array of connected streams. - * Each stream represents a callable that will be called when write() is called. + * LogEngineCollection class * - * @var array + * @var LogEngineCollection */ - protected static $_streams = array(); + protected static $_Collection; + +/** + * initialize ObjectCollection + * + * @return void + */ + protected static function _init() { + self::$_Collection = new LogEngineCollection(); + } /** * Configure and add a new logging stream to CakeLog @@ -90,47 +106,29 @@ class CakeLog { * @throws CakeLogException */ public static function config($key, $config) { + if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/', $key)) { + throw new CakeLogException(__d('cake_dev', 'Invalid key name')); + } if (empty($config['engine'])) { throw new CakeLogException(__d('cake_dev', 'Missing logger classname')); } - $loggerName = $config['engine']; - unset($config['engine']); - $className = self::_getLogger($loggerName); - $logger = new $className($config); - if (!$logger instanceof CakeLogInterface) { - throw new CakeLogException(sprintf( - __d('cake_dev', 'logger class %s does not implement a write method.'), $loggerName - )); + if (empty(self::$_Collection)) { + self::_init(); } - self::$_streams[$key] = $logger; + self::$_Collection->load($key, $config); return true; } -/** - * Attempts to import a logger class from the various paths it could be on. - * Checks that the logger class implements a write method as well. - * - * @param string $loggerName the plugin.className of the logger class you want to build. - * @return mixed boolean false on any failures, string of classname to use if search was successful. - * @throws CakeLogException - */ - protected static function _getLogger($loggerName) { - list($plugin, $loggerName) = pluginSplit($loggerName, true); - - App::uses($loggerName, $plugin . 'Log/Engine'); - if (!class_exists($loggerName)) { - throw new CakeLogException(__d('cake_dev', 'Could not load class %s', $loggerName)); - } - return $loggerName; - } - /** * Returns the keynames of the currently active streams * * @return array Array of configured log streams. */ public static function configured() { - return array_keys(self::$_streams); + if (empty(self::$_Collection)) { + self::_init(); + } + return self::$_Collection->attached(); } /** @@ -141,7 +139,78 @@ class CakeLog { * @return void */ public static function drop($streamName) { - unset(self::$_streams[$streamName]); + if (empty(self::$_Collection)) { + self::_init(); + } + self::$_Collection->unload($streamName); + } + +/** + * Checks wether $streamName is enabled + * + * @param string $streamName to check + * @return bool + * @throws CakeLogException + */ + public static function enabled($streamName) { + if (empty(self::$_Collection)) { + self::_init(); + } + if (!isset(self::$_Collection->{$streamName})) { + throw new CakeLogException(__d('cake_dev', 'Stream %s not found', $streamName)); + } + return self::$_Collection->enabled($streamName); + } + +/** + * Enable stream + * + * @param string $streamName to enable + * @return void + * @throws CakeLogException + */ + public static function enable($streamName) { + if (empty(self::$_Collection)) { + self::_init(); + } + if (!isset(self::$_Collection->{$streamName})) { + throw new CakeLogException(__d('cake_dev', 'Stream %s not found', $streamName)); + } + self::$_Collection->enable($streamName); + } + +/** + * Disable stream + * + * @param string $streamName to disable + * @return void + * @throws CakeLogException + */ + public static function disable($streamName) { + if (empty(self::$_Collection)) { + self::_init(); + } + if (!isset(self::$_Collection->{$streamName})) { + throw new CakeLogException(__d('cake_dev', 'Stream %s not found', $streamName)); + } + self::$_Collection->disable($streamName); + } + +/** + * Gets the logging engine from the active streams. + * + * @see BaseLog + * @param string $streamName Key name of a configured stream to get. + * @return $mixed instance of BaseLog or false if not found + */ + public static function stream($streamName) { + if (empty(self::$_Collection)) { + self::_init(); + } + if (!empty(self::$_Collection->{$streamName})) { + return self::$_Collection->{$streamName}; + } + return false; } /** @@ -150,8 +219,14 @@ class CakeLog { * @return void */ protected static function _autoConfig() { - self::_getLogger('FileLog'); - self::$_streams['default'] = new FileLog(array('path' => LOGS)); + if (empty(self::$_Collection)) { + self::_init(); + } + self::$_Collection->load('error', array( + 'engine' => 'FileLog', + 'types' => array('error', 'warning'), + 'path' => LOGS, + )); } /** @@ -174,36 +249,96 @@ class CakeLog { * * `CakeLog::write('warning', 'Stuff is broken here');` * - * @param string $type Type of message being written + * @param mixed $type Type of message being written. When value is an integer + * or a string matching the recognized levels, then it will + * be treated log levels. Otherwise it's treated as scope. * @param string $message Message content to log + * @param mixed $scope string or array * @return boolean Success */ - public static function write($type, $message) { - if (!defined('LOG_ERROR')) { - define('LOG_ERROR', 2); - } - if (!defined('LOG_ERR')) { - define('LOG_ERR', LOG_ERROR); + public static function write($type, $message, $scope = array()) { + if (empty(self::$_Collection)) { + self::_init(); } $levels = array( + LOG_ERROR => 'error', + LOG_ERR => 'error', LOG_WARNING => 'warning', LOG_NOTICE => 'notice', - LOG_INFO => 'info', LOG_DEBUG => 'debug', - LOG_ERR => 'error', - LOG_ERROR => 'error' + LOG_INFO => 'info', ); if (is_int($type) && isset($levels[$type])) { $type = $levels[$type]; } + if (is_string($type) && empty($scope) && !in_array($type, $levels)) { + $scope = $type; + } if (empty(self::$_streams)) { self::_autoConfig(); } - foreach (self::$_streams as $logger) { - $logger->write($type, $message); + foreach (self::$_Collection->enabled() as $streamName) { + $logger = self::$_Collection->{$streamName}; + $config = $logger->config(); + $types = $config['types']; + $scopes = $config['scopes']; + if (is_string($scope)) { + $inScope = in_array($scope, $scopes); + } else { + $intersect = array_intersect($scope, $scopes); + $inScope = !empty($intersect); + } + if (empty($types) || in_array($type, $types) || in_array($type, $scopes) && $inScope) { + $logger->write($type, $message); + } } return true; } +/** + * Convenience method to log error messages + * + * @return boolean Success + */ + public static function error($message) { + return self::write(LOG_ERROR, $message); + } + +/** + * Convenience method to log warning messages + * + * @return boolean Success + */ + public static function warning($message) { + return self::write(LOG_WARNING, $message); + } + +/** + * Convenience method to log notice messages + * + * @return boolean Success + */ + public static function notice($message) { + return self::write(LOG_NOTICE, $message); + } + +/** + * Convenience method to log debug messages + * + * @return boolean Success + */ + public static function debug($message) { + return self::write(LOG_DEBUG, $message); + } + +/** + * Convenience method to log info messages + * + * @return boolean Success + */ + public static function info($message) { + return self::write(LOG_INFO, $message); + } + } diff --git a/lib/Cake/Log/Engine/BaseLog.php b/lib/Cake/Log/Engine/BaseLog.php new file mode 100644 index 000000000..69bcdf781 --- /dev/null +++ b/lib/Cake/Log/Engine/BaseLog.php @@ -0,0 +1,61 @@ +config($config); + } + +/** + * Sets instance config. When $config is null, returns config array + * + * @param array $config engine configuration + * @return array + */ + public function config($config = array()) { + if (!empty($config)) { + if (isset($config['types']) && is_string($config['types'])) { + $config['types'] = array($config['types']); + } + $this->_config = $config; + } + return $this->_config; + } + +} diff --git a/lib/Cake/Log/Engine/ConsoleLog.php b/lib/Cake/Log/Engine/ConsoleLog.php new file mode 100644 index 000000000..518a8e445 --- /dev/null +++ b/lib/Cake/Log/Engine/ConsoleLog.php @@ -0,0 +1,79 @@ + 'php://stderr', + 'types' => null, + 'scopes' => array(), + 'outputAs' => ConsoleOutput::COLOR, + ), $this->_config); + $config = $this->config($config); + if ($config['stream'] instanceof ConsoleOutput) { + $this->_output = $config['stream']; + } elseif (is_string($config['stream'])) { + $this->_output = new ConsoleOutput($config['stream']); + } else { + throw new CakeLogException('`stream` not a ConsoleOutput nor string'); + } + $this->_output->outputAs($config['outputAs']); + } + +/** + * Implements writing to console. + * + * @param string $type The type of log you are making. + * @param string $message The message you want to log. + * @return boolean success of write. + */ + public function write($type, $message) { + $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message . "\n"; + return $this->_output->write(sprintf('<%s>%s', $type, $output, $type), false); + } + +} diff --git a/lib/Cake/Log/Engine/FileLog.php b/lib/Cake/Log/Engine/FileLog.php index 81ea12ffc..7ab738c1e 100644 --- a/lib/Cake/Log/Engine/FileLog.php +++ b/lib/Cake/Log/Engine/FileLog.php @@ -17,7 +17,7 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -App::uses('CakeLogInterface', 'Log'); +App::uses('BaseLog', 'Log/Engine'); /** * File Storage stream for Logging. Writes logs to different files @@ -25,7 +25,7 @@ App::uses('CakeLogInterface', 'Log'); * * @package Cake.Log.Engine */ -class FileLog implements CakeLogInterface { +class FileLog extends BaseLog { /** * Path to save log files on. @@ -37,15 +37,26 @@ class FileLog implements CakeLogInterface { /** * Constructs a new File Logger. * - * Options + * Config * * - `path` the path to save logs on. * * @param array $options Options for the FileLog, see above. */ - public function __construct($options = array()) { - $options += array('path' => LOGS); - $this->_path = $options['path']; + public function __construct($config = array()) { + parent::__construct($config); + $config = Set::merge(array( + 'path' => LOGS, + 'file' => null, + 'types' => null, + 'scopes' => array(), + ), $this->_config); + $config = $this->config($config); + $this->_path = $config['path']; + $this->_file = $config['file']; + if (!empty($this->_file) && !preg_match('/\.log$/', $this->_file)) { + $this->_file .= '.log'; + } } /** @@ -58,10 +69,14 @@ class FileLog implements CakeLogInterface { public function write($type, $message) { $debugTypes = array('notice', 'info', 'debug'); - if ($type == 'error' || $type == 'warning') { + if (!empty($this->_file)) { + $filename = $this->_path . $this->_file; + } elseif ($type == 'error' || $type == 'warning') { $filename = $this->_path . 'error.log'; } elseif (in_array($type, $debugTypes)) { $filename = $this->_path . 'debug.log'; + } elseif (in_array($type, $this->_config['scopes'])) { + $filename = $this->_path . $this->_file; } else { $filename = $this->_path . $type . '.log'; } diff --git a/lib/Cake/Log/LogEngineCollection.php b/lib/Cake/Log/LogEngineCollection.php new file mode 100644 index 000000000..ce9e27303 --- /dev/null +++ b/lib/Cake/Log/LogEngineCollection.php @@ -0,0 +1,73 @@ +_getLogger($loggerName); + $logger = new $className($options); + if (!$logger instanceof CakeLogInterface) { + throw new CakeLogException(sprintf( + __d('cake_dev', 'logger class %s does not implement a write method.'), $loggerName + )); + } + $this->_loaded[$name] = $logger; + if ($enable) { + $this->enable($name); + } + return $logger; + } + +/** + * Attempts to import a logger class from the various paths it could be on. + * Checks that the logger class implements a write method as well. + * + * @param string $loggerName the plugin.className of the logger class you want to build. + * @return mixed boolean false on any failures, string of classname to use if search was successful. + * @throws CakeLogException + */ + protected static function _getLogger($loggerName) { + list($plugin, $loggerName) = pluginSplit($loggerName, true); + + App::uses($loggerName, $plugin . 'Log/Engine'); + if (!class_exists($loggerName)) { + throw new CakeLogException(__d('cake_dev', 'Could not load class %s', $loggerName)); + } + return $loggerName; + } + +} diff --git a/lib/Cake/Test/Case/BasicsTest.php b/lib/Cake/Test/Case/BasicsTest.php index cd00c1f43..9e4404374 100644 --- a/lib/Cake/Test/Case/BasicsTest.php +++ b/lib/Cake/Test/Case/BasicsTest.php @@ -596,9 +596,14 @@ class BasicsTest extends CakeTestCase { public function testLogError() { @unlink(LOGS . 'error.log'); + // disable stderr output for this test + CakeLog::disable('stderr'); + LogError('Testing LogError() basic function'); LogError("Testing with\nmulti-line\nstring"); + CakeLog::enable('stderr'); + $result = file_get_contents(LOGS . 'error.log'); $this->assertRegExp('/Error: Testing LogError\(\) basic function/', $result); $this->assertNotRegExp("/Error: Testing with\nmulti-line\nstring/", $result); diff --git a/lib/Cake/Test/Case/Console/ShellTest.php b/lib/Cake/Test/Case/Console/ShellTest.php index 4a0bf1b58..4d7248243 100644 --- a/lib/Cake/Test/Case/Console/ShellTest.php +++ b/lib/Cake/Test/Case/Console/ShellTest.php @@ -44,6 +44,13 @@ class ShellTestShell extends Shell { */ public $stopped; +/** + * testMessage property + * + * @var string + */ + public $testMessage = 'all your base are belong to us'; + /** * stop method * @@ -63,6 +70,10 @@ class ShellTestShell extends Shell { protected function no_access() { } + + public function log_something() { + $this->log($this->testMessage); + } //@codingStandardsIgnoreEnd public function mergeVars($properties, $class, $normalize = true) { @@ -799,4 +810,34 @@ TEXT; $this->assertEquals('plugin.test', $parser->command()); } +/** + * Test file and console and logging + */ + public function testFileAndConsoleLogging() { + // file logging + $this->Shell->log_something(); + $this->assertTrue(file_exists(LOGS . 'error.log')); + + unlink(LOGS . 'error.log'); + $this->assertFalse(file_exists(LOGS . 'error.log')); + + // both file and console logging + require_once CORE_TEST_CASES . DS . 'Log' . DS . 'Engine' . DS . 'ConsoleLogTest.php'; + $mock = $this->getMock('ConsoleLog', array('write'), array( + array('types' => 'error'), + )); + TestCakeLog::config('console', array( + 'engine' => 'ConsoleLog', + 'stream' => 'php://stderr', + )); + TestCakeLog::replace('console', $mock); + $mock->expects($this->once()) + ->method('write') + ->with('error', $this->Shell->testMessage); + $this->Shell->log_something(); + $this->assertTrue(file_exists(LOGS . 'error.log')); + $contents = file_get_contents(LOGS . 'error.log'); + $this->assertContains($this->Shell->testMessage, $contents); + } + } diff --git a/lib/Cake/Test/Case/Error/ErrorHandlerTest.php b/lib/Cake/Test/Case/Error/ErrorHandlerTest.php index 9b4500e6d..e4bd57902 100644 --- a/lib/Cake/Test/Case/Error/ErrorHandlerTest.php +++ b/lib/Cake/Test/Case/Error/ErrorHandlerTest.php @@ -48,6 +48,9 @@ class ErrorHandlerTest extends CakeTestCase { $request->base = ''; Router::setRequestInfo($request); Configure::write('debug', 2); + + CakeLog::disable('stdout'); + CakeLog::disable('stderr'); } /** @@ -60,6 +63,8 @@ class ErrorHandlerTest extends CakeTestCase { if ($this->_restoreError) { restore_error_handler(); } + CakeLog::enable('stdout'); + CakeLog::enable('stderr'); } /** @@ -221,7 +226,7 @@ class ErrorHandlerTest extends CakeTestCase { * * @return void */ - public function testLoadPluginHanlder() { + public function testLoadPluginHandler() { App::build(array( 'Plugin' => array( CAKE . 'Test' . DS . 'test_app' . DS . 'Plugin' . DS diff --git a/lib/Cake/Test/Case/Log/CakeLogTest.php b/lib/Cake/Test/Case/Log/CakeLogTest.php index a24731a01..518bdc94f 100644 --- a/lib/Cake/Test/Case/Log/CakeLogTest.php +++ b/lib/Cake/Test/Case/Log/CakeLogTest.php @@ -78,6 +78,28 @@ class CakeLogTest extends CakeTestCase { CakeLog::config('fail', array()); } +/** + * test config() with valid key name + * + * @return void + */ + public function testValidKeyName() { + CakeLog::config('valid', array('engine' => 'FileLog')); + $stream = CakeLog::stream('valid'); + $this->assertInstanceOf('FileLog', $stream); + CakeLog::drop('valid'); + } + +/** + * test config() with invalid key name + * + * @expectedException CakeLogException + * @return void + */ + public function testInvalidKeyName() { + CakeLog::config('1nv', array('engine' => 'FileLog')); + } + /** * test that loggers have to implement the correct interface. * @@ -102,7 +124,7 @@ class CakeLogTest extends CakeTestCase { $this->assertTrue(file_exists(LOGS . 'error.log')); $result = CakeLog::configured(); - $this->assertEquals(array('default'), $result); + $this->assertEquals(array('error'), $result); unlink(LOGS . 'error.log'); } @@ -170,4 +192,295 @@ class CakeLogTest extends CakeTestCase { unlink(LOGS . 'error.log'); } +/** + * test selective logging + */ + public function testSelectiveLogging() { + if (file_exists(LOGS . 'spam.log')) { + unlink(LOGS . 'spam.log'); + } + if (file_exists(LOGS . 'eggs.log')) { + unlink(LOGS . 'eggs.log'); + } + CakeLog::config('spam', array( + 'engine' => 'FileLog', + 'types' => 'info', + 'file' => 'spam', + )); + CakeLog::config('eggs', array( + 'engine' => 'FileLog', + 'types' => array('eggs', 'info', 'error', 'warning'), + 'file' => 'eggs', + )); + + $testMessage = 'selective logging'; + CakeLog::write(LOG_WARNING, $testMessage); + + $this->assertTrue(file_exists(LOGS . 'eggs.log')); + $this->assertFalse(file_exists(LOGS . 'spam.log')); + + CakeLog::write(LOG_INFO, $testMessage); + $this->assertTrue(file_exists(LOGS . 'spam.log')); + + $contents = file_get_contents(LOGS . 'spam.log'); + $this->assertContains('Info: ' . $testMessage, $contents); + $contents = file_get_contents(LOGS . 'eggs.log'); + $this->assertContains('Info: ' . $testMessage, $contents); + + if (file_exists(LOGS . 'spam.log')) { + unlink(LOGS . 'spam.log'); + } + if (file_exists(LOGS . 'eggs.log')) { + unlink(LOGS . 'eggs.log'); + } + } + +/** + * test enable + * @expectedException CakeLogException + */ + public function testStreamEnable() { + CakeLog::config('spam', array( + 'engine' => 'FileLog', + 'file' => 'spam', + )); + $this->assertTrue(CakeLog::enabled('spam')); + CakeLog::drop('spam'); + CakeLog::enable('bogus_stream'); + } + +/** + * test disable + * @expectedException CakeLogException + */ + public function testStreamDisable() { + CakeLog::config('spam', array( + 'engine' => 'FileLog', + 'file' => 'spam', + )); + $this->assertTrue(CakeLog::enabled('spam')); + CakeLog::disable('spam'); + $this->assertFalse(CakeLog::enabled('spam')); + CakeLog::drop('spam'); + CakeLog::enable('bogus_stream'); + } + +/** + * test enabled() invalid stream + * @expectedException CakeLogException + */ + public function testStreamEnabledInvalid() { + CakeLog::enabled('bogus_stream'); + } + +/** + * test disable invalid stream + * @expectedException CakeLogException + */ + public function testStreamDisableInvalid() { + CakeLog::disable('bogus_stream'); + } + + protected function _deleteLogs() { + @unlink(LOGS . 'shops.log'); + @unlink(LOGS . 'error.log'); + @unlink(LOGS . 'debug.log'); + } + +/** + * test backward compatible scoped logging + */ + public function testScopedLoggingBC() { + 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'); + } + + CakeLog::config('debug', array( + 'engine' => 'FileLog', + 'types' => array('notice', 'info', 'debug'), + 'file' => 'debug', + )); + CakeLog::config('error', array( + 'engine' => 'FileLog', + 'types' => array('error', 'warning'), + 'file' => 'error', + )); + CakeLog::config('shops', array( + 'engine' => 'FileLog', + 'types' => array('info', 'notice', 'warning'), + 'scopes' => array('transactions', 'orders'), + 'file' => 'shops', + )); + + CakeLog::write('info', 'info message'); + $this->assertFalse(file_exists(LOGS . 'error.log')); + $this->assertTrue(file_exists(LOGS . 'shops.log')); + $this->assertTrue(file_exists(LOGS . 'debug.log')); + + $this->_deleteLogs(); + + CakeLog::write('transactions', 'transaction message'); + $this->assertTrue(file_exists(LOGS . 'shops.log')); + $this->assertFalse(file_exists(LOGS . 'transactions.log')); + $this->assertFalse(file_exists(LOGS . 'error.log')); + $this->assertFalse(file_exists(LOGS . 'debug.log')); + + $this->_deleteLogs(); + + CakeLog::write('error', 'error message'); + $this->assertTrue(file_exists(LOGS . 'error.log')); + $this->assertFalse(file_exists(LOGS . 'debug.log')); + $this->assertFalse(file_exists(LOGS . 'shops.log')); + + $this->_deleteLogs(); + + CakeLog::write('orders', 'order message'); + $this->assertFalse(file_exists(LOGS . 'error.log')); + $this->assertFalse(file_exists(LOGS . 'debug.log')); + $this->assertFalse(file_exists(LOGS . 'orders.log')); + $this->assertTrue(file_exists(LOGS . 'shops.log')); + + $this->_deleteLogs(); + + CakeLog::write('warning', 'warning message'); + $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 scoped logging + */ + public function testScopedLogging() { + 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'); + } + + CakeLog::config('debug', array( + 'engine' => 'FileLog', + 'types' => array('notice', 'info', 'debug'), + 'file' => 'debug', + )); + CakeLog::config('error', array( + 'engine' => 'FileLog', + 'types' => array('error', 'warning'), + 'file' => 'error', + )); + CakeLog::config('shops', array( + 'engine' => 'FileLog', + 'types' => array('info', 'notice', 'warning'), + 'scopes' => array('transactions', 'orders'), + 'file' => 'shops', + )); + + CakeLog::write('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::write('transactions', 'transaction message', 'orders'); + $this->assertTrue(file_exists(LOGS . 'shops.log')); + $this->assertFalse(file_exists(LOGS . 'transactions.log')); + $this->assertFalse(file_exists(LOGS . 'error.log')); + $this->assertFalse(file_exists(LOGS . 'debug.log')); + + $this->_deleteLogs(); + + CakeLog::write('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::write('orders', 'order message', 'transactions'); + $this->assertFalse(file_exists(LOGS . 'error.log')); + $this->assertFalse(file_exists(LOGS . 'debug.log')); + $this->assertFalse(file_exists(LOGS . 'orders.log')); + $this->assertTrue(file_exists(LOGS . 'shops.log')); + + $this->_deleteLogs(); + + CakeLog::write('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 + */ + public function testConvenienceMethods() { + @unlink(LOGS . 'error.log'); + @unlink(LOGS . 'debug.log'); + + CakeLog::config('debug', array( + 'engine' => 'FileLog', + 'types' => array('notice', 'info', 'debug'), + 'file' => 'debug', + )); + CakeLog::config('error', array( + 'engine' => 'FileLog', + 'types' => array('error', 'warning'), + 'file' => 'error', + )); + + $testMessage = 'error message'; + CakeLog::error($testMessage); + $contents = file_get_contents(LOGS . 'error.log'); + $this->assertContains($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->assertFalse(file_exists(LOGS . 'debug.log')); + $this->_deleteLogs(); + + $testMessage = 'info message'; + CakeLog::info($testMessage); + $contents = file_get_contents(LOGS . 'debug.log'); + $this->assertContains($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->assertFalse(file_exists(LOGS . 'error.log')); + $this->_deleteLogs(); + } + } diff --git a/lib/Cake/Test/Case/Log/Engine/ConsoleLogTest.php b/lib/Cake/Test/Case/Log/Engine/ConsoleLogTest.php new file mode 100644 index 000000000..e871c8ebb --- /dev/null +++ b/lib/Cake/Test/Case/Log/Engine/ConsoleLogTest.php @@ -0,0 +1,119 @@ + + * Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) + * + * Licensed under The MIT License + * Redistributions of files must retain the above copyright notice + * + * @copyright Copyright 2005-2012, Cake Software Foundation, Inc. (http://cakefoundation.org) + * @link http://book.cakephp.org/2.0/en/development/testing.html CakePHP(tm) Tests + * @package Cake.Test.Case.Log.Engine + * @since CakePHP(tm) v 1.3 + * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + */ +App::uses('ConsoleLog', 'Log/Engine'); + +class TestConsoleLog extends ConsoleLog { + +} + +class TestCakeLog extends CakeLog { + + public static function replace($key, &$engine) { + self::$_Collection->{$key} = $engine; + } + +} + +/** + * ConsoleLogTest class + * + * @package Cake.Test.Case.Log.Engine + */ +class ConsoleLogTest extends CakeTestCase { + + public function setUp() { + parent::setUp(); + CakeLog::config('debug', array( + 'engine' => 'FileLog', + 'types' => array('notice', 'info', 'debug'), + 'file' => 'debug', + )); + CakeLog::config('error', array( + 'engine' => 'FileLog', + 'types' => array('error', 'warning'), + 'file' => 'error', + )); + } + + public function tearDown() { + parent::tearDown(); + if (file_exists(LOGS . 'error.log')) { + unlink(LOGS . 'error.log'); + } + if (file_exists(LOGS . 'debug.log')) { + unlink(LOGS . 'debug.log'); + } + } + +/** + * Test writing to ConsoleOutput + */ + public function testConsoleOutputWrites() { + TestCakeLog::config('test_console_log', array( + 'engine' => 'TestConsoleLog', + )); + + $mock = $this->getMock('TestConsoleLog', array('write'), array( + array('types' => 'error'), + )); + TestCakeLog::replace('test_console_log', $mock); + + $message = 'Test error message'; + $mock->expects($this->once()) + ->method('write'); + TestCakeLog::write(LOG_ERROR, $message); + } + +/** + * Test logging to both ConsoleLog and FileLog + */ + public function testCombinedLogWriting() { + TestCakeLog::config('test_console_log', array( + 'engine' => 'TestConsoleLog', + )); + $mock = $this->getMock('TestConsoleLog', array('write'), array( + array('types' => 'error'), + )); + TestCakeLog::replace('test_console_log', $mock); + + // log to both file and console + $message = 'Test error message'; + $mock->expects($this->once()) + ->method('write'); + TestCakeLog::write(LOG_ERROR, $message); + $this->assertTrue(file_exists(LOGS . 'error.log'), 'error.log missing'); + $logOutput = file_get_contents(LOGS . 'error.log'); + $this->assertContains($message, $logOutput); + + // TestConsoleLog is only interested in `error` type + $message = 'Test info message'; + $mock->expects($this->never()) + ->method('write'); + TestCakeLog::write(LOG_INFO, $message); + + // checks that output is correctly written in the correct logfile + $this->assertTrue(file_exists(LOGS . 'error.log'), 'error.log missing'); + $this->assertTrue(file_exists(LOGS . 'debug.log'), 'debug.log missing'); + $logOutput = file_get_contents(LOGS . 'error.log'); + $this->assertNotContains($message, $logOutput); + $logOutput = file_get_contents(LOGS . 'debug.log'); + $this->assertContains($message, $logOutput); + } + +} diff --git a/lib/Cake/Test/test_app/Lib/Log/Engine/TestAppLog.php b/lib/Cake/Test/test_app/Lib/Log/Engine/TestAppLog.php index 0acb792a1..8ccf3a713 100644 --- a/lib/Cake/Test/test_app/Lib/Log/Engine/TestAppLog.php +++ b/lib/Cake/Test/test_app/Lib/Log/Engine/TestAppLog.php @@ -17,10 +17,11 @@ * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -App::uses('CakeLogInterface', 'Log'); +App::uses('BaseLog', 'Log/Engine'); -class TestAppLog implements CakeLogInterface { +class TestAppLog extends BaseLog { public function write($type, $message) { } + } diff --git a/lib/Cake/Test/test_app/Plugin/TestPlugin/Lib/Log/Engine/TestPluginLog.php b/lib/Cake/Test/test_app/Plugin/TestPlugin/Lib/Log/Engine/TestPluginLog.php index d9ce333d7..33ffaf4a1 100644 --- a/lib/Cake/Test/test_app/Plugin/TestPlugin/Lib/Log/Engine/TestPluginLog.php +++ b/lib/Cake/Test/test_app/Plugin/TestPlugin/Lib/Log/Engine/TestPluginLog.php @@ -16,8 +16,12 @@ * @since CakePHP(tm) v 1.3 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ -class TestPluginLog implements CakeLogInterface { + +App::uses('BaseLog', 'Log/Engine'); + +class TestPluginLog extends BaseLog { public function write($type, $message) { } + }