From 14739500f45d6980729e320e27244b3db1725038 Mon Sep 17 00:00:00 2001 From: Brock H Caldwell Date: Sun, 13 Jul 2025 14:19:11 -0500 Subject: [PATCH] fix: cleaner monitor logging --- composer.json | 1 + composer.lock | 264 +++++++++++++++++- config/bundles.php | 1 + config/packages/monolog.yaml | 78 ++++++ .../Action/Handler/MonitorMovieHandler.php | 76 ----- .../Handler/MonitorTvEpisodeHandler.php | 3 - .../Action/Handler/MonitorTvShowHandler.php | 5 +- .../Framework/Scheduler/MonitorDispatcher.php | 10 +- symfony.lock | 12 + 9 files changed, 367 insertions(+), 83 deletions(-) create mode 100644 config/packages/monolog.yaml delete mode 100644 src/Monitor/Action/Handler/MonitorMovieHandler.php diff --git a/composer.json b/composer.json index 17d2d7f..eacb779 100644 --- a/composer.json +++ b/composer.json @@ -43,6 +43,7 @@ "symfony/mailer": "7.3.*", "symfony/mercure-bundle": "^0.3.9", "symfony/messenger": "7.3.*", + "symfony/monolog-bundle": "^3.10", "symfony/runtime": "7.3.*", "symfony/scheduler": "7.3.*", "symfony/security-bundle": "7.3.*", diff --git a/composer.lock b/composer.lock index 1af60d7..a2cb7df 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": "0f98dada0a01d471cebf4eb1b51b9006", + "content-hash": "cf9a5b3cdb6e21e270da6c4efee09005", "packages": [ { "name": "1tomany/rich-bundle", @@ -2681,6 +2681,109 @@ }, "time": "2025-02-06T08:48:15+00:00" }, + { + "name": "monolog/monolog", + "version": "3.9.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/10d85740180ecba7896c87e06a166e0c95a0e3b6", + "reference": "10d85740180ecba7896c87e06a166e0c95a0e3b6", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", + "predis/predis": "^1.1 || ^2", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.9.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2025-03-24T10:02:05+00:00" + }, { "name": "nesbot/carbon", "version": "3.10.0", @@ -7578,6 +7681,165 @@ ], "time": "2025-02-19T08:51:26+00:00" }, + { + "name": "symfony/monolog-bridge", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bridge.git", + "reference": "1b188c8abbbef25b111da878797514b7a8d33990" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/1b188c8abbbef25b111da878797514b7a8d33990", + "reference": "1b188c8abbbef25b111da878797514b7a8d33990", + "shasum": "" + }, + "require": { + "monolog/monolog": "^3", + "php": ">=8.2", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/security-core": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Monolog\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Monolog with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/monolog-bridge/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-03-21T12:17:46+00:00" + }, + { + "name": "symfony/monolog-bundle", + "version": "v3.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bundle.git", + "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181", + "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181", + "shasum": "" + }, + "require": { + "monolog/monolog": "^1.25.1 || ^2.0 || ^3.0", + "php": ">=7.2.5", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/monolog-bridge": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^6.3 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MonologBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony MonologBundle", + "homepage": "https://symfony.com", + "keywords": [ + "log", + "logging" + ], + "support": { + "issues": "https://github.com/symfony/monolog-bundle/issues", + "source": "https://github.com/symfony/monolog-bundle/tree/v3.10.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-06T17:08:13+00:00" + }, { "name": "symfony/options-resolver", "version": "v7.3.0", diff --git a/config/bundles.php b/config/bundles.php index 3b49b60..cc6203d 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -23,4 +23,5 @@ return [ SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true], Drenso\OidcBundle\DrensoOidcBundle::class => ['all' => true], SpomkyLabs\PwaBundle\SpomkyLabsPwaBundle::class => ['all' => true], + Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], ]; diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml new file mode 100644 index 0000000..3beabd4 --- /dev/null +++ b/config/packages/monolog.yaml @@ -0,0 +1,78 @@ +monolog: + channels: + - deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists + - monitor + +when@dev: + monolog: + handlers: + main: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + channels: ["!event"] + # uncomment to get logging in your browser + # you may have to allow bigger header sizes in your Web server configuration + #firephp: + # type: firephp + # level: info + #chromephp: + # type: chromephp + # level: info + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine", "!console"] + monitor: + type: stream + action_level: debug + path: "%kernel.logs_dir%/monitors.log" + channels: [monitor] + +when@test: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + channels: ["!event"] + nested: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + monitor: + type: stream + action_level: info + path: "%kernel.logs_dir%/monitors.log" + channels: [monitor] + +when@prod: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + buffer_size: 50 # How many messages should be saved? Prevent memory leaks + nested: + type: stream + path: php://stderr + level: debug + formatter: monolog.formatter.json + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine"] + deprecation: + type: stream + channels: [deprecation] + path: php://stderr + formatter: monolog.formatter.json + monitor: + type: stream + action_level: info + path: "%kernel.logs_dir%/monitors.log" + channels: [monitor] diff --git a/src/Monitor/Action/Handler/MonitorMovieHandler.php b/src/Monitor/Action/Handler/MonitorMovieHandler.php deleted file mode 100644 index e190c26..0000000 --- a/src/Monitor/Action/Handler/MonitorMovieHandler.php +++ /dev/null @@ -1,76 +0,0 @@ - */ -readonly class MonitorMovieHandler implements HandlerInterface -{ - public function __construct( - private MonitorRepository $movieMonitorRepository, - private GetMovieOptionsHandler $getMovieOptionsHandler, - private EntityManagerInterface $entityManager, - private MessageBusInterface $bus, - private LoggerInterface $logger, - private Security $security, - ) {} - - public function handle(CommandInterface $command): ResultInterface - { - $this->logger->info('> [MonitorMovieHandler] Executing MonitorMovieHandler'); - /** @var Monitor $monitor */ - $monitor = $this->movieMonitorRepository->find($command->movieMonitorId); - $monitor->setStatus('In Progress'); - - $this->logger->info('> [MonitorMovieHandler] Searching for "' . $monitor->getTitle() . '" download options'); - $results = $this->getMovieOptionsHandler->handle( - new GetMovieOptionsCommand($monitor->getTmdbId(), $monitor->getImdbId()) - ); - $this->logger->info('> [MonitorMovieHandler] Found ' . count($results->results) . ' download options'); - - $result = $this->monitorOptionEvaluator->evaluateOptions($monitor, $results->results); - - if (null !== $result) { - $this->logger->info('> [MonitorMovieHandler] 1 result found: dispatching DownloadMediaCommand for "' . $result->title . '"'); - $this->bus->dispatch(new DownloadMediaCommand( - $result->url, - $monitor->getTitle(), - $result->filename, - 'movies', - $monitor->getImdbId(), - $monitor->getUser()->getId(), - )); - $monitor->setStatus('Complete'); - $monitor->setDownloadedAt(new DateTimeIMmutable()); - } else { - $monitor->setStatus('Active'); - } - - $monitor->setLastSearch(new DateTimeImmutable()); - $monitor->incrementSearchCount(); - $this->entityManager->flush(); - - return new MonitorMovieResult( - status: 'OK', - result: [ - 'monitor' => $monitor, - ] - ); - } -} diff --git a/src/Monitor/Action/Handler/MonitorTvEpisodeHandler.php b/src/Monitor/Action/Handler/MonitorTvEpisodeHandler.php index 538a420..3d29f3d 100644 --- a/src/Monitor/Action/Handler/MonitorTvEpisodeHandler.php +++ b/src/Monitor/Action/Handler/MonitorTvEpisodeHandler.php @@ -5,7 +5,6 @@ namespace App\Monitor\Action\Handler; use App\Base\Util\EpisodeId; use App\Download\Action\Command\DownloadMediaCommand; use App\Download\DownloadOptionEvaluator; -use App\Download\Framework\Entity\Download; use App\Download\Framework\Repository\DownloadRepository; use App\Monitor\Action\Command\MonitorMovieCommand; use App\Monitor\Action\Result\MonitorTvEpisodeResult; @@ -16,7 +15,6 @@ use App\Torrentio\Action\Handler\GetTvShowOptionsHandler; use App\User\Dto\UserPreferencesFactory; use Carbon\Carbon; use DateTimeImmutable; -use Doctrine\ORM\EntityManagerInterface; use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\HandlerInterface; use OneToMany\RichBundle\Contract\ResultInterface; @@ -29,7 +27,6 @@ readonly class MonitorTvEpisodeHandler implements HandlerInterface public function __construct( private GetTvShowOptionsHandler $getTvShowOptionsHandler, private DownloadOptionEvaluator $downloadOptionEvaluator, - private EntityManagerInterface $entityManager, private MessageBusInterface $bus, private LoggerInterface $logger, private MonitorRepository $monitorRepository, diff --git a/src/Monitor/Action/Handler/MonitorTvShowHandler.php b/src/Monitor/Action/Handler/MonitorTvShowHandler.php index cec13ec..d9cf93e 100644 --- a/src/Monitor/Action/Handler/MonitorTvShowHandler.php +++ b/src/Monitor/Action/Handler/MonitorTvShowHandler.php @@ -18,19 +18,22 @@ use OneToMany\RichBundle\Contract\CommandInterface; use OneToMany\RichBundle\Contract\HandlerInterface; use OneToMany\RichBundle\Contract\ResultInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\Attribute\Target; /** @implements HandlerInterface */ readonly class MonitorTvShowHandler implements HandlerInterface { public function __construct( + #[Target('monitorLogger')] + private LoggerInterface $logger, private MonitorRepository $monitorRepository, private EntityManagerInterface $entityManager, private MonitorTvEpisodeHandler $monitorTvEpisodeHandler, private MediaFiles $mediaFiles, - private LoggerInterface $logger, private Tmdb $tmdb, ) {} + public function handle(CommandInterface $command): ResultInterface { $this->logger->info('> [MonitorTvShowHandler] Executing MonitorTvShowHandler'); diff --git a/src/Monitor/Framework/Scheduler/MonitorDispatcher.php b/src/Monitor/Framework/Scheduler/MonitorDispatcher.php index b298542..ddfd105 100644 --- a/src/Monitor/Framework/Scheduler/MonitorDispatcher.php +++ b/src/Monitor/Framework/Scheduler/MonitorDispatcher.php @@ -9,6 +9,7 @@ use App\Monitor\Action\Command\MonitorTvShowCommand; use App\Monitor\Framework\Repository\MonitorRepository; use Carbon\Carbon; use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\Attribute\Target; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Scheduler\Attribute\AsCronTask; @@ -16,13 +17,14 @@ use Symfony\Component\Scheduler\Attribute\AsCronTask; class MonitorDispatcher { public function __construct( + #[Target('monitorLogger')] private readonly LoggerInterface $logger, private readonly MonitorRepository $monitorRepository, private readonly MessageBusInterface $bus, ) {} public function __invoke() { - $this->logger->info('[MonitorDispatcher] Executing MonitorDispatcher'); + $this->logger->info('[MonitorDispatcher] > Executing MonitorDispatcher'); $this->cleanupStuckMonitors(); @@ -34,15 +36,19 @@ class MonitorDispatcher ]; $monitors = $this->monitorRepository->findBy(['status' => ['New', 'Active']]); + $this->logger->info('[MonitorDispatcher] ' . count($monitors) . ' monitors found'); foreach ($monitors as $monitor) { + $this->logger->info('[MonitorDispatcher] - Evaluating monitor ' . $monitor->getId() . ' for "' . $monitor->getTitle() . '"'); $monitor->setStatus('In Progress'); $this->monitorRepository->getEntityManager()->flush(); $command = $monitorHandlers[$monitor->getMonitorType()]; - $this->logger->info('[MonitorDispatcher] Dispatching ' . $command . ' for ' . $monitor->getTitle()); + $this->logger->info('[MonitorDispatcher] Dispatching ' . $command . ' for ' . $monitor->getTitle()); $this->bus->dispatch(new $command($monitor->getId())); } + + $this->logger->info('[MonitorDispatcher] < Complete'); } private function cleanupStuckMonitors(): void diff --git a/symfony.lock b/symfony.lock index f53f406..73ad2c3 100644 --- a/symfony.lock +++ b/symfony.lock @@ -217,6 +217,18 @@ "config/packages/messenger.yaml" ] }, + "symfony/monolog-bundle": { + "version": "3.10", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.7", + "ref": "aff23899c4440dd995907613c1dd709b6f59503f" + }, + "files": [ + "config/packages/monolog.yaml" + ] + }, "symfony/property-info": { "version": "7.3", "recipe": {