@@ -20,7 +20,11 @@ import {
2020 isSchedulerNetworkError ,
2121} from '../../const.js'
2222import { loadSchedules } from '../scheduleStore.js'
23- import type { Schedule , ScreenshotParams } from '../../types/domain.js'
23+ import type {
24+ Schedule ,
25+ ScreenshotParams ,
26+ WebhookResult ,
27+ } from '../../types/domain.js'
2428import { schedulerLogger } from '../logger.js'
2529
2630const log = schedulerLogger ( )
@@ -32,6 +36,7 @@ export type ScreenshotFunction = (params: ScreenshotParams) => Promise<Buffer>
3236export interface ExecutionResult {
3337 success : boolean
3438 savedPath : string
39+ webhook ?: WebhookResult
3540}
3641
3742/**
@@ -53,10 +58,23 @@ export class ScheduleExecutor {
5358
5459 const result = await this . #executeWithRetry( schedule )
5560
56- log . info `Completed: ${ schedule . name } in ${ Date . now ( ) - startTime } ms`
61+ this . #logResult ( schedule . name , result , Date . now ( ) - startTime )
5762 return result
5863 }
5964
65+ /** Logs execution result with full details */
66+ #logResult( name : string , result : ExecutionResult , durationMs : number ) : void {
67+ const webhookStatus = this . #formatWebhookStatus( result . webhook )
68+ log . info `Completed: ${ name } in ${ durationMs } ms | saved: ${ result . savedPath } | webhook: ${ webhookStatus } `
69+ }
70+
71+ /** Formats webhook status for logging */
72+ #formatWebhookStatus( webhook : WebhookResult | undefined ) : string {
73+ if ( ! webhook ) return 'not configured'
74+ if ( webhook . success ) return `${ webhook . statusCode } OK → ${ webhook . url } `
75+ return `FAILED (${ webhook . error } ) → ${ webhook . url } `
76+ }
77+
6078 /** Retry wrapper for network failures */
6179 async #executeWithRetry( schedule : Schedule ) : Promise < ExecutionResult > {
6280 for ( let attempt = 1 ; attempt <= SCHEDULER_MAX_RETRIES ; attempt ++ ) {
@@ -75,13 +93,25 @@ export class ScheduleExecutor {
7593 async #executeOnce( schedule : Schedule ) : Promise < ExecutionResult > {
7694 const params = buildParams ( schedule )
7795 const imageBuffer = await this . #screenshotFn( params )
78- const savedPath = await this . #saveAndCleanup( schedule , imageBuffer , params . format )
79- await this . #uploadIfConfigured( schedule , imageBuffer , params . format )
80- return { success : true , savedPath }
96+ const savedPath = await this . #saveAndCleanup(
97+ schedule ,
98+ imageBuffer ,
99+ params . format
100+ )
101+ const webhook = await this . #uploadIfConfigured(
102+ schedule ,
103+ imageBuffer ,
104+ params . format
105+ )
106+ return { success : true , savedPath, webhook }
81107 }
82108
83109 /** Saves screenshot and runs LRU cleanup */
84- async #saveAndCleanup( schedule : Schedule , imageBuffer : Buffer , format : string ) : Promise < string > {
110+ async #saveAndCleanup(
111+ schedule : Schedule ,
112+ imageBuffer : Buffer ,
113+ format : string
114+ ) : Promise < string > {
85115 const { outputPath } = saveScreenshot ( {
86116 outputDir : this . #outputDir,
87117 scheduleName : schedule . name ,
@@ -91,31 +121,62 @@ export class ScheduleExecutor {
91121 log . info `Saved: ${ outputPath } `
92122
93123 const schedules = await loadSchedules ( )
94- const maxFiles = schedules . filter ( ( s ) => s . enabled ) . length * SCHEDULER_RETENTION_MULTIPLIER
124+ const maxFiles =
125+ schedules . filter ( ( s ) => s . enabled ) . length * SCHEDULER_RETENTION_MULTIPLIER
95126 const { deletedCount } = cleanupOldScreenshots ( {
96127 outputDir : this . #outputDir,
97128 maxFiles,
98129 filePattern : SCHEDULER_IMAGE_FILE_PATTERN ,
99130 } )
100131
101- if ( deletedCount > 0 ) log . debug `Cleanup: Deleted ${ deletedCount } old file(s)`
132+ if ( deletedCount > 0 )
133+ log . debug `Cleanup: Deleted ${ deletedCount } old file(s)`
102134 return outputPath
103135 }
104136
105- /** Uploads to webhook if configured */
106- async #uploadIfConfigured( schedule : Schedule , imageBuffer : Buffer , format : string ) : Promise < void > {
107- if ( ! schedule . webhook_url ) return
137+ /** Uploads to webhook if configured, returns result for UI feedback */
138+ async #uploadIfConfigured(
139+ schedule : Schedule ,
140+ imageBuffer : Buffer ,
141+ format : string
142+ ) : Promise < WebhookResult | undefined > {
143+ if ( ! schedule . webhook_url ) return undefined
144+
145+ const webhookUrl = schedule . webhook_url
108146
109147 try {
110- await uploadToWebhook ( {
111- webhookUrl : schedule . webhook_url ,
148+ const result = await uploadToWebhook ( {
149+ webhookUrl,
112150 webhookHeaders : schedule . webhook_headers ,
113151 imageBuffer,
114152 format : format as 'png' | 'jpeg' | 'bmp' ,
115153 } )
154+
155+ log . info `Schedule "${ schedule . name } " webhook success: ${ result . status } ${ result . statusText } `
156+
157+ return {
158+ attempted : true ,
159+ success : true ,
160+ statusCode : result . status ,
161+ url : webhookUrl ,
162+ }
116163 } catch ( err ) {
117- // Error already logged by uploadToWebhook, just re-log for schedule context
118- log . error `Schedule "${ schedule . name } " webhook failed: ${ ( err as Error ) . message } `
164+ const errorMessage = ( err as Error ) . message
165+ log . error `Schedule "${ schedule . name } " webhook failed: ${ errorMessage } `
166+
167+ // Extract status code from error message if present (e.g., "HTTP 404: Not Found")
168+ const statusMatch = errorMessage . match ( / H T T P ( \d + ) : / )
169+ const statusCode = statusMatch ?. [ 1 ]
170+ ? parseInt ( statusMatch [ 1 ] , 10 )
171+ : undefined
172+
173+ return {
174+ attempted : true ,
175+ success : false ,
176+ statusCode,
177+ error : errorMessage ,
178+ url : webhookUrl ,
179+ }
119180 }
120181 }
121182
0 commit comments