Skip to content

Commit ac8234b

Browse files
authored
Merge pull request #11 from Naoray/feat/enhance-templates
feat: enhance templates
2 parents d3aceb5 + 387a522 commit ac8234b

23 files changed

+701
-389
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ This will copy the templates to `resources/views/vendor/github-monolog/` where y
118118
- `comment.md`: Template for comments on existing issues
119119
- `previous_exception.md`: Template for previous exceptions in the chain
120120

121+
> **Important**: The templates use HTML comments as section markers (e.g. `<!-- stacktrace:start -->` and `<!-- stacktrace:end -->`). These markers are used to intelligently remove empty sections from the rendered output. Please keep these markers intact when customizing the templates.
122+
121123
Available template variables:
122124
- `{level}`: Log level (error, warning, etc.)
123125
- `{message}`: The error message or log content

phpstan-baseline.neon

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
parameters:
2+
ignoreErrors:
3+
-
4+
message: '#^Parameter \#1 \$callback of method Illuminate\\Support\\Collection\<int,string\>\:\:filter\(\) expects \(callable\(string, int\)\: bool\)\|null, Closure\(string, string\)\: bool given\.$#'
5+
identifier: argument.type
6+
count: 1
7+
path: src/Issues/SectionMapping.php

resources/views/comment.md

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,50 @@
1-
# New Occurrence
1+
## Error Details
2+
**Type:** {level}
3+
**Message:** {message}
24

3-
**Log Level:** {level}
5+
<!-- stacktrace:start -->
6+
---
47

5-
{message}
6-
7-
**Simplified Stack Trace:**
8-
```php
8+
## Stack Trace
9+
```shell
910
{simplified_stack_trace}
1011
```
1112

1213
<details>
13-
<summary>Complete Stack Trace</summary>
14+
<summary>📋 View Complete Stack Trace</summary>
1415

15-
```php
16+
```shell
1617
{full_stack_trace}
1718
```
1819
</details>
1920

21+
<!-- prev-stacktrace:start -->
2022
<details>
21-
<summary>Previous Exceptions</summary>
23+
<summary>🔍 View Previous Exceptions</summary>
2224

25+
```shell
2326
{previous_exceptions}
27+
```
2428
</details>
29+
<!-- prev-stacktrace:end -->
30+
<!-- stacktrace:end -->
2531

32+
<!-- context:start -->
33+
---
34+
35+
## Context
36+
```json
2637
{context}
38+
```
39+
<!-- context:end -->
2740

41+
<!-- extra:start -->
42+
---
43+
44+
## Extra Data
45+
```json
2846
{extra}
47+
```
48+
<!-- extra:end -->
49+
50+
<!-- Signature: {signature} -->

resources/views/issue.md

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,50 @@
1+
## Error Details
12
**Log Level:** {level}
3+
**Class:** {class}
4+
**Message:** {message}
25

3-
{message}
6+
<!-- stacktrace:start -->
7+
---
48

5-
**Simplified Stack Trace:**
6-
```php
9+
## Stack Trace
10+
```shell
711
{simplified_stack_trace}
812
```
913

1014
<details>
11-
<summary>Complete Stack Trace</summary>
15+
<summary>📋 View Complete Stack Trace</summary>
1216

13-
```php
17+
```shell
1418
{full_stack_trace}
1519
```
1620
</details>
1721

22+
<!-- prev-stacktrace:start -->
1823
<details>
19-
<summary>Previous Exceptions</summary>
24+
<summary>🔍 View Previous Exceptions</summary>
2025

2126
{previous_exceptions}
27+
2228
</details>
29+
<!-- prev-stacktrace:end -->
30+
<!-- stacktrace:end -->
31+
32+
<!-- context:start -->
33+
---
2334

35+
## Context
36+
```json
2437
{context}
38+
```
39+
<!-- context:end -->
2540

41+
<!-- extra:start -->
42+
---
43+
44+
## Extra Data
45+
```json
2646
{extra}
47+
```
48+
<!-- extra:end -->
2749

2850
<!-- Signature: {signature} -->

resources/views/previous_exception.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
### Previous Exception #{count}
2-
**Type:** {type}
1+
## Previous Exception #{count}
2+
{message}
33

4-
**Simplified Stack Trace:**
5-
```php
4+
<!-- prev-exception-stacktrace:start -->
5+
```shell
66
{simplified_stack_trace}
77
```
88

9-
<details>
10-
<summary>Complete Stack Trace</summary>
9+
---
1110

12-
```php
11+
```shell
1312
{full_stack_trace}
1413
```
15-
</details>
14+
<!-- prev-exception-stacktrace:end -->

