@@ -210,50 +210,72 @@ Optional<String> readError(String payload) {
210210 }
211211
212212 @ VisibleForTesting
213- void fetchAndLogLambdaLogs (
214- RunContext runContext ,
215- String functionArn ,
216- Instant startTime
217- ) {
213+ void fetchAndLogLambdaLogs (RunContext runContext , String functionArn , Instant startTime ) {
218214 var logger = runContext .logger ();
219-
220- // Extract function name from ARN
221215 String functionName ;
216+
222217 try {
223- functionName = functionArn . split ( ":function:" )[ 1 ]. split ( ":" )[ 0 ] ;
218+ functionName = extractFunctionName ( functionArn ) ;
224219 } catch (Exception e ) {
225220 logger .warn ("Unable to determine Lambda function name from ARN: {}" , functionArn );
226221 return ;
227222 }
228223
229224 String logGroupName = "/aws/lambda/" + functionName ;
230225
226+ // Polling logic: 5 attempts, waiting 3 seconds between each
227+ int maxAttempts = 5 ;
228+ long sleepMillis = 3000 ;
229+ boolean logsFound = false ;
230+
231231 try (CloudWatchLogsClient logsClient = getCloudWatchLogsClient (runContext )) {
232- FilterLogEventsRequest request = FilterLogEventsRequest .builder ()
233- .logGroupName (logGroupName )
234- .startTime (startTime .toEpochMilli ())
235- .build ();
236-
237- // Fetch logs using CloudWatch paginator
238- logsClient .filterLogEventsPaginator (request )
239- .events ()
240- .stream ()
241- // Hard cap to prevent excessive log volume in a single task execution
242- .limit (1_000 )
243- .map (FilteredLogEvent ::message )
244- .filter (message -> message != null && !message .isBlank ())
245- .forEach (message -> logger .info ("[lambda] {}" , message ));
232+ for (int i = 0 ; i < maxAttempts ; i ++) {
233+ FilterLogEventsRequest request = FilterLogEventsRequest .builder ()
234+ .logGroupName (logGroupName )
235+ .startTime (startTime .toEpochMilli ())
236+ .build ();
237+
238+ var response = logsClient .filterLogEvents (request );
239+ var events = response .events ();
246240
241+ if (events != null && !events .isEmpty ()) {
242+ events .stream ()
243+ .limit (1000 )
244+ .map (FilteredLogEvent ::message )
245+ .filter (message -> message != null && !message .isBlank ())
246+ .forEach (message -> logger .info ("[lambda] {}" , message .trim ()));
247+ logsFound = true ;
248+ break ; // Exit early if found logs
249+ }
250+
251+ // Wait before next retry, but not after the last attempt
252+ if (i < maxAttempts - 1 ) {
253+ Thread .sleep (sleepMillis );
254+ }
255+ }
256+
257+ if (!logsFound ) {
258+ logger .debug ("No CloudWatch logs found for {} after {} attempts." , functionName , maxAttempts );
259+ }
260+
261+ } catch (InterruptedException e ) {
262+ Thread .currentThread ().interrupt ();
263+ logger .warn ("Log fetching interrupted for Lambda {}" , functionArn );
247264 } catch (Exception e ) {
248- // Logs must never fail the task execution
249- logger .warn (
250- "Failed to fetch CloudWatch logs for Lambda {}: {}" ,
251- functionArn ,
252- e .getMessage ()
253- );
265+ logger .warn ("Failed to fetch CloudWatch logs for Lambda {}: {}" , functionArn , e .getMessage ());
254266 }
255267 }
256268
269+ @ VisibleForTesting
270+ private String extractFunctionName (String functionArnOrName ) {
271+ if (functionArnOrName .contains (":function:" )) {
272+ // Handle Full ARN
273+ return functionArnOrName .split (":function:" )[1 ].split (":" )[0 ];
274+ }
275+ // Handle just the name
276+ return functionArnOrName ;
277+ }
278+
257279 @ VisibleForTesting
258280 void handleError (String functionArn , ContentType contentType , SdkBytes payload ) {
259281 String errorPayload ;
0 commit comments