22Implementation of hooks and APIs for outputting log messages.
33"""
44
5- import sys
65import traceback
76import inspect
87import json as pyjson
2221from ._validation import ValidationError
2322
2423
25- class _DestinationsSendError (Exception ):
26- """
27- An error occured sending to one or more destinations.
28-
29- @ivar errors: A list of tuples output from C{sys.exc_info()}.
30- """
31-
32- def __init__ (self , errors ):
33- self .errors = errors
34- Exception .__init__ (self , errors )
24+ # Action type for log messages due to a (hopefully temporarily) broken
25+ # destination.
26+ DESTINATION_FAILURE = "eliot:destination_failure"
3527
3628
3729class BufferingDestination (object ):
@@ -70,24 +62,57 @@ def addGlobalFields(self, **fields):
7062 """
7163 self ._globalFields .update (fields )
7264
73- def send (self , message ):
65+ def send (self , message , logger = None ):
7466 """
7567 Deliver a message to all destinations.
7668
7769 The passed in message might be mutated.
7870
71+ This should never raise an exception.
72+
7973 @param message: A message dictionary that can be serialized to JSON.
8074 @type message: L{dict}
75+
76+ @param logger: The ``ILogger`` that wrote the message, if any.
8177 """
8278 message .update (self ._globalFields )
8379 errors = []
80+ is_destination_error_message = (
81+ message .get ("message_type" , None ) == DESTINATION_FAILURE
82+ )
8483 for dest in self ._destinations :
8584 try :
8685 dest (message )
86+ except Exception as e :
87+ # If the destination is broken not because of a specific
88+ # message, but rather continously, we will get a
89+ # "eliot:destination_failure" log message logged, and so we
90+ # want to ensure it doesn't do infinite recursion.
91+ if not is_destination_error_message :
92+ errors .append (e )
93+
94+ for exception in errors :
95+ from ._action import log_message
96+
97+ try :
98+ new_msg = {
99+ MESSAGE_TYPE_FIELD : DESTINATION_FAILURE ,
100+ REASON_FIELD : safeunicode (exception ),
101+ EXCEPTION_FIELD : exception .__class__ .__module__
102+ + "."
103+ + exception .__class__ .__name__ ,
104+ "message" : _safe_unicode_dictionary (message ),
105+ }
106+ if logger is not None :
107+ # This is really only useful for testing, should really
108+ # figure out way to get rid of this mechanism...
109+ new_msg ["__eliot_logger__" ] = logger
110+ log_message (** new_msg )
87111 except :
88- errors .append (sys .exc_info ())
89- if errors :
90- raise _DestinationsSendError (errors )
112+ # Nothing we can do here, raising exception to caller will
113+ # break business logic, better to have that continue to
114+ # work even if logging isn't.
115+ pass
91116
92117 def add (self , * destinations ):
93118 """
@@ -144,6 +169,28 @@ def write(dictionary, serializer=None):
144169 """
145170
146171
172+ def _safe_unicode_dictionary (dictionary ):
173+ """
174+ Serialize a dictionary to a unicode string no matter what it contains.
175+
176+ The resulting dictionary will loosely follow Python syntax but it is
177+ not expected to actually be a lossless encoding in all cases.
178+
179+ @param dictionary: A L{dict} to serialize.
180+
181+ @return: A L{unicode} string representing the input dictionary as
182+ faithfully as can be done without putting in too much effort.
183+ """
184+ try :
185+ return str (
186+ dict (
187+ (saferepr (key ), saferepr (value )) for (key , value ) in dictionary .items ()
188+ )
189+ )
190+ except :
191+ return saferepr (dictionary )
192+
193+
147194@implementer (ILogger )
148195class Logger (object ):
149196 """
@@ -155,29 +202,6 @@ class Logger(object):
155202 """
156203
157204 _destinations = Destinations ()
158- _log_tracebacks = True
159-
160- def _safeUnicodeDictionary (self , dictionary ):
161- """
162- Serialize a dictionary to a unicode string no matter what it contains.
163-
164- The resulting dictionary will loosely follow Python syntax but it is
165- not expected to actually be a lossless encoding in all cases.
166-
167- @param dictionary: A L{dict} to serialize.
168-
169- @return: A L{unicode} string representing the input dictionary as
170- faithfully as can be done without putting in too much effort.
171- """
172- try :
173- return str (
174- dict (
175- (saferepr (key ), saferepr (value ))
176- for (key , value ) in dictionary .items ()
177- )
178- )
179- except :
180- return saferepr (dictionary )
181205
182206 def write (self , dictionary , serializer = None ):
183207 """
@@ -193,38 +217,12 @@ def write(self, dictionary, serializer=None):
193217
194218 log_message (
195219 "eliot:serialization_failure" ,
196- message = self . _safeUnicodeDictionary (dictionary ),
220+ message = _safe_unicode_dictionary (dictionary ),
197221 __eliot_logger__ = self ,
198222 )
199223 return
200224
201- try :
202- self ._destinations .send (dictionary )
203- except _DestinationsSendError as e :
204- from ._action import log_message
205-
206- if self ._log_tracebacks :
207- for (exc_type , exception , exc_traceback ) in e .errors :
208- # Can't use same Logger as serialization errors because
209- # if destination continues to error out we will get
210- # infinite recursion. So instead we have to manually
211- # construct a Logger that won't retry.
212- logger = Logger ()
213- logger ._log_tracebacks = False
214- logger ._destinations = self ._destinations
215- msg = {
216- MESSAGE_TYPE_FIELD : "eliot:destination_failure" ,
217- REASON_FIELD : safeunicode (exception ),
218- EXCEPTION_FIELD : exc_type .__module__ + "." + exc_type .__name__ ,
219- "message" : self ._safeUnicodeDictionary (dictionary ),
220- "__eliot_logger__" : logger ,
221- }
222- log_message (** msg )
223- else :
224- # Nothing we can do here, raising exception to caller will
225- # break business logic, better to have that continue to
226- # work even if logging isn't.
227- pass
225+ self ._destinations .send (dictionary , self )
228226
229227
230228def exclusively (f ):
0 commit comments