src/GithubMonologServiceProvider.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,38 @@
55
use Illuminate\Support\ServiceProvider;
66
use Naoray\LaravelGithubMonolog\Issues\Formatters\ExceptionFormatter;
77
use Naoray\LaravelGithubMonolog\Issues\Formatters\IssueFormatter;
8+
use Naoray\LaravelGithubMonolog\Issues\Formatters\PreviousExceptionFormatter;
89
use Naoray\LaravelGithubMonolog\Issues\Formatters\StackTraceFormatter;
910
use Naoray\LaravelGithubMonolog\Issues\StubLoader;
1011
use Naoray\LaravelGithubMonolog\Issues\TemplateRenderer;
12+
use Naoray\LaravelGithubMonolog\Issues\TemplateSectionCleaner;
1113

1214
class GithubMonologServiceProvider extends ServiceProvider
1315
{
1416
public function register(): void
1517
{
1618
$this->app->bind(StackTraceFormatter::class);
1719
$this->app->bind(StubLoader::class);
20+
$this->app->bind(TemplateSectionCleaner::class);
21+
1822
$this->app->bind(ExceptionFormatter::class, function ($app) {
1923
return new ExceptionFormatter(
2024
stackTraceFormatter: $app->make(StackTraceFormatter::class),
2125
);
2226
});
2327

28+
$this->app->bind(PreviousExceptionFormatter::class, function ($app) {
29+
return new PreviousExceptionFormatter(
30+
exceptionFormatter: $app->make(ExceptionFormatter::class),
31+
stubLoader: $app->make(StubLoader::class),
32+
);
33+
});
34+
2435
$this->app->singleton(TemplateRenderer::class, function ($app) {
2536
return new TemplateRenderer(
2637
exceptionFormatter: $app->make(ExceptionFormatter::class),
38+
previousExceptionFormatter: $app->make(PreviousExceptionFormatter::class),
39+
sectionCleaner: $app->make(TemplateSectionCleaner::class),
2740
stubLoader: $app->make(StubLoader::class),
2841
);
2942
});

src/Issues/Formatter.php

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Monolog\Formatter\FormatterInterface;
66
use Monolog\LogRecord;
77
use Naoray\LaravelGithubMonolog\Issues\TemplateRenderer;
8-
use Throwable;
98

109
class IssueFormatter implements FormatterInterface
1110
{
@@ -19,29 +18,15 @@ public function format(LogRecord $record): Formatted
1918
throw new \RuntimeException('Record is missing github_issue_signature in extra data. Make sure the DeduplicationHandler is configured correctly.');
2019
}
2120

22-
$exception = $this->getException($record);
23-
2421
return new Formatted(
25-
title: $this->templateRenderer->renderTitle($record, $exception),
26-
body: $this->templateRenderer->render($this->templateRenderer->getIssueStub(), $record, $record->extra['github_issue_signature'], $exception),
27-
comment: $this->templateRenderer->render($this->templateRenderer->getCommentStub(), $record, null, $exception),
22+
title: $this->templateRenderer->renderTitle($record),
23+
body: $this->templateRenderer->render($this->templateRenderer->getIssueStub(), $record, $record->extra['github_issue_signature']),
24+
comment: $this->templateRenderer->render($this->templateRenderer->getCommentStub(), $record, null),
2825
);
2926
}
3027

3128
public function formatBatch(array $records): array
3229
{
3330
return array_map([$this, 'format'], $records);
3431
}
35-
36-
private function hasErrorException(LogRecord $record): bool
37-
{
38-
return $record->level->value >= \Monolog\Level::Error->value
39-
&& isset($record->context['exception'])
40-
&& $record->context['exception'] instanceof Throwable;
41-
}
42-
43-
private function getException(LogRecord $record): ?Throwable
44-
{
45-
return $this->hasErrorException($record) ? $record->context['exception'] : null;
46-
}
4732
}

src/Issues/Formatters/IssueFormatter.php

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
use Monolog\Formatter\FormatterInterface;
66
use Monolog\LogRecord;
77
use Naoray\LaravelGithubMonolog\Issues\TemplateRenderer;
8-
use Throwable;
98

109
class IssueFormatter implements FormatterInterface
1110
{
@@ -19,29 +18,15 @@ public function format(LogRecord $record): Formatted
1918
throw new \RuntimeException('Record is missing github_issue_signature in extra data. Make sure the DeduplicationHandler is configured correctly.');
2019
}
2120

22-
$exception = $this->getException($record);
23-
2421
return new Formatted(
25-
title: $this->templateRenderer->renderTitle($record, $exception),
26-
body: $this->templateRenderer->render($this->templateRenderer->getIssueStub(), $record, $record->extra['github_issue_signature'], $exception),
27-
comment: $this->templateRenderer->render($this->templateRenderer->getCommentStub(), $record, null, $exception),
22+
title: $this->templateRenderer->renderTitle($record),
23+
body: $this->templateRenderer->render($this->templateRenderer->getIssueStub(), $record, $record->extra['github_issue_signature']),
24+
comment: $this->templateRenderer->render($this->templateRenderer->getCommentStub(), $record, null),
2825
);
2926
}
3027

