30
30
from newrelic .core .config import is_expected_error , should_ignore_error
31
31
from newrelic .core .trace_cache import trace_cache
32
32
33
+ from newrelic .packages import six
34
+
33
35
_logger = logging .getLogger (__name__ )
34
36
35
37
@@ -255,13 +257,15 @@ def _observe_exception(self, exc_info=None, ignore=None, expected=None, status_c
255
257
if getattr (value , "_nr_ignored" , None ):
256
258
return
257
259
258
- module , name , fullnames , message = parse_exc_info ((exc , value , tb ))
260
+ module , name , fullnames , message_raw = parse_exc_info ((exc , value , tb ))
259
261
fullname = fullnames [0 ]
260
262
261
263
# Check to see if we need to strip the message before recording it.
262
264
263
265
if settings .strip_exception_messages .enabled and fullname not in settings .strip_exception_messages .allowlist :
264
266
message = STRIP_EXCEPTION_MESSAGE
267
+ else :
268
+ message = message_raw
265
269
266
270
# Where expected or ignore are a callable they should return a
267
271
# tri-state variable with the following behavior.
@@ -344,7 +348,7 @@ def _observe_exception(self, exc_info=None, ignore=None, expected=None, status_c
344
348
is_expected = is_expected_error (exc_info , status_code = status_code , settings = settings )
345
349
346
350
# Record a supportability metric if error attributes are being
347
- # overiden .
351
+ # overridden .
348
352
if "error.class" in self .agent_attributes :
349
353
transaction ._record_supportability ("Supportability/SpanEvent/Errors/Dropped" )
350
354
@@ -353,19 +357,31 @@ def _observe_exception(self, exc_info=None, ignore=None, expected=None, status_c
353
357
self ._add_agent_attribute ("error.message" , message )
354
358
self ._add_agent_attribute ("error.expected" , is_expected )
355
359
356
- return fullname , message , tb , is_expected
360
+ return fullname , message , message_raw , tb , is_expected
357
361
358
362
def notice_error (self , error = None , attributes = None , expected = None , ignore = None , status_code = None ):
359
363
attributes = attributes if attributes is not None else {}
360
364
365
+ # If no exception details provided, use current exception.
366
+
367
+ # Pull from sys.exc_info if no exception is passed
368
+ if not error or None in error :
369
+ error = sys .exc_info ()
370
+
371
+ # If no exception to report, exit
372
+ if not error or None in error :
373
+ return
374
+
375
+ exc , value , tb = error
376
+
361
377
recorded = self ._observe_exception (
362
378
error ,
363
379
ignore = ignore ,
364
380
expected = expected ,
365
381
status_code = status_code ,
366
382
)
367
383
if recorded :
368
- fullname , message , tb , is_expected = recorded
384
+ fullname , message , message_raw , tb , is_expected = recorded
369
385
transaction = self .transaction
370
386
settings = transaction and transaction .settings
371
387
@@ -392,16 +408,45 @@ def notice_error(self, error=None, attributes=None, expected=None, ignore=None,
392
408
)
393
409
custom_params = {}
394
410
395
- if settings and settings .code_level_metrics and settings .code_level_metrics .enabled :
396
- source = extract_code_from_traceback (tb )
397
- else :
398
- source = None
411
+ # Extract additional details about the exception
412
+
413
+ source = None
414
+ error_group_name = None
415
+ if settings :
416
+ if settings .code_level_metrics and settings .code_level_metrics .enabled :
417
+ source = extract_code_from_traceback (tb )
418
+
419
+ if settings .error_collector and settings .error_collector .error_group_callback is not None :
420
+ try :
421
+ # Call callback to obtain error group name
422
+ input_attributes = {}
423
+ input_attributes .update (transaction ._custom_params )
424
+ input_attributes .update (attributes )
425
+ error_group_name_raw = settings .error_collector .error_group_callback (value , {
426
+ "traceback" : tb ,
427
+ "error.class" : exc ,
428
+ "error.message" : message_raw ,
429
+ "error.expected" : is_expected ,
430
+ "custom_params" : input_attributes ,
431
+ "transactionName" : getattr (transaction , "name" , None ),
432
+ "response.status" : getattr (transaction , "_response_code" , None ),
433
+ "request.method" : getattr (transaction , "_request_method" , None ),
434
+ "request.uri" : getattr (transaction , "_request_uri" , None ),
435
+ })
436
+ if error_group_name_raw :
437
+ _ , error_group_name = process_user_attribute ("error.group.name" , error_group_name_raw )
438
+ if error_group_name is None or not isinstance (error_group_name , six .string_types ):
439
+ raise ValueError ("Invalid attribute value for error.group.name. Expected string, got: %s" % repr (error_group_name_raw ))
440
+ except Exception :
441
+ _logger .error ("Encountered error when calling error group callback:\n %s" , "" .join (traceback .format_exception (* sys .exc_info ())))
442
+ error_group_name = None
399
443
400
444
transaction ._create_error_node (
401
445
settings ,
402
446
fullname ,
403
447
message ,
404
448
is_expected ,
449
+ error_group_name ,
405
450
custom_params ,
406
451
self .guid ,
407
452
tb ,
@@ -634,6 +679,7 @@ def get_service_linking_metadata(application=None, settings=None):
634
679
if not settings :
635
680
if application is None :
636
681
from newrelic .api .application import application_instance
682
+
637
683
application = application_instance (activate = False )
638
684
639
685
if application is not None :
0 commit comments