Skip to content

[Bug] TypeError crashes start_forever() when logging unknown exceptions in stream.py #75

@ysdfxp

Description

@ysdfxp

Description
When using DingTalkStreamClient and running client.start_forever(), if the underlying WebSocket encounters a network issue (such as TimeoutError during the opening handshake or HTTP 502 Bad Gateway from the DingTalk open connection gateway), the SDK attempts to catch and log the exception.

However, the logging statement in dingtalk_stream/stream.py uses incorrect string formatting parameters, which raises a TypeError: not all arguments converted during string formatting. This TypeError crashes the internal asyncio event loop and forces client.start_forever() to exit completely, rather than cleanly catching the network exception and attempting a reconnection.

Error Logs
Here is the core traceback showing the logging module crashing due to the incorrectly formatted logger call:
Traceback (most recent call last):

... (WebSockets TimeoutError or HTTP 502 traceback here) ...

File "/usr/local/lib/python3.12/site-packages/dingtalk_stream/stream.py", line 74, in start
async with websockets.connect(uri) as websocket:
File "/usr/local/lib/python3.12/site-packages/websockets/asyncio/client.py", line 590, in aenter
return await self
File "/usr/local/lib/python3.12/site-packages/websockets/asyncio/client.py", line 581, in await_impl
raise TimeoutError("timed out during opening handshake") from exc
TimeoutError: timed out during opening handshake

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "/usr/local/lib/python3.12/logging/init.py", line 1160, in emit
msg = self.format(record)
File "/usr/local/lib/python3.12/logging/init.py", line 999, in format
return fmt.format(record)
File "/usr/local/lib/python3.12/logging/init.py", line 703, in format
record.message = record.getMessage()
File "/usr/local/lib/python3.12/logging/init.py", line 392, in getMessage
msg = msg % self.args
TypeError: not all arguments converted during string formatting
Call stack:

... (Django/App call stack) ...

File "/app/ai/management/commands/dingtalk_stream.py", line 60, in handle
client.start_forever()
File "/usr/local/lib/python3.12/site-packages/dingtalk_stream/stream.py", line 142, in start_forever
asyncio.run(self.start())

... (Asyncio call stack) ...

File "/usr/local/lib/python3.12/asyncio/events.py", line 88, in _run
self._context.run(self._callback, *self._args)
File "/usr/local/lib/python3.12/site-packages/dingtalk_stream/stream.py", line 89, in start
self.logger.exception('unknown exception', e)
Message: 'unknown exception'
Arguments: (TimeoutError('timed out during opening handshake'),)

Root Cause
In dingtalk_stream/stream.py (around line 89), there is the following code snippet:

python
except Exception as e:
self.logger.exception('unknown exception', e)
The standard Python logging.exception() method expects the first argument to be a message string with formatting placeholders (like %s). Because the string 'unknown exception' does not contain any format specifiers, passing e as the second argument causes the logging module to raise a TypeError when it evaluates msg % self.args.

Since this occurs inside an asyncio task, the unhandled TypeError causes the entire event loop to crash, breaking the persistence of client.start_forever().

Suggested Fix
Update the logging statement in stream.py to use proper string formatting, or let the exception() method handle the traceback implicitly:

python

Option 1: Use proper formatting placeholders

self.logger.exception('unknown exception: %s', e)

Option 2: Let exception() handle the stack trace automatically without passing e as a positional argument

self.logger.exception('unknown exception')

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions