Repository: open-telemetry/opentelemetry-python

Issue:
opentelemetry.sdk._logs.LoggingHandler panics when exc_info is set to False for logger.error
**Describe your environment** Describe any aspect of your environment relevant to the problem, including your Python version, [platform](https://docs.python.org/3/library/platform.html), version numbers of installed dependencies, information about your cloud hosting provider, etc. If you're reporting a problem with a specific version of a library in this repo, please check whether the problem has been fixed on main.

**Steps to reproduce**
Describe exactly how to reproduce the error. Include a code sample if applicable.
```python
import logging
from opentelemetry.sdk._logs import LogEmitter, LoggingHandler
from unittest.mock import Mock

logger = logging.getLogger(__name__)
log_emitter= Mock(spec=LogEmitter)
handler = LoggingHandler(level=logging.NOTSET, log_emitter=log_emitter)
logger.addHandler(handler)
logger.error("some message", exc_info=False) # will raise exception: cannot unpack non-iterable bool object

```
**What is the expected behavior?**
What did you expect to see?

It should log error message without throwing exception

**What is the actual behavior?**
What did you see instead?

It throws exception
```python
Exception has occurred: TypeError
cannot unpack non-iterable bool object
```

**Additional context**
Python's logger interface allows non-Exception type to be passed in through `exc_info` [see also](https://docs.python.org/3/library/logging.html#logging.Logger.debug)
> If exc_info does not evaluate as false, it causes exception information to be added to the logging message. If an exception tuple (in the format returned by [sys.exc_info()](https://docs.python.org/3/library/sys.html#sys.exc_info)) or an exception instance is provided, it is used; otherwise, sys.exc_info() is called to get the exception information.
Add any other context about the problem here.

So `exc_info` is replaced by `logging` module *only if* it evaluates `True`, but we only check if `exc_info is not None` here in handler, which would attemp to unpack variable that evaluated as `False` later, that is not replaced by `logging` module.
opentelemetry.sdk._logs.LoggingHandler panics when exc_info is set to False for logger.error
**Describe your environment** Describe any aspect of your environment relevant to the problem, including your Python version, [platform](https://docs.python.org/3/library/platform.html), version numbers of installed dependencies, information about your cloud hosting provider, etc. If you're reporting a problem with a specific version of a library in this repo, please check whether the problem has been fixed on main.

**Steps to reproduce**
Describe exactly how to reproduce the error. Include a code sample if applicable.
```python
import logging
from opentelemetry.sdk._logs import LogEmitter, LoggingHandler
from unittest.mock import Mock

logger = logging.getLogger(__name__)
log_emitter= Mock(spec=LogEmitter)
handler = LoggingHandler(level=logging.NOTSET, log_emitter=log_emitter)
logger.addHandler(handler)
logger.error("some message", exc_info=False) # will raise exception: cannot unpack non-iterable bool object

```
**What is the expected behavior?**
What did you expect to see?

It should log error message without throwing exception

**What is the actual behavior?**
What did you see instead?

It throws exception
```python
Exception has occurred: TypeError
cannot unpack non-iterable bool object
```

**Additional context**
Python's logger interface allows non-Exception type to be passed in through `exc_info` [see also](https://docs.python.org/3/library/logging.html#logging.Logger.debug)
> If exc_info does not evaluate as false, it causes exception information to be added to the logging message. If an exception tuple (in the format returned by [sys.exc_info()](https://docs.python.org/3/library/sys.html#sys.exc_info)) or an exception instance is provided, it is used; otherwise, sys.exc_info() is called to get the exception information.
Add any other context about the problem here.

So `exc_info` is replaced by `logging` module *only if* it evaluates `True`, but we only check if `exc_info is not None` here in handler, which would attemp to unpack variable that evaluated as `False` later, that is not replaced by `logging` module.


Discussion:

Resolving patch:
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py
--- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py
@@ -321,7 +321,7 @@ def _get_attributes(record: logging.LogRecord) -> Attributes:
         attributes = {
             k: v for k, v in vars(record).items() if k not in _RESERVED_ATTRS
         }
-        if record.exc_info is not None:
+        if record.exc_info:
             exc_type = ""
             message = ""
             stack_trace = ""


Test patch:
diff --git a/opentelemetry-sdk/tests/logs/test_handler.py b/opentelemetry-sdk/tests/logs/test_handler.py
--- a/opentelemetry-sdk/tests/logs/test_handler.py
+++ b/opentelemetry-sdk/tests/logs/test_handler.py
@@ -107,6 +107,27 @@ def test_log_record_exception(self):
         self.assertTrue("division by zero" in stack_trace)
         self.assertTrue(__file__ in stack_trace)
 
+    def test_log_exc_info_false(self):
+        """Exception information will be included in attributes"""
+        emitter_mock = Mock(spec=LogEmitter)
+        logger = get_logger(log_emitter=emitter_mock)
+        try:
+            raise ZeroDivisionError("division by zero")
+        except ZeroDivisionError:
+            logger.error("Zero Division Error", exc_info=False)
+        args, _ = emitter_mock.emit.call_args_list[0]
+        log_record = args[0]
+
+        self.assertIsNotNone(log_record)
+        self.assertEqual(log_record.body, "Zero Division Error")
+        self.assertNotIn(SpanAttributes.EXCEPTION_TYPE, log_record.attributes)
+        self.assertNotIn(
+            SpanAttributes.EXCEPTION_MESSAGE, log_record.attributes
+        )
+        self.assertNotIn(
+            SpanAttributes.EXCEPTION_STACKTRACE, log_record.attributes
+        )
+
     def test_log_record_trace_correlation(self):
         emitter_mock = Mock(spec=LogEmitter)
         logger = get_logger(log_emitter=emitter_mock)