Skip to content

Commit

Permalink
Initial Release
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromegamez committed Sep 24, 2022
1 parent 81f0c65 commit 405cf16
Show file tree
Hide file tree
Showing 16 changed files with 649 additions and 58 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
tests:
name: PHP ${{ matrix.php }}
name: PHP ${{ matrix.php }} (${{ matrix.dependencies}} dependencies)
runs-on: ubuntu-20.04

strategy:
Expand Down
9 changes: 8 additions & 1 deletion .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
use Ergebnis\PhpCsFixer\Config\Factory;
use Beste\PhpCsFixer\Config\RuleSet\Php81;

$config = Factory::fromRuleSet(new Php81());
$config = Factory::fromRuleSet(new Php81(), [
'php_unit_method_casing' => [
'case' => 'snake_case',
],
'php_unit_test_annotation' => [
'style' => 'annotation',
],
]);

$config->getFinder()->in(__DIR__);

Expand Down
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

## [Unreleased]

<!--
## [1.0.0] - 2022-09-24

Initial Release

[Unreleased]: https://github.com/beste/psr-testlogger/compare/1.0.0...main
[1.0.0]: https://github.com/beste/psr-testlogger/tree/1.0.0
-->
81 changes: 77 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,87 @@

PSR-3 compliant test logger for developers who like tests and want to check if their application logs messages as they expect.

