Repository: Seldaek/monolog (php)

Issue:
IntrospectionProcessorTests: $expect and $actual are the same object by reference. 
Monolog version 2

Three tests in IntrospectionProcessorTest (testLevelTooLow, testLevelEqual, testLevelHigher) aren't actually testing anything. Because `$expected = $input` is a reference, the changes made to `$expected['extra']` are made to $input and carried forward to $actual. You can demonstrate this by adding a `return $record` at the immediate start of `InstrospectionProcessor::__invoke` -- the tests still pass despite bypassing all the code.

I have a PR for this I'll open in a few minutes. 

Pull request description:
Fix IntrospectionProcessor tests resulting in false positives
Three tests in IntrospectionProcessorTest (testLevelTooLow, testLevelEqual, testLevelHigher) aren't actually testing anything. Because `$expected = $input` is a reference, the changes made to `$expected['extra']` are made to $input and carried forward to $actual. You can demonstrate this by adding a `return $record` at the immediate start of `InstrospectionProcessor::__invoke` -- the tests still pass despite bypassing all the code.

Fixes #1895

Patch:
diff --git a/src/Monolog/Formatter/NormalizerFormatter.php b/src/Monolog/Formatter/NormalizerFormatter.php
index 134c6879..358b3938 100644
--- a/src/Monolog/Formatter/NormalizerFormatter.php
+++ b/src/Monolog/Formatter/NormalizerFormatter.php
@@ -31,6 +31,8 @@ class NormalizerFormatter implements FormatterInterface
 
     private int $jsonEncodeOptions = Utils::DEFAULT_JSON_FLAGS;
 
+    protected string $basePath = '';
+
     /**
      * @param string|null $dateFormat The format of the timestamp: one supported by DateTime::format
      * @throws \RuntimeException If the function json_encode does not exist
@@ -140,6 +142,21 @@ class NormalizerFormatter implements FormatterInterface
         return $this;
     }
 
+    /**
+     * Setting a base path will hide the base path from exception and stack trace file names to shorten them
+     * @return $this
+     */
+    public function setBasePath(string $path = ''): self
+    {
+        if ($path !== '') {
+            $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+        }
+
+        $this->basePath = $path;
+
+        return $this;
+    }
+
     /**
      * Provided as extension point
      *
@@ -247,11 +264,16 @@ class NormalizerFormatter implements FormatterInterface
             return (array) $e->jsonSerialize();
         }
 
+        $file = $e->getFile();
+        if ($this->basePath !== '') {
+            $file = preg_replace('{^'.preg_quote($this->basePath).'}', '', $file);
+        }
+
         $data = [
             'class' => Utils::getClass($e),
             'message' => $e->getMessage(),
             'code' => (int) $e->getCode(),
-            'file' => $e->getFile().':'.$e->getLine(),
+            'file' => $file.':'.$e->getLine(),
         ];
 
         if ($e instanceof \SoapFault) {
@@ -275,7 +297,11 @@ class NormalizerFormatter implements FormatterInterface
         $trace = $e->getTrace();
         foreach ($trace as $frame) {
             if (isset($frame['file'], $frame['line'])) {
-                $data['trace'][] = $frame['file'].':'.$frame['line'];
+                $file = $frame['file'];
+                if ($this->basePath !== '') {
+                    $file = preg_replace('{^'.preg_quote($this->basePath).'}', '', $file);
+                }
+                $data['trace'][] = $file.':'.$frame['line'];
             }
         }
 


Test patch:
diff --git a/tests/Monolog/Formatter/JsonFormatterTest.php b/tests/Monolog/Formatter/JsonFormatterTest.php
index 2d908e4d..a3ab36dd 100644
--- a/tests/Monolog/Formatter/JsonFormatterTest.php
+++ b/tests/Monolog/Formatter/JsonFormatterTest.php
@@ -118,6 +118,18 @@ class JsonFormatterTest extends TestCase
         $this->assertContextContainsFormattedException($formattedException, $message);
     }
 
+    public function testBasePathWithException(): void
+    {
+        $formatter = new JsonFormatter();
+        $formatter->setBasePath(dirname(dirname(dirname(__DIR__))));
+        $exception = new \RuntimeException('Foo');
+
+        $message = $this->formatRecordWithExceptionInContext($formatter, $exception);
+
+        $parsed = json_decode($message, true);
+        self::assertSame('tests/Monolog/Formatter/JsonFormatterTest.php:' . (__LINE__ - 5), $parsed['context']['exception']['file']);
+    }
+
     public function testDefFormatWithPreviousException()
     {
         $formatter = new JsonFormatter();
diff --git a/tests/Monolog/Formatter/NormalizerFormatterTest.php b/tests/Monolog/Formatter/NormalizerFormatterTest.php
index 0886fe8f..9744052f 100644
--- a/tests/Monolog/Formatter/NormalizerFormatterTest.php
+++ b/tests/Monolog/Formatter/NormalizerFormatterTest.php
@@ -81,6 +81,20 @@ class NormalizerFormatterTest extends TestCase
         ], $formatted);
     }
 
+    public function testFormatExceptionWithBasePath(): void
+    {
+        $formatter = new NormalizerFormatter('Y-m-d');
+        $formatter->setBasePath(dirname(dirname(dirname(__DIR__))));
+        $e = new \LogicException('bar');
+        $formatted = $formatter->normalizeValue([
+            'exception' => $e,
+        ]);
+
+        self::assertSame('tests/Monolog/Formatter/NormalizerFormatterTest.php:' . (__LINE__ - 5), $formatted['exception']['file']);
+        self::assertStringStartsWith('vendor/phpunit/phpunit/src/Framework/TestCase.php:', $formatted['exception']['trace'][0]);
+        self::assertStringStartsWith('vendor/phpunit/phpunit/src/Framework/TestCase.php:', $formatted['exception']['trace'][1]);
+    }
+
     public function testFormatSoapFaultException()
     {
         if (!class_exists('SoapFault')) {
diff --git a/tests/Monolog/Processor/IntrospectionProcessorTest.php b/tests/Monolog/Processor/IntrospectionProcessorTest.php
index d18dbd00..a967ee1f 100644
--- a/tests/Monolog/Processor/IntrospectionProcessorTest.php
+++ b/tests/Monolog/Processor/IntrospectionProcessorTest.php
@@ -68,7 +68,7 @@ class IntrospectionProcessorTest extends TestCase
     {
         $input = $this->getRecord(Level::Debug);
 
-        $expected = $input;
+        $expected = clone $input;
 
         $processor = new IntrospectionProcessor(Level::Critical);
         $actual = $processor($input);
@@ -80,7 +80,7 @@ class IntrospectionProcessorTest extends TestCase
     {
         $input = $this->getRecord(Level::Critical);
 
-        $expected = $input;
+        $expected = clone $input;
         $expected['extra'] = [
             'file' => null,
             'line' => null,
@@ -99,7 +99,7 @@ class IntrospectionProcessorTest extends TestCase
     {
         $input = $this->getRecord(Level::Emergency);
 
-        $expected = $input;
+        $expected = clone $input;
         $expected['extra'] = [
             'file' => null,
             'line' => null,