feat: Add configurable logging with channel and level filtering#14
Conversation
The package can now be installed and tested without PHP or Composer on the host, matching the setup used by sibling packages. Common workflows are exposed as make targets.
Discovery logging now goes through an injected logger with a configurable channel and minimum level (warning by default), so consuming applications no longer get debug noise in their logs and can route or silence plugin engine output independently of application logging. A missing plugins directory now means no plugins: it is treated as a normal state logged at debug instead of producing a warning on every request, and a discovery scan emits a single summary line instead of one line per directory entry. The configured plugins path is now honored instead of being hardcoded.
Discovery results can now be compiled to a cache file that is loaded on boot, removing the filesystem scan and manifest parsing from every request. Deploys opt in the same way they cache configuration and routes, and clearing the cache returns to live discovery. Enabling, disabling, or rediscovering plugins refreshes an existing cache so it never goes stale.
Code Review SummaryThis PR introduces configurable logging with channel/level filtering and a plugin discovery cache system. It also provides a robust Docker-based development environment. 🚀 Key Improvements
💡 Minor Suggestions
|
| public function log($level, string|\Stringable $message, array $context = []): void | ||
| { | ||
| // Unknown levels pass through so the inner logger can decide how to handle them | ||
| $severity = static::SEVERITY[strtolower((string) $level)] ?? PHP_INT_MAX; |
There was a problem hiding this comment.
When a log level is unknown, defaulting to PHP_INT_MAX ensures it is always logged. This is safe, but it might be clearer to explicitly define a fallback behavior in the class documentation.
| $severity = static::SEVERITY[strtolower((string) $level)] ?? PHP_INT_MAX; | |
| $severity = static::SEVERITY[strtolower((string) $level)] ?? 999; // Ensure unknown levels always pass through |
| $path = $this->getCachedPluginsPath(); | ||
|
|
||
| File::ensureDirectoryExists(dirname($path)); | ||
| File::put($path, '<?php return '.var_export($plugins, true).';'.PHP_EOL); |
There was a problem hiding this comment.
Using var_export is standard for Laravel caching, but for improved performance and security in production environments, consider using OPcache friendly exports or JSON if the structure becomes deeply nested, though array export is perfectly acceptable here for configuration-like data.
| File::put($path, '<?php return '.var_export($plugins, true).';'.PHP_EOL); | |
| File::put($path, '<?php return '.var_export($plugins, true).';'); |
These files predate the linter check and were failing it, blocking CI on any branch. No behavior change.
|
|
||
| public function handle() | ||
| { | ||
| $this->pluginManager->clearCachedPlugins(); |
There was a problem hiding this comment.
The cache generation logic does not handle potential filesystem errors (like permission denied) which could lead to a 'Plugins cached successfully' message even if the File::put fails.
| $this->pluginManager->clearCachedPlugins(); | |
| try { | |
| $this->pluginManager->clearCachedPlugins(); | |
| $path = $this->pluginManager->cachePlugins(); | |
| $this->info("Plugins cached successfully at [{$path}]."); | |
| } catch (\Exception $e) { | |
| $this->error("Failed to cache plugins: {$e->getMessage()}"); | |
| return 1; | |
| } |
| $this->pluginsPath = $app['config']->get('plugins.path') ?? base_path('plugins'); | ||
| $this->classLoader = require base_path('vendor/autoload.php'); | ||
| $this->output = $output ?? new OutputStyle(new ArrayInput([]), new NullOutput); | ||
| $this->logger = $logger ?? ($app->bound('log') ? $app->make('log') : new NullLogger); |
There was a problem hiding this comment.
While Laravel 11 uses PHP 8.2+, explicitly checking bound('log') is safe, but using the Application contract's make method with a fallback is cleaner. Additionally, if the app is bound, it's better to resolve the default logger through the container to ensure the LevelFilteringLogger or application default is used.
| $this->logger = $logger ?? ($app->bound('log') ? $app->make('log') : new NullLogger); | |
| $this->logger = $logger ?? ($app->has('log') ? $app->make('log') : new NullLogger()); |
| FROM php:8.3-cli | ||
|
|
||
| # Install system dependencies | ||
| RUN apt-get update && apt-get upgrade -y && apt-get install -y \ |
There was a problem hiding this comment.
Running apt-get upgrade in a Dockerfile is generally discouraged as it makes the build non-deterministic and increases image size. Stick to apt-get update and specific package installations.
| RUN apt-get update && apt-get upgrade -y && apt-get install -y \ | |
| RUN apt-get update && apt-get install -y \ |
Bumps the package to 1.1.0 with changelog entries for configurable logging, the compiled plugin cache, and the dockerized development environment. Generalizes the package description and drops the stale unreleased notes that duplicated the 1.0.0 entry.
|
|
||
| protected int $minSeverity; | ||
|
|
||
| public function __construct(LoggerInterface $logger, string $minLevel = LogLevel::DEBUG) |
There was a problem hiding this comment.
The default value here is redundant since the minSeverity logic already defaults to DEBUG (0) via the null coalescing operator on line 37. Removing it simplifies the signature.
| public function __construct(LoggerInterface $logger, string $minLevel = LogLevel::DEBUG) | |
| public function __construct(LoggerInterface $logger, string $minLevel) |
| */ | ||
| protected function loadCachedPlugins(): array | ||
| { | ||
| $plugins = require $this->getCachedPluginsPath(); |
There was a problem hiding this comment.
The require statement should ideally be wrapped in a check to ensure the file exists, or the error should be gracefully handled, although pluginsAreCached check elsewhere usually prevents this.
| $plugins = require $this->getCachedPluginsPath(); | |
| if (! $this->pluginsAreCached()) { | |
| return []; | |
| } | |
| $plugins = require $this->getCachedPluginsPath(); |
No description provided.