Skip to content

Commit

Permalink
Capture exception information in log attributes (#2531)
Browse files Browse the repository at this point in the history
  • Loading branch information
lzchen authored Mar 21, 2022
1 parent f7b33c6 commit 97a36ea
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix incorrect installation of some exporter “convenience” packages into
“site-packages/src”
([#2525](https://github.com/open-telemetry/opentelemetry-python/pull/2525))
- Capture exception information as part of log attributes
([#2531](https://github.com/open-telemetry/opentelemetry-python/pull/2531))
- Change OTLPHandler to LoggingHandler
([#2528](https://github.com/open-telemetry/opentelemetry-python/pull/2528))
- Fix delta histogram sum not being reset on collection
Expand Down
22 changes: 21 additions & 1 deletion opentelemetry-sdk/src/opentelemetry/sdk/_logs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import logging
import os
import threading
import traceback
from typing import Any, Callable, Optional, Tuple, Union, cast

from opentelemetry.sdk._logs.severity import SeverityNumber, std_to_otlp
Expand All @@ -28,6 +29,7 @@
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.util import ns_to_iso_str
from opentelemetry.sdk.util.instrumentation import InstrumentationInfo
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace import (
format_span_id,
format_trace_id,
Expand Down Expand Up @@ -319,9 +321,27 @@ def __init__(

@staticmethod
def _get_attributes(record: logging.LogRecord) -> Attributes:
return {
attributes = {
k: v for k, v in vars(record).items() if k not in _RESERVED_ATTRS
}
if record.exc_info is not None:
exc_type = ""
message = ""
stack_trace = ""
exctype, value, tb = record.exc_info
if exctype is not None:
exc_type = exctype.__name__
if value is not None and value.args:
message = value.args[0]
if tb is not None:
# https://github.com/open-telemetry/opentelemetry-specification/blob/9fa7c656b26647b27e485a6af7e38dc716eba98a/specification/trace/semantic_conventions/exceptions.md#stacktrace-representation
stack_trace = "".join(
traceback.format_exception(*record.exc_info)
)
attributes[SpanAttributes.EXCEPTION_TYPE] = exc_type
attributes[SpanAttributes.EXCEPTION_MESSAGE] = message
attributes[SpanAttributes.EXCEPTION_STACKTRACE] = stack_trace
return attributes

def _translate(self, record: logging.LogRecord) -> LogRecord:
timestamp = int(record.created * 1e9)
Expand Down
32 changes: 31 additions & 1 deletion opentelemetry-sdk/tests/logs/test_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging
import unittest
from unittest.mock import Mock

from opentelemetry.sdk import trace
from opentelemetry.sdk._logs import LogEmitter, LoggingHandler
from opentelemetry.sdk._logs.severity import SeverityNumber
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.trace import INVALID_SPAN_CONTEXT


Expand Down Expand Up @@ -77,6 +77,36 @@ def test_log_record_user_attributes(self):
self.assertIsNotNone(log_record)
self.assertEqual(log_record.attributes, {"http.status_code": 200})

def test_log_record_exception(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.exception("Zero Division Error")
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.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_record_trace_correlation(self):
emitter_mock = Mock(spec=LogEmitter)
logger = get_logger(log_emitter=emitter_mock)
Expand Down

0 comments on commit 97a36ea

Please sign in to comment.