@@ -316,6 +316,8 @@ class WatchedSubprocess:
316316
317317 selector : selectors .BaseSelector = attrs .field (factory = selectors .DefaultSelector )
318318
319+ log : FilteringBoundLogger
320+
319321 @classmethod
320322 def start (
321323 cls ,
@@ -363,17 +365,17 @@ def start(
363365 # other end of the pair open
364366 cls ._close_unused_sockets (child_stdin , child_stdout , child_stderr , child_comms , child_logs )
365367
368+ logger = logger or cast ("FilteringBoundLogger" , structlog .get_logger (logger_name = "task" ).bind ())
366369 proc = cls (
367370 pid = pid ,
368371 stdin = feed_stdin ,
369372 process = psutil .Process (pid ),
370373 requests_fd = requests_fd ,
374+ log = logger ,
371375 ** constructor_kwargs ,
372376 )
373377
374- logger = logger or cast ("FilteringBoundLogger" , structlog .get_logger (logger_name = "task" ).bind ())
375378 proc ._register_pipe_readers (
376- logger = logger ,
377379 stdout = read_stdout ,
378380 stderr = read_stderr ,
379381 requests = read_msgs ,
@@ -382,26 +384,24 @@ def start(
382384
383385 return proc
384386
385- def _register_pipe_readers (
386- self , logger : FilteringBoundLogger , stdout : socket , stderr : socket , requests : socket , logs : socket
387- ):
387+ def _register_pipe_readers (self , stdout : socket , stderr : socket , requests : socket , logs : socket ):
388388 """Register handlers for subprocess communication channels."""
389389 # self.selector is a way of registering a handler/callback to be called when the given IO channel has
390390 # activity to read on (https://www.man7.org/linux/man-pages/man2/select.2.html etc, but better
391391 # alternatives are used automatically) -- this is a way of having "event-based" code, but without
392392 # needing full async, to read and process output from each socket as it is received.
393393
394- self .selector .register (stdout , selectors .EVENT_READ , self ._create_socket_handler (logger , "stdout" ))
394+ self .selector .register (stdout , selectors .EVENT_READ , self ._create_socket_handler (self . log , "stdout" ))
395395 self .selector .register (
396396 stderr ,
397397 selectors .EVENT_READ ,
398- self ._create_socket_handler (logger , "stderr" , log_level = logging .ERROR ),
398+ self ._create_socket_handler (self . log , "stderr" , log_level = logging .ERROR ),
399399 )
400400 self .selector .register (
401401 logs ,
402402 selectors .EVENT_READ ,
403403 make_buffered_socket_reader (
404- process_log_messages_from_subprocess (logger ), on_close = self ._on_socket_closed
404+ process_log_messages_from_subprocess (self . log ), on_close = self ._on_socket_closed
405405 ),
406406 )
407407 self .selector .register (
@@ -658,8 +658,24 @@ def wait(self) -> int:
658658 self .client .task_instances .finish (
659659 id = self .id , state = self .final_state , when = datetime .now (tz = timezone .utc )
660660 )
661+
662+ # Now at the last possible moment, when all logs and comms with the subprocess has finished, lets
663+ # upload the remote logs
664+ self ._upload_logs ()
665+
661666 return self ._exit_code
662667
668+ def _upload_logs (self ):
669+ """
670+ Upload all log files found to the remote storage.
671+
672+ We upload logs from here after the task has finished to give us the best possible chance of logs being
673+ uploaded in case the task task.
674+ """
675+ from airflow .sdk .log import upload_to_remote
676+
677+ upload_to_remote (self .log )
678+
663679 def _monitor_subprocess (self ):
664680 """
665681 Monitor the subprocess until it exits.
0 commit comments