Skip to content

Commit ccb6138

Browse files
authored
Merge pull request #2430 from Exirel/common-connection-errors-output
irc: Improve output for common connection errors
2 parents 6e64c96 + c4a34f7 commit ccb6138

File tree

3 files changed

+123
-19
lines changed

3 files changed

+123
-19
lines changed

sopel/irc/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,13 +448,19 @@ def on_message_sent(self, raw: str) -> None:
448448
)
449449
self.dispatch(pretrigger)
450450

451+
@deprecated(
452+
'This method was used to log errors with asynchat; '
453+
'use logging.getLogger("sopel.exception") instead.',
454+
version='8.0',
455+
removed_in='9.0',
456+
)
451457
def on_error(self) -> None:
452458
"""Handle any uncaptured error in the bot itself."""
453459
LOGGER.error('Fatal error in core, please review exceptions log.')
454460

455461
err_log = logging.getLogger('sopel.exceptions')
456462
err_log.error(
457-
'Fatal error in core, handle_error() was called.\n'
463+
'Fatal error in core, bot.on_error() was called.\n'
458464
'Last Line:\n%s',
459465
self.last_raw_line,
460466
)

sopel/irc/abstract_backends.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from __future__ import annotations
1616

1717
import abc
18+
import logging
1819
from typing import Optional, TYPE_CHECKING
1920

2021
from .utils import safe
@@ -41,6 +42,17 @@ def __init__(self, bot: AbstractBot):
4142
def is_connected(self) -> bool:
4243
"""Tell if the backend is connected or not."""
4344

45+
def log_exception(self) -> None:
46+
"""Log an exception to ``sopel.exceptions``.
47+
48+
The IRC backend must use this method to log any exception that isn't
49+
caught by the bot itself (i.e. while handling messages), such as
50+
connection errors, SSL errors, etc.
51+
"""
52+
err_log = logging.getLogger('sopel.exceptions')
53+
err_log.exception('Exception in core')
54+
err_log.error('----------------------------------------')
55+
4456
@abc.abstractmethod
4557
def on_irc_error(self, pretrigger: PreTrigger) -> None:
4658
"""Action to perform when the server sends an error event.

sopel/irc/backends.py

Lines changed: 104 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import asyncio
1818
import logging
1919
import signal
20+
import socket
2021
import ssl
2122
import threading
2223
from typing import Dict, List, Optional, Tuple, TYPE_CHECKING
@@ -56,7 +57,7 @@ def __init__(
5657
):
5758
super().__init__(bot)
5859

59-
def is_connected(self) -> False:
60+
def is_connected(self) -> bool:
6061
"""Check if the backend is connected to an IRC server.
6162
6263
**Always returns False:** This backend type is never connected.
@@ -377,40 +378,124 @@ def get_connection_kwargs(self) -> Dict:
377378
'local_addr': self._source_address,
378379
}
379380

380-
async def _run_forever(self) -> None:
381-
self._loop = asyncio.get_running_loop()
382-
383-
# register signal handlers
384-
for quit_signal in QUIT_SIGNALS:
385-
self._loop.add_signal_handler(quit_signal, self._signal_quit)
386-
for restart_signal in RESTART_SIGNALS:
387-
self._loop.add_signal_handler(restart_signal, self._signal_restart)
381+
async def _connect_to_server(
382+
self, **connection_kwargs
383+
) -> Tuple[
384+
Optional[asyncio.StreamReader],
385+
Optional[asyncio.StreamWriter],
386+
]:
387+
reader: Optional[asyncio.StreamReader] = None
388+
writer: Optional[asyncio.StreamWriter] = None
388389

