Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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 @@ -41,6 +41,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- `opentelemetry-instrumentation-boto`: Remove instrumentation
([#4303](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4303))
- `opentelemetry-instrumentation-logging`: Use `LogRecord.getMessage()` to format and extract each log record's body text to more closely match the expected usage of the logging system. Previously, in some cases the `LogRecord.msg` attribute was accessed directly.
Comment thread
pR0Ps marked this conversation as resolved.
Outdated
([#4372](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/4372))

## Version 1.40.0/0.61b0 (2026-03-04)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
get_logger,
get_logger_provider,
)
from opentelemetry.attributes import _VALID_ANY_VALUE_TYPES
from opentelemetry.context import get_current
from opentelemetry.semconv._incubating.attributes import code_attributes
from opentelemetry.semconv.attributes import exception_attributes
Expand Down Expand Up @@ -168,25 +167,7 @@ def _translate(self, record: logging.LogRecord) -> LogRecord:
if self.formatter:
body = self.format(record)
else:
# `record.getMessage()` uses `record.msg` as a template to format
# `record.args` into. There is a special case in `record.getMessage()`
# where it will only attempt formatting if args are provided,
# otherwise, it just stringifies `record.msg`.
#
# Since the OTLP body field has a type of 'any' and the logging module
# is sometimes used in such a way that objects incorrectly end up
# set as record.msg, in those cases we would like to bypass
# `record.getMessage()` completely and set the body to the object
# itself instead of its string representation.
# For more background, see: https://github.com/open-telemetry/opentelemetry-python/pull/4216
if not record.args and not isinstance(record.msg, str):
# if record.msg is not a value we can export, cast it to string
if not isinstance(record.msg, _VALID_ANY_VALUE_TYPES):
body = str(record.msg)
else:
body = record.msg
else:
body = record.getMessage()
body = record.getMessage()

# related to https://github.com/open-telemetry/opentelemetry-python/issues/3548
# Severity Text = WARN as defined in https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#displaying-severity.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,43 @@ def test_log_body_is_always_string_with_formatter(self):

logger.removeHandler(handler)

def test_simple_log_record_processor_custom_single_obj(self):
"""
Tests that logging a single non-string object uses getMessage
"""
processor, logger, handler = set_up_test_logging(logging.WARNING)

# NOTE: the behaviour of `record.getMessage` is detailed in the
# `logging.Logger.debug` documentation:
# > The msg is the message format string, and the args are the arguments
# > which are merged into msg using the string formatting operator. [...]
# > No % formatting operation is performed on msg when no args are supplied.

# This test uses the presence of '%s' in the first arg to determine if
# formatting was applied

# string msg with no args - getMessage bypasses formatting and sets the string directly
logger.warning("a string with a percent-s: %s")
# string msg with args - getMessage formats args into the msg
logger.warning("a string with a percent-s: %s", "and arg")
# non-string msg with args - getMessage stringifies msg and formats args into it
logger.warning(["a non-string with a percent-s", "%s"], "and arg")
# non-string msg with no args - getMessage stringifies the object and bypasses formatting
logger.warning(["a non-string with a percent-s", "%s"])

logger.removeHandler(handler)

assert processor.emit_count() == 4
expected = [
("a string with a percent-s: %s"),
("a string with a percent-s: and arg"),
("['a non-string with a percent-s', 'and arg']"),
("['a non-string with a percent-s', '%s']"),
]
for index, msg in enumerate(expected):
record = processor.get_log_record(index)
self.assertEqual(record.log_record.body, msg)

@patch.dict(os.environ, {"OTEL_SDK_DISABLED": "true"})
def test_handler_root_logger_with_disabled_sdk_does_not_go_into_recursion_error(
self,
Expand Down
Loading