From 2ea68fec4b4b722c04803394cc6c70b636fee34d Mon Sep 17 00:00:00 2001 From: Matthew Turland Date: Mon, 21 Feb 2022 10:50:27 -0600 Subject: [PATCH] Add LoggingCompositeBuffer --- README.md | 18 ++++ src/LoggingCompositeBuffer.php | 84 ++++++++++++++++ tests/LoggingCompositeBufferTest.php | 140 +++++++++++++++++++++++++++ 3 files changed, 242 insertions(+) create mode 100644 src/LoggingCompositeBuffer.php create mode 100644 tests/LoggingCompositeBufferTest.php diff --git a/README.md b/README.md index f4a1c6f..e72a8f9 100644 --- a/README.md +++ b/README.md @@ -366,6 +366,24 @@ $logger = new Logger; ServiceLocator::set(LoggerInterface::class, $logger); ``` +[Core buffer implementations](#buffering) do not implement logging. However, as of Flystream 0.4.0, a buffer instance can be wrapped in an instance of the `LoggingCompositeBuffer` class to log calls to its methods. An example of doing this with the default `MemoryBuffer` buffer implementation is shown below. + +```php +buffer = $buffer; + $this->logger = $logger; + } + + /** + * {@inheritdoc} + */ + public function write(string $data) + { + $context = $this->getContext([ + 'data' => $data, + ]); + + $context['when'] = 'before'; + $this->logger->info(__METHOD__, $context); + + $return = $context['return'] = $this->buffer->write($data); + + $context['when'] = 'after'; + $this->logger->info(__METHOD__, $context); + + return $return; + } + + public function flush( + FilesystemOperator $filesystem, + string $path, + array $context + ): void { + $context = $this->getContext([ + 'filesystem_class' => get_class($filesystem), + 'filesystem_id' => spl_object_id($filesystem), + 'path' => $path, + 'context' => $context, + ]); + + $context['when'] = 'before'; + $this->logger->info(__METHOD__, $context); + + $this->buffer->flush($filesystem, $path, $context); + + $context['when'] = 'after'; + $this->logger->info(__METHOD__, $context); + } + + public function close(): void + { + $context = $this->getContext(); + + $context['when'] = 'before'; + $this->logger->info(__METHOD__, $context); + + $this->buffer->close(); + + $context['when'] = 'after'; + $this->logger->info(__METHOD__, $context); + } + + private function getContext(array $add = []): array + { + $default = [ + 'buffer_class' => get_class($this->buffer), + 'buffer_id' => spl_object_id($this->buffer), + ]; + return array_merge($default, $add); + } +} diff --git a/tests/LoggingCompositeBufferTest.php b/tests/LoggingCompositeBufferTest.php new file mode 100644 index 0000000..cc5e889 --- /dev/null +++ b/tests/LoggingCompositeBufferTest.php @@ -0,0 +1,140 @@ +logger = new Logger(__FILE__); + $this->logger->pushHandler(new TestHandler()); + + $this->nestedBuffer = new class implements BufferInterface { + public bool $flushCalled = false; + public bool $closeCalled = false; + public function write(string $data) { + return strlen($data); + } + public function flush( + FilesystemOperator $filesystem, + string $path, + array $context + ): void { + $this->flushCalled = true; + } + public function close(): void { + $this->closeCalled = true; + } + }; + + $this->buffer = new LoggingCompositeBuffer($this->nestedBuffer, $this->logger); + $this->filesystem = new Filesystem(new InMemoryFilesystemAdapter()); +}); + +it('logs writes', function () { + $result = $this->buffer->write('foo'); + expect($result)->toBe(3); + + $records = $this->logger->popHandler()->getRecords(); + expect($records) + ->toBeArray() + ->toHaveCount(2) + ->toHaveKeys([0, 1]) + ->each + ->toBeArray() + ->toHaveKeys(['message', 'context', 'level_name']); + + expect($records[0]['message'])->toBe('Elazar\Flystream\LoggingCompositeBuffer::write'); + expect($records[0]['context']) + ->toHaveKeys(['buffer_class', 'buffer_id']) + ->toMatchArray([ + 'data' => 'foo', + 'when' => 'before', + ]); + expect($records[0]['level_name'])->toBe('INFO'); + + expect($records[1]['message'])->toBe('Elazar\Flystream\LoggingCompositeBuffer::write'); + expect($records[1]['context']) + ->toHaveKeys(['buffer_class', 'buffer_id']) + ->toMatchArray([ + 'data' => 'foo', + 'when' => 'after', + 'return' => 3, + ]); + expect($records[1]['level_name'])->toBe('INFO'); +}); + +it('logs flushes', function () { + expect($this->nestedBuffer->flushCalled)->toBeFalse(); + $this->buffer->flush( + $this->filesystem, + '/foo', + ['bar' => 'baz'] + ); + expect($this->nestedBuffer->flushCalled)->toBeTrue(); + + $records = $this->logger->popHandler()->getRecords(); + expect($records) + ->toBeArray() + ->toHaveCount(2) + ->toHaveKeys([0, 1]) + ->each + ->toBeArray() + ->toHaveKeys(['message', 'context', 'level_name']); + + expect($records[0]['message'])->toBe('Elazar\Flystream\LoggingCompositeBuffer::flush'); + expect($records[0]['context']) + ->toHaveKeys(['buffer_class', 'buffer_id', 'filesystem_id']) + ->toMatchArray([ + 'filesystem_class' => 'League\Flysystem\Filesystem', + 'path' => '/foo', + 'context' => ['bar' => 'baz'], + 'when' => 'before', + ]); + expect($records[0]['level_name'])->toBe('INFO'); + + expect($records[1]['message'])->toBe('Elazar\Flystream\LoggingCompositeBuffer::flush'); + expect($records[1]['context']) + ->toHaveKeys(['buffer_class', 'buffer_id', 'filesystem_id']) + ->toMatchArray([ + 'filesystem_class' => 'League\Flysystem\Filesystem', + 'path' => '/foo', + 'context' => ['bar' => 'baz'], + 'when' => 'after', + ]); + expect($records[1]['level_name'])->toBe('INFO'); +}); + +it('logs closes', function () { + expect($this->nestedBuffer->closeCalled)->toBeFalse(); + $this->buffer->close(); + expect($this->nestedBuffer->closeCalled)->toBeTrue(); + + $records = $this->logger->popHandler()->getRecords(); + expect($records) + ->toBeArray() + ->toHaveCount(2) + ->toHaveKeys([0, 1]) + ->each + ->toBeArray() + ->toHaveKeys(['message', 'context', 'level_name']); + + expect($records[0]['message'])->toBe('Elazar\Flystream\LoggingCompositeBuffer::close'); + expect($records[0]['context']) + ->toHaveKeys(['buffer_class', 'buffer_id']) + ->toMatchArray([ + 'when' => 'before', + ]); + expect($records[0]['level_name'])->toBe('INFO'); + + expect($records[1]['message'])->toBe('Elazar\Flystream\LoggingCompositeBuffer::close'); + expect($records[1]['context']) + ->toHaveKeys(['buffer_class', 'buffer_id']) + ->toMatchArray([ + 'when' => 'after', + ]); + expect($records[1]['level_name'])->toBe('INFO'); +});