Commit 55db5766 authored by Bertrand Gauthier's avatar Bertrand Gauthier
Browse files

Exécution de script SQL : Amélioration des logs

parent 2a57e214
Pipeline #4826 passed with stage
in 25 seconds
......@@ -31,6 +31,8 @@ class ConsoleController extends AbstractConsoleController
$logFilepath = $this->params('logfile');
$serviceName = "doctrine.connection.$connection";
$request = implode(' ', $this->getRequest()->getContent());
$logger = $this->createLogger();
$logger->info("### Exécution de scripts SQL ###");
......@@ -50,7 +52,7 @@ class ConsoleController extends AbstractConsoleController
if ($result->isSuccess()) {
$logger->info("Exécution terminée avec succès.");
} else {
$logger->info("/!\ UNE ERREUR A ÉTÉ RENCONTRÉE /!\ ");
$logger->info("OUPS, UNE ERREUR EST SURVENUE !");
}
$logger->info("Durée : " . $result->getDurationInSec() . " sec");
......
......@@ -2,7 +2,6 @@
namespace UnicaenApp\Process;
use DateInterval;
use Exception;
use UnicaenApp\Exception\LogicException;
use UnicaenApp\Exception\RuntimeException;
......@@ -13,17 +12,13 @@ use Zend\Log\Writer\Stream;
use Zend\Log\Writer\WriterInterface;
/**
* Classe permettant de représenter le résultat de l'exécution d'un processus inconnu quelconque, çàd :
* - un témoin de réussite ou non
* - des logs (via un 'log writer' ajouté à un logger externe)
* - une exception éventuelle en cas d'erreur.
* - une date de début d'exécution
* - une date de fin d'exécution
* - la calcul de la durée d'exécution
* Classe permettant de représenter le résultat de l'exécution d'un processus inconnu quelconque.
*
* @see ProcessResultInterface
*
* @author Unicaen
*/
abstract class ProcessResult
abstract class ProcessResult implements ProcessResultInterface
{
/**
* @var resource
......
<?php
namespace UnicaenApp\Process;
use Exception;
/**
* Interface décrivant le résultat de l'exécution d'un processus inconnu quelconque, çàd :
* - un témoin de réussite ou non
* - des logs d'exécution
* - une exception éventuelle en cas d'erreur.
* - une date de début d'exécution
* - une date de fin d'exécution
* - le calcul de la durée d'exécution
*
* @author Unicaen
*/
interface ProcessResultInterface
{
/**
* Retourne les logs.
*
* @return string
*/
public function getLog();
/**
* Retourne le booléen indiquant si l'exécution est couronnée de succès ou non.
*
* @return bool
*/
public function isSuccess();
/**
* Positionne le booléen indiquant si l'exécution est couronnée de succès ou non.
*
* @param bool $success
* @return self
*/
public function setIsSuccess($success = true);
/**
* @return float
*/
public function getStartMicrotime();
/**
* @param float|null $startMicrotime
* @return self
*/
public function setStartMicrotime($startMicrotime = null);
/**
* @return float
*/
public function getEndMicrotime();
/**
* @param float|null $endMicrotime
* @return self
*/
public function setEndMicrotime($endMicrotime = null);
/**
* @return float
*/
public function getDurationInSec();
/**
* Retourne l'éventuelle exception rencontrée lors de l'exécution.
*
* @return null|Exception
*/
public function getException();
/**
* Renseigne l'exception rencontrée lors de l'exécution.
*
* @param Exception $exception
* @return self
*/
public function setException(Exception $exception);
}
......@@ -6,13 +6,18 @@ use Doctrine\DBAL\Connection;
use Doctrine\DBAL\DBALException;
use Exception;
use UnicaenApp\Exception\RuntimeException;
use Zend\Log\Logger;
use Zend\Log\LoggerAwareTrait;
use Zend\Stdlib\Glob;
class RunSQLProcess
{
use LoggerAwareTrait;
const QUERIES_SPLIT_PATTERN = "#^/$#m";
const LOG_FILE_EXT = '.log.sql';
const LOG_FILE_EXT_PATTERN = '.log.*.sql';
const LOG_FILE_EXT_TEMPLATE = '.log.%d.sql';
/**
* @var string
*/
......@@ -28,10 +33,15 @@ class RunSQLProcess
*/
private $connection;
/**
* @var string[]
*/
private $queries;
/**
* @var RunSQLQueryStack
*/
private $queryStack;
private $executedQueriesStack;
/**
* @param string $scriptPath
......@@ -74,16 +84,13 @@ class RunSQLProcess
public function executeScript()
{
$this->validateScriptPath();
$queries = $this->extractQueriesFromScript();
$this->extractQueriesFromScript();
$this->logger->info("+ Exécution du script '$this->scriptPath'.");
$this->logger->info(sprintf("'--> Requêtes trouvées : %d", count($queries)));
$result = $this->executeQueries($queries);
$this->logger->info(sprintf("'--> Requêtes trouvées : %d", count($this->queries)));
$logFilePath = $this->logQueries();
$this->logger->info("'--> Logs : " . $logFilePath);
$result = $this->executeQueries();
$this->createLogFile();
return $result;
}
......@@ -98,10 +105,8 @@ class RunSQLProcess
{
$this->logger->info("+ Exécution d'une requête.");
$result = $this->executeQueries([$query]);
$logFilePath = $this->logQueries();
$this->logger->info("'--> Logs : " . $logFilePath);
$this->queries = [$query];
$result = $this->executeQueries();
return $result;
}
......@@ -118,95 +123,139 @@ class RunSQLProcess
/**
* Extrait les requêtes contenues dans le script.
*
* @return string[]
*/
protected function extractQueriesFromScript()
{
$parts = preg_split("#^/$#m", file_get_contents($this->scriptPath));
$parts = preg_split(self::QUERIES_SPLIT_PATTERN, file_get_contents($this->scriptPath));
$queries = array_filter(array_map('trim', $parts));
if (count($queries) === 0) {
throw new RuntimeException("Aucune requête trouvée dans le script '$this->scriptPath'");
}
return $queries;
$this->queries = $queries;
}
/**
* Exécute dans la transaction courante les requêtes spécifiées.
*
* @param string[] $queries
* @return RunSQLResult
*/
private function executeQueries($queries)
private function executeQueries()
{
$result = new RunSQLResult();
$result->attachLogger($this->logger);
$result->setIsSuccess(true);
$this->queryStack = new RunSQLQueryStack();
$this->executedQueriesStack = new RunSQLQueryStack();
try {
foreach ($queries as $q) {
$this->queryStack->startQuery($q);
$this->connection->executeQuery($q);
$this->queryStack->stopQuery();
foreach ($this->queries as $query) {
$this->executedQueriesStack->startQuery($query);
$this->connection->executeQuery($query);
$this->executedQueriesStack->stopQuery();
}
} catch (DBALException $e) {
$result->setIsSuccess(false);
$result->setException($e);
$this->queryStack->stopQueryWithException($e);
$this->executedQueriesStack->stopQueryWithException($e);
}
$result->setEndMicrotime();
$this->logger->info(sprintf("'--> Requêtes exécutées : %d", count($this->executedQueriesStack->getQueries())));
return $result;
}
/**
* @return string
*/
private function logQueries()
private function createLogFile()
{
$logFilePath = $this->generateLogFilePath();
$logger = new Logger();
$logger->addWriter('stream', null, ['stream' => $logFilePath]);
foreach ($this->queryStack->getQueries() as $query) {
$logger->info("Requête : " . $query['sql']);
if (isset($query['exception'])) {
$logger->info("Statut : SUCCÈS");
/** @var Exception $exception */
$exception = $query['exception'];
$logger->info($exception->getMessage());
$logFilePath = $this->computeLogFilePath();
$executedQueries = $this->executedQueriesStack->getQueries();
$remainingQueries = $this->computeRemainingQueries();
$comment = function($line, $with = '--') {
return $with . ' ' . str_replace(PHP_EOL, PHP_EOL . $with . ' ', $line);
};
$lines = [];
$lines[] = "----------------------------------------------------------------------------------------------";
$lines[] = "--";
$lines[] = "-- Log d'exécution du script {$this->scriptPath}.";
$lines[] = "--";
$lines[] = "-- " . date_create()->format('d/m/Y H:m:s');
$lines[] = "--";
$lines[] = "----------------------------------------------------------------------------------------------";
$lines[] = "";
$lines[] = "";
$lines[] = "--------------------- REQUÊTES EXÉCUTÉES ---------------------";
$lines[] = "";
foreach ($executedQueries as $query) {
$hasSucceeded = ! isset($query['exception']);
if ($hasSucceeded) {
$lines[] = $comment($query['sql']);
$lines[] = $comment("/");
$lines[] = $comment("SUCCÈS");
} else {
$logger->info("Statut : ÉCHEC");
$exception = $query['exception']; /** @var Exception $exception */
$lines[] = $comment($query['sql']);
$lines[] = $comment("/");
$lines[] = $comment("ÉCHEC", '------');
$lines[] = $comment("=====", '------');
$lines[] = $comment($exception->getMessage(), '------');
$lines[] = $comment("=====", '------');
}
$logger->info("Temps d'exécution : " . $query['executionMS'] . " sec");
$logger->info("---------------------------------------------------------------------------------------");
$lines[] = $comment($query['executionMS'] . " sec");
$lines[] = "";
}
$lines[] = "";
$lines[] = "";
$lines[] = "--------------------- REQUÊTES RESTANTES ---------------------";
$lines[] = "";
foreach ($remainingQueries as $query) {
$lines[] = $query;
$lines[] = "/";
}
return $logFilePath;
file_put_contents($logFilePath, implode(PHP_EOL, $lines));
$this->logger->info(sprintf("'--> Log script : %s", $logFilePath));
}
/**
* @return string[]
*/
private function computeRemainingQueries()
{
$executedQueries = $this->executedQueriesStack->getQueries();
$offset = count($executedQueries);
if ($this->executedQueriesStack->lastQueryHasException()) {
$offset--; // si la dernière requête a échouée, on la remet quand même dans la liste des requêtes restantes
}
return array_slice($this->queries, $offset, null, true);
}
/**
* @return string
*/
private function generateLogFilePath()
private function computeLogFilePath()
{
if ($this->logFilePath !== null) {
return $this->logFilePath;
}
if ($this->scriptPath) {
$this->logFilePath = sys_get_temp_dir() . '/' . basename($this->scriptPath) . '.log';
} else {
$this->logFilePath = sys_get_temp_dir() . '/' . uniqid('unicaen-app-run-sql-') . '.log';
}
$dir = sys_get_temp_dir();
$scriptName = basename($this->scriptPath);
$filepathPattern = $dir . '/' . $scriptName . self::LOG_FILE_EXT_PATTERN;
$filepathTemplate = $dir . '/' . $scriptName . self::LOG_FILE_EXT_TEMPLATE;
$existingFiles = Glob::glob($filepathPattern);
return $this->logFilePath;
return sprintf($filepathTemplate, count($existingFiles) + 1);
}
}
\ No newline at end of file
......@@ -19,9 +19,17 @@ class RunSQLQueryStack extends DebugStack
/**
* @return array
*/
public function getQueries(): array
public function getQueries()
{
return $this->queries;
}
/**
* @return bool
*/
public function lastQueryHasException()
{
return isset($this->queries[$this->currentQuery]['exception']);
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment