Skip to content

Commit

Permalink
Add custom log provider option
Browse files Browse the repository at this point in the history
  • Loading branch information
scheb committed Dec 27, 2020
1 parent bd2d46d commit f437f6b
Show file tree
Hide file tree
Showing 13 changed files with 188 additions and 1 deletion.
21 changes: 21 additions & 0 deletions app/src/Tombstone/LogProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Scheb\Tombstone\TestApplication\Tombstone;

use Scheb\Tombstone\Analyzer\Cli\ConsoleOutputInterface;
use Scheb\Tombstone\Analyzer\Log\LogProviderInterface;

class LogProvider implements LogProviderInterface
{
public static function create(array $config, ConsoleOutputInterface $consoleOutput): LogProviderInterface
{
return new self();
}

public function getVampires(): iterable
{
return [];
}
}
3 changes: 3 additions & 0 deletions app/tombstone.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ tombstones:
- "*.js"
logs:
directory: logs
custom:
file: "src/Tombstone/LogProvider.php"
class: 'Scheb\Tombstone\TestApplication\Tombstone\LogProvider'
report:
php: report/tombstone-report.php
checkstyle: report/checkstyle.xml
Expand Down
8 changes: 8 additions & 0 deletions doc/analyzer/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ logs:
# including sub-directories will be processed.
directory: logs

# Alternatively, if you can't write/read logs in the analyzer file format, you can configure a
# custom log provider, that allows you read read logs from any source in any format.
custom:
class: Acme\Tombstone\CustomLogProvider

# Optional, in case the autoloader doesn't automatically find the class file
file: src/tombstone/CustomLogProvider.php

# Report generation options. See the "Report Formats" documentation for more details on this.
report:
php: report/tombstone-report.php # Generate a PHP dump of the result in this file
Expand Down
1 change: 1 addition & 0 deletions doc/analyzer/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Index
-----

- [Installation](installation.md)
- [Custom Log Providers](log_providers.md)
- [Configuration Reference](configuration.md)
- [Report Formats](report_formats.md)

Expand Down
59 changes: 59 additions & 0 deletions doc/analyzer/log_providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
Custom Log Providers
====================

You may not want to use the default analyzer log format, but instead write logs in a custom format to a custom logging
system. In the tombstone logger package this is supported by implementing a custom log handler.

The analyzer requires a "log provider" to read log data from your custom log storage. Implement a class with the
interface `Scheb\Tombstone\Analyzer\Log\LogProviderInterface` for that purpose:

```php
<?php
namespace Acme\Tombstone;

use Scheb\Tombstone\Analyzer\Cli\ConsoleOutputInterface;
use Scheb\Tombstone\Analyzer\Log\LogProviderInterface;
use Scheb\Tombstone\Core\Model\Vampire;

class LogProvider implements LogProviderInterface
{
/**
* @param array $config All config options from the YAML file. Additional config options are passed through as-is.
* @param ConsoleOutputInterface $consoleOutput Can be used to write output to the console.
*/
public static function create(array $config, ConsoleOutputInterface $consoleOutput): LogProviderInterface
{
return new self();
}

/**
* Must return an iterable (array or \Traversable) of Vampire objects.
*
* @return iterable<int, Vampire>
*/
public function getVampires(): iterable
{
// Here goes the logic to retrieve log data
}
}
```

The static `create()` function is there to create an instance of your log provider. You can read configuration data from
the YAML configuration via the `$config` array. Any additional config options from that file, that aren't used by the
analyzer, are passed through as-is, allowing you to pass custom configuration to your implementation.

`getVampires` is the method to retrieve the tombstone log data from your logging system. It has to be an iterable
(`array` or `\Traversable`) of `Scheb\Tombstone\Core\Model\Vampire` objects.

Once you have implemented your custom log provider, configure it in the analyzer's YAML config file:

