home · contact · privacy
Refactor logging.
authorChristian Heller <c.heller@plomlompom.de>
Mon, 4 Aug 2025 13:09:11 +0000 (15:09 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Mon, 4 Aug 2025 13:09:11 +0000 (15:09 +0200)
ircplom/client.py
ircplom/client_tui.py
ircplom/events.py
ircplom/tui_base.py

index d4e91b6592b4120f49c0e4640a92db2e9f48b8e5..c95c99d403ba79b03a35c851a7ad76012684ad87 100644 (file)
@@ -7,14 +7,19 @@ from threading import Thread
 from typing import Optional
 from uuid import UUID, uuid4
 # ourselves
-from ircplom.events import (AffectiveEvent, ExceptionEvent, PayloadMixin,
-                            QueueMixin)
+from ircplom.events import (AffectiveEvent, ExceptionEvent, Logger,
+                            PayloadMixin, QueueMixin)
 from ircplom.irc_conn import (BaseIrcConnection, IrcConnAbortException,
                               IrcMessage)
 
 ClientsDb = dict[UUID, 'Client']
 CHAT_GLOB = '*'
 
+_LOG_PREFIX_PRIVMSG = ''
+_LOG_PREFIX_SEND_FMT = '> '
+_LOG_PREFIX_SEND_RAW = '=>|'
+_LOG_PREFIX_RECV_RAW = '<-|'
+
 
 @dataclass
 class ClientIdMixin:
@@ -92,6 +97,7 @@ class Client(ABC, ClientQueueMixin):
         self._cap_neg_states: dict[str, bool] = {}
         self.caps: dict[str, _ServerCapability] = {}
         self.id_ = uuid4()
+        self.log = Logger(self._log)
         self.update_login(nick_confirmed=False,
                           nickname=self.conn_setup.nickname)
         self.start_connecting()
@@ -106,7 +112,7 @@ class Client(ABC, ClientQueueMixin):
                                            client_id=self.id_)
                 self._cput(ClientEvent.make_subtype('on_connect'))
             except IrcConnAbortException as e:
-                self.log(f'# ALERT: {e}')
+                self.log.alert(str(e))
             except Exception as e:  # pylint: disable=broad-exception-caught
                 self._put(ExceptionEvent(e))
 
@@ -114,7 +120,7 @@ class Client(ABC, ClientQueueMixin):
 
     def on_connect(self) -> None:
         'Steps to perform right after connection.'
