Skip to content

Commit 8d5b96f

Browse files
committed
refactor: clean up and test exception logging
1 parent 0e6892e commit 8d5b96f

File tree

4 files changed

+138
-12
lines changed

4 files changed

+138
-12
lines changed

docs/2-features/14-exception-handling.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ final readonly class CreateUser
3737

3838
## Disabling exception logging
3939

40-
The default logging reporter, {b`Tempest\Core\Exceptions\ExceptionReporter`}, is automatically added to the list of reporters. To disable it, create an {b`Tempest\Core\Exceptions\ExceptionsConfig`} [configuration file](../1-essentials/06-configuration.md#configuration-files) and set `logging` to `false`:
40+
The default logging reporter, {b`Tempest\Core\Exceptions\LoggingExceptionReporter`}, is automatically added to the list of reporters. To disable it, create a {b`Tempest\Core\Exceptions\ExceptionsConfig`} [configuration file](../1-essentials/06-configuration.md#configuration-files) and set `logging` to `false`:
4141

4242
```php app/exceptions.config.php
4343
use Tempest\Core\Exceptions\ExceptionsConfig;

packages/core/src/Exceptions/LoggingExceptionReporter.php

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Tempest\Core\Exceptions;
44

55
use Tempest\Core\ProvidesContext;
6-
use Tempest\Debug\Debug;
76
use Tempest\Log\Logger;
87
use Throwable;
98

@@ -18,17 +17,13 @@ public function __construct(
1817

1918
public function report(Throwable $throwable): void
2019
{
21-
$items = [
22-
'class' => $throwable::class,
23-
'exception' => $throwable->getMessage(),
24-
'trace' => $throwable->getTraceAsString(),
25-
'context' => $throwable instanceof ProvidesContext
20+
$this->logger->error(
21+
message: $throwable->getMessage() ?: '(no message)',
22+
context: $throwable instanceof ProvidesContext
2623
? $throwable->context()
2724
: [],
28-
];
25+
);
2926

30-
$this->logger->error($throwable->getMessage(), $items);
31-
32-
Debug::resolve()->log($items, writeToOut: false);
27+
$this->logger->error($throwable->getTraceAsString());
3328
}
3429
}

packages/http/src/HttpRequestFailed.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ public function context(): array
3434
'request_uri' => $this->request?->uri,
3535
'request_method' => $this->request?->method->value,
3636
'status_code' => $this->status->value,
37-
'message' => $this->message !== '' ? $this->message : null,
3837
'original_response' => $this->cause ? $this->cause::class : null,
3938
]);
4039
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Tempest\Integration\Core\Exceptions;
6+
7+
use Exception;
8+
use PHPUnit\Framework\Attributes\Test;
9+
use Tempest\Core\Exceptions\LoggingExceptionReporter;
10+
use Tempest\Core\ProvidesContext;
11+
use Tempest\Log\Logger;
12+
use Tests\Tempest\Integration\FrameworkIntegrationTestCase;
13+
14+
final class LoggingExceptionReporterTest extends FrameworkIntegrationTestCase
15+
{
16+
#[Test]
17+
public function logs_exception_with_message(): void
18+
{
19+
$logger = new TestLogger();
20+
$reporter = new LoggingExceptionReporter($logger);
21+
$reporter->report(new Exception('Something went wrong'));
22+
23+
$this->assertCount(2, $logger->logs);
24+
$this->assertSame('error', $logger->logs[0]['level']);
25+
$this->assertSame('Something went wrong', $logger->logs[0]['message']);
26+
}
27+
28+
#[Test]
29+
public function logs_exception_with_fallback_message(): void
30+
{
31+
$logger = new TestLogger();
32+
$reporter = new LoggingExceptionReporter($logger);
33+
$reporter->report(new Exception(''));
34+
35+
$this->assertCount(2, $logger->logs);
36+
$this->assertSame('error', $logger->logs[0]['level']);
37+
$this->assertSame('(no message)', $logger->logs[0]['message']);
38+
}
39+
40+
#[Test]
41+
public function logs_exception_trace_separately(): void
42+
{
43+
$logger = new TestLogger();
44+
$reporter = new LoggingExceptionReporter($logger);
45+
$reporter->report(new Exception('Test'));
46+
47+
$this->assertCount(2, $logger->logs);
48+
$this->assertSame('error', $logger->logs[1]['level']);
49+
$this->assertIsString($logger->logs[1]['message']);
50+
$this->assertStringStartsWith('#0 ', $logger->logs[1]['message']);
51+
}
52+
53+
#[Test]
54+
public function logs_exception_with_context_when_exception_provides_context(): void
55+
{
56+
$logger = new TestLogger();
57+
$reporter = new LoggingExceptionReporter($logger);
58+
$reporter->report(new ExceptionWithContext('Test'));
59+
60+
$this->assertCount(2, $logger->logs);
61+
$this->assertSame(['foo' => 'bar', 'baz' => 'qux'], $logger->logs[0]['context']);
62+
}
63+
64+
#[Test]
65+
public function logs_exception_with_empty_context_when_exception_does_not_provide_context(): void
66+
{
67+
$logger = new TestLogger();
68+
$reporter = new LoggingExceptionReporter($logger);
69+
$reporter->report(new Exception('Test'));
70+
71+
$this->assertCount(2, $logger->logs);
72+
$this->assertEmpty($logger->logs[0]['context']);
73+
}
74+
}
75+
76+
final class TestLogger implements Logger
77+
{
78+
public array $logs = [];
79+
80+
public function emergency(string|\Stringable $message, array $context = []): void
81+
{
82+
$this->logs[] = ['level' => 'emergency', 'message' => $message, 'context' => $context];
83+
}
84+
85+
public function alert(string|\Stringable $message, array $context = []): void
86+
{
87+
$this->logs[] = ['level' => 'alert', 'message' => $message, 'context' => $context];
88+
}
89+
90+
public function critical(string|\Stringable $message, array $context = []): void
91+
{
92+
$this->logs[] = ['level' => 'critical', 'message' => $message, 'context' => $context];
93+
}
94+
95+
public function error(string|\Stringable $message, array $context = []): void
96+
{
97+
$this->logs[] = ['level' => 'error', 'message' => $message, 'context' => $context];
98+
}
99+
100+
public function warning(string|\Stringable $message, array $context = []): void
101+
{
102+
$this->logs[] = ['level' => 'warning', 'message' => $message, 'context' => $context];
103+
}
104+
105+
public function notice(string|\Stringable $message, array $context = []): void
106+
{
107+
$this->logs[] = ['level' => 'notice', 'message' => $message, 'context' => $context];
108+
}
109+
110+
public function info(string|\Stringable $message, array $context = []): void
111+
{
112+
$this->logs[] = ['level' => 'info', 'message' => $message, 'context' => $context];
113+
}
114+
115+
public function debug(string|\Stringable $message, array $context = []): void
116+
{
117+
$this->logs[] = ['level' => 'debug', 'message' => $message, 'context' => $context];
118+
}
119+
120+
public function log(mixed $level, string|\Stringable $message, array $context = []): void
121+
{
122+
$this->logs[] = ['level' => $level, 'message' => $message, 'context' => $context];
123+
}
124+
}
125+
126+
final class ExceptionWithContext extends Exception implements ProvidesContext
127+
{
128+
public function context(): iterable
129+
{
130+
return ['foo' => 'bar', 'baz' => 'qux'];
131+
}
132+
}

0 commit comments

Comments
 (0)