```yaml
logs:
custom:
class: Acme\Tombstone\CustomLogProvider

# Optional, in case the autoloader doesn't automatically find the class file
file: src/tombstone/CustomLogProvider.php
```
When you have a custom log provider configured, it is no longer necessary to have a logs `directory` configured.
5 changes: 5 additions & 0 deletions doc/logger/handlers_formatters.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ use Scheb\Tombstone\Logger\Handler\AnalyzerLogHandler;
$analyzerLogHandler = new AnalyzerLogHandler('logs/tombstones', 102400);
```

In some environments it may not be possible to use the `AnalyzerLogHandler` to write tombstone logs, as it depends on
the file system. In such a case it's recommended to implement a custom handler to write tombstone logs to a log storage
of your choice. Then, in order to generate a report with the analyzer, implement a
[custom log provider](../analyzer/log_providers.md) class.

Formatters
----------

Expand Down
16 changes: 16 additions & 0 deletions src/analyzer/Cli/AnalyzeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Scheb\Tombstone\Analyzer\Config\YamlConfigProvider;
use Scheb\Tombstone\Analyzer\Log\AnalyzerLogProvider;
use Scheb\Tombstone\Analyzer\Log\LogCollector;
use Scheb\Tombstone\Analyzer\Log\LogProviderInterface;
use Scheb\Tombstone\Analyzer\Matching\MethodNameStrategy;
use Scheb\Tombstone\Analyzer\Matching\PositionStrategy;
use Scheb\Tombstone\Analyzer\Matching\Processor;
Expand Down Expand Up @@ -106,6 +107,21 @@ private function createLogCollector(array $config, VampireIndex $vampireIndex):
if (isset($config['logs']['directory'])) {
$logProviders[] = AnalyzerLogProvider::create($config, $this->output);
}
if (isset($config['logs']['custom'])) {
if (isset($config['logs']['custom']['file'])) {
/** @psalm-suppress UnresolvableInclude */
require_once $config['logs']['custom']['file'];
}

$reflectionClass = new \ReflectionClass($config['logs']['custom']['class']);
if (!$reflectionClass->implementsInterface(LogProviderInterface::class)) {
throw new \Exception(sprintf('Class %s must implement %s', $config['logs']['custom']['class'], LogProviderInterface::class));
}

/** @var LogProviderInterface $logReader */
$logReader = $reflectionClass->newInstance();
$logProviders[] = $logReader;
}

return new LogCollector($logProviders, $vampireIndex);
}
Expand Down
24 changes: 23 additions & 1 deletion src/analyzer/Config/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,26 @@ public function getConfigTreeBuilder()
->isRequired()
->children()
->scalarNode('directory')
->isRequired()
->cannotBeEmpty()
->validate()
->ifTrue($this->isNoDirectory())
->thenInvalid('Must be a valid directory path, given: %s')
->end()
->end()
->arrayNode('custom')
->children()
->scalarNode('file')
->validate()
->ifTrue($this->isNoFile())
->thenInvalid('Must be a valid file path, given: %s')
->end()
->end()
->scalarNode('class')
->isRequired()
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->end()
->arrayNode('report')
Expand Down Expand Up @@ -108,6 +121,15 @@ public function getConfigTreeBuilder()
return $treeBuilder;
}

private function isNoFile(): callable
{
return function (string $path): bool {
$path = realpath($path);

return !(false !== $path && is_file($path));
};
}

private function isNoDirectory(): callable
{
return function (string $path): bool {
Expand Down
4 changes: 4 additions & 0 deletions src/analyzer/Config/YamlConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public function readConfiguration(): array
$config['logs']['directory'] = $this->resolvePath($config['logs']['directory']);
}

if (isset($config['logs']['custom']['file'])) {
$config['logs']['custom']['file'] = $this->resolvePath($config['logs']['custom']['file']);
}

if (isset($config['report']['php'])) {
$config['report']['php'] = $this->resolvePath($config['report']['php']);
}
Expand Down
6 changes: 6 additions & 0 deletions src/analyzer/Log/LogProviderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,15 @@

interface LogProviderInterface
{
/**
* @param array $config All config options from the YAML file. Additional config options are passed through.
* @param ConsoleOutputInterface $consoleOutput can be used to write output to the console
*/
public static function create(array $config, ConsoleOutputInterface $consoleOutput): self;

/**
* Must return an iterable (array or \Traversable) of Vampire objects.
*
* @return iterable<int, Vampire>
*/
public function getVampires(): iterable;
Expand Down
35 changes: 35 additions & 0 deletions tests/Analyzer/Config/ConfigurationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class ConfigurationTest extends TestCase
private const APPLICATION_DIR = self::ROOT_DIR.'/app';
private const REPORT_DIR = self::APPLICATION_DIR.'/report';
private const LOGS_DIR = self::APPLICATION_DIR.'/logs';
private const CUSTOM_LOG_PROVIDER = self::APPLICATION_DIR.'/src/Tombstone/LogProvider.php';

private const FULL_CONFIG = [
'source_code' => [
Expand All @@ -29,6 +30,10 @@ class ConfigurationTest extends TestCase
],
'logs' => [
'directory' => self::LOGS_DIR,
'custom' => [
'file' => self::CUSTOM_LOG_PROVIDER,
'class' => 'LogProvider',
],
],
'report' => [
'php' => self::REPORT_DIR.'/report.php',
Expand Down Expand Up @@ -170,6 +175,7 @@ public function getConfigTreeBuilder_missingLogNode_throwsException(): void
public function getConfigTreeBuilder_emptyLogDirectory_throwsException(): void
{
$config = self::FULL_CONFIG;
unset($config['logs']['custom']);
$config['logs']['directory'] = '';

$this->expectException(InvalidConfigurationException::class);
Expand All @@ -183,13 +189,42 @@ public function getConfigTreeBuilder_emptyLogDirectory_throwsException(): void
public function getConfigTreeBuilder_invalidLogDirectory_throwsException(): void
{
$config = self::FULL_CONFIG;
unset($config['logs']['custom']);
$config['logs']['directory'] = 'invalid';

$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessage('Must be a valid directory path, given: "invalid"');
$this->processConfiguration($config);
}

/**
* @test
*/
public function getConfigTreeBuilder_missingCustomLogProviderClass_throwsException(): void
{
$config = self::FULL_CONFIG;
unset($config['logs']['directory']);
unset($config['logs']['custom']['class']);

$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessageMatches('/"class".*must be configured/');
$this->processConfiguration($config);
}

/**
* @test
*/
public function getConfigTreeBuilder_customLogProviderInvalidFile_throwsException(): void
{
$config = self::FULL_CONFIG;
unset($config['logs']['directory']);
$config['logs']['custom']['file'] = 'invalid'; // Not a valid file

$this->expectException(InvalidConfigurationException::class);
$this->expectExceptionMessage('Must be a valid file path, given: "invalid"');
$this->processConfiguration($config);
}

/**
* @test
*/
Expand Down
4 changes: 4 additions & 0 deletions tests/Analyzer/Config/YamlConfigProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ public function processConfiguration_fullConfig_haveAllValuesSet(): void
],
'logs' => [
'directory' => self::CONFIG_DIR.'logs',
'custom' => [
'file' => self::CONFIG_DIR.'src'.DIRECTORY_SEPARATOR.'Tombstone'.DIRECTORY_SEPARATOR.'LogProvider.php',
'class' => 'Scheb\Tombstone\Analyzer\TestApplication\Tombstone\LogProvider',
],
],
'report' => [
'php' => self::CONFIG_DIR.'report'.DIRECTORY_SEPARATOR.'tombstone-report.php',
Expand Down
3 changes: 3 additions & 0 deletions tests/Analyzer/Config/fixtures/full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ source_code:
- "*.js"
logs:
directory: logs
custom:
file: "src/Tombstone/LogProvider.php"
class: 'Scheb\Tombstone\Analyzer\TestApplication\Tombstone\LogProvider'
report:
php: report/tombstone-report.php
checkstyle: report/checkstyle.xml
Expand Down

0 comments on commit f437f6b

Please sign in to comment.