3128
public function formatBatch(array $records): array
3229
{
3330
return array_map([$this, 'format'], $records);
3431
}
35-
36-
private function hasErrorException(LogRecord $record): bool
37-
{
38-
return $record->level->value >= \Monolog\Level::Error->value
39-
&& isset($record->context['exception'])
40-
&& $record->context['exception'] instanceof Throwable;
41-
}
42-
43-
private function getException(LogRecord $record): ?Throwable
44-
{
45-
return $this->hasErrorException($record) ? $record->context['exception'] : null;
46-
}
4732
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<?php
2+
3+
namespace Naoray\LaravelGithubMonolog\Issues\Formatters;
4+
5+
use Illuminate\Support\Str;
6+
use Monolog\LogRecord;
7+
use Naoray\LaravelGithubMonolog\Issues\InteractsWithLogRecord;
8+
use Naoray\LaravelGithubMonolog\Issues\StubLoader;
9+
use Throwable;
10+
11+
class PreviousExceptionFormatter
12+
{
13+
use InteractsWithLogRecord;
14+
15+
private const MAX_PREVIOUS_EXCEPTIONS = 3;
16+
17+
private string $previousExceptionStub;
18+
19+
public function __construct(
20+
private readonly ExceptionFormatter $exceptionFormatter,
21+
private readonly StubLoader $stubLoader,
22+
) {
23+
$this->previousExceptionStub = $this->stubLoader->load('previous_exception');
24+
}
25+
26+
public function format(LogRecord $record): string
27+
{
28+
$exception = $this->getException($record);
29+
30+
if (! $exception instanceof Throwable) {
31+
return '';
32+
}
33+
34+
if (! $previous = $exception->getPrevious()) {
35+
return '';
36+
}
37+
38+
$exceptions = collect()
39+
->range(1, self::MAX_PREVIOUS_EXCEPTIONS)
40+
->map(function ($count) use (&$previous, $record) {
41+
if (! $previous) {
42+
return null;
43+
}
44+
45+
$current = $previous;
46+
$previous = $previous->getPrevious();
47+
48+
$details = $this->exceptionFormatter->format(
49+
$record->with(
50+
context: ['exception' => $current],
51+
extra: []
52+
)
53+
);
54+
55+
return Str::of($this->previousExceptionStub)
56+
->replace(
57+
['{count}', '{message}', '{simplified_stack_trace}', '{full_stack_trace}'],
58+
[$count, $current->getMessage(), $details['simplified_stack_trace'], str_replace(base_path(), '', $details['full_stack_trace'])]
59+
)
60+
->toString();
61+
})
62+
->filter()
63+
->join("\n\n");
64+
65+
if (empty($exceptions)) {
66+
return '';
67+
}
68+
69+
if ($previous) {
70+
$exceptions .= "\n\n> Note: Additional previous exceptions were truncated\n";
71+
}
72+
73+
return $exceptions;
74+
}
75+
}

src/Issues/Formatters/StackTraceFormatter.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@ public function format(string $stackTrace): string
2828

2929
$line = str_replace(base_path(), '', $line);
3030

31-
if (str_contains((string) $line, '/vendor/') && ! Str::isMatch("/BoundMethod\.php\([0-9]+\): App/", $line)) {
31+
// Stack trace lines start with #\d. Here we pad the numbers 0-9
32+
// with a preceding zero to keep everything in line visually.
33+
$line = preg_replace('/^(\e\[0m)#(\d)(?!\d)/', '$1#0$2', $line);
34+
35+
if ($this->isVendorFrame($line)) {
3236
return self::VENDOR_FRAME_PLACEHOLDER;
3337
}
3438

@@ -48,19 +52,30 @@ private function formatInitialException(string $line): array
4852
];
4953
}
5054

55+
private function isVendorFrame($line): bool
56+
{
57+
return str_contains((string) $line, self::VENDOR_FRAME_PLACEHOLDER)
58+
|| str_contains((string) $line, '/vendor/') && ! Str::isMatch("/BoundMethod\.php\([0-9]+\): App/", $line)
59+
|| str_ends_with($line, '{main}');
60+
}
61+
5162
private function collapseVendorFrames(Collection $lines): Collection
5263
{
5364
$hasVendorFrame = false;
5465

5566
return $lines->filter(function ($line) use (&$hasVendorFrame) {
56-
$isVendorFrame = str_contains($line, '[Vendor frames]');
67+
$isVendorFrame = $this->isVendorFrame($line);
5768

5869
if ($isVendorFrame) {
70+
// Skip the line if a vendor frame has already been added.
5971
if ($hasVendorFrame) {
6072
return false;
6173
}
74+
75+
// Otherwise, mark that a vendor frame has been added.
6276
$hasVendorFrame = true;
6377
} else {
78+
// Reset the flag if the current line is not a vendor frame.
6479
$hasVendorFrame = false;
6580
}
6681

0 commit comments

Comments
 (0)