diff --git a/CHANGELOG.md b/CHANGELOG.md index 9130b344751..d8e875da0e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#4935](https://github.com/open-telemetry/opentelemetry-python/pull/4935)) - `opentelemetry-sdk`: upgrade vendored OTel configuration schema from v1.0.0-rc.3 to v1.0.0 ([#4965](https://github.com/open-telemetry/opentelemetry-python/pull/4965)) +- Further improve compatibility with other logging libraries that override + `LogRecord.getMessage()` in order to customize message formatting + ([#4327](https://github.com/open-telemetry/opentelemetry-python/pull/4327)) ## Version 1.40.0/0.61b0 (2026-03-04) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py index 9029f867a7e..973c164f5ea 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/_logs/_internal/__init__.py @@ -39,7 +39,7 @@ get_logger, get_logger_provider, ) -from opentelemetry.attributes import _VALID_ANY_VALUE_TYPES, BoundedAttributes +from opentelemetry.attributes import BoundedAttributes from opentelemetry.context import get_current from opentelemetry.context.context import Context from opentelemetry.metrics import MeterProvider, get_meter_provider @@ -572,25 +572,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. diff --git a/opentelemetry-sdk/tests/logs/test_export.py b/opentelemetry-sdk/tests/logs/test_export.py index 12909b2e225..b79ebde8ba4 100644 --- a/opentelemetry-sdk/tests/logs/test_export.py +++ b/opentelemetry-sdk/tests/logs/test_export.py @@ -292,8 +292,8 @@ def test_simple_log_record_processor_different_msg_types(self): "Temperature hits high 420 C in Hyderabad", "CRITICAL", ), - (["list", "of", "strings"], "WARN"), - ({"key": "value"}, "ERROR"), + ("['list', 'of', 'strings']", "WARN"), + ("{'key': 'value'}", "ERROR"), ] emitted = [ (item.log_record.body, item.log_record.severity_text) @@ -307,8 +307,7 @@ def test_simple_log_record_processor_different_msg_types(self): def test_simple_log_record_processor_custom_single_obj(self): """ - Tests that special-case handling for logging a single non-string object - is correctly applied. + Tests that logging a single non-string object uses getMessage """ exporter = InMemoryLogRecordExporter() log_record_processor = BatchLogRecordProcessor(exporter) @@ -334,9 +333,7 @@ def test_simple_log_record_processor_custom_single_obj(self): 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: - # - normally getMessage would stringify the object and bypass formatting - # - SPECIAL CASE: bypass stringification as well to keep the raw object + # non-string msg with no args - getMessage stringifies the object and bypasses formatting logger.warning(["a non-string with a percent-s", "%s"]) log_record_processor.shutdown() @@ -345,7 +342,7 @@ def test_simple_log_record_processor_custom_single_obj(self): ("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"]), + ("['a non-string with a percent-s', '%s']"), ] for emitted, expected in zip(finished_logs, expected): self.assertEqual(emitted.log_record.body, expected)