Browse Source

Merge pull request #261 from ishan-biztech/add-custom-log-class

Add custom log class for CakePHP 3
Mark Sch 5 years ago
parent
commit
fcda7eb460
7 changed files with 346 additions and 1 deletions
  1. 1 0
      .gitignore
  2. 1 1
      docs/Contributing.md
  3. 3 0
      docs/README.md
  4. 55 0
      docs/Utility/FileLog.md
  5. 86 0
      src/Utility/FileLog.php
  6. 183 0
      tests/TestCase/Utility/FileLogTest.php
  7. 17 0
      tests/bootstrap.php

+ 1 - 0
.gitignore

@@ -5,3 +5,4 @@ phpunit.phar
 composer.phar
 .idea/
 .phpunit.result.cache
+phpunit.xml

+ 1 - 1
docs/Contributing.md

@@ -15,7 +15,7 @@ There are a few guidelines that I need contributors to follow:
 * Coding standards (`composer cs-check` to check and `composer cs-fix` to fix)
 * Passing tests (you can enable travis to assert your changes pass) for Windows and Unix (`php phpunit.phar`)
 
-## i18n 
+## i18n
 Check if translations via pot file need to be changed.
 Run
 ```

+ 3 - 0
docs/README.md

@@ -60,6 +60,9 @@ Widgets:
 Entity:
 * [Enum](Entity/Enum.md)
 
+Utility:
+* [FileLog](Utility/FileLog.md)
+
 ## Basic enhancements of the core
 
 ### Model

+ 55 - 0
docs/Utility/FileLog.md

@@ -0,0 +1,55 @@
+# FileLog
+
+Log class let's you write logs into custom log files.
+
+#### With default `Cake\Log\Log` class:
+
+To write log data into custom file with default CakePHP `Cake\Log\Log` class feels like repeating ourselves.
+
+1. Configure FileLog Adapter:
+```php
+use Cake\Log\Log;
+
+Log::config('custom_file', [
+    'className' => 'File',
+    'path' => LOGS,
+    'levels' => ['debug'],
+    'file' => 'my_file.log',
+]);
+```
+
+2. Write logs into file:
+```php
+Log::write('debug', "Something didn't work!");
+```
+
+With above approach, we have multiple issues:
+- It surely logs data into `custom_file.log` file but also it will log into level specific `$level.log` file too, so we end up duplicating the log data.
+- When we have to write more logs into same file from different parts of the project, we have to copy-paste this code every time.
+
+Or you hack it with doing something like setting configurations in `bootstrap.php` and use scope to log data. But each time you start new project you have to remember to copy paste that config and use in your project in order to write data into custom log files.
+
+#### With `Tools\Utility\FileLog` class:
+
+You can directly pass data to log and filename to write the data into.
+
+##### Usage:
+
+```php
+use Tools\Utility\FileLog;
+
+FileLog::write("Something didn't work!", 'my_file');
+
+// Somewhere else in any file
+FileLog::write([
+    'user' => [
+        'id' => '1',
+        'name' => 'John',
+        'email' => 'john@example.com',
+    ]
+], 'user_data');
+```
+
+That's it! Above will create two separate files in `log/` directory named `my_file.log` and `user_data.log` store data into which we passed in first argument. By default if you don't pass the `$filename` in second param in `FileLog::write` method, it will create `custom_log.log` file.
+
+You can write string, array, objects, etc into log files. It will pretty print your array/object so it's more readable. Also, it will not duplicate records into `$level.log` file.

+ 86 - 0
src/Utility/FileLog.php

@@ -0,0 +1,86 @@
+<?php
+
+namespace Tools\Utility;
+
+use Cake\Log\Log as CoreLog;
+use Exception;
+
+/**
+ * Wrapper class to log data into custom file(s).
+ */
+class FileLog {
+
+	/**
+	 * Debug configuration.
+	 *
+	 * @var mixed|null
+	 */
+	protected static $_debugConfig;
+
+	/**
+	 * Initialize configurations.
+	 *
+	 * @param string $filename Filename to log.
+	 * @return void
+	 */
+	protected static function _init($filename) {
+		if ($filename === null) {
+			$filename = 'custom_log';
+		}
+
+		CoreLog::setConfig('custom', [
+			'className' => 'File',
+			'path' => LOGS,
+			'levels' => [],
+			'scopes' => ['custom'],
+			'file' => $filename,
+		]);
+
+		static::$_debugConfig = CoreLog::getConfig('debug');
+
+		CoreLog::drop('debug');
+	}
+
+	/**
+	 * Log data into custom file
+	 *
+	 * @param array|string $data Data to store
+	 * @param string|null $filename Filename of log file
+	 * @param bool $traceKey Add trace string key into log data
+	 * @return bool Success
+	 */
+	public static function write($data, $filename = null, $traceKey = false) {
+		static::_init($filename);
+
+		// Pretty print array or object
+		if (is_array($data) || is_object($data)) {
+			if ($traceKey) {
+				try {
+					throw new Exception('Trace string', 1);
+				} catch (\Exception $e) {
+					$data['trace_string'] = $e->getTraceAsString();
+				}
+			}
+
+			$data = print_r($data, true);
+		}
+
+		$logged = CoreLog::write('debug', $data, ['scope' => 'custom']);
+
+		static::_cleanUp();
+
+		return $logged;
+	}
+
+	/**
+	 * Drop custom log config, set default `debug` config in log registry.
+	 *
+	 * @return void
+	 */
+	protected static function _cleanUp() {
+		CoreLog::drop('custom');
+
+		CoreLog::setConfig('debug', static::$_debugConfig);
+	}
+
+}

+ 183 - 0
tests/TestCase/Utility/FileLogTest.php

@@ -0,0 +1,183 @@
+<?php
+
+namespace Tools\Test\Utility;
+
+use Cake\ORM\TableRegistry;
+use Tools\TestSuite\TestCase;
+use Tools\Utility\FileLog;
+
+/**
+ * FileLogTest class
+ */
+class FileLogTest extends TestCase {
+
+	/**
+	 * Default filename with path to use in test case.
+	 *
+	 * @var string
+	 */
+	const TEST_DEFAULT_FILENAME_STRING = 'custom_log';
+	const TEST_DEFAULT_FILEPATH_STRING = LOGS . self::TEST_DEFAULT_FILENAME_STRING . '.log';
+
+	/**
+	 * Filename with path to use in string test case.
+	 *
+	 * @var string
+	 */
+	const TEST_FILENAME_STRING = 'my_file';
+	const TEST_FILEPATH_STRING = LOGS . self::TEST_FILENAME_STRING . '.log';
+
+	/**
+	 * Filename with path to use in array test case.
+	 *
+	 * @var string
+	 */
+	const TEST_FILENAME_ARRAY1 = 'array_file1';
+	const TEST_FILEPATH_ARRAY1 = LOGS . self::TEST_FILENAME_ARRAY1 . '.log';
+	const TEST_FILENAME_ARRAY2 = 'array_file2';
+	const TEST_FILEPATH_ARRAY2 = LOGS . self::TEST_FILENAME_ARRAY2 . '.log';
+
+	/**
+	 * Filename with path to use in object test case.
+	 *
+	 * @var string
+	 */
+	const TEST_FILENAME_OBJECT = 'object';
+	const TEST_FILEPATH_OBJECT = LOGS . self::TEST_FILENAME_OBJECT . '.log';
+
+	/**
+	 * setUp method
+	 *
+	 * @return void
+	 */
+	public function setUp() {
+		parent::setUp();
+	}
+
+	/**
+	 * testLogsStringData method
+	 *
+	 * @return void
+	 */
+	public function testLogsStringData() {
+		if (file_exists(static::TEST_FILEPATH_STRING)) {
+			unlink(static::TEST_FILEPATH_STRING);
+		}
+
+		$result = FileLog::write('It works!', static::TEST_FILENAME_STRING);
+
+		$this->assertTrue($result);
+		$this->assertFileExists(static::TEST_FILEPATH_STRING);
+		$this->assertRegExp(
+			'/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Debug: It works!/',
+			file_get_contents(static::TEST_FILEPATH_STRING)
+		);
+
+		unlink(static::TEST_FILEPATH_STRING);
+	}
+
+	/**
+	 * testLogsArray method
+	 *
+	 * @return void
+	 */
+	public function testLogsArray() {
+		if (file_exists(static::TEST_FILEPATH_ARRAY1)) {
+			unlink(static::TEST_FILEPATH_ARRAY1);
+		}
+		if (file_exists(static::TEST_FILEPATH_ARRAY2)) {
+			unlink(static::TEST_FILEPATH_ARRAY2);
+		}
+
+		$result1 = FileLog::write(
+			[
+				'user' => [
+					'id' => 1,
+					'firstname' => 'John Doe',
+					'email' => 'john.doe@example.com',
+				],
+			],
+			static::TEST_FILENAME_ARRAY1
+		);
+
+		$result2 = FileLog::write(
+			[
+				'user' => [
+					'id' => 2,
+					'firstname' => 'Jane Doe',
+					'email' => 'jane.doe@example.com',
+				],
+			],
+			static::TEST_FILENAME_ARRAY2
+		);
+
+		// Assert for `TEST_FILENAME_ARRAY1`
+		$this->assertTrue($result1);
+		$this->assertFileExists(static::TEST_FILEPATH_ARRAY1);
+		$fileContents = file_get_contents(static::TEST_FILEPATH_ARRAY1);
+		$this->assertRegExp(
+			'/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Debug: Array([\s\S]*)\(([\s\S]*)[user]([\s\S]*)\[id\] => 1/',
+			$fileContents
+		);
+
+		// Assert for `TEST_FILENAME_ARRAY2`
+		$this->assertTrue($result2);
+		$this->assertFileExists(static::TEST_FILEPATH_ARRAY2);
+		$fileContents = file_get_contents(static::TEST_FILEPATH_ARRAY2);
+		$this->assertRegExp(
+			'/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Debug: Array([\s\S]*)\(([\s\S]*)[user]([\s\S]*)\[id\] => 2/',
+			$fileContents
+		);
+
+		unlink(static::TEST_FILEPATH_ARRAY1);
+		unlink(static::TEST_FILEPATH_ARRAY2);
+	}
+
+	/**
+	 * testLogsObject method
+	 *
+	 * @return void
+	 */
+	public function testLogsObject() {
+		if (file_exists(static::TEST_FILEPATH_OBJECT)) {
+			unlink(static::TEST_FILEPATH_OBJECT);
+		}
+
+		$result = FileLog::write(
+			TableRegistry::getTableLocator()->get('Posts'),
+			static::TEST_FILENAME_OBJECT
+		);
+
+		$this->assertTrue($result);
+		$this->assertFileExists(static::TEST_FILEPATH_OBJECT);
+		$this->assertRegExp(
+			'/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Debug: TestApp.Model.Table.PostsTable Object/',
+			file_get_contents(static::TEST_FILEPATH_OBJECT)
+		);
+
+		unlink(static::TEST_FILEPATH_OBJECT);
+	}
+
+	/**
+	 * testLogsIntoDefaultFile method
+	 *
+	 * @return void
+	 */
+	public function testLogsIntoDefaultFile() {
+		if (file_exists(static::TEST_DEFAULT_FILEPATH_STRING)) {
+			unlink(static::TEST_DEFAULT_FILEPATH_STRING);
+		}
+
+		$result = FileLog::write('It works with default too!');
+
+		$this->assertTrue($result);
+		$this->assertFileExists(static::TEST_DEFAULT_FILEPATH_STRING);
+		$this->assertRegExp(
+			'/^2[0-9]{3}-[0-9]+-[0-9]+ [0-9]+:[0-9]+:[0-9]+ Debug: It works with default too!/',
+			file_get_contents(static::TEST_DEFAULT_FILEPATH_STRING)
+		);
+
+		unlink(static::TEST_DEFAULT_FILEPATH_STRING);
+	}
+
+}

+ 17 - 0
tests/bootstrap.php

@@ -73,6 +73,23 @@ $cache = [
 
 Cake\Cache\Cache::setConfig($cache);
 
+Cake\Log\Log::setConfig('debug', [
+	'className' => 'Cake\Log\Engine\FileLog',
+	'path' => LOGS,
+	'file' => 'debug',
+	'levels' => ['notice', 'info', 'debug'],
+	'url' => env('LOG_DEBUG_URL', null),
+]);
+Cake\Log\Log::setConfig('error', [
+	'className' => 'Cake\Log\Engine\FileLog',
+	'path' => LOGS,
+	'file' => 'error',
+	'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
+	'url' => env('LOG_ERROR_URL', null),
+]);
+
+Cake\Utility\Security::setSalt('foo');
+
 Cake\Core\Plugin::getCollection()->add(new Tools\Plugin());
 
 Cake\Routing\DispatcherFactory::add('Routing');