[![Current version](https://img.shields.io/packagist/v/beste/psr-testlogger.svg?logo=composer)](https://packagist.org/packages/beste/psr-testlogger)
[![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/beste/psr-testlogger)](https://packagist.org/packages/beste/psr-testlogger)
[![Monthly Downloads](https://img.shields.io/packagist/dm/beste/psr-testlogger.svg)](https://packagist.org/packages/beste/psr-testlogger/stats)
[![Total Downloads](https://img.shields.io/packagist/dt/beste/psr-testlogger.svg)](https://packagist.org/packages/beste/psr-testlogger/stats)
[![Tests](https://github.com/beste/psr-testlogger/actions/workflows/tests.yml/badge.svg)](https://github.com/beste/psr-testlogger/actions/workflows/tests.yml)
[![Sponsor](https://img.shields.io/static/v1?logo=GitHub&label=Sponsor&message=%E2%9D%A4&color=ff69b4)](https://github.com/sponsors/jeromegamez)

## Installation

```shell
composer require beste/psr-testlogger
composer require --dev beste/psr-testlogger
```

## Running tests
## Usage

```shell
composer run tests
In your unit tests, inject the `Beste\Psr\Log\TestLogger` class into tested subjects that expect a
`Prs\Log\LoggerInterface`.

The test logger records all log messages and exposes them via the `records` property which is
an instance of `Beste\Psr\Log\Records`.

```php
use Beste\Psr\Log\Record;
use Beste\Psr\Log\TestLogger;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

final class Subject
{
public function __construct(
public readonly LoggerInterface $logger
) {}

public function doSomething(): void
{
$this->logger->info('Doing something');
$this->logger->warning('1st problem');
$this->logger->warning('2nd problem');
$this->logger->critical('Uh oh!');
}
}

final class SubjectTest extends \PHPUnit\Framework\TestCase
{
private TestLogger $logger;
private Subject $subject;

protected function setUp() : void{
$this->logger = TestLogger::create();
}

/** @test */
public function it_does_something(): void
{
$this->subject->doSomething();

self::assertCount(4, $this->logger->records);

self::assertEqualsCanonicalizing(
[LogLevel::INFO, LogLevel::WARNING, LogLevel::CRITICAL],
$this->logger->records->levels()
);

self::assertTrue($this->logger->records->includeMessagesWithLevel('info'));
self::assertCount(1, $this->logger->records->filteredByLevel('info'));
self::assertCount(3, $this->logger->records->filteredByLevel('info', 'warning'));

self::assertTrue($this->logger->records->includeMessagesContaining('problem'));
self::assertCount(2, $this->logger->records->filteredByMessageContaining('problem'));

self::assertTrue($this->logger->records->includeMessagesMatching('/^\d{1,}(st|nd)/i'));
self::assertCount(2, $this->logger->records->filteredByMessageMatching('/^\d{1,}(st|nd)/i'));

// You can filter by your own criteria. If you discover criteria not natively
// covered by the test logger, please consider a pull request.
self::assertTrue($this->logger->records->includeMessagesBy(fn (Record $r) => str_contains($r->level, 'n')));
self::assertCount(3, $this->logger->records->filteredBy(fn (Record $r) => str_contains($r->level, 'n')));
}
}
```

## License

This project is published under the [MIT License](LICENSE).
14 changes: 11 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "beste/psr-testlogger",
"description": "PSR-3 compliant test logger for developers who like tests and want to check if their application logs messages as they expect.",
"keywords": ["psr", "psr-3", "log", "test", "tests", "phpunit"],
"keywords": ["log", "logging", "psr-3", "testing"],
"license": "MIT",
"type": "library",
"authors": [
Expand All @@ -11,15 +11,18 @@
}
],
"require": {
"php": "^8.1"
"php": "^8.1",
"ext-mbstring": "*",
"psr/log": "^2.0|^3.0"
},
"require-dev": {
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.8.2",
"phpstan/phpstan-deprecation-rules": "^1.0",
"phpstan/phpstan-phpunit": "^1.1.1",
"phpstan/phpstan-strict-rules": "^1.4",
"phpunit/phpunit": "^9.5.23"
"phpunit/phpunit": "^9.5.23",
"symfony/var-dumper": "^6.1"
},
"autoload": {
"psr-4": {
Expand All @@ -28,6 +31,7 @@
},
"autoload-dev": {
"psr-4": {
"Beste\\Psr\\Log\\Example\\": "examples",
"Beste\\Psr\\Log\\Tests\\": "tests"
}
},
Expand All @@ -38,6 +42,10 @@
"sort-packages": true
},
"scripts": {
"coverage": [
"XDEBUG_MODE=coverage phpunit --testdox --coverage-html=.build/coverage",
"open .build/coverage/index.html"
],
"cs": "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff --verbose",
"install-dev-tools": [
"mkdir --parents .build",
Expand Down
5 changes: 5 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ parameters:
paths:
- src/
- tests/

ignoreErrors:
-
message: '#Parameter \#1 \$level.+contravariant#'
path: src/TestLogger.php
19 changes: 19 additions & 0 deletions src/Context.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Beste\Psr\Log;

/**
* @phpstan-type BestePsrLogContextShape array<string, mixed>
*/
final class Context
{
/**
* @param BestePsrLogContextShape $data
*/
public function __construct(
public readonly array $data = [],
) {
}
}
72 changes: 72 additions & 0 deletions src/Message.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace Beste\Psr\Log;

use DateTimeInterface;
use Stringable;

/**
* @phpstan-import-type BestePsrLogContextShape from Context
*/
final class Message implements Stringable
{
private string $value;

/**
* @param Context|BestePsrLogContextShape|null $context
*/
public function __construct(
string $message,
Context|array|null $context = null,
) {
if (!$context instanceof Context) {
$context = new Context($context ?? []);
}

$this->value = self::processMessage($message, $context);
}

public function __toString()
{
return $this->value;
}

public function contains(string $needle): bool
{
return str_contains(mb_strtolower($this->value), mb_strtolower($needle)) !== false;
}

public function matches(string $pattern): bool
{
return preg_match($pattern, $this->value) > 0;
}

private static function processMessage(string $message, Context $context): string
{
if (str_contains($message, '{') === false) {
return $message;
}

$replacements = [];

foreach ($context->data as $key => $value) {
$placeholder = '{' . $key . '}';

if (str_contains($message, $placeholder) === false) {
continue;
}

if ($value === null || \is_scalar($value) || $value instanceof Stringable) {
$replacements[$placeholder] = $value;
} elseif ($value instanceof DateTimeInterface) {
$replacements[$placeholder] = $value->format(\DATE_ATOM);
} else {
$replacements[$placeholder] = '[' . \gettype($value) . ']';
}
}

return strtr($message, $replacements);
}
}
20 changes: 0 additions & 20 deletions src/Placeholder.php

This file was deleted.

40 changes: 40 additions & 0 deletions src/Record.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Beste\Psr\Log;

use Psr\Log\LogLevel;

/**
* @phpstan-type BestePsrLogRecordShape array{
* level: LogLevel::*,
* message: string,
* context: array<string, mixed>
* }
*/
final class Record
{
private function __construct(
/** @var LogLevel::* $level */
public readonly string $level,
public readonly Message $message,
public readonly Context $context,
) {
}

/**
* @param LogLevel::* $level
* @param array<string, mixed> $context
*/
public static function with(string $level, string $message, array $context): self
{
$c = new Context($context);

return new self(
$level,
new Message($message, $context),
new Context($context),
);
}
}
Loading

0 comments on commit 405cf16

Please sign in to comment.