@@ -196,14 +196,16 @@ func (e *Engine) executeScan(ctx context.Context, job *ScanJob, scanners []*Scan
196196 callback .OnScannerStarted (job , scanner .ID )
197197 e .updateScannerStatus (job , scanner .ID , ScanJobStatusRunning , time .Now (), time.Time {}, "" , 0 )
198198
199- report , err := e .runSingleScanner (ctx , scanner , req )
199+ report , scanLogs , err := e .runSingleScanner (ctx , scanner , req )
200200 if err != nil {
201201 e .logger .Warn ("Scanner failed" ,
202202 zap .String ("scanner" , scanner .ID ),
203203 zap .String ("server" , req .ServerName ),
204204 zap .Error (err ),
205205 )
206206 e .updateScannerStatus (job , scanner .ID , ScanJobStatusFailed , time.Time {}, time .Now (), err .Error (), 0 )
207+ // Store logs even on failure
208+ e .setScannerLogs (job , scanner .ID , scanLogs )
207209 callback .OnScannerFailed (job , scanner .ID , err )
208210 return
209211 }
@@ -216,6 +218,7 @@ func (e *Engine) executeScan(ctx context.Context, job *ScanJob, scanners []*Scan
216218 mu .Unlock ()
217219
218220 e .updateScannerStatus (job , scanner .ID , ScanJobStatusCompleted , time.Time {}, time .Now (), "" , len (report .Findings ))
221+ e .setScannerLogs (job , scanner .ID , scanLogs )
219222 callback .OnScannerCompleted (job , scanner .ID , report )
220223 }(i , s )
221224 }
@@ -252,8 +255,8 @@ func (e *Engine) executeScan(ctx context.Context, job *ScanJob, scanners []*Scan
252255 callback .OnScanCompleted (job , reports )
253256}
254257
255- // runSingleScanner executes one scanner and returns its report
256- func (e * Engine ) runSingleScanner (ctx context.Context , s * ScannerPlugin , req ScanRequest ) (* ScanReport , error ) {
258+ // runSingleScanner executes one scanner and returns its report plus execution logs
259+ func (e * Engine ) runSingleScanner (ctx context.Context , s * ScannerPlugin , req ScanRequest ) (* ScanReport , scannerLogs , error ) {
257260 // Parse timeout
258261 timeout := 120 * time .Second
259262 if s .Timeout != "" {
@@ -266,7 +269,7 @@ func (e *Engine) runSingleScanner(ctx context.Context, s *ScannerPlugin, req Sca
266269 jobID := fmt .Sprintf ("scan-%s-%d" , req .ServerName , time .Now ().UnixNano ())
267270 reportDir , err := PrepareReportDir (e .dataDir , jobID , s .ID )
268271 if err != nil {
269- return nil , fmt .Errorf ("failed to prepare report directory: %w" , err )
272+ return nil , scannerLogs {}, fmt .Errorf ("failed to prepare report directory: %w" , err )
270273 }
271274
272275 // Build env vars: scanner config + request env
@@ -310,8 +313,9 @@ func (e *Engine) runSingleScanner(ctx context.Context, s *ScannerPlugin, req Sca
310313 }
311314
312315 stdout , stderr , exitCode , err := e .docker .RunScanner (ctx , cfg )
316+ logs := scannerLogs {Stdout : stdout , Stderr : stderr , ExitCode : exitCode }
313317 if err != nil {
314- return nil , fmt .Errorf ("scanner %s execution failed: %w" , s .ID , err )
318+ return nil , logs , fmt .Errorf ("scanner %s execution failed: %w" , s .ID , err )
315319 }
316320
317321 e .logger .Debug ("Scanner finished" ,
@@ -331,12 +335,13 @@ func (e *Engine) runSingleScanner(ctx context.Context, s *ScannerPlugin, req Sca
331335 if len (stdout ) > 0 {
332336 reportData = []byte (stdout )
333337 } else {
334- return nil , fmt .Errorf ("scanner %s produced no output (exit code: %d, stderr: %s)" , s .ID , exitCode , truncate (stderr , 500 ))
338+ return nil , logs , fmt .Errorf ("scanner %s produced no output (exit code: %d, stderr: %s)" , s .ID , exitCode , truncate (stderr , 500 ))
335339 }
336340 }
337341
338342 // Parse results
339- return e .parseResults (reportData , s .ID )
343+ report , parseErr := e .parseResults (reportData , s .ID )
344+ return report , logs , parseErr
340345}
341346
342347// parseResults parses scanner output into a ScanReport
@@ -393,6 +398,27 @@ func (e *Engine) parseResults(data []byte, scannerID string) (*ScanReport, error
393398 return report , nil
394399}
395400
401+ // scannerLogs captures stdout/stderr from a scanner execution
402+ type scannerLogs struct {
403+ Stdout string
404+ Stderr string
405+ ExitCode int
406+ }
407+
408+ // setScannerLogs stores stdout/stderr on a scanner's job status
409+ func (e * Engine ) setScannerLogs (job * ScanJob , scannerID string , logs scannerLogs ) {
410+ e .mu .Lock ()
411+ defer e .mu .Unlock ()
412+ for i := range job .ScannerStatuses {
413+ if job .ScannerStatuses [i ].ScannerID == scannerID {
414+ job .ScannerStatuses [i ].Stdout = truncate (logs .Stdout , 50000 )
415+ job .ScannerStatuses [i ].Stderr = truncate (logs .Stderr , 50000 )
416+ job .ScannerStatuses [i ].ExitCode = logs .ExitCode
417+ return
418+ }
419+ }
420+ }
421+
396422// updateScannerStatus updates a specific scanner's status within a job
397423func (e * Engine ) updateScannerStatus (job * ScanJob , scannerID , status string , startedAt , completedAt time.Time , errMsg string , findingsCount int ) {
398424 e .mu .Lock ()
0 commit comments