Skip to content

Commit f1059a7

Browse files
authored
Merge branch 'main' into bump-semconv-1320
2 parents 796fec8 + 0556052 commit f1059a7

File tree

4 files changed

+56
-5
lines changed

4 files changed

+56
-5
lines changed

CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
- Bump semantic conventions to 1.32.0
2323
([#4530](https://github.com/open-telemetry/opentelemetry-python/pull/4530))
2424
- Fix ExplicitBucketHistogramAggregation to handle multiple explicit bucket boundaries advisories
25-
([#4521](https://github.com/open-telemetry/opentelemetry-python/pull/4521))
25+
([#4521](https://github.com/open-telemetry/opentelemetry-python/pull/4521))
26+
- opentelemetry-sdk: Fix serialization of objects in log handler
27+
([#4528](https://github.com/open-telemetry/opentelemetry-python/pull/4528))
2628

2729
## Version 1.31.0/0.52b0 (2025-03-12)
2830

opentelemetry-api/src/opentelemetry/attributes/__init__.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,24 @@
1616
import threading
1717
from collections import OrderedDict
1818
from collections.abc import MutableMapping
19-
from typing import Optional, Sequence, Tuple, Union
19+
from typing import Mapping, Optional, Sequence, Tuple, Union
2020

2121
from opentelemetry.util import types
2222

2323
# bytes are accepted as a user supplied value for attributes but
2424
# decoded to strings internally.
2525
_VALID_ATTR_VALUE_TYPES = (bool, str, bytes, int, float)
26+
# AnyValue possible values
27+
_VALID_ANY_VALUE_TYPES = (
28+
type(None),
29+
bool,
30+
bytes,
31+
int,
32+
float,
33+
str,
34+
Sequence,
35+
Mapping,
36+
)
2637

2738

2839
_logger = logging.getLogger(__name__)

opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
get_logger_provider,
3737
std_to_otel,
3838
)
39-
from opentelemetry.attributes import BoundedAttributes
39+
from opentelemetry.attributes import _VALID_ANY_VALUE_TYPES, BoundedAttributes
4040
from opentelemetry.sdk.environment_variables import (
4141
OTEL_ATTRIBUTE_COUNT_LIMIT,
4242
OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT,
@@ -523,8 +523,11 @@ def _translate(self, record: logging.LogRecord) -> LogRecord:
523523
# itself instead of its string representation.
524524
# For more background, see: https://github.com/open-telemetry/opentelemetry-python/pull/4216
525525
if not record.args and not isinstance(record.msg, str):
526-
# no args are provided so it's *mostly* safe to use the message template as the body
527-
body = record.msg
526+
# if record.msg is not a value we can export, cast it to string
527+
if not isinstance(record.msg, _VALID_ANY_VALUE_TYPES):
528+
body = str(record.msg)
529+
else:
530+
body = record.msg
528531
else:
529532
body = record.getMessage()
530533

opentelemetry-sdk/tests/logs/test_handler.py

+35
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def test_log_record_exception(self):
153153
log_record = processor.get_log_record(0)
154154

155155
self.assertIsNotNone(log_record)
156+
self.assertTrue(isinstance(log_record.body, str))
156157
self.assertEqual(log_record.body, "Zero Division Error")
157158
self.assertEqual(
158159
log_record.attributes[SpanAttributes.EXCEPTION_TYPE],
@@ -226,6 +227,40 @@ def test_log_exc_info_false(self):
226227
SpanAttributes.EXCEPTION_STACKTRACE, log_record.attributes
227228
)
228229

230+
def test_log_record_exception_with_object_payload(self):
231+
processor, logger = set_up_test_logging(logging.ERROR)
232+
233+
class CustomException(Exception):
234+
def __str__(self):
235+
return "CustomException stringified"
236+
237+
try:
238+
raise CustomException("CustomException message")
239+
except CustomException as exception:
240+
with self.assertLogs(level=logging.ERROR):
241+
logger.exception(exception)
242+
243+
log_record = processor.get_log_record(0)
244+
245+
self.assertIsNotNone(log_record)
246+
self.assertTrue(isinstance(log_record.body, str))
247+
self.assertEqual(log_record.body, "CustomException stringified")
248+
self.assertEqual(
249+
log_record.attributes[SpanAttributes.EXCEPTION_TYPE],
250+
CustomException.__name__,
251+
)
252+
self.assertEqual(
253+
log_record.attributes[SpanAttributes.EXCEPTION_MESSAGE],
254+
"CustomException message",
255+
)
256+
stack_trace = log_record.attributes[
257+
SpanAttributes.EXCEPTION_STACKTRACE
258+
]
259+
self.assertIsInstance(stack_trace, str)
260+
self.assertTrue("Traceback" in stack_trace)
261+
self.assertTrue("CustomException" in stack_trace)
262+
self.assertTrue(__file__ in stack_trace)
263+
229264
def test_log_record_trace_correlation(self):
230265
processor, logger = set_up_test_logging(logging.WARNING)
231266

0 commit comments

Comments
 (0)