diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 89432d5b339..cecaaeba4b4 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -491,7 +491,15 @@ def _get_attributes(record: logging.LogRecord) -> Attributes: if exctype is not None: attributes[SpanAttributes.EXCEPTION_TYPE] = exctype.__name__ if value is not None and value.args: - attributes[SpanAttributes.EXCEPTION_MESSAGE] = value.args[0] + if type(value.args[0]) is str: + attributes[SpanAttributes.EXCEPTION_MESSAGE] = value.args[ + 0 + ] + else: + if value.args[0] is not None and value.args[0].args: + attributes[SpanAttributes.EXCEPTION_MESSAGE] = ( + value.args[0].args[0] + ) if tb is not None: # https://github.com/open-telemetry/opentelemetry-specification/blob/9fa7c656b26647b27e485a6af7e38dc716eba98a/specification/trace/semantic_conventions/exceptions.md#stacktrace-representation attributes[SpanAttributes.EXCEPTION_STACKTRACE] = "".join( diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py index 3886c9996a9..c276031ca07 100644 --- a/opentelemetry-sdk/tests/logs/test_handler.py +++ b/opentelemetry-sdk/tests/logs/test_handler.py @@ -137,7 +137,7 @@ def test_log_record_user_attributes(self): # so only check that the attribute is present. self.assertTrue(SpanAttributes.CODE_LINENO in log_record.attributes) self.assertTrue(isinstance(log_record.attributes, BoundedAttributes)) - + def test_log_record_exception(self): """Exception information will be included in attributes""" processor, logger = set_up_test_logging(logging.ERROR) @@ -169,6 +169,37 @@ def test_log_record_exception(self): self.assertTrue("division by zero" in stack_trace) self.assertTrue(__file__ in stack_trace) + def test_log_record_recursive_exception(self): + """Exception information will be included in attributes even though it is recursive""" + processor, logger = set_up_test_logging(logging.ERROR) + + try: + raise ZeroDivisionError(ZeroDivisionError("division by zero")) + except ZeroDivisionError: + with self.assertLogs(level=logging.ERROR): + logger.exception("Zero Division Error") + + log_record = processor.get_log_record(0) + + self.assertIsNotNone(log_record) + self.assertEqual(log_record.body, "Zero Division Error") + self.assertEqual( + log_record.attributes[SpanAttributes.EXCEPTION_TYPE], + ZeroDivisionError.__name__, + ) + self.assertEqual( + log_record.attributes[SpanAttributes.EXCEPTION_MESSAGE], + "division by zero", + ) + stack_trace = log_record.attributes[ + SpanAttributes.EXCEPTION_STACKTRACE + ] + self.assertIsInstance(stack_trace, str) + self.assertTrue("Traceback" in stack_trace) + self.assertTrue("ZeroDivisionError" in stack_trace) + self.assertTrue("division by zero" in stack_trace) + self.assertTrue(__file__ in stack_trace) + def test_log_exc_info_false(self): """Exception information will not be included in attributes""" processor, logger = set_up_test_logging(logging.NOTSET)