4444use Exception ;
4545use InvalidArgumentException ;
4646use LogicException ;
47+ use Monolog \Handler \StreamHandler ;
48+ use Monolog \Level ;
49+ use Monolog \Logger ;
50+ use Monolog \Processor \ProcessorInterface ;
4751use Psr \Log \LoggerInterface ;
4852use Symfony \Component \DependencyInjection \Attribute \Autowire ;
53+ use Symfony \Component \DependencyInjection \Attribute \AutowireIterator ;
4954use Symfony \Component \HttpClient \Exception \TransportException ;
5055use Symfony \Component \HttpFoundation \File \UploadedFile ;
5156use Symfony \Component \PropertyAccess \Exception \UnexpectedTypeException ;
5863use Symfony \Contracts \HttpClient \Exception \HttpExceptionInterface ;
5964use Symfony \Contracts \HttpClient \Exception \TransportExceptionInterface ;
6065use Symfony \Contracts \HttpClient \HttpClientInterface ;
66+ use Traversable ;
6167use ZipArchive ;
6268
6369class ExternalContestSourceService
@@ -66,6 +72,8 @@ class ExternalContestSourceService
6672
6773 protected ?ExternalContestSource $ source = null ;
6874
75+ protected LoggerInterface $ logger ;
76+
6977 protected bool $ contestLoaded = false ;
7078 protected ?ContestData $ cachedContestData = null ;
7179 protected ?ApiInfo $ cachedApiInfoData = null ;
@@ -100,19 +108,28 @@ class ExternalContestSourceService
100108 'submission ' => [],
101109 ];
102110
111+ /**
112+ * @param Traversable<ProcessorInterface> $logProcessors
113+ */
103114 public function __construct (
104115 HttpClientInterface $ httpClient ,
105116 protected readonly DOMJudgeService $ dj ,
106117 protected readonly EntityManagerInterface $ em ,
107- #[Autowire(service: 'monolog.logger.event-feed-importer ' )]
108- protected readonly LoggerInterface $ logger ,
109118 protected readonly ConfigurationService $ config ,
110119 protected readonly EventLogService $ eventLog ,
111120 protected readonly SubmissionService $ submissionService ,
112121 protected readonly ScoreboardService $ scoreboardService ,
113122 protected readonly SerializerInterface &DenormalizerInterface &NormalizerInterface $ serializer ,
123+ #[AutowireIterator(tag: 'monolog.processor ' )]
124+ protected readonly Traversable $ logProcessors ,
125+ #[Autowire(service: 'monolog.processor.psr_log_message ' )]
126+ protected readonly ProcessorInterface $ psrLogMessageProcessor ,
127+ #[Autowire(param: 'kernel.logs_dir ' )]
128+ protected readonly string $ logDir ,
129+ #[Autowire(param: 'kernel.environment ' )]
130+ protected readonly string $ env ,
114131 #[Autowire('%domjudge.version% ' )]
115- string $ domjudgeVersion
132+ string $ domjudgeVersion,
116133 ) {
117134 $ clientOptions = [
118135 'headers ' => [
@@ -269,6 +286,8 @@ public function getLastReadEventId(): ?string
269286 */
270287 public function import (bool $ fromStart , array $ eventsToSkip , ?callable $ progressReporter = null ): bool
271288 {
289+ $ this ->configureLogger ();
290+
272291 // We need the verdicts to validate judgement-types.
273292 $ this ->verdicts = $ this ->config ->getVerdicts (['final ' , 'external ' ]);
274293
@@ -2087,4 +2106,19 @@ protected function removeWarning(EventType $eventType, ?string $entityId, string
20872106 $ this ->em ->flush ();
20882107 }
20892108 }
2109+
2110+ protected function configureLogger (): void
2111+ {
2112+ // Configure a logger to use a file per contest.
2113+ // For this we need to create our own logger.
2114+ // This code is based on what Symfony does with their default loggers.
2115+ $ name = sprintf ('event-feed-importer-%s ' , $ this ->getSourceContestId ());
2116+ $ logFile = sprintf ('%s/%s.log ' , $ this ->logDir , $ name );
2117+ $ handler = new StreamHandler (
2118+ $ logFile ,
2119+ $ this ->env === 'dev ' ? Level::Debug : Level::Info,
2120+ );
2121+ $ handler ->pushProcessor ($ this ->psrLogMessageProcessor );
2122+ $ this ->logger = new Logger ($ name , [$ handler ], iterator_to_array ($ this ->logProcessors ));
2123+ }
20902124}
0 commit comments