@@ -88,6 +88,7 @@ class BaseSpan(object):
8888 def __init__ (self , labels = None ):
8989 self ._child_durations = ChildDuration (self )
9090 self .labels = {}
91+ self .outcome = "unknown"
9192 if labels :
9293 self .label (** labels )
9394
@@ -132,6 +133,12 @@ def tag(self, **tags):
132133 for key in tags .keys ():
133134 self .labels [LABEL_RE .sub ("_" , compat .text_type (key ))] = encoding .keyword_field (compat .text_type (tags [key ]))
134135
136+ def set_success (self ):
137+ self .outcome = "success"
138+
139+ def set_failure (self ):
140+ self .outcome = "failure"
141+
135142
136143class Transaction (BaseSpan ):
137144 def __init__ (self , tracer , transaction_type = "custom" , trace_parent = None , is_sampled = True , start = None ):
@@ -176,6 +183,10 @@ def end(self, skip_frames=0, duration=None):
176183 reset_on_collect = True ,
177184 ** {"transaction.name" : self .name , "transaction.type" : self .transaction_type }
178185 ).update (self .duration )
186+ if self .outcome == "unknown" and self .context :
187+ status_code = self .context .get ("response" , {}).get ("status_code" , None )
188+ if isinstance (status_code , int ):
189+ self .outcome = "success" if status_code < 500 else "failure"
179190 if self ._breakdown :
180191 for (span_type , span_subtype ), timer in compat .iteritems (self ._span_timers ):
181192 labels = {
@@ -274,17 +285,22 @@ def begin_span(
274285 start = start ,
275286 )
276287
277- def end_span (self , skip_frames = 0 , duration = None ):
288+ def end_span (self , skip_frames = 0 , duration = None , outcome = None ):
278289 """
279290 End the currently active span
280291 :param skip_frames: numbers of frames to skip in the stack trace
281292 :param duration: override duration, mostly useful for testing
293+ :param outcome: outcome of the span, either success, failure or unknown
282294 :return: the ended span
283295 """
284296 span = execution_context .get_span ()
285297 if span is None :
286298 raise LookupError ()
287299
300+ # only overwrite span outcome if it is still unknown
301+ if span .outcome == "unknown" :
302+ span .outcome = outcome
303+
288304 span .end (skip_frames = skip_frames , duration = duration )
289305 return span
290306
@@ -309,6 +325,7 @@ def to_dict(self):
309325 "duration" : self .duration * 1000 , # milliseconds
310326 "result" : encoding .keyword_field (str (self .result )),
311327 "timestamp" : int (self .timestamp * 1000000 ), # microseconds
328+ "outcome" : self .outcome ,
312329 "sampled" : self .is_sampled ,
313330 "span_count" : {"started" : self ._span_counter - self .dropped_spans , "dropped" : self .dropped_spans },
314331 }
@@ -346,6 +363,7 @@ class Span(BaseSpan):
346363 "frames" ,
347364 "labels" ,
348365 "sync" ,
366+ "outcome" ,
349367 "_child_durations" ,
350368 )
351369
@@ -423,6 +441,7 @@ def to_dict(self):
423441 "action" : encoding .keyword_field (self .action ),
424442 "timestamp" : int (self .timestamp * 1000000 ), # microseconds
425443 "duration" : self .duration * 1000 , # milliseconds
444+ "outcome" : self .outcome ,
426445 }
427446 if self .sync is not None :
428447 result ["sync" ] = self .sync
@@ -512,6 +531,14 @@ def action(self):
512531 def context (self ):
513532 return None
514533
534+ @property
535+ def outcome (self ):
536+ return "unknown"
537+
538+ @outcome .setter
539+ def outcome (self , value ):
540+ return
541+
515542
516543class Tracer (object ):
517544 def __init__ (self , frames_collector_func , frames_processing_func , queue_func , config , agent ):
@@ -663,7 +690,8 @@ def __exit__(self, exc_type, exc_val, exc_tb):
663690
664691 if transaction and transaction .is_sampled :
665692 try :
666- span = transaction .end_span (self .skip_frames , duration = self .duration )
693+ outcome = "failure" if exc_val else "success"
694+ span = transaction .end_span (self .skip_frames , duration = self .duration , outcome = outcome )
667695 if exc_val and not isinstance (span , DroppedSpan ):
668696 try :
669697 exc_val ._elastic_apm_span_id = span .id
0 commit comments