diff --git a/src/Instrumentation/Laravel/src/Watchers/LogWatcher.php b/src/Instrumentation/Laravel/src/Watchers/LogWatcher.php index 65b5751ac..169921235 100644 --- a/src/Instrumentation/Laravel/src/Watchers/LogWatcher.php +++ b/src/Instrumentation/Laravel/src/Watchers/LogWatcher.php @@ -10,6 +10,8 @@ use OpenTelemetry\API\Instrumentation\CachedInstrumentation; use OpenTelemetry\API\Logs\LogRecord; use OpenTelemetry\API\Logs\Severity; +use OpenTelemetry\SDK\Common\Exception\StackTraceFormatter; +use Throwable; use TypeError; class LogWatcher extends Watcher @@ -52,8 +54,21 @@ public function recordLog(MessageLogged $log): void // Should this fail, we should continue to emit the LogRecord. } + $contextToEncode = array_filter($log->context); + + $exception = $this->getExceptionFromContext($log->context); + + if ($exception !== null) { + unset($contextToEncode['exception']); + } + $attributes = [ - 'context' => json_encode(array_filter($log->context)), + 'context' => json_encode($contextToEncode), + ...$exception !== null ? [ + 'exception.type' => $exception::class, + 'exception.message' => $exception->getMessage(), + 'exception.stacktrace' => StackTraceFormatter::format($exception), + ] : [], ]; $logger = $this->instrumentation->logger(); @@ -65,4 +80,16 @@ public function recordLog(MessageLogged $log): void $logger->emit($record); } + + private function getExceptionFromContext(array $context): ?Throwable + { + if ( + ! isset($context['exception']) || + ! $context['exception'] instanceof Throwable + ) { + return null; + } + + return $context['exception']; + } } diff --git a/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php b/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php index a4dfe7c00..b15c6edb7 100644 --- a/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php +++ b/src/Instrumentation/Laravel/tests/Integration/LaravelInstrumentationTest.php @@ -4,6 +4,7 @@ namespace OpenTelemetry\Tests\Contrib\Instrumentation\Laravel\Integration; +use Exception; use Illuminate\Routing\Router; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Http; @@ -106,6 +107,16 @@ public function test_route_span_name_if_not_found(): void $this->assertSame('GET', $span->getName()); } + public function test_records_exception_in_logs(): void + { + $this->router()->get('/exception', fn () => throw new Exception('Test exception')); + $this->call('GET', '/exception'); + $logRecord = $this->storage[0]; + $this->assertEquals(Exception::class, $logRecord->getAttributes()->get(TraceAttributes::EXCEPTION_TYPE)); + $this->assertEquals('Test exception', $logRecord->getAttributes()->get(TraceAttributes::EXCEPTION_MESSAGE)); + $this->assertNotNull($logRecord->getAttributes()->get(TraceAttributes::EXCEPTION_STACKTRACE)); + } + private function router(): Router { /** @psalm-suppress PossiblyNullReference */