Skip to content

Commit 9e69c15

Browse files
committed
opentelemetry-sdk: fix serialization of objects in log handler
We should convert to string objects that are not AnyValues because otherwise exporter will fail later in the pipeline. While the export of all AnyValue types is not correct yet, exporter tests expects to being able to handle them and so they are already used in the handler.
1 parent 7090f38 commit 9e69c15

File tree

3 files changed

+60
-4
lines changed

3 files changed

+60
-4
lines changed

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

+42
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,47 @@ 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 CustomObject:
234+
pass
235+
236+
class CustomException(Exception):
237+
def __init__(self, data):
238+
self.data = data
239+
240+
def __str__(self):
241+
return "CustomException stringified"
242+
243+
try:
244+
raise CustomException(CustomObject())
245+
except CustomException as e:
246+
with self.assertLogs(level=logging.ERROR):
247+
logger.exception(e)
248+
249+
log_record = processor.get_log_record(0)
250+
251+
self.assertIsNotNone(log_record)
252+
self.assertTrue(isinstance(log_record.body, str))
253+
self.assertEqual(log_record.body, "CustomException stringified")
254+
self.assertEqual(
255+
log_record.attributes[SpanAttributes.EXCEPTION_TYPE],
256+
CustomException.__name__,
257+
)
258+
self.assertTrue(
259+
"<tests.logs.test_handler.TestLoggingHandler.test_log_record_exception_with_object_payload.<locals>.CustomObject"
260+
in log_record.attributes[SpanAttributes.EXCEPTION_MESSAGE]
261+
)
262+
stack_trace = log_record.attributes[
263+
SpanAttributes.EXCEPTION_STACKTRACE
264+
]
265+
self.assertIsInstance(stack_trace, str)
266+
self.assertTrue("Traceback" in stack_trace)
267+
self.assertTrue("CustomException" in stack_trace)
268+
self.assertTrue("CustomObject" in stack_trace)
269+
self.assertTrue(__file__ in stack_trace)
270+
229271
def test_log_record_trace_correlation(self):
230272
processor, logger = set_up_test_logging(logging.WARNING)
231273

0 commit comments

Comments
 (0)