Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

- opentelemetry-sdk: use stable code attributes: `code.function` -> `code.function.name`, `code.lineno` -> `code.line.number`, `code.filepath` -> `code.file.path`
([#4508](https://github.com/open-telemetry/opentelemetry-python/pull/4508))
- Fix serialization of extended attributes for logs signal
([#4342](https://github.com/open-telemetry/opentelemetry-python/pull/4342))
- docs: updated and added to the metrics and log examples
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.util import ns_to_iso_str
from opentelemetry.sdk.util.instrumentation import InstrumentationScope
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.semconv._incubating.attributes import code_attributes
from opentelemetry.semconv.attributes import exception_attributes
from opentelemetry.trace import (
format_span_id,
format_trace_id,
Expand Down Expand Up @@ -487,22 +488,24 @@ def _get_attributes(record: logging.LogRecord) -> _ExtendedAttributes:
}

# Add standard code attributes for logs.
attributes[SpanAttributes.CODE_FILEPATH] = record.pathname
attributes[SpanAttributes.CODE_FUNCTION] = record.funcName
attributes[SpanAttributes.CODE_LINENO] = record.lineno
attributes[code_attributes.CODE_FILE_PATH] = record.pathname
attributes[code_attributes.CODE_FUNCTION_NAME] = record.funcName
attributes[code_attributes.CODE_LINE_NUMBER] = record.lineno

if record.exc_info:
exctype, value, tb = record.exc_info
if exctype is not None:
attributes[SpanAttributes.EXCEPTION_TYPE] = exctype.__name__
attributes[exception_attributes.EXCEPTION_TYPE] = (
exctype.__name__
)
if value is not None and value.args:
attributes[SpanAttributes.EXCEPTION_MESSAGE] = str(
attributes[exception_attributes.EXCEPTION_MESSAGE] = str(
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
attributes[SpanAttributes.EXCEPTION_STACKTRACE] = "".join(
traceback.format_exception(*record.exc_info)
# https://opentelemetry.io/docs/specs/semconv/exceptions/exceptions-spans/#stacktrace-representation
attributes[exception_attributes.EXCEPTION_STACKTRACE] = (
"".join(traceback.format_exception(*record.exc_info))
)
return attributes

Expand Down
37 changes: 21 additions & 16 deletions opentelemetry-sdk/tests/logs/test_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
LoggingHandler,
LogRecordProcessor,
)
from opentelemetry.semconv.trace import SpanAttributes
from opentelemetry.semconv._incubating.attributes import code_attributes
from opentelemetry.semconv.attributes import exception_attributes
from opentelemetry.trace import INVALID_SPAN_CONTEXT


Expand Down Expand Up @@ -127,17 +128,19 @@ def test_log_record_user_attributes(self):
self.assertEqual(len(log_record.attributes), 4)
self.assertEqual(log_record.attributes["http.status_code"], 200)
self.assertTrue(
log_record.attributes[SpanAttributes.CODE_FILEPATH].endswith(
log_record.attributes[code_attributes.CODE_FILE_PATH].endswith(
"test_handler.py"
)
)
self.assertEqual(
log_record.attributes[SpanAttributes.CODE_FUNCTION],
log_record.attributes[code_attributes.CODE_FUNCTION_NAME],
"test_log_record_user_attributes",
)
# The line of the log statement is not a constant (changing tests may change that),
# so only check that the attribute is present.
self.assertTrue(SpanAttributes.CODE_LINENO in log_record.attributes)
self.assertTrue(
code_attributes.CODE_LINE_NUMBER in log_record.attributes
)
self.assertTrue(isinstance(log_record.attributes, BoundedAttributes))

def test_log_record_exception(self):
Expand All @@ -156,15 +159,15 @@ def test_log_record_exception(self):
self.assertTrue(isinstance(log_record.body, str))
self.assertEqual(log_record.body, "Zero Division Error")
self.assertEqual(
log_record.attributes[SpanAttributes.EXCEPTION_TYPE],
log_record.attributes[exception_attributes.EXCEPTION_TYPE],
ZeroDivisionError.__name__,
)
self.assertEqual(
log_record.attributes[SpanAttributes.EXCEPTION_MESSAGE],
log_record.attributes[exception_attributes.EXCEPTION_MESSAGE],
"division by zero",
)
stack_trace = log_record.attributes[
SpanAttributes.EXCEPTION_STACKTRACE
exception_attributes.EXCEPTION_STACKTRACE
]
self.assertIsInstance(stack_trace, str)
self.assertTrue("Traceback" in stack_trace)
Expand All @@ -189,15 +192,15 @@ def test_log_record_recursive_exception(self):
self.assertIsNotNone(log_record)
self.assertEqual(log_record.body, "Zero Division Error")
self.assertEqual(
log_record.attributes[SpanAttributes.EXCEPTION_TYPE],
log_record.attributes[exception_attributes.EXCEPTION_TYPE],
ZeroDivisionError.__name__,
)
self.assertEqual(
log_record.attributes[SpanAttributes.EXCEPTION_MESSAGE],
log_record.attributes[exception_attributes.EXCEPTION_MESSAGE],
"division by zero",
)
stack_trace = log_record.attributes[
SpanAttributes.EXCEPTION_STACKTRACE
exception_attributes.EXCEPTION_STACKTRACE
]
self.assertIsInstance(stack_trace, str)
self.assertTrue("Traceback" in stack_trace)
Expand All @@ -219,12 +222,14 @@ def test_log_exc_info_false(self):

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
exception_attributes.EXCEPTION_TYPE, log_record.attributes
)
self.assertNotIn(
exception_attributes.EXCEPTION_MESSAGE, log_record.attributes
)
self.assertNotIn(
SpanAttributes.EXCEPTION_STACKTRACE, log_record.attributes
exception_attributes.EXCEPTION_STACKTRACE, log_record.attributes
)

def test_log_record_exception_with_object_payload(self):
Expand All @@ -246,15 +251,15 @@ def __str__(self):
self.assertTrue(isinstance(log_record.body, str))
self.assertEqual(log_record.body, "CustomException stringified")
self.assertEqual(
log_record.attributes[SpanAttributes.EXCEPTION_TYPE],
log_record.attributes[exception_attributes.EXCEPTION_TYPE],
CustomException.__name__,
)
self.assertEqual(
log_record.attributes[SpanAttributes.EXCEPTION_MESSAGE],
log_record.attributes[exception_attributes.EXCEPTION_MESSAGE],
"CustomException message",
)
stack_trace = log_record.attributes[
SpanAttributes.EXCEPTION_STACKTRACE
exception_attributes.EXCEPTION_STACKTRACE
]
self.assertIsInstance(stack_trace, str)
self.assertTrue("Traceback" in stack_trace)
Expand Down