389390
# open connection
390391
try:
391-
self._reader, self._writer = await asyncio.open_connection(
392-
**self.get_connection_kwargs(),
392+
reader, writer = await asyncio.open_connection(
393+
**connection_kwargs,
394+
)
395+
396+
# SSL Errors (certificate verification and generic SSL errors)
397+
except ssl.SSLCertVerificationError as err:
398+
LOGGER.error(
399+
'Unable to connect due to '
400+
'SSL certificate verification failure: %s',
401+
err,
393402
)
394-
except ssl.SSLError:
395-
LOGGER.exception('Unable to connect due to SSL error.')
403+
self.log_exception()
396404
# tell the bot to quit without restart
397405
self.bot.hasquit = True
398406
self.bot.wantsrestart = False
399-
return
400-
except Exception:
401-
LOGGER.exception('Unable to connect.')
407+
except ssl.SSLError as err:
408+
LOGGER.error('Unable to connect due to an SSL error: %s', err)
409+
self.log_exception()
410+
# tell the bot to quit without restart
411+
self.bot.hasquit = True
412+
self.bot.wantsrestart = False
413+
414+
# Specific connection error (invalid address and timeout)
415+
except socket.gaierror as err:
416+
LOGGER.error(
417+
'Unable to connect due to invalid IRC server address: %s',
418+
err,
419+
)
420+
LOGGER.error(
421+
'You should verify that "%s:%s" is the correct address '
422+
'to connect to the IRC server.',
423+
connection_kwargs.get('host'),
424+
connection_kwargs.get('port'),
425+
)
426+
self.log_exception()
427+
# tell the bot to quit without restart
428+
self.bot.hasquit = True
429+
self.bot.wantsrestart = False
430+
except TimeoutError as err:
431+
LOGGER.error('Unable to connect due to a timeout: %s', err)
432+
self.log_exception()
433+
# tell the bot to quit without restart
434+
self.bot.hasquit = True
435+
self.bot.wantsrestart = False
436+
437+
# Generic connection error
438+
except ConnectionError as err:
439+
LOGGER.error('Unable to connect: %s', err)
440+
self.log_exception()
441+
# tell the bot to quit without restart
442+
self.bot.hasquit = True
443+
self.bot.wantsrestart = False
444+
445+
# Generic OSError (used for any unspecific connection error)
446+
except OSError as err:
447+
LOGGER.error('Unable to connect: %s', err)
448+
LOGGER.error(
449+
'You should verify that "%s:%s" is the correct address '
450+
'to connect to the IRC server.',
451+
connection_kwargs.get('host'),
452+
connection_kwargs.get('port'),
453+
)
454+
self.log_exception()
455+
# tell the bot to quit without restart
456+
self.bot.hasquit = True
457+
self.bot.wantsrestart = False
458+
459+
# Unexpected error
460+
except Exception as err:
461+
LOGGER.error(
462+
'Unable to connect due to an unexpected error: %s',
463+
err,
464+
)
465+
self.log_exception()
402466
# until there is a way to prevent an infinite loop of connection
403467
# error and reconnect, we have to tell the bot to quit here
404468
# TODO: prevent infinite connection failure loop
405469
self.bot.hasquit = True
406470
self.bot.wantsrestart = False
407-
return
408471

409-
self._connected = True
472+
return reader, writer
410473

474+
async def _run_forever(self) -> None:
475+
self._loop = asyncio.get_running_loop()
476+
connection_kwargs = self.get_connection_kwargs()
477+
478+
# register signal handlers
479+
for quit_signal in QUIT_SIGNALS:
480+
self._loop.add_signal_handler(quit_signal, self._signal_quit)
481+
for restart_signal in RESTART_SIGNALS:
482+
self._loop.add_signal_handler(restart_signal, self._signal_restart)
483+
484+
# connect to socket
485+
LOGGER.debug('Attempt connection.')
486+
self._reader, self._writer = await self._connect_to_server(
487+
**connection_kwargs
488+
)
489+
if not self._reader or not self._writer:
490+
LOGGER.debug('Connection attempt failed.')
491+
return
492+
493+
# on socket connection
411494
LOGGER.debug('Connection registered.')
495+
self._connected = True
412496
self.bot.on_connect()
413497

498+
# read forever
414499
LOGGER.debug('Waiting for messages...')
415500
self._read_task = asyncio.create_task(self.read_forever())
416501
try:
@@ -420,6 +505,7 @@ async def _run_forever(self) -> None:
420505
else:
421506
LOGGER.debug('Reader received EOF.')
422507

508+
# on socket disconnection
423509
self._connected = False
424510

425511
# cancel timeout tasks

0 commit comments

Comments
 (0)