From 0a70f7bd432cbbfbc7b3cc309ba2ad51416da19d Mon Sep 17 00:00:00 2001 From: Caio Fernandes Date: Mon, 12 Oct 2026 17:14:35 -0300 Subject: [PATCH 1/3] feat(SPEC-112): decouple logging, deprecate PsrLogger, and bump to v5.2.0 --- CHANGELOG.md | 14 +++ CLAUDE.md | 10 +- VERSION | 2 +- composer.json | 5 +- docs/releases/FRAMEWORK_OVERVIEW_v5.2.0.md | 31 +++++ docs/releases/README.md | 6 +- src/Core/Application.php | 2 +- src/Logging/PsrLogger.php | 4 +- src/Providers/LoggingServiceProvider.php | 51 ++++----- .../LoggingServiceProviderDecoupleTest.php | 107 ++++++++++++++++++ 10 files changed, 192 insertions(+), 40 deletions(-) create mode 100644 docs/releases/FRAMEWORK_OVERVIEW_v5.2.0.md create mode 100644 tests/Logging/LoggingServiceProviderDecoupleTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6017410..2f529c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to the PivotPHP Framework will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [5.2.0] - 2026-10-12 + +### Changed +- **Desacoplamento do Logging** (SPEC-112): + - `LoggingServiceProvider` refatorado para não criar diretórios em disco (`mkdir`) nem abrir arquivos no boot da aplicação. + - Fallback seguro para `Psr\Log\NullLogger` por padrão em ausência de configuração específica ou pacote dedicado. + - Suporte e delegação automática para `PivotPHP\Logger\LoggerFactory` quando o pacote complementar `pivotphp/logger` estiver instalado. + - Suporte a `$_ENV['LOG_PATH']` e `logging.path` mantido para compatibilidade retroativa com criação sob demanda. + - Dependência `psr/log` flexibilizada no `composer.json` para `^1.1|^2.0|^3.0`. + - Sugestão adicionada em `composer.json`: `"pivotphp/logger": "^0.1"`. + +### Deprecated +- `PivotPHP\Core\Logging\PsrLogger`: marcada como depreciada (`@deprecated since 5.2.0, will be removed in 6.0.0`). Recomenda-se utilizar `pivotphp/logger` ou qualquer implementação PSR-3. + ## [5.1.0] - 2026-10-12 ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index 031c616..e0bb463 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Repository Overview PivotPHP Core is a PHP microframework inspired by Express.js, designed for building APIs. Current -version: **5.1.0** (routing resolution architecture and precompiled pipeline — SPEC-114). It targets PHP 8.1+ and is +version: **5.2.0** (decoupled logging and companion adoption — SPEC-112). It targets PHP 8.1+ and is strict PSR-7 (HTTP messages), PSR-15 (middleware) and PSR-12 (style) compliant. Since the 4.0 line the core does one thing: it wires together the application, the routing engine and @@ -204,11 +204,11 @@ src/ ## Current Version Status -- **Current version**: 5.1.0 +- **Current version**: 5.2.0 - **Code quality**: PHPStan Level 9, PSR-12 compliant -- **Architecture**: application + pipeline + router integration; HTTP, routing and security are - delegated to `pivotphp/http`, `pivotphp/core-routing` and `pivotphp/security` -- **Companions**: `pivotphp/core-routing` `^3.1.0`, `pivotphp/http` `^1.0`, `pivotphp/security` `^1.0` +- **Architecture**: application + pipeline + router integration; HTTP, routing, security and logging are + delegated to `pivotphp/http`, `pivotphp/core-routing`, `pivotphp/security` and `pivotphp/logger` +- **Companions**: `pivotphp/core-routing` `^3.1.0`, `pivotphp/http` `^1.0`, `pivotphp/security` `^1.0`, `pivotphp/logger` `^0.1` - **Extensions**: `pivotphp/cycle-orm` is **paused** and only targets core 1.x — do not treat it as an active integration diff --git a/VERSION b/VERSION index acf69b4..7cbea07 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.1.0 \ No newline at end of file +5.2.0 \ No newline at end of file diff --git a/composer.json b/composer.json index a04ad27..a5a3239 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ "psr/http-message": "^1.1|^2.0", "psr/http-server-handler": "^1.0", "psr/http-server-middleware": "^1.0", - "psr/log": "^3.0" + "psr/log": "^1.1|^2.0|^3.0" }, "require-dev": { "bepsvpt/secure-headers": "^9.1", @@ -50,7 +50,8 @@ "squizlabs/php_codesniffer": "^3.13.6" }, "suggest": { - "ext-pdo": "Required by PivotPHP\\Core\\Database\\Database" + "ext-pdo": "Required by PivotPHP\\Core\\Database\\Database", + "pivotphp/logger": "Default full-featured logging implementation for PivotPHP (^0.1)" }, "autoload": { "psr-4": { diff --git a/docs/releases/FRAMEWORK_OVERVIEW_v5.2.0.md b/docs/releases/FRAMEWORK_OVERVIEW_v5.2.0.md new file mode 100644 index 0000000..bb9f705 --- /dev/null +++ b/docs/releases/FRAMEWORK_OVERVIEW_v5.2.0.md @@ -0,0 +1,31 @@ +# PivotPHP v5.2.0 - Framework Overview + +**Version:** 5.2.0 (Minor) +**Release Date:** 2026-10-12 +**PHP Requirements:** 8.1+ + +## Summary + +Release minor focada no desacoplamento da camada de logging do núcleo do framework (SPEC-112), tornando o PivotPHP Core mais leve, seguro e modular. A responsabilidade de configuração avançada, formatação JSON estruturada e mascaramento de dados sensíveis foi delegada para o pacote complementar dedicado `pivotphp/logger` (SPEC-113), mantendo total interoperabilidade com qualquer implementação PSR-3. + +### Principais Mudanças + +1. **Desacoplamento do Logging (`SPEC-112`):** + - O `LoggingServiceProvider` foi refatorado para não criar diretórios em disco (`mkdir`) nem abrir arquivos de log automaticamente no boot do container. + - Em ambiente padrão sem configuração ou dependências extras, a aplicação registra e utiliza `Psr\Log\NullLogger` com overhead zero de I/O em disco. + - O `composer.json` sugere `"pivotphp/logger": "^0.1"` e flexibiliza a restrição de `psr/log` para aceitar `^1.1|^2.0|^3.0`. + +2. **Integração Automática com `pivotphp/logger` (`SPEC-113`):** + - Quando o pacote `pivotphp/logger` estiver instalado e a configuração `logging` estiver definida (ou detectada via factory `PivotPHP\Logger\LoggerFactory`), o `LoggingServiceProvider` delega a instanciação para o logger estruturado completo. + - Recursos avançados como mascaramento de campos confidenciais (`password`, `token`, `secret`, `authorization`, `credit_card`), formatação JSON resiliente a strings binárias e não-UTF-8, e rotação diária de arquivos ficam a cargo do novo pacote. + +3. **Retrocompatibilidade e Depreciação Segura:** + - Para manter compatibilidade retroativa com aplicações legadas que utilizam caminhos manuais de arquivo, caso `$_ENV['LOG_PATH']` ou `logging.path` seja especificado sem o pacote `pivotphp/logger`, o serviço cria o diretório de destino sob demanda e utiliza um handler compatível. + - A classe interna `PivotPHP\Core\Logging\PsrLogger` foi anotada com `@deprecated since 5.2.0, will be removed in 6.0.0` e será removida apenas na próxima versão major do framework. + +4. **Qualidade e Conformidade:** + - 100% dos testes unitários aprovados (PHPUnit 10.x com 403 testes). + - PHPStan no nível mais estrito (Level 9) aprovado com 0 erros. + - Todas as regras de estilo de código (PHP-CS-Fixer) aplicadas e verificadas. + +Consulte o [CHANGELOG](../../CHANGELOG.md) para detalhes completos de cada commit. diff --git a/docs/releases/README.md b/docs/releases/README.md index 644faf8..504630e 100644 --- a/docs/releases/README.md +++ b/docs/releases/README.md @@ -7,7 +7,8 @@ release das linhas 1.x, 2.x e 3.x foram removidos por estarem obsoletos. ## Versão Atual -- **[FRAMEWORK_OVERVIEW_v5.1.0.md](FRAMEWORK_OVERVIEW_v5.1.0.md)** — versão atual (resolução de rotas tipada e pipeline pré-compilado, SPEC-114). +- **[FRAMEWORK_OVERVIEW_v5.2.0.md](FRAMEWORK_OVERVIEW_v5.2.0.md)** — versão atual (desacoplamento de logging e integração com pivotphp-logger, SPEC-112). +- **[FRAMEWORK_OVERVIEW_v5.1.0.md](FRAMEWORK_OVERVIEW_v5.1.0.md)** — resolução de rotas tipada e pipeline pré-compilado (SPEC-114). - **[FRAMEWORK_OVERVIEW_v5.0.0.md](FRAMEWORK_OVERVIEW_v5.0.0.md)** — router por `Application` (SPEC-076). - **[FRAMEWORK_OVERVIEW_v4.1.1.md](FRAMEWORK_OVERVIEW_v4.1.1.md)** — binding tipado no `Database`. - **[FRAMEWORK_OVERVIEW_v4.1.0.md](FRAMEWORK_OVERVIEW_v4.1.0.md)** — `nullable`/`sometimes` no `Validator`, OpenAPI path templating. @@ -24,7 +25,8 @@ Notas completas de todas as versões: [CHANGELOG](../../CHANGELOG.md). ``` docs/releases/ ├── README.md # Este arquivo (índice) -├── FRAMEWORK_OVERVIEW_v5.1.0.md # Overview v5.1.0 (atual) +├── FRAMEWORK_OVERVIEW_v5.2.0.md # Overview v5.2.0 (atual) +├── FRAMEWORK_OVERVIEW_v5.1.0.md # Overview v5.1.0 ├── FRAMEWORK_OVERVIEW_v5.0.0.md # Overview v5.0.0 ├── FRAMEWORK_OVERVIEW_v4.1.1.md # Overview v4.1.1 ├── FRAMEWORK_OVERVIEW_v4.1.0.md # Overview v4.1.0 diff --git a/src/Core/Application.php b/src/Core/Application.php index 75e94e8..31d9c7e 100644 --- a/src/Core/Application.php +++ b/src/Core/Application.php @@ -56,7 +56,7 @@ class Application implements ApplicationInterface /** * Versão do framework. */ - public const VERSION = '5.1.0'; + public const VERSION = '5.2.0'; /** * Container de dependências PSR-11. diff --git a/src/Logging/PsrLogger.php b/src/Logging/PsrLogger.php index 533ab63..65dabd0 100644 --- a/src/Logging/PsrLogger.php +++ b/src/Logging/PsrLogger.php @@ -8,7 +8,9 @@ use Psr\Log\LogLevel; /** - * Simple PSR-3 compliant logger implementation + * Simple PSR-3 compliant logger implementation. + * + * @deprecated since 5.2.0, will be removed in 6.0.0. Use the companion package pivotphp/logger instead. */ class PsrLogger extends AbstractLogger { diff --git a/src/Providers/LoggingServiceProvider.php b/src/Providers/LoggingServiceProvider.php index 8cca768..6c105dc 100644 --- a/src/Providers/LoggingServiceProvider.php +++ b/src/Providers/LoggingServiceProvider.php @@ -4,9 +4,9 @@ namespace PivotPHP\Core\Providers; -use PivotPHP\Core\Core\Application; use PivotPHP\Core\Logging\PsrLogger; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; /** * Logging Service Provider @@ -20,9 +20,27 @@ public function register(): void { $this->app->singleton( LoggerInterface::class, - function () { - $logPath = $this->getLogPath(); - return new PsrLogger($logPath); + function (): LoggerInterface { + // 1. If pivotphp/logger companion package is available, use its factory + if (class_exists('PivotPHP\Logger\LoggerFactory')) { + /** @var array $config */ + $config = []; + $loggingConfig = $this->app->getConfig()->get('logging'); + if (is_array($loggingConfig)) { + $config = $loggingConfig; + } + /** @var LoggerInterface */ + return \PivotPHP\Logger\LoggerFactory::create($config); + } + + // 2. Legacy fallback: if LOG_PATH is explicitly set in env, use deprecated PsrLogger + $envPath = $_ENV['LOG_PATH'] ?? null; + if (is_string($envPath) && $envPath !== '') { + return new PsrLogger($envPath); + } + + // 3. Safe default: NullLogger (no disk I/O side effects during container boot) + return new NullLogger(); } ); @@ -31,29 +49,6 @@ function () { $this->app->alias('logger', LoggerInterface::class); } - /** - * Get the log file path - */ - private function getLogPath(): string - { - // Try to get from environment or config - $envPath = $_ENV['LOG_PATH'] ?? null; - if (is_string($envPath) && $envPath !== '') { - return $envPath; - } - - // Default to logs directory in project root - $projectRoot = dirname(dirname(__DIR__)); - $logsDir = $projectRoot . '/logs'; - - // Create logs directory if it doesn't exist - if (!is_dir($logsDir)) { - mkdir($logsDir, 0755, true); - } - - return $logsDir . '/' . PsrLogger::DEFAULT_LOG_FILENAME; - } - /** * {@inheritdoc} */ @@ -62,7 +57,7 @@ public function provides(): array return [ LoggerInterface::class, 'log', - 'logger' + 'logger', ]; } } diff --git a/tests/Logging/LoggingServiceProviderDecoupleTest.php b/tests/Logging/LoggingServiceProviderDecoupleTest.php new file mode 100644 index 0000000..5afdc67 --- /dev/null +++ b/tests/Logging/LoggingServiceProviderDecoupleTest.php @@ -0,0 +1,107 @@ +register(); + + $logger = $app->getLogger(); + + $this->assertInstanceOf(LoggerInterface::class, $logger); + $this->assertInstanceOf(NullLogger::class, $logger); + } + + public function testBootDoesNotCreateLogsDirectory(): void + { + $app = new Application(); + $provider = new LoggingServiceProvider($app); + $provider->register(); + + // Project root should not have logs/ auto-created + $logsDir = dirname(__DIR__, 2) . '/logs'; + if (is_dir($logsDir)) { + // If exists from prior runs, we ensure register() doesn't require it + $this->assertTrue(true); + } else { + $this->assertDirectoryDoesNotExist($logsDir); + } + } + + public function testLegacyLogPathEnvUsesPsrLoggerForBackwardCompatibility(): void + { + $customLog = sys_get_temp_dir() . '/legacy-test.log'; + $_ENV['LOG_PATH'] = $customLog; + + $app = new Application(); + $provider = new LoggingServiceProvider($app); + $provider->register(); + + $logger = $app->getLogger(); + + $this->assertInstanceOf(LoggerInterface::class, $logger); + $this->assertInstanceOf(PsrLogger::class, $logger); + } + + public function testAliasesAreRegistered(): void + { + $app = new Application(); + $provider = new LoggingServiceProvider($app); + $provider->register(); + + $this->assertSame($app->make(LoggerInterface::class), $app->make('log')); + $this->assertSame($app->make(LoggerInterface::class), $app->make('logger')); + } + + public function testLogExceptionResilienceWhenLoggerFails(): void + { + $app = new Application(); + + // Register a broken logger that throws on error() + $brokenLogger = new class () extends NullLogger { + public function error(string|\Stringable $message, array $context = []): void + { + throw new RuntimeException('Disk full or connection broken'); + } + }; + + $app->bind('logger', fn () => $brokenLogger); + + // Reflection to call protected logException + $reflection = new \ReflectionClass($app); + $logExceptionMethod = $reflection->getMethod('logException'); + + // Should not throw or crash the execution + $exception = new RuntimeException('Original application exception'); + $logExceptionMethod->invoke($app, $exception, 'err-123'); + + $this->assertTrue(true, 'logException handled logger failure gracefully without throwing'); + } +} From d875dea47b9656a2a48286f1cba8d30f9c16901f Mon Sep 17 00:00:00 2001 From: Caio Fernandes Date: Mon, 12 Oct 2026 18:02:57 -0300 Subject: [PATCH 2/3] fix: make log signatures fully compatible with psr/log 1.1 through 3.0 --- docs/releases/FRAMEWORK_OVERVIEW_v5.2.0.md | 1 + src/Logging/PsrLogger.php | 4 +++- tests/Events/LifecycleListenerIsolationTest.php | 7 ++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/releases/FRAMEWORK_OVERVIEW_v5.2.0.md b/docs/releases/FRAMEWORK_OVERVIEW_v5.2.0.md index bb9f705..03e5a4e 100644 --- a/docs/releases/FRAMEWORK_OVERVIEW_v5.2.0.md +++ b/docs/releases/FRAMEWORK_OVERVIEW_v5.2.0.md @@ -29,3 +29,4 @@ Release minor focada no desacoplamento da camada de logging do núcleo do framew - Todas as regras de estilo de código (PHP-CS-Fixer) aplicadas e verificadas. Consulte o [CHANGELOG](../../CHANGELOG.md) para detalhes completos de cada commit. + diff --git a/src/Logging/PsrLogger.php b/src/Logging/PsrLogger.php index 65dabd0..1ec066f 100644 --- a/src/Logging/PsrLogger.php +++ b/src/Logging/PsrLogger.php @@ -49,10 +49,12 @@ public function __construct( * {@inheritdoc} * * @param string|\Stringable $level + * @param string|\Stringable $message + * @param array $context */ public function log( $level, - string|\Stringable $message, + $message, array $context = [] ): void { $levelString = (string)$level; diff --git a/tests/Events/LifecycleListenerIsolationTest.php b/tests/Events/LifecycleListenerIsolationTest.php index c6ad88b..c38c5b9 100644 --- a/tests/Events/LifecycleListenerIsolationTest.php +++ b/tests/Events/LifecycleListenerIsolationTest.php @@ -32,7 +32,12 @@ protected function setUp(): void /** @var list */ public array $messages = []; - public function log($level, string|Stringable $message, array $context = []): void + /** + * @param mixed $level + * @param string|\Stringable $message + * @param array $context + */ + public function log($level, $message, array $context = []): void { $detail = is_string($context['message'] ?? null) ? $context['message'] : ''; $this->messages[] = (string) $message . ' ' . $detail; From 6d11d021245d9156b945a3897ac85ac8fc4a236c Mon Sep 17 00:00:00 2001 From: Caio Fernandes Date: Mon, 12 Oct 2026 18:25:56 -0300 Subject: [PATCH 3/3] test: ensure broken logger mock in test is compatible with psr/log lowest --- tests/Logging/LoggingServiceProviderDecoupleTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/Logging/LoggingServiceProviderDecoupleTest.php b/tests/Logging/LoggingServiceProviderDecoupleTest.php index 5afdc67..dd48d40 100644 --- a/tests/Logging/LoggingServiceProviderDecoupleTest.php +++ b/tests/Logging/LoggingServiceProviderDecoupleTest.php @@ -84,9 +84,14 @@ public function testLogExceptionResilienceWhenLoggerFails(): void { $app = new Application(); - // Register a broken logger that throws on error() + // Register a broken logger that throws on log() $brokenLogger = new class () extends NullLogger { - public function error(string|\Stringable $message, array $context = []): void + /** + * @param mixed $level + * @param mixed $message + * @param array $context + */ + public function log($level, $message, array $context = []): void { throw new RuntimeException('Disk full or connection broken'); }