From d8f6ca4f903ebf2ea0abd91354968e5357aefdeb Mon Sep 17 00:00:00 2001 From: darkdef Date: Tue, 2 Jan 2024 21:57:11 +0300 Subject: [PATCH] DbLoggerInterface --- src/Driver/Pdo/AbstractPdoCommand.php | 21 ++--- src/Driver/Pdo/AbstractPdoConnection.php | 37 ++++---- src/Driver/Pdo/AbstractPdoTransaction.php | 50 ++++------- src/Logger/Context/AbstractContext.php | 33 +++++++ src/Logger/Context/ConnectionContext.php | 18 ++++ src/Logger/Context/QueryContext.php | 23 +++++ src/Logger/Context/TransactionContext.php | 23 +++++ src/Logger/ContextInterface.php | 18 ++++ src/Logger/DbLogger.php | 104 ++++++++++++++++++++++ src/Logger/DbLoggerAwareInterface.php | 20 +++++ src/Logger/DbLoggerAwareTrait.php | 33 +++++++ src/Logger/DbLoggerEvent.php | 27 ++++++ src/Logger/DbLoggerInterface.php | 22 +++++ 13 files changed, 367 insertions(+), 62 deletions(-) create mode 100644 src/Logger/Context/AbstractContext.php create mode 100644 src/Logger/Context/ConnectionContext.php create mode 100644 src/Logger/Context/QueryContext.php create mode 100644 src/Logger/Context/TransactionContext.php create mode 100644 src/Logger/ContextInterface.php create mode 100644 src/Logger/DbLogger.php create mode 100644 src/Logger/DbLoggerAwareInterface.php create mode 100644 src/Logger/DbLoggerAwareTrait.php create mode 100644 src/Logger/DbLoggerEvent.php create mode 100644 src/Logger/DbLoggerInterface.php diff --git a/src/Driver/Pdo/AbstractPdoCommand.php b/src/Driver/Pdo/AbstractPdoCommand.php index 4285e2148..3a77b191c 100644 --- a/src/Driver/Pdo/AbstractPdoCommand.php +++ b/src/Driver/Pdo/AbstractPdoCommand.php @@ -7,9 +7,6 @@ use PDO; use PDOException; use PDOStatement; -use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerAwareTrait; -use Psr\Log\LogLevel; use Throwable; use Yiisoft\Db\Command\AbstractCommand; use Yiisoft\Db\Command\Param; @@ -17,6 +14,10 @@ use Yiisoft\Db\Exception\ConvertException; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidParamException; +use Yiisoft\Db\Logger\Context\QueryContext; +use Yiisoft\Db\Logger\DbLoggerAwareInterface; +use Yiisoft\Db\Logger\DbLoggerAwareTrait; +use Yiisoft\Db\Logger\DbLoggerEvent; use Yiisoft\Db\Profiler\Context\CommandContext; use Yiisoft\Db\Profiler\ProfilerAwareInterface; use Yiisoft\Db\Profiler\ProfilerAwareTrait; @@ -32,9 +33,9 @@ * * It also provides methods for binding parameter values and retrieving query results. */ -abstract class AbstractPdoCommand extends AbstractCommand implements PdoCommandInterface, LoggerAwareInterface, ProfilerAwareInterface +abstract class AbstractPdoCommand extends AbstractCommand implements PdoCommandInterface, DbLoggerAwareInterface, ProfilerAwareInterface { - use LoggerAwareTrait; + use DbLoggerAwareTrait; use ProfilerAwareTrait; /** @@ -255,21 +256,13 @@ protected function internalGetQueryResult(int $queryMode): mixed return $result; } - /** - * Logs the current database query if query logging is on and returns the profiling token if profiling is on. - */ - protected function logQuery(string $rawSql, string $category): void - { - $this->logger?->log(LogLevel::INFO, $rawSql, [$category]); - } - protected function queryInternal(int $queryMode): mixed { $logCategory = self::class . '::' . $this->getQueryMode($queryMode); if ($this->logger !== null) { $rawSql = $this->getRawSql(); - $this->logQuery($rawSql, $logCategory); + $this->logger->log(DbLoggerEvent::QUERY, new QueryContext(__METHOD__, $rawSql, $logCategory)); } $queryContext = new CommandContext(__METHOD__, $logCategory, $this->getSql(), $this->getParams()); diff --git a/src/Driver/Pdo/AbstractPdoConnection.php b/src/Driver/Pdo/AbstractPdoConnection.php index abcaf7198..b089cf563 100644 --- a/src/Driver/Pdo/AbstractPdoConnection.php +++ b/src/Driver/Pdo/AbstractPdoConnection.php @@ -6,16 +6,18 @@ use PDO; use PDOException; -use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerAwareTrait; -use Psr\Log\LogLevel; use Throwable; use Yiisoft\Db\Cache\SchemaCache; use Yiisoft\Db\Connection\AbstractConnection; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidCallException; use Yiisoft\Db\Exception\InvalidConfigException; -use Yiisoft\Db\Profiler\Context\ConnectionContext; +use Yiisoft\Db\Logger\Context\ConnectionContext as LoggerContext; +use Yiisoft\Db\Logger\Context\TransactionContext as LoggerTransactionContext; +use Yiisoft\Db\Logger\DbLoggerAwareInterface; +use Yiisoft\Db\Logger\DbLoggerAwareTrait; +use Yiisoft\Db\Logger\DbLoggerEvent; +use Yiisoft\Db\Profiler\Context\ConnectionContext as ProfilerContext; use Yiisoft\Db\Profiler\ProfilerAwareInterface; use Yiisoft\Db\Profiler\ProfilerAwareTrait; use Yiisoft\Db\QueryBuilder\QueryBuilderInterface; @@ -36,9 +38,9 @@ * * It implements the ConnectionInterface, which defines the interface for interacting with a database connection. */ -abstract class AbstractPdoConnection extends AbstractConnection implements PdoConnectionInterface, LoggerAwareInterface, ProfilerAwareInterface +abstract class AbstractPdoConnection extends AbstractConnection implements PdoConnectionInterface, DbLoggerAwareInterface, ProfilerAwareInterface { - use LoggerAwareTrait; + use DbLoggerAwareTrait; use ProfilerAwareTrait; protected PDO|null $pdo = null; @@ -80,7 +82,7 @@ public function __sleep(): array public function beginTransaction(string $isolationLevel = null): TransactionInterface { $transaction = parent::beginTransaction($isolationLevel); - if ($this->logger !== null && $transaction instanceof LoggerAwareInterface) { + if ($this->logger !== null && $transaction instanceof DbLoggerAwareInterface) { $transaction->setLogger($this->logger); } @@ -98,16 +100,17 @@ public function open(): void } $token = 'Opening DB connection: ' . $this->driver->getDsn(); - $connectionContext = new ConnectionContext(__METHOD__); + $profilerContext = new ProfilerContext(__METHOD__); + $loggerContext = new LoggerContext(__METHOD__, $this->getDriver()->getDsn()); try { - $this->logger?->log(LogLevel::INFO, $token); - $this->profiler?->begin($token, $connectionContext); + $this->logger?->log(DbLoggerEvent::CONNECTION_BEGIN, $loggerContext); + $this->profiler?->begin($token, $profilerContext); $this->initConnection(); - $this->profiler?->end($token, $connectionContext); + $this->profiler?->end($token, $profilerContext); } catch (PDOException $e) { - $this->profiler?->end($token, $connectionContext->setException($e)); - $this->logger?->log(LogLevel::ERROR, $token); + $this->profiler?->end($token, $profilerContext->setException($e)); + $this->logger?->log(DbLoggerEvent::CONNECTION_ERROR, $loggerContext->setException($e)); throw new Exception($e->getMessage(), (array) $e->errorInfo, $e); } @@ -117,9 +120,9 @@ public function close(): void { if ($this->pdo !== null) { $this->logger?->log( - LogLevel::DEBUG, - 'Closing DB connection: ' . $this->driver->getDsn() . ' ' . __METHOD__, - ); + DbLoggerEvent::CONNECTION_BEGIN, + new LoggerContext(__METHOD__, $this->getDriver()->getDsn()), + ); $this->pdo = null; $this->transaction = null; @@ -225,7 +228,7 @@ protected function rollbackTransactionOnLevel(TransactionInterface $transaction, try { $transaction->rollBack(); } catch (Throwable $e) { - $this->logger?->log(LogLevel::ERROR, (string) $e, [__METHOD__]); + $this->logger?->log(DbLoggerEvent::TRANSACTION_ROLLBACK_ON_LEVEL, (new LoggerTransactionContext(__METHOD__, $level))->setException($e)); /** hide this exception to be able to continue throwing original exception outside */ } } diff --git a/src/Driver/Pdo/AbstractPdoTransaction.php b/src/Driver/Pdo/AbstractPdoTransaction.php index 647b0941b..691084df6 100644 --- a/src/Driver/Pdo/AbstractPdoTransaction.php +++ b/src/Driver/Pdo/AbstractPdoTransaction.php @@ -4,13 +4,14 @@ namespace Yiisoft\Db\Driver\Pdo; -use Psr\Log\LoggerAwareInterface; -use Psr\Log\LoggerAwareTrait; -use Psr\Log\LogLevel; use Throwable; use Yiisoft\Db\Exception\Exception; use Yiisoft\Db\Exception\InvalidConfigException; use Yiisoft\Db\Exception\NotSupportedException; +use Yiisoft\Db\Logger\Context\TransactionContext; +use Yiisoft\Db\Logger\DbLoggerAwareInterface; +use Yiisoft\Db\Logger\DbLoggerAwareTrait; +use Yiisoft\Db\Logger\DbLoggerEvent; use Yiisoft\Db\Transaction\TransactionInterface; /** @@ -35,9 +36,9 @@ * } * ``` */ -abstract class AbstractPdoTransaction implements TransactionInterface, LoggerAwareInterface +abstract class AbstractPdoTransaction implements TransactionInterface, DbLoggerAwareInterface { - use LoggerAwareTrait; + use DbLoggerAwareTrait; /** * @var int The nesting level of the transaction. @@ -52,16 +53,13 @@ public function begin(string $isolationLevel = null): void { $this->db->open(); + $loggerContext = new TransactionContext(__METHOD__, $this->level, $isolationLevel); if ($this->level === 0) { if ($isolationLevel !== null) { $this->setTransactionIsolationLevel($isolationLevel); } - $this->logger?->log( - LogLevel::DEBUG, - 'Begin transaction' . ($isolationLevel ? ' with isolation level ' . $isolationLevel : '') - . ' ' . __METHOD__ - ); + $this->logger?->log(DbLoggerEvent::TRANSACTION_BEGIN_TRANS, $loggerContext); $this->db->getPDO()?->beginTransaction(); $this->level = 1; @@ -70,14 +68,11 @@ public function begin(string $isolationLevel = null): void } if ($this->db->isSavepointEnabled()) { - $this->logger?->log(LogLevel::DEBUG, 'Set savepoint ' . $this->level . ' ' . __METHOD__); + $this->logger?->log(DbLoggerEvent::TRANSACTION_BEGIN_SAVEPOINT, $loggerContext); $this->createSavepoint('LEVEL' . $this->level); } else { - $this->logger?->log( - LogLevel::DEBUG, - 'Transaction not started: nested transaction not supported ' . __METHOD__ - ); + $this->logger?->log(DbLoggerEvent::TRANSACTION_BEGIN_NESTED_ERROR, $loggerContext); throw new NotSupportedException('Transaction not started: nested transaction not supported.'); } @@ -93,21 +88,19 @@ public function commit(): void $this->level--; + $loggerContext = new TransactionContext(__METHOD__, $this->level); if ($this->level === 0) { - $this->logger?->log(LogLevel::DEBUG, 'Commit transaction ' . __METHOD__); + $this->logger?->log(DbLoggerEvent::TRANSACTION_COMMIT, $loggerContext); $this->db->getPDO()?->commit(); return; } if ($this->db->isSavepointEnabled()) { - $this->logger?->log(LogLevel::DEBUG, 'Release savepoint ' . $this->level . ' ' . __METHOD__); + $this->logger?->log(DbLoggerEvent::TRANSACTION_RELEASE_SAVEPOINT, $loggerContext); $this->releaseSavepoint('LEVEL' . $this->level); } else { - $this->logger?->log( - LogLevel::INFO, - 'Transaction not committed: nested transaction not supported ' . __METHOD__ - ); + $this->logger?->log(DbLoggerEvent::TRANSACTION_COMMIT_NESTED_ERROR, $loggerContext); } } @@ -134,21 +127,19 @@ public function rollBack(): void $this->level--; + $loggerContext = new TransactionContext(__METHOD__, $this->level); if ($this->level === 0) { - $this->logger?->log(LogLevel::INFO, 'Roll back transaction ' . __METHOD__); + $this->logger?->log(DbLoggerEvent::TRANSACTION_ROLLBACK, $loggerContext); $this->db->getPDO()?->rollBack(); return; } if ($this->db->isSavepointEnabled()) { - $this->logger?->log(LogLevel::DEBUG, 'Roll back to savepoint ' . $this->level . ' ' . __METHOD__); + $this->logger?->log(DbLoggerEvent::TRANSACTION_ROLLBACK_SAVEPOINT, $loggerContext); $this->rollBackSavepoint('LEVEL' . $this->level); } else { - $this->logger?->log( - LogLevel::INFO, - 'Transaction not rolled back: nested transaction not supported ' . __METHOD__ - ); + $this->logger?->log(DbLoggerEvent::TRANSACTION_ROLLBACK_NESTED_ERROR, $loggerContext); } } @@ -158,10 +149,7 @@ public function setIsolationLevel(string $level): void throw new Exception('Failed to set isolation level: transaction was inactive.'); } - $this->logger?->log( - LogLevel::DEBUG, - 'Setting transaction isolation level to ' . $this->level . ' ' . __METHOD__ - ); + $this->logger?->log(DbLoggerEvent::TRANSACTION_SET_ISOLATION_LEVEL, new TransactionContext(__METHOD__, $this->level, $level)); $this->setTransactionIsolationLevel($level); } diff --git a/src/Logger/Context/AbstractContext.php b/src/Logger/Context/AbstractContext.php new file mode 100644 index 000000000..716a9e203 --- /dev/null +++ b/src/Logger/Context/AbstractContext.php @@ -0,0 +1,33 @@ +methodName; + } + + public function setException(Throwable $e): static + { + $this->exception = $e; + return $this; + } + + public function getException(): Throwable|null + { + return $this->exception; + } +} diff --git a/src/Logger/Context/ConnectionContext.php b/src/Logger/Context/ConnectionContext.php new file mode 100644 index 000000000..7dad4255f --- /dev/null +++ b/src/Logger/Context/ConnectionContext.php @@ -0,0 +1,18 @@ +dsn; + } +} diff --git a/src/Logger/Context/QueryContext.php b/src/Logger/Context/QueryContext.php new file mode 100644 index 000000000..38003c660 --- /dev/null +++ b/src/Logger/Context/QueryContext.php @@ -0,0 +1,23 @@ +rawSql; + } + + public function getCategory(): string + { + return $this->category; + } +} diff --git a/src/Logger/Context/TransactionContext.php b/src/Logger/Context/TransactionContext.php new file mode 100644 index 000000000..2917907cb --- /dev/null +++ b/src/Logger/Context/TransactionContext.php @@ -0,0 +1,23 @@ +level; + } + + public function getIsolationLevel(): string|null + { + return $this->isolationLevel; + } +} diff --git a/src/Logger/ContextInterface.php b/src/Logger/ContextInterface.php new file mode 100644 index 000000000..6b25fe210 --- /dev/null +++ b/src/Logger/ContextInterface.php @@ -0,0 +1,18 @@ + LogLevel::INFO, + DbLoggerEvent::CONNECTION_END => LogLevel::DEBUG, + DbLoggerEvent::CONNECTION_ERROR => LogLevel::ERROR, + + DbLoggerEvent::TRANSACTION_BEGIN_TRANS => LogLevel::DEBUG, + DbLoggerEvent::TRANSACTION_BEGIN_SAVEPOINT => LogLevel::DEBUG, + DbLoggerEvent::TRANSACTION_BEGIN_NESTED_ERROR => LogLevel::DEBUG, + + DbLoggerEvent::TRANSACTION_COMMIT => LogLevel::DEBUG, + DbLoggerEvent::TRANSACTION_RELEASE_SAVEPOINT => LogLevel::DEBUG, + DbLoggerEvent::TRANSACTION_COMMIT_NESTED_ERROR => LogLevel::INFO, + + DbLoggerEvent::TRANSACTION_ROLLBACK => LogLevel::INFO, + DbLoggerEvent::TRANSACTION_ROLLBACK_SAVEPOINT => LogLevel::DEBUG, + DbLoggerEvent::TRANSACTION_ROLLBACK_NESTED_ERROR => LogLevel::INFO, + + DbLoggerEvent::TRANSACTION_SET_ISOLATION_LEVEL => LogLevel::DEBUG, + DbLoggerEvent::TRANSACTION_ROLLBACK_ON_LEVEL => LogLevel::ERROR, + + DbLoggerEvent::QUERY => LogLevel::INFO, + ]; + + public function __construct(protected PsrLoggerInterface $logger) + { + } + + public function log(string $logEvent, ContextInterface $context): void + { + if ($context instanceof ConnectionContext) { + $this->logConnection($logEvent, $context->getMethodName(), $context->getDsn()); + } + + if ($context instanceof TransactionContext) { + $this->logTransaction($logEvent, $context->getIsolationLevel(), $context->getLevel(), $context->getMethodName(), $context->getException()); + } + + if ($context instanceof QueryContext) { + $this->logger->log(self::$LOG_LEVELS[$logEvent] ?? self::DEFAULT_LOG_LEVEL, $context->getRawSql(), [$context->getCategory()]); + } + } + + public function setLevel(string $logEvent, string $level): void + { + self::$LOG_LEVELS[$logEvent] = $level; + } + + private function logConnection(string $logEvent, string $methodName, string $dsn): void + { + $logLevel = self::$LOG_LEVELS[$logEvent] ?? self::DEFAULT_LOG_LEVEL; + $message = match ($logEvent) { + DbLoggerEvent::CONNECTION_BEGIN, DbLoggerEvent::CONNECTION_ERROR => + 'Opening DB connection: ' . $dsn, + DbLoggerEvent::CONNECTION_END => 'Closing DB connection: ' . $dsn . ' ' . $methodName, + }; + $this->logger->log($logLevel, $message); + } + + private function logTransaction(string $logType, string|null $isolationLevel, int $level, string $methodName, ?Throwable $exception = null): void + { + $logLevel = self::$LOG_LEVELS[$logType] ?? self::DEFAULT_LOG_LEVEL; + + if ($logType === DbLoggerEvent::TRANSACTION_ROLLBACK_ON_LEVEL) { + $this->logger->log($logLevel, (string)$exception, [$methodName]); + return; + } + + $message = match ($logType) { + DbLoggerEvent::TRANSACTION_BEGIN_TRANS => 'Begin transaction' . ($isolationLevel ? ' with isolation level ' . $isolationLevel : '') . ' ' . $methodName, + DbLoggerEvent::TRANSACTION_BEGIN_SAVEPOINT => 'Set savepoint ' . $level . ' ' . $methodName, + DbLoggerEvent::TRANSACTION_BEGIN_NESTED_ERROR => 'Transaction not started: nested transaction not supported ' . $methodName, + + DbLoggerEvent::TRANSACTION_COMMIT => 'Commit transaction ' . $methodName, + DbLoggerEvent::TRANSACTION_RELEASE_SAVEPOINT => 'Release savepoint ' . $level . ' ' . $methodName, + DbLoggerEvent::TRANSACTION_COMMIT_NESTED_ERROR => 'Transaction not committed: nested transaction not supported ' . $methodName, + + DbLoggerEvent::TRANSACTION_ROLLBACK => 'Roll back transaction ' . $methodName, + DbLoggerEvent::TRANSACTION_ROLLBACK_SAVEPOINT => 'Roll back to savepoint ' . $level . ' ' . $methodName, + DbLoggerEvent::TRANSACTION_ROLLBACK_NESTED_ERROR => 'Transaction not rolled back: nested transaction not supported ' . $methodName, + + DbLoggerEvent::TRANSACTION_SET_ISOLATION_LEVEL => 'Setting transaction isolation level to ' . $level . ' ' . $methodName, + }; + $this->logger->log($logLevel, $message); + } +} diff --git a/src/Logger/DbLoggerAwareInterface.php b/src/Logger/DbLoggerAwareInterface.php new file mode 100644 index 000000000..17abc8838 --- /dev/null +++ b/src/Logger/DbLoggerAwareInterface.php @@ -0,0 +1,20 @@ +logger = $logger; + } +} diff --git a/src/Logger/DbLoggerEvent.php b/src/Logger/DbLoggerEvent.php new file mode 100644 index 000000000..47d0357bd --- /dev/null +++ b/src/Logger/DbLoggerEvent.php @@ -0,0 +1,27 @@ +