feat(logging): Implement database logging with app_log table
- Created migration to set up app_log table for storing application logs. - Added AppLog entity and repository for ORM handling of logs. - Developed DbLogger service to persist logs of level WARNING and above to the database while maintaining existing logging behavior. - Implemented tests for admin log retrieval and DbLogger functionality to ensure proper logging behavior. - Enhanced logging context sanitization for better error tracking.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Admin;
|
||||
|
||||
use App\Tests\ApiTestCase;
|
||||
|
||||
/**
|
||||
* GET /api/v1/admin/logs — admin-only, paginated, filterable by level.
|
||||
*/
|
||||
class AdminLogsTest extends ApiTestCase
|
||||
{
|
||||
private function seedLog(string $level, string $message): void
|
||||
{
|
||||
$this->em->getConnection()->insert('app_log', [
|
||||
'level' => $level,
|
||||
'message' => $message,
|
||||
'channel' => 'app',
|
||||
'path' => '/test',
|
||||
'created_at' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function testNonAdminForbidden(): void
|
||||
{
|
||||
$user = $this->createUser(['ROLE_USER']);
|
||||
$this->authJson('GET', '/api/v1/admin/logs', $user);
|
||||
self::assertSame(403, $this->responseCode());
|
||||
}
|
||||
|
||||
public function testAdminSeesLogsFilteredByLevel(): void
|
||||
{
|
||||
$marker = 'ADMINLOGTEST_' . bin2hex(random_bytes(5));
|
||||
$this->seedLog('error', $marker . '_err');
|
||||
$this->seedLog('warning', $marker . '_warn');
|
||||
|
||||
$admin = $this->createUser(['ROLE_ADMIN']);
|
||||
$body = $this->authJson('GET', '/api/v1/admin/logs?level=error&search=' . $marker, $admin);
|
||||
|
||||
self::assertSame(200, $this->responseCode());
|
||||
self::assertTrue($body['success']);
|
||||
self::assertCount(1, $body['data']);
|
||||
self::assertSame('error', $body['data'][0]['level']);
|
||||
self::assertSame($marker . '_err', $body['data'][0]['message']);
|
||||
self::assertArrayHasKey('totalRecords', $body['meta']);
|
||||
|
||||
$this->em->getConnection()->executeStatement('DELETE FROM app_log WHERE message LIKE ?', [$marker . '%']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Shared;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* The `logger` service is decorated by App\Shared\Logging\DbLogger, which persists
|
||||
* warning-and-above to the app_log table while leaving info/debug on stderr only.
|
||||
*/
|
||||
class DbLoggerTest extends KernelTestCase
|
||||
{
|
||||
public function testWarningPersistedButInfoIsNot(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$c = static::getContainer();
|
||||
/** @var LoggerInterface $logger */
|
||||
$logger = $c->get('logger');
|
||||
/** @var Connection $conn */
|
||||
$conn = $c->get('doctrine.dbal.default_connection');
|
||||
|
||||
$marker = 'DBLOGGER_TEST_' . bin2hex(random_bytes(5));
|
||||
|
||||
$logger->info($marker . '_info');
|
||||
$logger->warning($marker . '_warn', ['exception' => new \RuntimeException('boom')]);
|
||||
|
||||
$warn = (int) $conn->fetchOne('SELECT COUNT(*) FROM app_log WHERE message = ?', [$marker . '_warn']);
|
||||
$info = (int) $conn->fetchOne('SELECT COUNT(*) FROM app_log WHERE message = ?', [$marker . '_info']);
|
||||
|
||||
self::assertSame(1, $warn, 'warning must be persisted to app_log');
|
||||
self::assertSame(0, $info, 'info must NOT be persisted to app_log');
|
||||
|
||||
// The Throwable in context is stored as a compact string, not a raw object.
|
||||
$row = $conn->fetchAssociative('SELECT context, level, channel FROM app_log WHERE message = ?', [$marker . '_warn']);
|
||||
self::assertSame('warning', $row['level']);
|
||||
self::assertSame('app', $row['channel']);
|
||||
self::assertStringContainsString('RuntimeException: boom', (string) $row['context']);
|
||||
|
||||
$conn->executeStatement('DELETE FROM app_log WHERE message LIKE ?', [$marker . '%']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user