3838_WORKER_TERMINATOR = object ()
3939_LOGGER = logging .getLogger (__name__ )
4040
41+ _CLOSE_THREAD_SHUTDOWN_ERROR_MSG = (
42+ "CloudLoggingHandler shutting down, cannot send logs entries to Cloud Logging due to "
43+ "inconsistent threading behavior at shutdown. To avoid this issue, flush the logging handler "
44+ "manually or switch to StructuredLogHandler. You can also close the CloudLoggingHandler manually "
45+ "via handler.close or client.close."
46+ )
47+
4148
4249def _get_many (queue_ , * , max_items = None , max_latency = 0 ):
4350 """Get multiple items from a Queue.
@@ -140,9 +147,11 @@ def _thread_main(self):
140147 else :
141148 batch .log (** item )
142149
143- self ._safely_commit_batch (batch )
150+ # We cannot commit logs upstream if the main thread is shutting down
151+ if threading .main_thread ().is_alive ():
152+ self ._safely_commit_batch (batch )
144153
145- for _ in items :
154+ for it in items :
146155 self ._queue .task_done ()
147156
148157 _LOGGER .debug ("Background thread exited gracefully." )
@@ -162,7 +171,7 @@ def start(self):
162171 )
163172 self ._thread .daemon = True
164173 self ._thread .start ()
165- atexit .register (self ._main_thread_terminated )
174+ atexit .register (self ._handle_exit )
166175
167176 def stop (self , * , grace_period = None ):
168177 """Signals the background thread to stop.
@@ -202,26 +211,26 @@ def stop(self, *, grace_period=None):
202211
203212 return success
204213
205- def _main_thread_terminated (self ):
206- """Callback that attempts to send pending logs before termination."""
214+ def _close (self , close_msg ):
215+ """Callback that attempts to send pending logs before termination if the main thread is alive ."""
207216 if not self .is_alive :
208217 return
209218
210219 if not self ._queue .empty ():
211- print (
212- "Program shutting down, attempting to send %d queued log "
213- "entries to Cloud Logging..." % (self ._queue .qsize (),),
214- file = sys .stderr ,
215- )
220+ print (close_msg , file = sys .stderr )
216221
217- if self .stop (grace_period = self ._grace_period ):
222+ if threading .main_thread ().is_alive () and self .stop (
223+ grace_period = self ._grace_period
224+ ):
218225 print ("Sent all pending logs." , file = sys .stderr )
219- else :
226+ elif not self . _queue . empty () :
220227 print (
221228 "Failed to send %d pending logs." % (self ._queue .qsize (),),
222229 file = sys .stderr ,
223230 )
224231
232+ self ._thread = None
233+
225234 def enqueue (self , record , message , ** kwargs ):
226235 """Queues a log entry to be written by the background thread.
227236
@@ -251,6 +260,26 @@ def flush(self):
251260 """Submit any pending log records."""
252261 self ._queue .join ()
253262
263+ def close (self ):
264+ """Signals the worker thread to stop, then closes the transport thread.
265+
266+ This call will attempt to send pending logs before termination, and
267+ should be followed up by disowning the transport object.
268+ """
269+ atexit .unregister (self ._handle_exit )
270+ self ._close (
271+ "Background thread shutting down, attempting to send %d queued log "
272+ "entries to Cloud Logging..." % (self ._queue .qsize (),)
273+ )
274+
275+ def _handle_exit (self ):
276+ """Handle system exit.
277+
278+ Since we cannot send pending logs during system shutdown due to thread errors,
279+ log an error message to stderr to notify the user.
280+ """
281+ self ._close (_CLOSE_THREAD_SHUTDOWN_ERROR_MSG )
282+
254283
255284class BackgroundThreadTransport (Transport ):
256285 """Asynchronous transport that uses a background thread."""
@@ -285,6 +314,7 @@ def __init__(
285314 """
286315 self .client = client
287316 logger = self .client .logger (name , resource = resource )
317+ self .grace_period = grace_period
288318 self .worker = _Worker (
289319 logger ,
290320 grace_period = grace_period ,
@@ -307,3 +337,7 @@ def send(self, record, message, **kwargs):
307337 def flush (self ):
308338 """Submit any pending log records."""
309339 self .worker .flush ()
340+
341+ def close (self ):
342+ """Closes the worker thread."""
343+ self .worker .close ()
0 commit comments