Skip to content

Commit 613f4ba

Browse files
committed
feat: add psr log exception reporter implementation
1 parent 9306823 commit 613f4ba

4 files changed

Lines changed: 135 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ All notable changes to this project will be documented in this file. This projec
77

88
### Added
99

10+
- New `PsrLogExceptionReporter` provides a default exception reporter implementation that logs the exception as an
11+
error.
1012
- Updated doc block for `Contracts::assert()` to add PHPStan assertion that the precondition is `true` if the method
1113
call does not throw.
1214

docs/guide/infrastructure/exception-reporting.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ interface ExceptionReporter
7878
Your infrastructure layer should have an adapter that implements this port. This means you can tie your application
7979
layer to any logging service you are using.
8080

81+
### PSR Log Implementation
82+
83+
If all you need to do is log a reported exception as an error, we provide a default implementation that uses a PSR
84+
logger. The implementation is: `CloudCreativity\Modules\Infrastructure\ExceptionReporter\PsrLogExceptionReporter`
85+
86+
Provide an instance of the PSR Log as the only constructor argument.
87+
8188
### Laravel Example
8289

8390
For example, it is easy to implement this port in Laravel as it already provides an exception reporter. Our
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
/*
4+
* Copyright 2025 Cloud Creativity Limited
5+
*
6+
* Use of this source code is governed by an MIT-style
7+
* license that can be found in the LICENSE file or at
8+
* https://opensource.org/licenses/MIT.
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace CloudCreativity\Modules\Infrastructure\ExceptionReporter;
14+
15+
use CloudCreativity\Modules\Contracts\Application\Ports\Driven\ExceptionReporter;
16+
use Psr\Log\LoggerInterface;
17+
use Throwable;
18+
19+
final class PsrLogExceptionReporter implements ExceptionReporter
20+
{
21+
/**
22+
* PsrLogExceptionReporter constructor.
23+
*
24+
* @param LoggerInterface $logger
25+
*/
26+
public function __construct(private readonly LoggerInterface $logger)
27+
{
28+
}
29+
30+
/**
31+
* @inheritDoc
32+
*/
33+
public function report(Throwable $ex): void
34+
{
35+
$this->logger->error(
36+
$ex->getMessage() ?: 'Unexpected error: ' . $ex::class,
37+
['exception' => $ex],
38+
);
39+
}
40+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
/*
4+
* Copyright 2025 Cloud Creativity Limited
5+
*
6+
* Use of this source code is governed by an MIT-style
7+
* license that can be found in the LICENSE file or at
8+
* https://opensource.org/licenses/MIT.
9+
*/
10+
11+
declare(strict_types=1);
12+
13+
namespace CloudCreativity\Modules\Tests\Unit\Infrastructure\ExceptionReporter;
14+
15+
use CloudCreativity\Modules\Contracts\Application\Ports\Driven\ExceptionReporter;
16+
use CloudCreativity\Modules\Infrastructure\ExceptionReporter\PsrLogExceptionReporter;
17+
use LogicException;
18+
use PHPUnit\Framework\MockObject\MockObject;
19+
use PHPUnit\Framework\TestCase;
20+
use Psr\Log\LoggerInterface;
21+
22+
class PsrLogExceptionReporterTest extends TestCase
23+
{
24+
/**
25+
* @var LoggerInterface&MockObject
26+
*/
27+
private LoggerInterface&MockObject $logger;
28+
29+
/**
30+
* @var PsrLogExceptionReporter
31+
*/
32+
private PsrLogExceptionReporter $reporter;
33+
34+
/**
35+
* @return void
36+
*/
37+
protected function setUp(): void
38+
{
39+
parent::setUp();
40+
$this->logger = $this->createMock(LoggerInterface::class);
41+
$this->reporter = new PsrLogExceptionReporter($this->logger);
42+
}
43+
44+
/**
45+
* @return void
46+
*/
47+
protected function tearDown(): void
48+
{
49+
parent::tearDown();
50+
unset($this->logger, $this->reporter);
51+
}
52+
53+
public function testItReportsException(): void
54+
{
55+
$exception = new LogicException('Test exception');
56+
57+
$this->logger
58+
->expects($this->once())
59+
->method('error')
60+
->with(
61+
$exception->getMessage(),
62+
['exception' => $exception],
63+
);
64+
65+
$this->reporter->report($exception);
66+
67+
$this->assertInstanceOf(ExceptionReporter::class, $this->reporter);
68+
}
69+
70+
public function testItReportsDefaultMessageIfExceptionHasEmptyMessage(): void
71+
{
72+
$exception = new LogicException();
73+
74+
$this->logger
75+
->expects($this->once())
76+
->method('error')
77+
->with(
78+
'Unexpected error: LogicException',
79+
['exception' => $exception],
80+
);
81+
82+
$this->reporter->report($exception);
83+
84+
$this->assertInstanceOf(ExceptionReporter::class, $this->reporter);
85+
}
86+
}

0 commit comments

Comments
 (0)