Skip to content

Commit cb44e4a

Browse files
authored
feat(formatter): add maxTraceLength config option to NormalizerFormatter (#2015)
Add maxTraceLength property to NormalizerFormatter to limit the number of stack trace frames included when normalizing exceptions. Allows users who need control over their log length to avoid truncating the log and causing invalid json syntax
1 parent b321dd6 commit cb44e4a

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

src/Monolog/Formatter/NormalizerFormatter.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ class NormalizerFormatter implements FormatterInterface
2828
protected string $dateFormat;
2929
protected int $maxNormalizeDepth = 9;
3030
protected int $maxNormalizeItemCount = 1000;
31+
protected ?int $maxTraceLength = null;
3132

3233
private int $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS;
3334

@@ -122,6 +123,24 @@ public function setMaxNormalizeItemCount(int $maxNormalizeItemCount): self
122123
return $this;
123124
}
124125

126+
/**
127+
* The maximum number of stack trace frames to include
128+
*/
129+
public function getMaxTraceLength(): ?int
130+
{
131+
return $this->maxTraceLength;
132+
}
133+
134+
/**
135+
* @return $this
136+
*/
137+
public function setMaxTraceLength(?int $maxTraceLength): self
138+
{
139+
$this->maxTraceLength = $maxTraceLength;
140+
141+
return $this;
142+
}
143+
125144
/**
126145
* Enables `json_encode` pretty print.
127146
*
@@ -290,7 +309,7 @@ protected function normalizeException(Throwable $e, int $depth = 0)
290309
}
291310
}
292311

293-
$trace = $e->getTrace();
312+
$trace = array_slice($e->getTrace(), 0, $this->maxTraceLength);
294313
foreach ($trace as $frame) {
295314
if (isset($frame['file'], $frame['line'])) {
296315
$file = $frame['file'];

tests/Monolog/Formatter/NormalizerFormatterTest.php

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,102 @@ public function testCanNormalizeIncompleteObject(): void
439439
], $result['context']['object']);
440440
}
441441

442+
public function testMaxTraceLengthDefault()
443+
{
444+
$formatter = new NormalizerFormatter();
445+
$this->assertNull($formatter->getMaxTraceLength());
446+
}
447+
448+
public function testMaxTraceLengthSetter()
449+
{
450+
$formatter = new NormalizerFormatter();
451+
$formatter->setMaxTraceLength(5);
452+
$this->assertEquals(5, $formatter->getMaxTraceLength());
453+
454+
$formatter->setMaxTraceLength(null);
455+
$this->assertNull($formatter->getMaxTraceLength());
456+
}
457+
458+
public function testMaxTraceLengthLimitsTrace()
459+
{
460+
$formatter = new NormalizerFormatter();
461+
$formatter->setMaxTraceLength(2);
462+
463+
$exception = $this->createDeepTraceException();
464+
$formatted = $formatter->normalizeValue(['exception' => $exception]);
465+
466+
$this->assertArrayHasKey('trace', $formatted['exception']);
467+
$this->assertCount(2, $formatted['exception']['trace']);
468+
}
469+
470+
public function testMaxTraceLengthZeroDoesNotIncludeTrace()
471+
{
472+
$formatter = new NormalizerFormatter();
473+
$formatter->setMaxTraceLength(0);
474+
475+
$exception = $this->createDeepTraceException();
476+
$formatted = $formatter->normalizeValue(['exception' => $exception]);
477+
478+
$this->assertArrayNotHasKey('trace', $formatted['exception']);
479+
}
480+
481+
public function testMaxTraceLengthNullAllowsUnlimited()
482+
{
483+
$formatter = new NormalizerFormatter();
484+
$formatter->setMaxTraceLength(null);
485+
486+
$exception = $this->createDeepTraceException();
487+
$formatted = $formatter->normalizeValue(['exception' => $exception]);
488+
489+
$this->assertArrayHasKey('trace', $formatted['exception']);
490+
$this->assertGreaterThan(2, count($formatted['exception']['trace']));
491+
}
492+
493+
public function testMaxTraceLengthWithPreviousException()
494+
{
495+
$formatter = new NormalizerFormatter();
496+
$formatter->setMaxTraceLength(1);
497+
498+
$previous = new \RuntimeException('Previous exception');
499+
$exception = new \LogicException('Main exception', 0, $previous);
500+
$formatted = $formatter->normalizeValue(['exception' => $exception]);
501+
502+
$this->assertArrayHasKey('trace', $formatted['exception']);
503+
$this->assertCount(1, $formatted['exception']['trace']);
504+
$this->assertArrayHasKey('previous', $formatted['exception']);
505+
$this->assertArrayHasKey('trace', $formatted['exception']['previous']);
506+
$this->assertCount(1, $formatted['exception']['previous']['trace']);
507+
}
508+
509+
public function testMaxTraceLengthWithBasePath()
510+
{
511+
$formatter = new NormalizerFormatter();
512+
$formatter->setMaxTraceLength(3);
513+
$formatter->setBasePath(dirname(__DIR__, 3));
514+
515+
$exception = $this->createDeepTraceException();
516+
$formatted = $formatter->normalizeValue(['exception' => $exception]);
517+
518+
$this->assertArrayHasKey('trace', $formatted['exception']);
519+
$this->assertCount(3, $formatted['exception']['trace']);
520+
521+
foreach ($formatted['exception']['trace'] as $traceItem) {
522+
$this->assertStringNotContainsString(dirname(__DIR__, 3), $traceItem);
523+
}
524+
}
525+
526+
private function createDeepTraceException(): \Exception
527+
{
528+
$createException = function(int $depth) use (&$createException): \Exception {
529+
if ($depth <= 0) {
530+
return new \RuntimeException('Test exception with deep trace');
531+
}
532+
return $createException($depth - 1);
533+
};
534+
535+
return $createException(3);
536+
}
537+
442538
private function throwHelper($arg)
443539
{
444540
throw new \RuntimeException('Thrown');

0 commit comments

Comments
 (0)