diff --git a/composer.json b/composer.json index 280cf44..7cbe430 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,7 @@ "scripts": { "test": "phpunit" }, - "version": "2.1.5", + "version": "2.1.6", "config": { "platform": { "php": "8.0" diff --git a/composer.lock b/composer.lock index adfacbb..41227c1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fb9ffcd7a86a1d38213bc2bfc74faeac", + "content-hash": "0db31b7e94eb7235da8ff4e6138907b2", "packages": [ { "name": "brick/math", diff --git a/src/Config/BaseConfig.php b/src/Config/BaseConfig.php index 9dd1140..97410c4 100644 --- a/src/Config/BaseConfig.php +++ b/src/Config/BaseConfig.php @@ -24,7 +24,6 @@ public function __construct(string $reporterName, LoggerInterface $logger) } } $this->logger = $logger; - } public function getReporterName(): string diff --git a/src/Config/ConfigLoader.php b/src/Config/ConfigLoader.php index fc5b116..553967b 100644 --- a/src/Config/ConfigLoader.php +++ b/src/Config/ConfigLoader.php @@ -60,6 +60,7 @@ private function loadFromJsonFile(): QaseConfig if (isset($data['environment'])) $config->setEnvironment($data['environment']); if (isset($data['rootSuite'])) $config->setRootSuite($data['rootSuite']); if (isset($data['debug'])) $config->setDebug($data['debug']); + if (isset($data['logging'])) $config->setLogging($data['logging']); if (isset($data['statusMapping'])) { $statusMapping = new StatusMapping(new \Qase\PhpCommons\Loggers\Logger(false)); @@ -131,6 +132,12 @@ private function overrideWithEnvVariables(): void case "qase_debug": $this->config->setDebug($value); break; + case "qase_logging_console": + $this->parseLoggingConsole($value); + break; + case "qase_logging_file": + $this->parseLoggingFile($value); + break; case "qase_status_mapping": $this->parseStatusMapping($value); break; @@ -294,6 +301,30 @@ private function parseExternalLinkUrl(string $value): void } } + /** + * Parse logging console setting from environment variable + * + * @param string $value Console logging setting (true/false) + */ + private function parseLoggingConsole(string $value): void + { + $logging = $this->config->getLogging() ?? []; + $logging['console'] = filter_var($value, FILTER_VALIDATE_BOOLEAN); + $this->config->setLogging($logging); + } + + /** + * Parse logging file setting from environment variable + * + * @param string $value File logging setting (true/false) + */ + private function parseLoggingFile(string $value): void + { + $logging = $this->config->getLogging() ?? []; + $logging['file'] = filter_var($value, FILTER_VALIDATE_BOOLEAN); + $this->config->setLogging($logging); + } + /** * Parse status mapping from environment variable * Format: "invalid=failed,skipped=passed" diff --git a/src/Loggers/Logger.php b/src/Loggers/Logger.php index 96b6126..7fb6d1e 100644 --- a/src/Loggers/Logger.php +++ b/src/Loggers/Logger.php @@ -16,21 +16,38 @@ class Logger implements LoggerInterface 'RESET' => "\033[0m", // Reset color ]; private bool $debug; + private bool $consoleEnabled; + private bool $fileEnabled; private string $logFilePath; - public function __construct(bool $debug = false) + public function __construct(bool $debug = false, ?array $loggingConfig = null) { $this->debug = $debug; $this->logFilePath = getcwd() . '/logs/log_' . date('Y-m-d') . '.log'; - if (!is_dir(getcwd() . '/logs')) { + // Set default logging configuration + $this->consoleEnabled = true; + $this->fileEnabled = true; + + // Override with config if provided + if ($loggingConfig !== null) { + if (isset($loggingConfig['console'])) { + $this->consoleEnabled = (bool)$loggingConfig['console']; + } + if (isset($loggingConfig['file'])) { + $this->fileEnabled = (bool)$loggingConfig['file']; + } + } + + // Create logs directory if file logging is enabled + if ($this->fileEnabled && !is_dir(getcwd() . '/logs')) { mkdir(getcwd() . '/logs', 0777, true); } } public function info(string $message): void { - $this->log($message, 'INFO'); + $this->writeLog($message, 'INFO'); } public function debug(string $message): void @@ -39,24 +56,31 @@ public function debug(string $message): void return; } - $this->log($message, 'DEBUG'); + $this->writeLog($message, 'DEBUG'); } public function error(string $message): void { - $this->log($message, 'ERROR'); + $this->writeLog($message, 'ERROR'); } - private function log(string $message, string $level): void - { - $color = self::LEVEL_COLORS[$level] ?? self::LEVEL_COLORS['RESET']; - $reset = self::LEVEL_COLORS['RESET']; + private function writeLog(string $message, string $level): void + { $timestamp = date('Y-m-d H:i:s'); - $formattedMessage = sprintf("%s %s%s %s %s%s\n", $color, self::PREFIX, $timestamp, $level, $message, $reset); + $logEntry = $timestamp . ' ' . $level . ' ' . $message . PHP_EOL; - echo $formattedMessage; + // Console output + if ($this->consoleEnabled) { + $color = self::LEVEL_COLORS[$level] ?? self::LEVEL_COLORS['RESET']; + $reset = self::LEVEL_COLORS['RESET']; + $formattedMessage = sprintf("%s %s%s %s %s%s\n", $color, self::PREFIX, $timestamp, $level, $message, $reset); + echo $formattedMessage; + } - file_put_contents($this->logFilePath, $timestamp . ' ' . $level . ' ' . $message . PHP_EOL, FILE_APPEND); + // File output + if ($this->fileEnabled) { + file_put_contents($this->logFilePath, $logEntry, FILE_APPEND); + } } } diff --git a/src/Models/Config/QaseConfig.php b/src/Models/Config/QaseConfig.php index 203c71e..318db91 100644 --- a/src/Models/Config/QaseConfig.php +++ b/src/Models/Config/QaseConfig.php @@ -13,6 +13,7 @@ class QaseConfig public ?string $rootSuite = null; public bool $debug; public array $statusMapping = []; + public ?array $logging = null; public TestopsConfig $testops; public ReportConfig $report; @@ -21,6 +22,7 @@ public function __construct() $this->mode = Mode::OFF; $this->fallback = Mode::OFF; $this->debug = false; + $this->logging = null; $this->testops = new TestopsConfig(); $this->report = new ReportConfig(); } @@ -98,4 +100,24 @@ public function setStatusMapping(array $statusMapping): void { $this->statusMapping = $statusMapping; } + + /** + * Get logging configuration + * + * @return array|null + */ + public function getLogging(): ?array + { + return $this->logging; + } + + /** + * Set logging configuration + * + * @param array|null $logging + */ + public function setLogging(?array $logging): void + { + $this->logging = $logging; + } } diff --git a/src/Reporters/ReporterFactory.php b/src/Reporters/ReporterFactory.php index 1bf5357..95f099e 100644 --- a/src/Reporters/ReporterFactory.php +++ b/src/Reporters/ReporterFactory.php @@ -22,7 +22,7 @@ public static function create(String $framework = "", String $reporterName = "") { $configLoader = new ConfigLoader(new Logger(true)); $config = $configLoader->getConfig(); - $logger = new Logger($config->getDebug()); + $logger = new Logger($config->getDebug(), $config->getLogging()); $hostInfo = new HostInfo(); $hostData = $hostInfo->getHostInfo($framework, $reporterName); $logger->debug("Host data: " . json_encode($hostData)); @@ -55,6 +55,6 @@ private static function createInternalReporter(LoggerInterface $logger, QaseConf private static function prepareTestopsReporter(LoggerInterface $logger, QaseConfig $config, StateInterface $state): InternalReporterInterface { $client = new ApiClientV2($logger, $config->testops); - return new TestOpsReporter($client, $config, $state); + return new TestOpsReporter($client, $config, $state, $logger); } } diff --git a/src/Reporters/TestOpsReporter.php b/src/Reporters/TestOpsReporter.php index 943ad3e..7329332 100644 --- a/src/Reporters/TestOpsReporter.php +++ b/src/Reporters/TestOpsReporter.php @@ -7,6 +7,7 @@ use Exception; use Qase\PhpCommons\Interfaces\ClientInterface; use Qase\PhpCommons\Interfaces\InternalReporterInterface; +use Qase\PhpCommons\Interfaces\LoggerInterface; use Qase\PhpCommons\Interfaces\StateInterface; use Qase\PhpCommons\Models\Config\QaseConfig; @@ -16,14 +17,16 @@ class TestOpsReporter implements InternalReporterInterface private ClientInterface $client; private QaseConfig $config; private StateInterface $state; + private LoggerInterface $logger; private ?int $runId = null; private ?array $cachedConfigurationGroups = null; - public function __construct(ClientInterface $client, QaseConfig $config, StateInterface $state) + public function __construct(ClientInterface $client, QaseConfig $config, StateInterface $state, LoggerInterface $logger) { $this->client = $client; $this->config = $config; $this->state = $state; + $this->logger = $logger; } /** @@ -293,8 +296,8 @@ private function updateExternalIssue(int $runId): void ] ); } catch (Exception $e) { - // Log error through the client's logger - error_log('Failed to update external issue: ' . $e->getMessage()); + // Log error through the centralized logger + $this->logger->error('Failed to update external issue: ' . $e->getMessage()); } } } diff --git a/tests/TestOpsReporterTest.php b/tests/TestOpsReporterTest.php index 0289592..cab548e 100644 --- a/tests/TestOpsReporterTest.php +++ b/tests/TestOpsReporterTest.php @@ -5,6 +5,7 @@ use Exception; use PHPUnit\Framework\TestCase; use Qase\PhpCommons\Interfaces\ClientInterface; +use Qase\PhpCommons\Interfaces\LoggerInterface; use Qase\PhpCommons\Interfaces\StateInterface; use Qase\PhpCommons\Models\Config\QaseConfig; use Qase\PhpCommons\Reporters\TestOpsReporter; @@ -14,12 +15,14 @@ class TestOpsReporterTest extends TestCase { private $clientMock; private $stateMock; + private $loggerMock; private QaseConfig $config; protected function setUp(): void { $this->clientMock = $this->createMock(ClientInterface::class); $this->stateMock = $this->createMock(StateInterface::class); + $this->loggerMock = $this->createMock(LoggerInterface::class); $this->config = $this->getConfig(); } @@ -30,7 +33,7 @@ public function testStartRunCreatesNewRunIfRunIdIsNull(): void $this->stateMock->method('startRun') ->willReturn(123); - $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock); + $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock, $this->loggerMock); $reporter->startRun(); $this->assertSame(123, $this->getPrivateProperty($reporter, 'runId')); @@ -47,7 +50,7 @@ public function testStartRunThrowsExceptionIfRunNotFound(): void $this->expectException(Exception::class); $this->expectExceptionMessage('Run with id 123 not found'); - $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock); + $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock, $this->loggerMock); $reporter->startRun(); } @@ -59,7 +62,7 @@ public function testStartRunUsesExistingRunId(): void ->with('TEST_PROJECT', 123) ->willReturn(true); - $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock); + $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock, $this->loggerMock); $reporter->startRun(); $this->assertSame(123, $this->getPrivateProperty($reporter, 'runId')); @@ -73,7 +76,7 @@ public function testSendResultsClearsResults(): void ->method('sendResults') ->with('TEST_PROJECT', 123, ['result1', 'result2']); - $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock); + $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock, $this->loggerMock); $reporter->startRun(); $reporter->addResult('result1'); @@ -91,7 +94,7 @@ public function testAddResultSendsResultsWhenBatchSizeIsReached(): void ->method('sendResults') ->with('TEST_PROJECT', 123, ['result1', 'result2']); - $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock); + $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock, $this->loggerMock); $reporter->startRun(); $reporter->addResult('result1'); @@ -116,7 +119,7 @@ public function testCompleteRunSendsRemainingResults(): void return is_callable($callback); })); - $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock); + $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock, $this->loggerMock); $reporter->startRun(); $reporter->addResult('result1'); @@ -141,7 +144,7 @@ public function testCompleteRunWithNoResultsDoesNothing(): void ->method('completeRun') ->with($this->isType('callable')); - $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock); + $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock, $this->loggerMock); $reporter->startRun(); $reporter->completeRun(); } @@ -155,7 +158,7 @@ public function testCompleteRunWithCompleteIsFalseDoesNotCompleteTestRun(): void $this->clientMock->expects($this->never()) ->method('completeTestRun'); - $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock); + $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock, $this->loggerMock); $reporter->startRun(); $reporter->completeRun(); } @@ -174,7 +177,7 @@ public function testAddResultFiltersOutExcludedStatuses(): void $this->createResultWithStatus('failed') ]); - $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock); + $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock, $this->loggerMock); $reporter->startRun(); // Add results with different statuses @@ -206,7 +209,7 @@ public function testAddResultIncludesAllResultsWhenNoFilterConfigured(): void ]] ); - $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock); + $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock, $this->loggerMock); $reporter->startRun(); // Add results with different statuses - all should be included @@ -230,7 +233,7 @@ public function testAddResultHandlesResultsWithoutStatus(): void $this->createResultWithStatus('passed') ]); - $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock); + $reporter = new TestOpsReporter($this->clientMock, $this->config, $this->stateMock, $this->loggerMock); $reporter->startRun(); // Add results - one without status should be included