-        self.log(msg='# connected to server', chat=CHAT_GLOB)
+        self.log.add(msg='connected to server', chat=CHAT_GLOB)
         self.try_send_cap('LS', ('302',))
         self.send(IrcMessage(verb='USER',
                              params=(getuser(), '0', '*',
@@ -164,7 +170,7 @@ class Client(ABC, ClientQueueMixin):
             self.cap_neg_set(verb, done=True)
 
     @abstractmethod
-    def log(self, msg: str, chat: str = '') -> None:
+    def _log(self, msg: str, chat: str = '') -> None:
         '''Write msg into log of chat, whatever shape that may have.
 
         Messages to chat=CHAT_GLOB are meant to appear in all widgets mapped to
@@ -174,11 +180,11 @@ class Client(ABC, ClientQueueMixin):
     def send(self, msg: IrcMessage, chat: str = '') -> None:
         'Send line-separator-delimited message over socket.'
         if not self.conn:
-            self.log('# ALERT: cannot send, connection seems closed')
+            self.log.alert('cannot send, connection seems closed')
             return
         self.conn.send(msg)
-        self.log(msg=f'> {msg.raw}', chat=chat)
-        self.log(msg=f'=>| {msg.raw}', chat=':raw')
+        self.log.add(msg.raw, prefix=_LOG_PREFIX_SEND_FMT, chat=chat)
+        self.log.add(msg.raw, prefix=_LOG_PREFIX_SEND_RAW, chat=':raw')
 
     def update_login(self, nick_confirmed: bool, nickname: str = '') -> None:
         '''Manage conn_setup..nickname, .nick_confirmed.
@@ -186,20 +192,21 @@ class Client(ABC, ClientQueueMixin):
         (Useful for subclass extension.)
         '''
         first_run = not hasattr(self.conn_setup, 'nickname')
-        prefix = 'nickname'
+        prefix = 'nickname'
         if first_run or (nickname and nickname != self.conn_setup.nickname):
             verb = ('set' if first_run
                     else f'changed from "{self.conn_setup.nickname}"')
             self.conn_setup.nickname = nickname
-            self.log(msg=f'{prefix} {verb} to "{nickname}"', chat=CHAT_GLOB)
+            self.log.add(f'{prefix} {verb} to "{nickname}"', chat=CHAT_GLOB)
         if first_run or nick_confirmed != self.nick_confirmed:
             self.nick_confirmed = nick_confirmed
             if not first_run:
-                self.log(f'{prefix} {"" if nick_confirmed else "un"}confirmed')
+                self.log.add(f'{prefix} {"" if nick_confirmed else "un"}'
+                             'confirmed')
 
     def close(self) -> None:
         'Close both recv Loop and socket.'
-        self.log(msg='# disconnecting from server', chat=CHAT_GLOB)
+        self.log.add(msg='disconnecting from server', chat=CHAT_GLOB)
         if self.conn:
             self.conn.close()
         self.conn = None
@@ -207,7 +214,7 @@ class Client(ABC, ClientQueueMixin):
 
     def handle_msg(self, msg: IrcMessage) -> None:
         'Process incoming msg towards appropriate client steps.'
-        self.log(f'<-| {msg.raw}', ':raw')
+        self.log.add(msg.raw, prefix=_LOG_PREFIX_RECV_RAW, chat=':raw')
         match msg.verb:
             case 'PING':
                 self.send(IrcMessage(verb='PONG', params=(msg.params[0],)))
@@ -216,7 +223,8 @@ class Client(ABC, ClientQueueMixin):
             case '001' | 'NICK':
                 self.update_login(nickname=msg.params[0], nick_confirmed=True)
             case 'PRIVMSG':
-                self.log(msg=str(msg.params), chat=msg.source)
+                self.log.add(msg=str(msg.params), prefix=_LOG_PREFIX_PRIVMSG,
+                             chat=msg.source)
             case 'CAP':
                 match msg.params[1]:
                     case 'LS' | 'LIST':
@@ -230,9 +238,9 @@ class Client(ABC, ClientQueueMixin):
                 if self.cap_neg_done('LIST'):
                     self.try_send_cap('END')
                     if not self.cap_neg('printing'):
-                        self.log('# server capabilities (enabled: "+"):')
+                        self.log.add('server capabilities (enabled: "+"):')
                         for cap_name, cap in self.caps.items():
-                            self.log('# ' + cap.str_for_log(cap_name))
+                            self.log.add(cap.str_for_log(cap_name))
                         self.cap_neg_set('printing', done=True)
                 elif self.cap_neg_done('LS'):
                     for cap_name in ('server-time', 'account-tag', 'sasl'):
@@ -257,8 +265,8 @@ class InitReconnectEvent(ClientEvent):
 
     def affect(self, target: 'Client') -> None:
         if target.conn:
-            target.log('# ALERT: reconnection called, but still seem '
-                       'connected, so nothing to do.')
+            target.log.alert('reconnection called, but still seem connected, '
+                             'so nothing to do.')
         else:
             target.start_connecting()
 
index 7c482ab854b8221550dd25bb85a32443298cf567..32deae0b708d2dcdf726474a65a239a6f5d98df0 100644 (file)
@@ -126,7 +126,7 @@ class _ClientPromptEvent(_ClientWindowEvent, PayloadMixin):
 
 class _ClientKnowingTui(Client):
 
-    def log(self, msg: str, chat: str = '') -> None:
+    def _log(self, msg: str, chat: str = '') -> None:
         self._cput(_ClientLogEvent, chat=chat, payload=msg)
 
     def update_login(self, nick_confirmed: bool, nickname: str = '') -> None:
index e06ce344c4d3b6d4a3777f86bb18ea7c95e41dfa..07c82bcd77277b5ed750a22f1845eb9fc150982e 100644 (file)
@@ -3,7 +3,10 @@ from abc import abstractmethod, ABC
 from dataclasses import dataclass
 from queue import SimpleQueue, Empty as QueueEmpty
 from threading import Thread
-from typing import Any, Iterator, Literal, Self
+from typing import Any, Callable, Iterator, Literal, Self
+
+_LOG_PREFIX_DEFAULT = '# '
+_LOG_PREFIX_ALERT = f'{_LOG_PREFIX_DEFAULT}ALERT: '
 
 
 @dataclass
@@ -85,3 +88,19 @@ class Loop(QueueMixin):
                     self._put(it_yield)
         except Exception as e:  # pylint: disable=broad-exception-caught
             self._put(ExceptionEvent(e))
+
+
+class Logger:
+    'Wrapper for logging tasks.'
+
+    def __init__(self, to_call: Callable[[str], None]) -> None:
+        self._to_call = to_call
+
+    def add(self, msg: str, prefix: str = _LOG_PREFIX_DEFAULT, **kwargs
+            ) -> None:
+        'Add msg to log.'
+        self._to_call(f'{prefix}{msg}', **kwargs)
+
+    def alert(self, msg: str, **kwargs) -> None:
+        'Add msg prefixed with _LOG_PREFIX_ALERT to log.'
+        self.add(msg, prefix=_LOG_PREFIX_ALERT, **kwargs)
index e19e90fee1624d4d84bc78111c27baf16bb3dad4..ef7da24384d906a661c8ce22b203206d769b7ccb 100644 (file)
@@ -11,7 +11,7 @@ from typing import Callable, Generator, Iterator, NamedTuple, Optional
 from blessed import Terminal as BlessedTerminal
 # ourselves
 from ircplom.events import (
-        AffectiveEvent, Loop, PayloadMixin, QueueMixin, QuitEvent)
+        AffectiveEvent, Logger, Loop, PayloadMixin, QueueMixin, QuitEvent)
 # from ircplom.irc_conn import IrcMessage
 
 _MIN_HEIGHT = 4
@@ -392,7 +392,7 @@ class _KeyboardEvent(TuiEvent, PayloadMixin):
         elif len(self.payload) == 1:
             target.window.prompt.insert(self.payload)
         else:
-            target.log(f'# ALERT: unknown keyboard input: {self.payload}')
+            target.log.alert(f'unknown keyboard input: {self.payload}')
         super().affect(target)
 
 
@@ -404,6 +404,9 @@ class BaseTui(QueueMixin):
         self.term = term
         self._window_idx = 0
         self.windows = [Window(idx=self._window_idx, term=self.term)]
+        self.log = Logger(
+            lambda msg:  # pylint: disable=unnecessary-lambda  # to keep …
+            self.window.log.append(msg))  # … up-to-date _what_ window's .log
         self._put(_SetScreenEvent())
 
     def cmd_name_to_cmd(self, cmd_name: str) -> Optional[Callable]:
@@ -428,10 +431,6 @@ class BaseTui(QueueMixin):
         'Currently selected Window.'
         return self.windows[self._window_idx]
 
-    def log(self, msg: str) -> None:
-        'Post msg to active window\'s log.'
-        self.window.log.append(msg)
-
     def _switch_window(self, idx: int) -> None:
         self._window_idx = idx
         self.window.draw()
@@ -466,7 +465,7 @@ class BaseTui(QueueMixin):
         else:
             alert = 'not prefixed by /'
         if alert:
-            self.log(f'# ALERT: invalid prompt command: {alert}')
+            self.log.alert(f'invalid prompt command: {alert}')
 
     def cmd__quit(self) -> None:
         'Trigger program exit.'