Description
Tracer Version(s)
3.4.1
Python Version(s)
Python 3.12.0
Pip Version(s)
pip 23.2.1
Bug Report
String Formatting Issue in DDTelemetryLogHandler
Issue Summary
A TypeError: not all arguments converted during string formatting
exception occurred in the DDTelemetryLogHandler.emit()
method when attempting to format log messages using the %
operator with record.msg
and record.args
:
self.telemetry_writer.add_error(1, record.msg % record.args, full_file_name, record.lineno)
This issue happened when I updated the following libraries:
- Python 3.9.13 -> 3.12.0
- Django 4.1.9 -> 5.2
- ddtrace 2.12.2 -> 3.4.1
Root Cause Analysis
The error occurs because of a mismatch between format specifiers in record.msg
and the arguments in record.args
. This mismatch can happen in several scenarios:
-
Tuple Handling: When
record.args
is already a tuple, direct application using%
can cause errors because the tuple is not unpacked correctly. -
Format Specifier Mismatch: If there are more or fewer format specifiers (
%s
,%d
, etc.) in the message than there are arguments provided. -
Missing Arguments: When a format specifier exists in the message, but no corresponding argument is provided.
-
Non-Iterable Value: If
record.args
is a non-iterable value when multiple format specifiers exist inrecord.msg
-
Arguments Mismatch: If there's a mismatch between the number of specifiers and provided arguments
-
Unexpected type: If
record.args
is an unexpected type that causes issues with the formatting mechanism
Solution Implemented
The issue was resolved by replacing the direct formatting with the LogRecord.getMessage()
method:
self.telemetry_writer.add_error(1, record.getMessage(), full_file_name, record.lineno)
def getMessage(self):
"""
Return the message for this LogRecord.
Return the message for this LogRecord after merging any user-supplied
arguments with the message.
"""
msg = str(self.msg)
if self.args:
msg = msg % self.args
return msg
This method internally manages all string formatting edge cases and safely returns a properly formatted log message. If using Python 3.12, the getMessage
method is already available in the standard logging package. However, a more robust solution should ensure consistent handling of these edge cases across different Python versions. I believe we can approach it with something like this:
try:
iter(possibly_iterable)
except TypeError:
# Handle non-iterable case
iterable = [possibly_iterable]
else:
# Handle iterable case
iterable = possibly_iterable
Or could we implement a solution to maintain multiple versions for compatibility?
Reproduction Code
No response
Error Logs
local-gameserver | Traceback (most recent call last):
local-gameserver | File "/usr/local/lib/python3.12/threading.py", line 1052, in _bootstrap_inner
local-gameserver | self.run()
local-gameserver | File "/usr/local/lib/python3.12/threading.py", line 989, in run
local-gameserver | self._target(*self._args, **self._kwargs)
local-gameserver | File "/usr/local/lib/python3.12/site-packages/concurrent_log_handler/queue.py", line 54, in _monitor
local-gameserver | super()._monitor() # type: ignore[misc]
local-gameserver | ^^^^^^^^^^^^^^^^^^
local-gameserver | File "/usr/local/lib/python3.12/logging/handlers.py", line 1585, in _monitor
local-gameserver | self.handle(record)
local-gameserver | File "/usr/local/lib/python3.12/logging/handlers.py", line 1566, in handle
local-gameserver | handler.handle(record)
local-gameserver | File "/usr/local/lib/python3.12/logging/init.py", line 1028, in handle
local-gameserver | self.emit(record)
local-gameserver | File "/usr/local/lib/python3.12/site-packages/ddtrace/internal/telemetry/logging.py", line 24, in emit
local-gameserver | self.telemetry_writer.add_error(1, record.msg % record.args, full_file_name, record.lineno)
local-gameserver | ~~~~~~~~~~~^~~~~~~~~~~~~
local-gameserver | TypeError: not all arguments converted during string formatting
Libraries in Use
aiohappyeyeballs==2.6.1
aiohttp==3.11.16
aiosignal==1.3.2
amqp==5.3.1
anyio==4.9.0
asgiref==3.8.1
attrs==25.3.0
bcrypt==4.3.0
billiard==4.2.1
boto3==1.37.34
botocore==1.37.34
bytecode==0.16.2
cachetools==5.5.2
cbor2==5.6.5
celery==5.5.1
celery-redbeat==2.3.2
certifi==2025.1.31
cffi==1.17.1
charset-normalizer==3.4.1
click==8.1.8
click-didyoumean==0.3.1
click-plugins==1.1.1
click-repl==0.3.0
concurrent-log-handler==0.9.25
cookies==2.2.1
cryptography==44.0.2
datadog==0.51.0
ddtrace==3.4.1
decorator==5.2.1
defusedxml==0.7.1
Deprecated==1.2.18
distlib==0.3.9
Django==5.2
django-annoying==0.10.8
django-datetime-widget==0.9.3
django-extensions==4.1
django-ipware==7.0.1
django-jenkins==0.110.0
django-ratelimit3==1.0.1
django-redis==5.4.0
django-suit==0.2.28
django-tables2==2.7.5
django-uuidfield==0.5.0
envier==0.6.1
filelock==3.18.0
flatbuffers==25.2.10
flower==2.0.1
frozenlist==1.5.0
gevent==24.11.1
google-api-core==2.24.2
google-api-python-client==2.167.0
google-auth==2.39.0
google-auth-httplib2==0.2.0
google-cloud-core==2.4.3
google-cloud-storage==3.1.0
google-crc32c==1.7.1
google-resumable-media==2.7.2
googleapis-common-protos==1.70.0
greenlet==3.2.0
h11==0.14.0
h2==4.2.0
hiredis==3.1.0
hpack==4.1.0
httpcore==1.0.8
httplib2==0.22.0
httpx==0.28.1
humanize==4.12.2
hyperframe==6.1.0
idna==3.10
importlib_metadata==8.6.1
ipaddress==1.0.23
jmespath==1.0.1
kombu==5.5.2
lazy-object-proxy==1.10.0
leaderboard==3.7.3
lxml==5.3.2
maxminddb==2.6.3
mock==5.2.0
multidict==6.4.3
ndg-httpsclient==0.5.1
numpy==2.2.4
oauth2client==4.1.3
oauthlib==3.2.2
opentelemetry-api==1.32.1
pandas==2.2.3
platformdirs==4.3.7
portalocker==3.1.1
prometheus_client==0.21.1
prompt_toolkit==3.0.51
propcache==0.3.1
proto-plus==1.26.1
protobuf==6.30.2
pubnub==10.3.0
pyasn1==0.6.1
pyasn1_modules==0.4.2
pycparser==2.22
pycryptodome==3.22.0
pycryptodomex==3.22.0
PyJWT==2.10.1
pylibmc==1.6.3
pynamodb==6.0.2
pyOpenSSL==25.0.0
pyparsing==3.2.3
python-dateutil==2.9.0.post0
python-ipware==3.0.0
python-oauth2==1.1.1
python-redis-lock==4.0.0
python-social-auth==0.3.6
python3-openid==3.2.0
pytz==2025.2
qless-py==0.11.4
redis==5.2.1
requests==2.32.3
requests-oauthlib==2.0.0
responses==0.4.0
rsa==4.9
s3transfer==0.11.4
setproctitle==1.3.5
setuptools==29.0.1
simplejson==3.20.1
six==1.17.0
sniffio==1.3.1
social-auth-app-django==5.4.3
social-auth-core==4.5.6
South==1.0.2
sqlparse==0.5.3
tenacity==9.1.2
theine==0.5.0
theine_core==0.4.5
titan-sdk==3.0.9
tornado==6.4.2
typing_extensions==4.13.2
tzdata==2025.2
tzlocal==5.3.1
unittest-xml-reporting==3.2.0
uritemplate==4.1.1
urllib3==2.4.0
uwsgidecorators==1.1.0
vine==5.1.0
virtualenv==20.30.0
wcwidth==0.2.13
wheel==0.42.0
wrapt==1.17.2
xmltodict==0.14.2
yarl==1.19.0
zipp==3.21.0
zope.event==5.0
zope.interface==7.2
Operating System
Linux 6c8efc41b480 6.9.8-orbstack-00170-g7b4100b7ced4 #1 SMP Thu Jul 11 03:32:20 UTC 2024 aarch64 GNU/Linux