From 0eb75eb73ddca81531b2e7d61022b9c032bd144f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20H=C3=B6rist?= <philipp@hoerist.com>
Date: Sat, 9 Mar 2019 23:16:27 +0100
Subject: [PATCH] Modules: Use LogAdapter

- Make all modules inherit from BaseModule
- Use LogAdapter in BaseModule
---
 gajim/common/modules/adhoc_commands.py       |  58 +++++-----
 gajim/common/modules/annotations.py          |   4 -
 gajim/common/modules/base.py                 |  23 ++--
 gajim/common/modules/bits_of_binary.py       |  18 ++--
 gajim/common/modules/blocking.py             |  24 ++---
 gajim/common/modules/bookmarks.py            |  21 ++--
 gajim/common/modules/caps.py                 |  21 ++--
 gajim/common/modules/carbons.py              |  18 ++--
 gajim/common/modules/chatstates.py           |  37 +++----
 gajim/common/modules/delimiter.py            |  25 ++---
 gajim/common/modules/discovery.py            |  64 +++++------
 gajim/common/modules/entity_time.py          |  43 ++++----
 gajim/common/modules/gateway.py              |  18 ++--
 gajim/common/modules/http_auth.py            |  16 ++-
 gajim/common/modules/httpupload.py           |  62 +++++------
 gajim/common/modules/iq.py                   |  13 +--
 gajim/common/modules/last_activity.py        |   8 +-
 gajim/common/modules/mam.py                  | 105 +++++++++----------
 gajim/common/modules/message.py              |  19 ++--
 gajim/common/modules/metacontacts.py         |  26 ++---
 gajim/common/modules/muc.py                  |  56 +++++-----
 gajim/common/modules/pep.py                  |   7 +-
 gajim/common/modules/ping.py                 |  37 ++++---
 gajim/common/modules/presence.py             |  47 ++++-----
 gajim/common/modules/privacylists.py         |  70 ++++++-------
 gajim/common/modules/pubsub.py               |  41 +++-----
 gajim/common/modules/receipts.py             |  18 ++--
 gajim/common/modules/register.py             |  29 ++---
 gajim/common/modules/roster.py               |  48 +++++----
 gajim/common/modules/roster_item_exchange.py |  32 +++---
 gajim/common/modules/search.py               |  26 ++---
 gajim/common/modules/security_labels.py      |  22 ++--
 gajim/common/modules/software_version.py     |  36 +++----
 gajim/common/modules/user_activity.py        |   6 +-
 gajim/common/modules/user_avatar.py          |  14 +--
 gajim/common/modules/user_location.py        |   6 +-
 gajim/common/modules/user_mood.py            |   6 +-
 gajim/common/modules/user_nickname.py        |   4 -
 gajim/common/modules/user_tune.py            |   6 +-
 gajim/common/modules/vcard_avatars.py        |  52 +++++----
 gajim/common/modules/vcard_temp.py           |  48 ++++-----
 41 files changed, 556 insertions(+), 678 deletions(-)

diff --git a/gajim/common/modules/adhoc_commands.py b/gajim/common/modules/adhoc_commands.py
index cd18a70a1d..93e856c97a 100644
--- a/gajim/common/modules/adhoc_commands.py
+++ b/gajim/common/modules/adhoc_commands.py
@@ -19,18 +19,15 @@
 # You should have received a copy of the GNU General Public License
 # along with Gajim. If not, see <http://www.gnu.org/licenses/>.
 
-import logging
-
 import nbxmpp
+from nbxmpp.structs import StanzaHandler
 
 from gajim.common import app
 from gajim.common import helpers
 from gajim.common.i18n import _
-from gajim.common.modules import dataforms
-
 from gajim.common.nec import NetworkIncomingEvent
-
-log = logging.getLogger('gajim.c.m.commands')
+from gajim.common.modules import dataforms
+from gajim.common.modules.base import BaseModule
 
 
 class AdHocCommand:
@@ -298,13 +295,15 @@ class LeaveGroupchatsCommand(AdHocCommand):
         return False
 
 
-class AdHocCommands:
+class AdHocCommands(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
-            ('iq', self._execute_command_received, 'set', nbxmpp.NS_COMMANDS)
+            StanzaHandler(name='iq',
+                          callback=self._execute_command_received,
+                          typ='set',
+                          ns=nbxmpp.NS_COMMANDS),
         ]
 
         # a list of all commands exposed: node -> command class
@@ -350,7 +349,7 @@ class AdHocCommands:
         try:
             jid = helpers.get_full_jid_from_iq(stanza)
         except helpers.InvalidFormat:
-            log.warning('Invalid JID: %s, ignoring it', stanza.getFrom())
+            self._log.warning('Invalid JID: %s, ignoring it', stanza.getFrom())
             return
         node = stanza.getTagAttr('query', 'node')
 
@@ -393,17 +392,17 @@ class AdHocCommands:
 
         return False
 
-    def _execute_command_received(self, _con, stanza):
+    def _execute_command_received(self, _con, stanza, _properties):
         jid = helpers.get_full_jid_from_iq(stanza)
 
         cmd = stanza.getTag('command')
         if cmd is None:
-            log.error('Malformed stanza (no command node) %s', stanza)
+            self._log.error('Malformed stanza (no command node) %s', stanza)
             raise nbxmpp.NodeProcessed
 
         node = cmd.getAttr('node')
         if node is None:
-            log.error('Malformed stanza (no node attr) %s', stanza)
+            self._log.error('Malformed stanza (no node attr) %s', stanza)
             raise nbxmpp.NodeProcessed
 
         sessionid = cmd.getAttr('sessionid')
@@ -414,12 +413,12 @@ class AdHocCommands:
                 self._con.connection.send(
                     nbxmpp.Error(
                         stanza, nbxmpp.NS_STANZAS + ' item-not-found'))
-                log.warning('Comand %s does not exist: %s', node, jid)
+                self._log.warning('Comand %s does not exist: %s', node, jid)
                 raise nbxmpp.NodeProcessed
 
             newcmd = self._commands[node]
             if not newcmd.is_visible_for(self.is_same_jid(jid)):
-                log.warning('Command not visible for jid: %s', jid)
+                self._log.warning('Command not visible for jid: %s', jid)
                 raise nbxmpp.NodeProcessed
 
             # generate new sessionid
@@ -430,14 +429,14 @@ class AdHocCommands:
             rc = obj.execute(stanza)
             if rc:
                 self._sessions[(jid, sessionid, node)] = obj
-            log.info('Comand %s executed: %s', node, jid)
+            self._log.info('Comand %s executed: %s', node, jid)
             raise nbxmpp.NodeProcessed
         else:
             # the command is already running, check for it
             magictuple = (jid, sessionid, node)
             if magictuple not in self._sessions:
                 # we don't have this session... ha!
-                log.warning('Invalid session %s', magictuple)
+                self._log.warning('Invalid session %s', magictuple)
                 raise nbxmpp.NodeProcessed
 
             action = cmd.getAttr('action')
@@ -461,7 +460,7 @@ class AdHocCommands:
                 # the command probably doesn't handle invoked action...
                 # stop the session, return error
                 del self._sessions[magictuple]
-                log.warning('Wrong action %s %s', node, jid)
+                self._log.warning('Wrong action %s %s', node, jid)
                 raise nbxmpp.NodeProcessed
 
             # delete the session if rc is False
@@ -474,7 +473,7 @@ class AdHocCommands:
         """
         Request the command list.
         """
-        log.info('Request Command List: %s', jid)
+        self._log.info('Request Command List: %s', jid)
         query = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_DISCO_ITEMS)
         query.setQuerynode(nbxmpp.NS_COMMANDS)
 
@@ -483,7 +482,7 @@ class AdHocCommands:
 
     def _command_list_received(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.info('Error: %s', stanza.getError())
+            self._log.info('Error: %s', stanza.getError())
 
             app.nec.push_incoming_event(
                 AdHocCommandError(None, conn=self._con,
@@ -497,7 +496,7 @@ class AdHocCommands:
                 (t.getAttr('node'), t.getAttr('name')) for t in items
             ]
 
-        log.info('Received: %s', commandlist)
+        self._log.info('Received: %s', commandlist)
         app.nec.push_incoming_event(
             AdHocCommandListReceived(
                 None, conn=self._con, commandlist=commandlist))
@@ -507,7 +506,7 @@ class AdHocCommands:
         """
         Send the command with data form. Wait for reply
         """
-        log.info('Send Command: %s %s %s %s', jid, node, session_id, action)
+        self._log.info('Send Command: %s %s %s %s', jid, node, session_id, action)
         stanza = nbxmpp.Iq(typ='set', to=jid)
         cmdnode = stanza.addChild('command',
                                   namespace=nbxmpp.NS_COMMANDS,
@@ -525,13 +524,13 @@ class AdHocCommands:
 
     def _action_response_received(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.info('Error: %s', stanza.getError())
+            self._log.info('Error: %s', stanza.getError())
 
             app.nec.push_incoming_event(
                 AdHocCommandError(None, conn=self._con,
                                   error=stanza.getError()))
             return
-        log.info('Received action response')
+        self._log.info('Received action response')
         command = stanza.getTag('command')
         app.nec.push_incoming_event(
             AdHocCommandActionResponse(
@@ -541,7 +540,7 @@ class AdHocCommands:
         """
         Send the command with action='cancel'
         """
-        log.info('Cancel: %s %s %s', jid, node, session_id)
+        self._log.info('Cancel: %s %s %s', jid, node, session_id)
         stanza = nbxmpp.Iq(typ='set', to=jid)
         stanza.addChild('command', namespace=nbxmpp.NS_COMMANDS,
                         attrs={
@@ -553,12 +552,11 @@ class AdHocCommands:
         self._con.connection.SendAndCallForResponse(
             stanza, self._cancel_result_received)
 
-    @staticmethod
-    def _cancel_result_received(stanza):
+    def _cancel_result_received(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.warning('Error: %s', stanza.getError())
+            self._log.warning('Error: %s', stanza.getError())
         else:
-            log.info('Cancel successful')
+            self._log.info('Cancel successful')
 
 
 class AdHocCommandError(NetworkIncomingEvent):
diff --git a/gajim/common/modules/annotations.py b/gajim/common/modules/annotations.py
index b9187bc76d..47259c6c76 100644
--- a/gajim/common/modules/annotations.py
+++ b/gajim/common/modules/annotations.py
@@ -18,15 +18,11 @@ from typing import Any
 from typing import Dict  # pylint: disable=unused-import
 from typing import Tuple
 
-import logging
-
 from nbxmpp.util import is_error_result
 
 from gajim.common.types import ConnectionT
 from gajim.common.modules.base import BaseModule
 
-log = logging.getLogger('gajim.c.m.annotations')
-
 
 class Annotations(BaseModule):
 
diff --git a/gajim/common/modules/base.py b/gajim/common/modules/base.py
index a8bc821bc2..f2eba9a850 100644
--- a/gajim/common/modules/base.py
+++ b/gajim/common/modules/base.py
@@ -26,29 +26,33 @@ from nbxmpp.structs import StanzaHandler
 from gajim.common import app
 from gajim.common.modules.util import LogAdapter
 
-log = logging.getLogger('gajim.c.m.base')
-
 
 class BaseModule:
 
     _nbxmpp_extends = ''
     _nbxmpp_methods = []  # type: List[str]
 
-    def __init__(self, con, logger=None):
+    def __init__(self, con, *args, plugin=False, **kwargs):
         self._con = con
         self._account = con.name
-        if logger is not None:
-            self._log = LogAdapter(logger, {'account': self._account})
+        self._log = self._set_logger(plugin)
         self._nbxmpp_callbacks = {}  # type: Dict[str, Any]
         self._stored_publish = None  # type: Callable
         self.handlers = []  # type: List[str]
 
+    def _set_logger(self, plugin):
+        logger_name = 'gajim.c.m.%s'
+        if plugin:
+            logger_name = 'gajim.p.%s'
+        logger_name = logger_name % self.__class__.__name__.lower()
+        logger = logging.getLogger(logger_name)
+        return LogAdapter(logger, {'account': self._account})
+
     def __getattr__(self, key):
         if key not in self._nbxmpp_methods:
             raise AttributeError
         if not app.account_is_connected(self._account):
-            log.warning('Account %s not connected, cant use %s',
-                        self._account, key)
+            self._log.warning('Account not connected, cant use %s', key)
             return
 
         module = self._con.connection.get_module(self._nbxmpp_extends)
@@ -60,8 +64,7 @@ class BaseModule:
 
     def _nbxmpp(self, module_name=None):
         if not app.account_is_connected(self._account):
-            log.warning('Account %s not connected, cant use nbxmpp method',
-                        self._account)
+            self._log.warning('Account not connected, cant use nbxmpp method')
             return Mock()
 
         if module_name is None:
@@ -81,5 +84,5 @@ class BaseModule:
     def send_stored_publish(self):
         if self._stored_publish is None:
             return
-        log.info('Send stored publish')
+        self._log.info('Send stored publish')
         self._stored_publish()
diff --git a/gajim/common/modules/bits_of_binary.py b/gajim/common/modules/bits_of_binary.py
index 37745ac1ea..0f8bc2d2cd 100644
--- a/gajim/common/modules/bits_of_binary.py
+++ b/gajim/common/modules/bits_of_binary.py
@@ -20,31 +20,35 @@ from base64 import b64decode
 from pathlib import Path
 
 import nbxmpp
+from nbxmpp.structs import StanzaHandler
 
 from gajim.common import app
 from gajim.common import configpaths
+from gajim.common.modules.base import BaseModule
 
 log = logging.getLogger('gajim.c.m.bob')
 
 
-class BitsOfBinary:
+class BitsOfBinary(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
-            ('iq', self._answer_bob_request, 'get', nbxmpp.NS_BOB)
+            StanzaHandler(name='iq',
+                          callback=self._answer_bob_request,
+                          typ='get',
+                          ns=nbxmpp.NS_BOB),
         ]
 
         # Used to track which cids are in-flight.
         self.awaiting_cids = {}
 
-    def _answer_bob_request(self, _con, stanza):
-        log.info('Request from %s for BoB data', stanza.getFrom())
+    def _answer_bob_request(self, _con, stanza, _properties):
+        self._log.info('Request from %s for BoB data', stanza.getFrom())
         iq = stanza.buildReply('error')
         err = nbxmpp.ErrorNode(nbxmpp.ERR_ITEM_NOT_FOUND)
         iq.addChild(node=err)
-        log.info('Sending item-not-found')
+        self._log.info('Sending item-not-found')
         self._con.connection.send(iq)
         raise nbxmpp.NodeProcessed
 
diff --git a/gajim/common/modules/blocking.py b/gajim/common/modules/blocking.py
index 7f5f7556e7..e47edd1219 100644
--- a/gajim/common/modules/blocking.py
+++ b/gajim/common/modules/blocking.py
@@ -14,9 +14,8 @@
 
 # XEP-0191: Blocking Command
 
-import logging
-
 import nbxmpp
+from nbxmpp.structs import StanzaHandler
 from nbxmpp.util import is_error_result
 
 from gajim.common import app
@@ -24,8 +23,6 @@ from gajim.common.nec import NetworkEvent
 from gajim.common.nec import NetworkIncomingEvent
 from gajim.common.modules.base import BaseModule
 
-log = logging.getLogger('gajim.c.m.blocking')
-
 
 class Blocking(BaseModule):
 
@@ -42,7 +39,10 @@ class Blocking(BaseModule):
         self.blocked = []
 
         self.handlers = [
-            ('iq', self._blocking_push_received, 'set', nbxmpp.NS_BLOCKING)
+            StanzaHandler(name='iq',
+                          callback=self._blocking_push_received,
+                          typ='set',
+                          ns=nbxmpp.NS_BLOCKING),
         ]
 
         self._register_callback('get_blocking_list',
@@ -60,18 +60,18 @@ class Blocking(BaseModule):
                          account=self._account,
                          feature=nbxmpp.NS_BLOCKING))
 
-        log.info('Discovered blocking: %s', from_)
+        self._log.info('Discovered blocking: %s', from_)
 
     def _blocking_list_received(self, result):
         if is_error_result(result):
-            log.info('Error: %s', result)
+            self._log.info('Error: %s', result)
             return
 
         self.blocked = result.blocking_list
         app.nec.push_incoming_event(
             BlockingEvent(None, conn=self._con, changed=self.blocked))
 
-    def _blocking_push_received(self, _con, stanza):
+    def _blocking_push_received(self, _con, stanza, _properties):
         reply = stanza.buildReply('result')
         childs = reply.getChildren()
         for child in childs:
@@ -89,7 +89,7 @@ class Blocking(BaseModule):
                 self.blocked = []
                 for jid in self.blocked:
                     self._presence_probe(jid)
-                log.info('Unblock all Push')
+                self._log.info('Unblock all Push')
                 raise nbxmpp.NodeProcessed
 
             for item in items:
@@ -100,7 +100,7 @@ class Blocking(BaseModule):
                     continue
                 self.blocked.remove(jid)
                 self._presence_probe(jid)
-                log.info('Unblock Push: %s', jid)
+                self._log.info('Unblock Push: %s', jid)
 
         block = stanza.getTag('block', namespace=nbxmpp.NS_BLOCKING)
         if block is not None:
@@ -111,7 +111,7 @@ class Blocking(BaseModule):
                 changed_list.append(jid)
                 self.blocked.append(jid)
                 self._set_contact_offline(jid)
-                log.info('Block Push: %s', jid)
+                self._log.info('Block Push: %s', jid)
 
         app.nec.push_incoming_event(
             BlockingEvent(None, conn=self._con, changed=changed_list))
@@ -124,7 +124,7 @@ class Blocking(BaseModule):
             contact.show = 'offline'
 
     def _presence_probe(self, jid: str) -> None:
-        log.info('Presence probe: %s', jid)
+        self._log.info('Presence probe: %s', jid)
         # Send a presence Probe to get the current Status
         probe = nbxmpp.Presence(jid, 'probe', frm=self._con.get_own_jid())
         self._nbxmpp().send(probe)
diff --git a/gajim/common/modules/bookmarks.py b/gajim/common/modules/bookmarks.py
index 46e61d70a7..41d3bc0bf7 100644
--- a/gajim/common/modules/bookmarks.py
+++ b/gajim/common/modules/bookmarks.py
@@ -18,7 +18,6 @@ from typing import Any
 from typing import List
 from typing import Optional
 
-import logging
 import copy
 
 import nbxmpp
@@ -33,9 +32,6 @@ from gajim.common.modules.base import BaseModule
 from gajim.common.modules.util import event_node
 
 
-log = logging.getLogger('gajim.c.m.bookmarks')
-
-
 class Bookmarks(BaseModule):
 
     _nbxmpp_extends = 'Bookmarks'
@@ -69,14 +65,15 @@ class Bookmarks(BaseModule):
         bookmarks = properties.pubsub_event.data
 
         if not properties.is_self_message:
-            log.warning('%s has an open access bookmarks node', properties.jid)
+            self._log.warning('%s has an open access bookmarks node',
+                              properties.jid)
             return
 
         if not self._pubsub_support() or not self.conversion:
             return
 
         if self._request_in_progress:
-            log.info('Ignore update, pubsub request in progress')
+            self._log.info('Ignore update, pubsub request in progress')
             return
 
         old_bookmarks = self._convert_to_set(self._bookmarks)
@@ -89,7 +86,7 @@ class Bookmarks(BaseModule):
         if nbxmpp.NS_BOOKMARK_CONVERSION not in features:
             return
         self._conversion = True
-        log.info('Discovered Bookmarks Conversion: %s', from_)
+        self._log.info('Discovered Bookmarks Conversion: %s', from_)
 
     def _act_on_changed_bookmarks(self, old_bookmarks):
         new_bookmarks = self._convert_to_set(self._bookmarks)
@@ -100,7 +97,7 @@ class Bookmarks(BaseModule):
         join = [jid for jid, autojoin in changed if autojoin]
         bookmarks = []
         for jid in join:
-            log.info('Schedule autojoin in 10s for: %s', jid)
+            self._log.info('Schedule autojoin in 10s for: %s', jid)
             bookmarks.append(self.get_bookmark_from_jid(jid))
         # If another client creates a MUC, the MUC is locked until the
         # configuration is finished. Give the user some time to finish
@@ -163,7 +160,7 @@ class Bookmarks(BaseModule):
 
     def _bookmarks_received(self, bookmarks):
         if is_error_result(bookmarks):
-            log.info('Error: %s', bookmarks)
+            self._log.info('Error: %s', bookmarks)
             bookmarks = []
 
         self._request_in_progress = False
@@ -202,7 +199,7 @@ class Bookmarks(BaseModule):
                 # auto-joined on re-connection
                 if bookmark.jid not in app.gc_connected[self._account]:
                     # we are not already connected
-                    log.info('Autojoin Bookmark: %s', bookmark.jid)
+                    self._log.info('Autojoin Bookmark: %s', bookmark.jid)
                     minimize = app.config.get_per('rooms', bookmark.jid,
                                                   'minimize_on_autojoin', True)
                     app.interface.join_gc_room(
@@ -237,8 +234,8 @@ class Bookmarks(BaseModule):
         return self.get_bookmark_from_jid(jid) is not None
 
     def purge_pubsub_bookmarks(self) -> None:
-        log.info('Purge/Delete Bookmarks on PubSub, '
-                 'because publish options are not available')
+        self._log.info('Purge/Delete Bookmarks on PubSub, '
+                       'because publish options are not available')
         self._con.get_module('PubSub').send_pb_purge('', 'storage:bookmarks')
         self._con.get_module('PubSub').send_pb_delete('', 'storage:bookmarks')
 
diff --git a/gajim/common/modules/caps.py b/gajim/common/modules/caps.py
index f8bb544c75..cf97606a80 100644
--- a/gajim/common/modules/caps.py
+++ b/gajim/common/modules/caps.py
@@ -17,22 +17,18 @@
 
 # XEP-0115: Entity Capabilities
 
-import logging
-
 import nbxmpp
 from nbxmpp.structs import StanzaHandler
 
 from gajim.common import caps_cache
 from gajim.common import app
 from gajim.common.nec import NetworkEvent
-
-log = logging.getLogger('gajim.c.m.caps')
+from gajim.common.modules.base import BaseModule
 
 
-class Caps:
+class Caps(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
             StanzaHandler(name='presence',
@@ -57,8 +53,9 @@ class Caps:
         node = properties.entity_caps.node
         caps_hash = properties.entity_caps.ver
 
-        log.info('Received from %s, type: %s, method: %s, node: %s, hash: %s',
-                 jid, properties.type, hash_method, node, caps_hash)
+        self._log.info(
+            'Received from %s, type: %s, method: %s, node: %s, hash: %s',
+            jid, properties.type, hash_method, node, caps_hash)
 
         client_caps = self._create_suitable_client_caps(
             node, caps_hash, hash_method, jid)
@@ -85,7 +82,7 @@ class Caps:
         if contact is not None:
             contact.client_caps = client_caps
         else:
-            log.info('Received Caps from unknown contact %s', from_)
+            self._log.info('Received Caps from unknown contact %s', from_)
 
     def _get_contact_or_gc_contact_for_jid(self, from_):
         contact = app.contacts.get_contact_from_full_jid(self._account,
@@ -106,7 +103,7 @@ class Caps:
 
         contact = self._get_contact_or_gc_contact_for_jid(from_)
         if not contact:
-            log.info('Received Disco from unknown contact %s', from_)
+            self._log.info('Received Disco from unknown contact %s', from_)
             return
 
         lookup = contact.client_caps.get_cache_lookup_strategy()
@@ -126,7 +123,7 @@ class Caps:
             node = caps_hash = hash_method = None
             contact.client_caps = self._create_suitable_client_caps(
                 node, caps_hash, hash_method)
-            log.warning(
+            self._log.warning(
                 'Computed and retrieved caps hash differ. Ignoring '
                 'caps of contact %s', contact.get_full_jid())
 
diff --git a/gajim/common/modules/carbons.py b/gajim/common/modules/carbons.py
index 0365b8a8ab..ae93a2f9d8 100644
--- a/gajim/common/modules/carbons.py
+++ b/gajim/common/modules/carbons.py
@@ -14,21 +14,15 @@
 
 # XEP-0280: Message Carbons
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
-
-log = logging.getLogger('gajim.c.m.carbons')
+from gajim.common.modules.base import BaseModule
 
 
-class Carbons:
+class Carbons(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
-
-        self.handlers = []
+        BaseModule.__init__(self, con)
 
         self.supported = False
 
@@ -37,16 +31,16 @@ class Carbons:
             return
 
         self.supported = True
-        log.info('Discovered carbons: %s', from_)
+        self._log.info('Discovered carbons: %s', from_)
 
         if app.config.get_per('accounts', self._account,
                               'enable_message_carbons'):
             iq = nbxmpp.Iq('set')
             iq.setTag('enable', namespace=nbxmpp.NS_CARBONS)
-            log.info('Activate')
+            self._log.info('Activate')
             self._con.connection.send(iq)
         else:
-            log.warning('Carbons deactivated (user setting)')
+            self._log.warning('Carbons deactivated (user setting)')
 
 
 def get_instance(*args, **kwargs):
diff --git a/gajim/common/modules/chatstates.py b/gajim/common/modules/chatstates.py
index 95ee888da0..1c3366e4b2 100644
--- a/gajim/common/modules/chatstates.py
+++ b/gajim/common/modules/chatstates.py
@@ -21,23 +21,23 @@ from typing import Optional
 from typing import Tuple
 
 import time
-import logging
 from functools import wraps
 
 import nbxmpp
+from nbxmpp.structs import StanzaHandler
 from gi.repository import GLib
 
 from gajim.common import app
 from gajim.common.nec import NetworkEvent
 from gajim.common.const import Chatstate as State
 from gajim.common.modules.misc import parse_delay
+from gajim.common.modules.base import BaseModule
 from gajim.common.connection_handlers_events import MessageOutgoingEvent
 from gajim.common.connection_handlers_events import GcMessageOutgoingEvent
 
 from gajim.common.types import ContactT
 from gajim.common.types import ConnectionT
 
-log = logging.getLogger('gajim.c.m.chatstates')
 
 INACTIVE_AFTER = 60
 PAUSED_AFTER = 10
@@ -63,13 +63,13 @@ def parse_chatstate(stanza: nbxmpp.Message) -> Optional[str]:
     return None
 
 
-class Chatstate:
+class Chatstate(BaseModule):
     def __init__(self, con: ConnectionT) -> None:
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
-            ('presence', self._presence_received),
+            StanzaHandler(name='presence',
+                          callback=self._presence_received),
         ]
 
         # Our current chatstate with a specific contact
@@ -90,7 +90,7 @@ class Chatstate:
     def enabled(self, value):
         if self._enabled == value:
             return
-        log.info('Chatstate module %s', 'enabled' if value else 'disabled')
+        self._log.info('Chatstate module %s', 'enabled' if value else 'disabled')
         self._enabled = value
 
         if value:
@@ -106,7 +106,8 @@ class Chatstate:
     @ensure_enabled
     def _presence_received(self,
                            _con: ConnectionT,
-                           stanza: nbxmpp.Presence) -> None:
+                           stanza: nbxmpp.Presence,
+                           _properties: Any) -> None:
         if stanza.getType() not in ('unavailable', 'error'):
             return
 
@@ -136,7 +137,7 @@ class Chatstate:
         self._last_mouse_activity.pop(jid, None)
         self._last_keyboard_activity.pop(jid, None)
 
-        log.info('Reset chatstate for %s', jid)
+        self._log.info('Reset chatstate for %s', jid)
 
         app.nec.push_outgoing_event(
             NetworkEvent('chatstate-received',
@@ -166,7 +167,7 @@ class Chatstate:
             return
 
         contact.chatstate = chatstate
-        log.info('Recv: %-10s - %s', chatstate, event.fjid)
+        self._log.info('Recv: %-10s - %s', chatstate, event.fjid)
         app.nec.push_outgoing_event(
             NetworkEvent('chatstate-received',
                          account=self._account,
@@ -207,7 +208,7 @@ class Chatstate:
                         else:
                             # Contact not found, maybe we left the group chat
                             # or the contact was removed from the roster
-                            log.info(
+                            self._log.info(
                                 'Contact %s not found, reset chatstate', jid)
                             self._chatstates.pop(jid, None)
                             self._last_mouse_activity.pop(jid, None)
@@ -275,9 +276,9 @@ class Chatstate:
         if setting == 'disabled':
             # Send a last 'active' state after user disabled chatstates
             if current_state is not None:
-                log.info('Disabled for %s', contact.jid)
-                log.info('Send last state: %-10s - %s',
-                         State.ACTIVE, contact.jid)
+                self._log.info('Disabled for %s', contact.jid)
+                self._log.info('Send last state: %-10s - %s',
+                               State.ACTIVE, contact.jid)
 
                 event_attrs = {'account': self._account,
                                'jid': contact.jid,
@@ -301,15 +302,15 @@ class Chatstate:
             # which are not allowed to see our status
             if not contact.is_pm_contact:
                 if contact and contact.sub in ('to', 'none'):
-                    log.info('Contact not subscribed: %s', contact.jid)
+                    self._log.info('Contact not subscribed: %s', contact.jid)
                     return
 
             if contact.show == 'offline':
-                log.info('Contact offline: %s', contact.jid)
+                self._log.info('Contact offline: %s', contact.jid)
                 return
 
             if not contact.supports(nbxmpp.NS_CHATSTATES):
-                log.info('Chatstates not supported: %s', contact.jid)
+                self._log.info('Chatstates not supported: %s', contact.jid)
                 return
 
         if state in (State.ACTIVE, State.COMPOSING):
@@ -322,7 +323,7 @@ class Chatstate:
         if current_state == state:
             return
 
-        log.info('Send: %-10s - %s', state, contact.jid)
+        self._log.info('Send: %-10s - %s', state, contact.jid)
 
         event_attrs = {'account': self._account,
                        'jid': contact.jid,
diff --git a/gajim/common/modules/delimiter.py b/gajim/common/modules/delimiter.py
index 000870a1b4..662fdf133d 100644
--- a/gajim/common/modules/delimiter.py
+++ b/gajim/common/modules/delimiter.py
@@ -14,25 +14,19 @@
 
 # XEP-0083: Nested Roster Groups
 
-import logging
-
 import nbxmpp
 
-log = logging.getLogger('gajim.c.m.delimiter')
+from gajim.common.modules.base import BaseModule
 
 
-class Delimiter:
+class Delimiter(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
         self.available = False
-
         self.delimiter = '::'
 
-        self.handlers = []
-
     def get_roster_delimiter(self):
-        log.info('Request')
+        self._log.info('Request')
         node = nbxmpp.Node('storage', attrs={'xmlns': 'roster:delimiter'})
         iq = nbxmpp.Iq('get', nbxmpp.NS_PRIVATE, payload=node)
 
@@ -41,11 +35,11 @@ class Delimiter:
 
     def _delimiter_received(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.info('Request error: %s', stanza.getError())
+            self._log.info('Request error: %s', stanza.getError())
         else:
             delimiter = stanza.getQuery().getTagData('roster')
             self.available = True
-            log.info('Delimiter received: %s', delimiter)
+            self._log.info('Delimiter received: %s', delimiter)
             if delimiter:
                 self.delimiter = delimiter
             else:
@@ -54,7 +48,7 @@ class Delimiter:
         self._con.connect_machine()
 
     def set_roster_delimiter(self):
-        log.info('Set delimiter')
+        self._log.info('Set delimiter')
         iq = nbxmpp.Iq('set', nbxmpp.NS_PRIVATE)
         roster = iq.getQuery().addChild('roster', namespace='roster:delimiter')
         roster.setData('::')
@@ -62,10 +56,9 @@ class Delimiter:
         self._con.connection.SendAndCallForResponse(
             iq, self._set_delimiter_response)
 
-    @staticmethod
-    def _set_delimiter_response(stanza):
+    def _set_delimiter_response(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.info('Store error: %s', stanza.getError())
+            self._log.info('Store error: %s', stanza.getError())
 
 
 def get_instance(*args, **kwargs):
diff --git a/gajim/common/modules/discovery.py b/gajim/common/modules/discovery.py
index e647f92589..adf3d2318c 100644
--- a/gajim/common/modules/discovery.py
+++ b/gajim/common/modules/discovery.py
@@ -14,28 +14,32 @@
 
 # XEP-0030: Service Discovery
 
-import logging
 import weakref
 
 import nbxmpp
+from nbxmpp.structs import StanzaHandler
 
 from gajim.common import app
 from gajim.common import helpers
 from gajim.common.caps_cache import muc_caps_cache
 from gajim.common.nec import NetworkIncomingEvent
+from gajim.common.modules.base import BaseModule
 from gajim.common.connection_handlers_events import InformationEvent
 
-log = logging.getLogger('gajim.c.m.discovery')
 
-
-class Discovery:
+class Discovery(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
-            ('iq', self._answer_disco_info, 'get', nbxmpp.NS_DISCO_INFO),
-            ('iq', self._answer_disco_items, 'get', nbxmpp.NS_DISCO_ITEMS),
+            StanzaHandler(name='iq',
+                          callback=self._answer_disco_info,
+                          typ='get',
+                          ns=nbxmpp.NS_DISCO_INFO),
+            StanzaHandler(name='iq',
+                          callback=self._answer_disco_items,
+                          typ='get',
+                          ns=nbxmpp.NS_DISCO_ITEMS),
         ]
 
     def disco_contact(self, jid, node=None):
@@ -60,7 +64,7 @@ class Discovery:
         log_str = 'Request info: %s %s'
         if namespace == nbxmpp.NS_DISCO_ITEMS:
             log_str = 'Request items: %s %s'
-        log.info(log_str, jid, node or '')
+        self._log.info(log_str, jid, node or '')
 
         # Create weak references so we can pass GUI object methods
         weak_success_cb = weakref.WeakMethod(success_cb)
@@ -77,7 +81,7 @@ class Discovery:
             if error_cb is not None:
                 error_cb()(stanza.getFrom(), stanza.getError())
             else:
-                log.info('Error: %s', stanza.getError())
+                self._log.info('Error: %s', stanza.getError())
             return
 
         from_ = stanza.getFrom()
@@ -90,10 +94,9 @@ class Discovery:
             items = self.parse_items_response(stanza)
             success_cb()(from_, node, items)
         else:
-            log.warning('Wrong query namespace: %s', stanza)
+            self._log.warning('Wrong query namespace: %s', stanza)
 
-    @classmethod
-    def parse_items_response(cls, stanza):
+    def parse_items_response(self, stanza):
         payload = stanza.getQueryPayload()
         items = []
         for item in payload:
@@ -102,12 +105,12 @@ class Discovery:
                 continue
             attr = item.getAttrs()
             if 'jid' not in attr:
-                log.warning('No jid attr in disco items: %s', stanza)
+                self._log.warning('No jid attr in disco items: %s', stanza)
                 continue
             try:
                 attr['jid'] = helpers.parse_jid(attr['jid'])
             except helpers.InvalidFormat:
-                log.warning('Invalid jid attr in disco items: %s', stanza)
+                self._log.warning('Invalid jid attr in disco items: %s', stanza)
                 continue
             items.append(attr)
         return items
@@ -144,7 +147,7 @@ class Discovery:
         self.disco_items(server, success_cb=self._server_items_received)
 
     def _server_items_received(self, _from, _node, items):
-        log.info('Server items received')
+        self._log.info('Server items received')
         for item in items:
             if 'node' in item:
                 # Only disco components
@@ -154,7 +157,7 @@ class Discovery:
 
     def _server_items_info_received(self, from_, *args):
         from_ = from_.getStripped()
-        log.info('Server item info received: %s', from_)
+        self._log.info('Server item info received: %s', from_)
         self._parse_transports(from_, *args)
         try:
             self._con.get_module('MUC').pass_disco(from_, *args)
@@ -172,7 +175,7 @@ class Discovery:
 
     def _account_info_received(self, from_, *args):
         from_ = from_.getStripped()
-        log.info('Account info received: %s', from_)
+        self._log.info('Account info received: %s', from_)
 
         self._con.get_module('MAM').pass_disco(from_, *args)
         self._con.get_module('PEP').pass_disco(from_, *args)
@@ -189,7 +192,7 @@ class Discovery:
         self.disco_info(server, success_cb=self._server_info_received)
 
     def _server_info_received(self, from_, *args):
-        log.info('Server info received: %s', from_)
+        self._log.info('Server info received: %s', from_)
 
         self._con.get_module('SecLabels').pass_disco(from_, *args)
         self._con.get_module('Blocking').pass_disco(from_, *args)
@@ -213,8 +216,8 @@ class Discovery:
             if category not in ('gateway', 'headline'):
                 continue
             transport_type = identity.get('type')
-            log.info('Found transport: %s %s %s',
-                     from_, category, transport_type)
+            self._log.info('Found transport: %s %s %s',
+                           from_, category, transport_type)
             jid = str(from_)
             if jid not in app.transport_type:
                 app.transport_type[jid] = transport_type
@@ -225,9 +228,9 @@ class Discovery:
             else:
                 self._con.available_transports[transport_type] = [jid]
 
-    def _answer_disco_items(self, _con, stanza):
+    def _answer_disco_items(self, _con, stanza, _properties):
         from_ = stanza.getFrom()
-        log.info('Answer disco items to %s', from_)
+        self._log.info('Answer disco items to %s', from_)
 
         if self._con.get_module('AdHocCommands').command_items_query(stanza):
             raise nbxmpp.NodeProcessed
@@ -242,9 +245,9 @@ class Discovery:
             self._con.get_module('AdHocCommands').command_list_query(stanza)
             raise nbxmpp.NodeProcessed
 
-    def _answer_disco_info(self, _con, stanza):
+    def _answer_disco_info(self, _con, stanza, _properties):
         from_ = stanza.getFrom()
-        log.info('Answer disco info %s', from_)
+        self._log.info('Answer disco info %s', from_)
         if str(from_).startswith('echo.'):
             # Service that echos all stanzas, ignore it
             raise nbxmpp.NodeProcessed
@@ -277,27 +280,26 @@ class Discovery:
             return
 
         iq = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_DISCO_INFO)
-        log.info('Request MUC info %s', jid)
+        self._log.info('Request MUC info %s', jid)
 
         self._con.connection.SendAndCallForResponse(
             iq, self._muc_info_response, {'callback': callback})
 
-    @staticmethod
-    def _muc_info_response(_con, stanza, callback):
+    def _muc_info_response(self, _con, stanza, callback):
         if not nbxmpp.isResultNode(stanza):
             error = stanza.getError()
             if error == 'item-not-found':
                 # Groupchat does not exist
-                log.info('MUC does not exist: %s', stanza.getFrom())
+                self._log.info('MUC does not exist: %s', stanza.getFrom())
                 callback()
             else:
-                log.info('MUC disco error: %s', error)
+                self._log.info('MUC disco error: %s', error)
                 app.nec.push_incoming_event(
                     InformationEvent(
                         None, dialog_name='unable-join-groupchat', args=error))
             return
 
-        log.info('MUC info received: %s', stanza.getFrom())
+        self._log.info('MUC info received: %s', stanza.getFrom())
         muc_caps_cache.append(stanza)
         callback()
 
diff --git a/gajim/common/modules/entity_time.py b/gajim/common/modules/entity_time.py
index 6676fbaef8..d304f6132a 100644
--- a/gajim/common/modules/entity_time.py
+++ b/gajim/common/modules/entity_time.py
@@ -14,26 +14,27 @@
 
 # XEP-0202: Entity Time
 
-import logging
 import time
 
 import nbxmpp
+from nbxmpp.structs import StanzaHandler
 
 from gajim.common import app
 from gajim.common.nec import NetworkEvent
+from gajim.common.modules.base import BaseModule
 from gajim.common.modules.date_and_time import parse_datetime
 from gajim.common.modules.date_and_time import create_tzinfo
 
-log = logging.getLogger('gajim.c.m.entity_time')
 
-
-class EntityTime:
+class EntityTime(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
-            ('iq', self._answer_request, 'get', nbxmpp.NS_TIME_REVISED),
+            StanzaHandler(name='iq',
+                          callback=self._answer_request,
+                          typ='get',
+                          ns=nbxmpp.NS_TIME_REVISED),
         ]
 
     def request_entity_time(self, jid, resource):
@@ -48,54 +49,52 @@ class EntityTime:
         iq = nbxmpp.Iq(to=jid, typ='get')
         iq.addChild('time', namespace=nbxmpp.NS_TIME_REVISED)
 
-        log.info('Requested: %s', jid)
+        self._log.info('Requested: %s', jid)
 
         self._con.connection.SendAndCallForResponse(iq, self._result_received)
 
     def _result_received(self, stanza):
         time_info = None
         if not nbxmpp.isResultNode(stanza):
-            log.info('Error: %s', stanza.getError())
+            self._log.info('Error: %s', stanza.getError())
         else:
             time_info = self._extract_info(stanza)
 
-        log.info('Received: %s %s',
-                 stanza.getFrom(), time_info)
+        self._log.info('Received: %s %s', stanza.getFrom(), time_info)
 
         app.nec.push_incoming_event(NetworkEvent('time-result-received',
                                                  conn=self._con,
                                                  jid=stanza.getFrom(),
                                                  time_info=time_info))
 
-    @staticmethod
-    def _extract_info(stanza):
+    def _extract_info(self, stanza):
         time_ = stanza.getTag('time')
         if not time_:
-            log.warning('No time node: %s', stanza)
+            self._log.warning('No time node: %s', stanza)
             return
 
         tzo = time_.getTag('tzo').getData()
         if not tzo:
-            log.warning('Wrong tzo node: %s', stanza)
+            self._log.warning('Wrong tzo node: %s', stanza)
             return
 
         remote_tz = create_tzinfo(tz_string=tzo)
         if remote_tz is None:
-            log.warning('Wrong tzo node: %s', stanza)
+            self._log.warning('Wrong tzo node: %s', stanza)
             return
 
         utc_time = time_.getTag('utc').getData()
         date_time = parse_datetime(utc_time, check_utc=True)
         if date_time is None:
-            log.warning('Wrong timezone defintion: %s %s',
-                        utc_time, stanza.getFrom())
+            self._log.warning('Wrong timezone defintion: %s %s',
+                              utc_time, stanza.getFrom())
             return
 
         date_time = date_time.astimezone(remote_tz)
         return date_time.strftime('%c %Z')
 
-    def _answer_request(self, _con, stanza):
-        log.info('%s asked for the time', stanza.getFrom())
+    def _answer_request(self, _con, stanza, _properties):
+        self._log.info('%s asked for the time', stanza.getFrom())
         if app.config.get_per('accounts', self._account, 'send_time_info'):
             iq = stanza.buildReply('result')
             time_ = iq.setTag('time', namespace=nbxmpp.NS_TIME_REVISED)
@@ -105,12 +104,12 @@ class EntityTime:
             zone = -(time.timezone, time.altzone)[isdst] / 60.0
             tzo = (zone / 60, abs(zone % 60))
             time_.setTagData('tzo', '%+03d:%02d' % (tzo))
-            log.info('Answer: %s %s', formated_time, '%+03d:%02d' % (tzo))
+            self._log.info('Answer: %s %s', formated_time, '%+03d:%02d' % (tzo))
         else:
             iq = stanza.buildReply('error')
             err = nbxmpp.ErrorNode(nbxmpp.ERR_SERVICE_UNAVAILABLE)
             iq.addChild(node=err)
-            log.info('Send service-unavailable')
+            self._log.info('Send service-unavailable')
         self._con.connection.send(iq)
         raise nbxmpp.NodeProcessed
 
diff --git a/gajim/common/modules/gateway.py b/gajim/common/modules/gateway.py
index a54656c8c2..26b74daf29 100644
--- a/gajim/common/modules/gateway.py
+++ b/gajim/common/modules/gateway.py
@@ -14,22 +14,16 @@
 
 # XEP-0100: Gateway Interaction
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
 from gajim.common.nec import NetworkEvent
-
-log = logging.getLogger('gajim.c.m.gateway')
+from gajim.common.modules.base import BaseModule
 
 
-class Gateway:
+class Gateway(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
-
-        self.handlers = []
+        BaseModule.__init__(self, con)
 
     def unsubscribe(self, agent):
         if not app.account_is_connected(self._account):
@@ -43,7 +37,7 @@ class Gateway:
 
     def _on_unsubscribe_result(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.info('Error: %s', stanza.getError())
+            self._log.info('Error: %s', stanza.getError())
             return
 
         agent = stanza.getFrom().getBare()
@@ -51,8 +45,8 @@ class Gateway:
         for jid in app.contacts.get_jid_list(self._account):
             if jid.endswith('@' + agent):
                 jid_list.append(jid)
-                log.info('Removing contact %s due to unregistered transport %s',
-                          jid, agent)
+                self._log.info('Removing contact %s due to'
+                               ' unregistered transport %s', jid, agent)
                 self._con.get_module('Presence').unsubscribe(jid)
                 # Transport contacts can't have 2 resources
                 if jid in app.to_be_removed[self._account]:
diff --git a/gajim/common/modules/http_auth.py b/gajim/common/modules/http_auth.py
index bd74389b19..dd84a8cd2c 100644
--- a/gajim/common/modules/http_auth.py
+++ b/gajim/common/modules/http_auth.py
@@ -14,22 +14,18 @@
 
 # XEP-0070: Verifying HTTP Requests via XMPP
 
-import logging
-
 import nbxmpp
 from nbxmpp.structs import StanzaHandler
 from nbxmpp.protocol import NS_HTTP_AUTH
 
 from gajim.common import app
 from gajim.common.nec import NetworkEvent
-
-log = logging.getLogger('gajim.c.m.http_auth')
+from gajim.common.modules.base import BaseModule
 
 
-class HTTPAuth:
+class HTTPAuth(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
             StanzaHandler(name='message',
@@ -47,7 +43,7 @@ class HTTPAuth:
         if not properties.is_http_auth:
             return
 
-        log.info('Auth request received')
+        self._log.info('Auth request received')
         auto_answer = app.config.get_per(
             'accounts', self._account, 'http_auth')
         if auto_answer in ('yes', 'no'):
@@ -66,14 +62,14 @@ class HTTPAuth:
 
     def build_http_auth_answer(self, stanza, answer):
         if answer == 'yes':
-            log.info('Auth request approved')
+            self._log.info('Auth request approved')
             confirm = stanza.getTag('confirm')
             reply = stanza.buildReply('result')
             if stanza.getName() == 'message':
                 reply.addChild(node=confirm)
             self._con.connection.send(reply)
         elif answer == 'no':
-            log.info('Auth request denied')
+            self._log.info('Auth request denied')
             err = nbxmpp.Error(stanza, nbxmpp.protocol.ERR_NOT_AUTHORIZED)
             self._con.connection.send(err)
 
diff --git a/gajim/common/modules/httpupload.py b/gajim/common/modules/httpupload.py
index 77deece651..46f1789bc0 100644
--- a/gajim/common/modules/httpupload.py
+++ b/gajim/common/modules/httpupload.py
@@ -24,7 +24,6 @@ from urllib.request import Request, urlopen
 from urllib.parse import urlparse
 import io
 import mimetypes
-import logging
 
 import nbxmpp
 from nbxmpp import NS_HTTPUPLOAD
@@ -34,6 +33,7 @@ from gajim.common import app
 from gajim.common import ged
 from gajim.common.i18n import _
 from gajim.common.nec import NetworkIncomingEvent
+from gajim.common.modules.base import BaseModule
 from gajim.common.connection_handlers_events import InformationEvent
 from gajim.common.connection_handlers_events import MessageOutgoingEvent
 from gajim.common.connection_handlers_events import GcMessageOutgoingEvent
@@ -41,18 +41,12 @@ from gajim.common.connection_handlers_events import GcMessageOutgoingEvent
 if sys.platform in ('win32', 'darwin'):
     import certifi
 
-log = logging.getLogger('gajim.c.m.httpupload')
-
-
 NS_HTTPUPLOAD_0 = NS_HTTPUPLOAD + ':0'
 
 
-class HTTPUpload:
+class HTTPUpload(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
-
-        self.handlers = []
+        BaseModule.__init__(self, con)
 
         self.available = False
         self.component = None
@@ -86,7 +80,7 @@ class HTTPUpload:
             return
 
         self.component = from_
-        log.info('Discovered component: %s', from_)
+        self._log.info('Discovered component: %s', from_)
 
         for form in data:
             form_dict = form.asDict()
@@ -98,10 +92,10 @@ class HTTPUpload:
                 break
 
         if self.max_file_size is None:
-            log.warning('%s does not provide maximum file size', self._account)
+            self._log.warning('Component does not provide maximum file size')
         else:
-            log.info('%s has a maximum file size of: %s MiB',
-                     self._account, self.max_file_size / (1024 * 1024))
+            self._log.info('Component has a maximum file size of: %s MiB',
+                           self.max_file_size / (1024 * 1024))
 
         self.available = True
 
@@ -151,7 +145,7 @@ class HTTPUpload:
         mime = mimetypes.MimeTypes().guess_type(path)[0]
         if not mime:
             mime = 'application/octet-stream'  # fallback mime type
-        log.info("Detected MIME type of file: %s", mime)
+        self._log.info("Detected MIME type of file: %s", mime)
 
         try:
             file = File(path, contact, mime=mime, encryption=encryption,
@@ -159,7 +153,7 @@ class HTTPUpload:
                         session=session, groupchat=groupchat)
             app.interface.show_httpupload_progress(file)
         except Exception as error:
-            log.exception('Error while loading file')
+            self._log.exception('Error while loading file')
             self.raise_information_event('open-file-error2', str(error))
             return
 
@@ -181,7 +175,7 @@ class HTTPUpload:
     def _request_slot(self, file):
         GLib.idle_add(self.raise_progress_event, 'request', file)
         iq = self._build_request(file)
-        log.info("Sending request for slot")
+        self._log.info("Sending request for slot")
         self._con.connection.SendAndCallForResponse(
             iq, self._received_slot, {'file': file})
 
@@ -218,12 +212,12 @@ class HTTPUpload:
         return stanza.getErrorMsg()
 
     def _received_slot(self, _con, stanza, file):
-        log.info("Received slot")
+        self._log.info("Received slot")
         if stanza.getType() == 'error':
             self.raise_progress_event('close', file)
             self.raise_information_event('request-upload-slot-error',
                                          self.get_slot_error_message(stanza))
-            log.error(stanza)
+            self._log.error(stanza)
             return
 
         try:
@@ -243,8 +237,8 @@ class HTTPUpload:
                         raise ValueError('Newline in header data')
                     file.headers[name] = data
         except Exception:
-            log.error("Got invalid stanza: %s", stanza)
-            log.exception('Error')
+            self._log.error("Got invalid stanza: %s", stanza)
+            self._log.exception('Error')
             self.raise_progress_event('close', file)
             self.raise_information_event('request-upload-slot-error2')
             return
@@ -258,13 +252,13 @@ class HTTPUpload:
         try:
             file.stream = StreamFileWithProgress(file)
         except Exception:
-            log.exception('Error')
+            self._log.exception('Error')
             self.raise_progress_event('close', file)
             self.raise_information_event('open-file-error')
             return
 
-        log.info('Uploading file to %s', file.put)
-        log.info('Please download from %s', file.get)
+        self._log.info('Uploading file to %s', file.put)
+        self._log.info('Please download from %s', file.get)
 
         thread = threading.Thread(target=self._upload_file, args=(file,))
         thread.daemon = True
@@ -279,14 +273,14 @@ class HTTPUpload:
 
             request = Request(
                 file.put, data=file.stream, headers=file.headers, method='PUT')
-            log.info("Opening Urllib upload request...")
+            self._log.info("Opening Urllib upload request...")
 
             if not app.config.get_per(
                     'accounts', self._account, 'httpupload_verify'):
                 context = ssl.create_default_context()
                 context.check_hostname = False
                 context.verify_mode = ssl.CERT_NONE
-                log.warning('CERT Verification disabled')
+                self._log.warning('CERT Verification disabled')
                 transfer = urlopen(request, timeout=30, context=context)
             else:
                 if sys.platform in ('win32', 'darwin'):
@@ -295,23 +289,23 @@ class HTTPUpload:
                 else:
                     transfer = urlopen(request, timeout=30)
             file.stream.close()
-            log.info('Urllib upload request done, response code: %s',
-                     transfer.getcode())
+            self._log.info('Urllib upload request done, response code: %s',
+                           transfer.getcode())
             GLib.idle_add(self._upload_complete, transfer.getcode(), file)
             return
         except UploadAbortedException as exc:
-            log.info(exc)
+            self._log.info(exc)
             error_msg = exc
         except urllib.error.URLError as exc:
             if isinstance(exc.reason, ssl.SSLError):
                 error_msg = exc.reason.reason
                 if error_msg == 'CERTIFICATE_VERIFY_FAILED':
-                    log.exception('Certificate verify failed')
+                    self._log.exception('Certificate verify failed')
             else:
-                log.exception('URLError')
+                self._log.exception('URLError')
                 error_msg = exc.reason
         except Exception as exc:
-            log.exception("Exception during upload")
+            self._log.exception("Exception during upload")
             error_msg = exc
         GLib.idle_add(self.raise_progress_event, 'close', file)
         GLib.idle_add(self._on_upload_error, file, error_msg)
@@ -319,7 +313,7 @@ class HTTPUpload:
     def _upload_complete(self, response_code, file):
         self.raise_progress_event('close', file)
         if 200 <= response_code < 300:
-            log.info("Upload completed successfully")
+            self._log.info("Upload completed successfully")
             message = file.get
             if file.user_data:
                 message += '#' + file.user_data
@@ -339,8 +333,8 @@ class HTTPUpload:
                     automatic_message=False, session=file.session))
 
         else:
-            log.error('Got unexpected http upload response code: %s',
-                      response_code)
+            self._log.error('Got unexpected http upload response code: %s',
+                            response_code)
             self.raise_information_event('httpupload-response-error',
                                          response_code)
 
diff --git a/gajim/common/modules/iq.py b/gajim/common/modules/iq.py
index 5d2adde76b..c309c88ad7 100644
--- a/gajim/common/modules/iq.py
+++ b/gajim/common/modules/iq.py
@@ -14,8 +14,6 @@
 
 # Iq handler
 
-import logging
-
 import nbxmpp
 from nbxmpp.const import Error
 from nbxmpp.structs import StanzaHandler
@@ -23,15 +21,12 @@ from nbxmpp.structs import StanzaHandler
 from gajim.common import app
 from gajim.common.nec import NetworkEvent
 from gajim.common.file_props import FilesProp
+from gajim.common.modules.base import BaseModule
 
 
-log = logging.getLogger('gajim.c.m.iq')
-
-
-class Iq:
+class Iq(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
             StanzaHandler(name='iq',
@@ -41,7 +36,7 @@ class Iq:
         ]
 
     def _iq_error_received(self, _con, _stanza, properties):
-        log.info('Error: %s', properties.error)
+        self._log.info('Error: %s', properties.error)
         if properties.error.type in (Error.JID_MALFORMED,
                                      Error.FORBIDDEN,
                                      Error.NOT_ACCEPTABLE):
diff --git a/gajim/common/modules/last_activity.py b/gajim/common/modules/last_activity.py
index 059398d322..0dec1a3dc8 100644
--- a/gajim/common/modules/last_activity.py
+++ b/gajim/common/modules/last_activity.py
@@ -14,8 +14,6 @@
 
 # XEP-0012: Last Activity
 
-import logging
-
 import nbxmpp
 from nbxmpp.structs import StanzaHandler
 
@@ -23,8 +21,6 @@ from gajim.common import app
 from gajim.common import idle
 from gajim.common.modules.base import BaseModule
 
-log = logging.getLogger('gajim.c.m.last_activity')
-
 
 class LastActivity(BaseModule):
     def __init__(self, con):
@@ -38,7 +34,7 @@ class LastActivity(BaseModule):
         ]
 
     def _answer_request(self, _con, stanza, properties):
-        log.info('Request from %s', properties.jid)
+        self._log.info('Request from %s', properties.jid)
 
         allow_send = app.config.get_per(
             'accounts', self._account, 'send_idle_time')
@@ -47,7 +43,7 @@ class LastActivity(BaseModule):
             query = iq.setQuery()
             seconds = idle.Monitor.get_idle_sec()
             query.attrs['seconds'] = seconds
-            log.info('Respond with seconds: %s', seconds)
+            self._log.info('Respond with seconds: %s', seconds)
         else:
             iq = stanza.buildReply('error')
             err = nbxmpp.ErrorNode(nbxmpp.ERR_SERVICE_UNAVAILABLE)
diff --git a/gajim/common/modules/mam.py b/gajim/common/modules/mam.py
index 4466e25f3b..d104de73bc 100644
--- a/gajim/common/modules/mam.py
+++ b/gajim/common/modules/mam.py
@@ -14,7 +14,6 @@
 
 # XEP-0313: Message Archive Management
 
-import logging
 import time
 from datetime import datetime, timedelta
 
@@ -34,14 +33,12 @@ from gajim.common.modules.misc import parse_delay
 from gajim.common.modules.misc import parse_oob
 from gajim.common.modules.misc import parse_correction
 from gajim.common.modules.util import get_eme_message
+from gajim.common.modules.base import BaseModule
 
-log = logging.getLogger('gajim.c.m.archiving')
 
-
-class MAM:
+class MAM(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
             StanzaHandler(name='message',
@@ -65,14 +62,14 @@ class MAM:
             return
 
         self.available = True
-        log.info('Discovered MAM %s: %s', self.archiving_namespace, from_)
+        self._log.info('Discovered MAM %s: %s', self.archiving_namespace, from_)
 
         app.nec.push_incoming_event(
             NetworkEvent('feature-discovered',
                          account=self._account,
                          feature=self.archiving_namespace))
 
-    def _from_valid_archive(self, stanza, properties):
+    def _from_valid_archive(self, _stanza, properties):
         if properties.type.is_groupchat:
             expected_archive = properties.jid
         else:
@@ -107,15 +104,16 @@ class MAM:
                                  stanza=stanza))
 
         if not self._from_valid_archive(stanza, properties):
-            log.warning('Message from invalid archive %s',
-                        properties.mam.archive)
+            self._log.warning('Message from invalid archive %s',
+                              properties.mam.archive)
             raise nbxmpp.NodeProcessed
 
-        log.info('Received message from archive: %s', properties.mam.archive)
+        self._log.info('Received message from archive: %s',
+                       properties.mam.archive)
         if not self._is_valid_request(properties):
-            log.warning('Invalid MAM Message: unknown query id %s',
-                        properties.mam.query_id)
-            log.debug(stanza)
+            self._log.warning('Invalid MAM Message: unknown query id %s',
+                              properties.mam.query_id)
+            self._log.debug(stanza)
             raise nbxmpp.NodeProcessed
 
         event_attrs = {}
@@ -136,8 +134,8 @@ class MAM:
                                          stanza_id,
                                          message_id,
                                          groupchat=groupchat):
-                log.info('Found duplicate with stanza-id: %s, message-id: %s',
-                         stanza_id, message_id)
+                self._log.info('Found duplicate with stanza-id: %s, '
+                               'message-id: %s', stanza_id, message_id)
                 raise nbxmpp.NodeProcessed
 
         event_attrs.update(
@@ -204,7 +202,7 @@ class MAM:
     def _decryption_finished(self, event):
         if not event.msgtxt:
             # For example Chatstates, Receipts, Chatmarkers
-            log.debug(event.message.getProperties())
+            self._log.debug(event.message.getProperties())
             return
 
         user_timestamp = parse_delay(event.stanza)
@@ -225,14 +223,14 @@ class MAM:
         if event.self_message:
             # Self messages can only be deduped with origin-id
             if event.origin_id is None:
-                log.warning('Self message without origin-id found')
+                self._log.warning('Self message without origin-id found')
                 return
             stanza_id = event.origin_id
 
         if event.namespace == nbxmpp.NS_MAM_1:
             if app.logger.search_for_duplicate(
                     self._account, with_, event.timestamp, event.msgtxt):
-                log.info('Found duplicate with fallback for mam:1')
+                self._log.info('Found duplicate with fallback for mam:1')
                 return
 
         app.logger.insert_into_logs(self._account,
@@ -258,20 +256,19 @@ class MAM:
         self._mam_query_ids[jid] = query_id
         return query_id
 
-    @staticmethod
-    def _parse_iq(stanza):
+    def _parse_iq(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.error('Error on MAM query: %s', stanza.getError())
+            self._log.error('Error on MAM query: %s', stanza.getError())
             raise InvalidMamIQ
 
         fin = stanza.getTag('fin')
         if fin is None:
-            log.error('Malformed MAM query result received: %s', stanza)
+            self._log.error('Malformed MAM query result received: %s', stanza)
             raise InvalidMamIQ
 
         set_ = fin.getTag('set', namespace=nbxmpp.NS_RSM)
         if set_ is None:
-            log.error(
+            self._log.error(
                 'Malformed MAM query result received (no "set" Node): %s',
                 stanza)
             raise InvalidMamIQ
@@ -288,7 +285,7 @@ class MAM:
 
     def request_archive_count(self, start_date, end_date):
         jid = self._con.get_own_jid().getStripped()
-        log.info('Request archive count from: %s', jid)
+        self._log.info('Request archive count from: %s', jid)
         query_id = self._get_query_id(jid)
         query = self._get_archive_query(
             query_id, start=start_date, end=end_date, max_=0)
@@ -306,7 +303,7 @@ class MAM:
         self._mam_query_ids.pop(jid)
 
         count = set_.getTagData('count')
-        log.info('Received archive count: %s', count)
+        self._log.info('Received archive count: %s', count)
         app.nec.push_incoming_event(ArchivingCountReceived(
             None, query_id=query_id, count=count))
 
@@ -314,7 +311,7 @@ class MAM:
         own_jid = self._con.get_own_jid().getStripped()
 
         if own_jid in self._mam_query_ids:
-            log.warning('MAM request for %s already running', own_jid)
+            self._log.warning('MAM request for %s already running', own_jid)
             return
 
         archive = app.logger.get_archive_infos(own_jid)
@@ -331,12 +328,12 @@ class MAM:
         start_date = None
         query_id = self._get_query_id(own_jid)
         if mam_id:
-            log.info('MAM query after: %s', mam_id)
+            self._log.info('MAM query after: %s', mam_id)
             query = self._get_archive_query(query_id, after=mam_id)
         else:
             # First Start, we request the last week
             start_date = datetime.utcnow() - timedelta(days=7)
-            log.info('First start: query archive start: %s', start_date)
+            self._log.info('First start: query archive start: %s', start_date)
             query = self._get_archive_query(query_id, start=start_date)
 
         if own_jid in self._catch_up_finished:
@@ -346,7 +343,7 @@ class MAM:
     def request_archive_on_muc_join(self, jid):
         archive = app.logger.get_archive_infos(jid)
         threshold = get_sync_threshold(jid, archive)
-        log.info('Threshold for %s: %s', jid, threshold)
+        self._log.info('Threshold for %s: %s', jid, threshold)
         query_id = self._get_query_id(jid)
         start_date = None
         if archive is None or archive.last_mam_id is None:
@@ -354,14 +351,15 @@ class MAM:
             # Depending on what a MUC saves, there could be thousands
             # of Messages even in just one day.
             start_date = datetime.utcnow() - timedelta(days=1)
-            log.info('First join: query archive %s from: %s', jid, start_date)
+            self._log.info('First join: query archive %s from: %s',
+                           jid, start_date)
             query = self._get_archive_query(
                 query_id, jid=jid, start=start_date)
 
         elif threshold == SyncThreshold.NO_THRESHOLD:
             # Not our first join and no threshold set
-            log.info('Request from archive: %s, after mam-id %s',
-                     jid, archive.last_mam_id)
+            self._log.info('Request from archive: %s, after mam-id %s',
+                           jid, archive.last_mam_id)
             query = self._get_archive_query(
                 query_id, jid=jid, after=archive.last_mam_id)
 
@@ -370,22 +368,22 @@ class MAM:
             # last join and check against threshold
             last_timestamp = archive.last_muc_timestamp
             if last_timestamp is None:
-                log.info('No last muc timestamp found ( mam:1? )')
+                self._log.info('No last muc timestamp found ( mam:1? )')
                 last_timestamp = 0
 
             last = datetime.utcfromtimestamp(float(last_timestamp))
             if datetime.utcnow() - last > timedelta(days=threshold):
                 # To much time has elapsed since last join, apply threshold
                 start_date = datetime.utcnow() - timedelta(days=threshold)
-                log.info('Too much time elapsed since last join, '
-                         'request from: %s, threshold: %s',
-                         start_date, threshold)
+                self._log.info('Too much time elapsed since last join, '
+                               'request from: %s, threshold: %s',
+                               start_date, threshold)
                 query = self._get_archive_query(
                     query_id, jid=jid, start=start_date)
             else:
                 # Request from last mam-id
-                log.info('Request from archive %s after %s:',
-                         jid, archive.last_mam_id)
+                self._log.info('Request from archive %s after %s:',
+                               jid, archive.last_mam_id)
                 query = self._get_archive_query(
                     query_id, jid=jid, after=archive.last_mam_id)
 
@@ -410,7 +408,7 @@ class MAM:
 
         last = set_.getTagData('last')
         if last is None:
-            log.info('End of MAM query, no items retrieved')
+            self._log.info('End of MAM query, no items retrieved')
             self._catch_up_finished.append(jid)
             self._mam_query_ids.pop(jid)
             return
@@ -434,17 +432,16 @@ class MAM:
                     jid, oldest_mam_timestamp=start_date.timestamp())
 
             self._catch_up_finished.append(jid)
-            log.info('End of MAM query, last mam id: %s', last)
+            self._log.info('End of MAM query, last mam id: %s', last)
 
     def request_archive_interval(self, start_date, end_date, after=None,
                                  query_id=None):
         jid = self._con.get_own_jid().getStripped()
         if after is None:
-            log.info('Request intervall from %s to %s from %s',
-                     start_date, end_date, jid)
+            self._log.info('Request intervall from %s to %s from %s',
+                           start_date, end_date, jid)
         else:
-            log.info('Query page after %s from %s',
-                     after, jid)
+            self._log.info('Query page after %s from %s', after, jid)
         if query_id is None:
             query_id = self._get_query_id(jid)
         self._mam_query_ids[jid] = query_id
@@ -477,14 +474,14 @@ class MAM:
                 None, query_id=query_id))
             app.logger.set_archive_infos(
                 jid, oldest_mam_timestamp=timestamp)
-            log.info('End of MAM request, no items retrieved')
+            self._log.info('End of MAM request, no items retrieved')
             return
 
         complete = fin.getAttr('complete')
         if complete != 'true':
             self.request_archive_interval(start_date, end_date, last, query_id)
         else:
-            log.info('Request finished')
+            self._log.info('Request finished')
             app.logger.set_archive_infos(
                 jid, oldest_mam_timestamp=timestamp)
             app.nec.push_incoming_event(ArchivingIntervalFinished(
@@ -534,7 +531,7 @@ class MAM:
             jid = self._con.get_own_jid().getStripped()
         if jid not in self._catch_up_finished:
             return
-        log.info('Save: %s: %s, %s', jid, stanza_id, timestamp)
+        self._log.info('Save: %s: %s, %s', jid, stanza_id, timestamp)
         if stanza_id is None:
             # mam:1
             app.logger.set_archive_infos(jid, last_muc_timestamp=timestamp)
@@ -544,7 +541,7 @@ class MAM:
                 jid, last_mam_id=stanza_id, last_muc_timestamp=timestamp)
 
     def request_mam_preferences(self):
-        log.info('Request MAM preferences')
+        self._log.info('Request MAM preferences')
         iq = nbxmpp.Iq('get', self.archiving_namespace)
         iq.setQuery('prefs')
         self._con.connection.SendAndCallForResponse(
@@ -552,15 +549,15 @@ class MAM:
 
     def _preferences_received(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.info('Error: %s', stanza.getError())
+            self._log.info('Error: %s', stanza.getError())
             app.nec.push_incoming_event(MAMPreferenceError(
                 None, conn=self._con, error=stanza.getError()))
             return
 
-        log.info('Received MAM preferences')
+        self._log.info('Received MAM preferences')
         prefs = stanza.getTag('prefs', namespace=self.archiving_namespace)
         if prefs is None:
-            log.error('Malformed stanza (no prefs node): %s', stanza)
+            self._log.error('Malformed stanza (no prefs node): %s', stanza)
             return
 
         rules = []
@@ -593,11 +590,11 @@ class MAM:
 
     def _preferences_saved(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.info('Error: %s', stanza.getError())
+            self._log.info('Error: %s', stanza.getError())
             app.nec.push_incoming_event(MAMPreferenceError(
                 None, conn=self._con, error=stanza.getError()))
         else:
-            log.info('Preferences saved')
+            self._log.info('Preferences saved')
             app.nec.push_incoming_event(
                 MAMPreferenceSaved(None, conn=self._con))
 
diff --git a/gajim/common/modules/message.py b/gajim/common/modules/message.py
index f0a931ea51..a7a5edb2aa 100644
--- a/gajim/common/modules/message.py
+++ b/gajim/common/modules/message.py
@@ -15,7 +15,6 @@
 # Message handler
 
 import time
-import logging
 
 import nbxmpp
 from nbxmpp.structs import StanzaHandler
@@ -28,6 +27,7 @@ from gajim.common.nec import NetworkIncomingEvent
 from gajim.common.nec import NetworkEvent
 from gajim.common.helpers import AdditionalDataDict
 from gajim.common.const import KindConstant
+from gajim.common.modules.base import BaseModule
 from gajim.common.modules.util import get_eme_message
 from gajim.common.modules.security_labels import parse_securitylabel
 from gajim.common.modules.user_nickname import parse_nickname
@@ -40,13 +40,9 @@ from gajim.common.modules.misc import parse_xhtml
 from gajim.common.connection_handlers_events import MessageErrorEvent
 
 
-log = logging.getLogger('gajim.c.m.message')
-
-
-class Message:
+class Message(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
             StanzaHandler(name='message',
@@ -67,7 +63,7 @@ class Message:
         if self._message_namespaces & set(stanza.getProperties()):
             return
 
-        log.info('Received from %s', stanza.getFrom())
+        self._log.info('Received from %s', stanza.getFrom())
 
         app.nec.push_incoming_event(NetworkEvent(
             'raw-message-received',
@@ -96,7 +92,7 @@ class Message:
         # Check groupchat messages for duplicates,
         # We do this because of MUC History messages
         if (properties.type.is_groupchat or
-            properties.is_self_message or
+                properties.is_self_message or
                 properties.is_muc_pm):
             if properties.type.is_groupchat:
                 archive_jid = stanza.getFrom().getStripped()
@@ -309,11 +305,10 @@ class Message:
             self._con.get_module('MAM').save_archive_id(
                 event.room_jid, event.stanza_id, event.timestamp)
 
-    @staticmethod
-    def _check_for_mam_compliance(room_jid, stanza_id):
+    def _check_for_mam_compliance(self, room_jid, stanza_id):
         namespace = caps_cache.muc_caps_cache.get_mam_namespace(room_jid)
         if stanza_id is None and namespace == nbxmpp.NS_MAM_2:
-            log.warning('%s announces mam:2 without stanza-id', room_jid)
+            self._log.warning('%s announces mam:2 without stanza-id', room_jid)
 
     def _get_unique_id(self, properties):
         if properties.is_self_message:
diff --git a/gajim/common/modules/metacontacts.py b/gajim/common/modules/metacontacts.py
index 3ed7bb891c..7a01cb9bb2 100644
--- a/gajim/common/modules/metacontacts.py
+++ b/gajim/common/modules/metacontacts.py
@@ -14,27 +14,22 @@
 
 # XEP-0209: Metacontacts
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
 from gajim.common import helpers
 from gajim.common.nec import NetworkEvent
-
-log = logging.getLogger('gajim.c.m.metacontacts')
+from gajim.common.modules.base import BaseModule
 
 
-class MetaContacts:
+class MetaContacts(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
-        self.available = False
+        BaseModule.__init__(self, con)
 
-        self.handlers = []
+        self.available = False
 
     def get_metacontacts(self):
-        log.info('Request')
+        self._log.info('Request')
         node = nbxmpp.Node('storage', attrs={'xmlns': 'storage:metacontacts'})
         iq = nbxmpp.Iq('get', nbxmpp.NS_PRIVATE, payload=node)
 
@@ -43,12 +38,12 @@ class MetaContacts:
 
     def _metacontacts_received(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.info('Request error: %s', stanza.getError())
+            self._log.info('Request error: %s', stanza.getError())
         else:
             self.available = True
             meta_list = self._parse_metacontacts(stanza)
 
-            log.info('Received: %s', meta_list)
+            self._log.info('Received: %s', meta_list)
 
             app.nec.push_incoming_event(NetworkEvent(
                 'metacontacts-received', conn=self._con, meta_list=meta_list))
@@ -94,14 +89,13 @@ class MetaContacts:
                 if 'order' in data:
                     dict_['order'] = data['order']
                 meta.addChild(name='meta', attrs=dict_)
-        log.info('Store: %s', tags_list)
+        self._log.info('Store: %s', tags_list)
         self._con.connection.SendAndCallForResponse(
             iq, self._store_response_received)
 
-    @staticmethod
-    def _store_response_received(stanza):
+    def _store_response_received(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.info('Store error: %s', stanza.getError())
+            self._log.info('Store error: %s', stanza.getError())
 
 
 def get_instance(*args, **kwargs):
diff --git a/gajim/common/modules/muc.py b/gajim/common/modules/muc.py
index 31688b57c1..e31a06ba18 100644
--- a/gajim/common/modules/muc.py
+++ b/gajim/common/modules/muc.py
@@ -103,7 +103,7 @@ class MUC(BaseModule):
             if identity.get('type') != 'text':
                 continue
             if nbxmpp.NS_MUC in features:
-                log.info('Discovered MUC: %s', from_)
+                self._log.info('Discovered MUC: %s', from_)
                 # TODO: make this nicer
                 self._con.muc_jid['jabber'] = from_
                 raise nbxmpp.NodeProcessed
@@ -122,7 +122,7 @@ class MUC(BaseModule):
         if password is not None:
             muc_x.setTagData('password', password)
 
-        log.debug('Send MUC join presence:\n%s', presence)
+        self._log.debug('Send MUC join presence:\n%s', presence)
 
         self._con.connection.send(presence)
 
@@ -171,7 +171,7 @@ class MUC(BaseModule):
             for contact in app.contacts.get_gc_contact_list(
                     self._account, properties.jid.getBare()):
                 contact.presence = PresenceType.UNAVAILABLE
-            log.info('MUC destroyed: %s', properties.jid.getBare())
+            self._log.info('MUC destroyed: %s', properties.jid.getBare())
             self._raise_muc_event('muc-destroyed', properties)
             return
 
@@ -183,19 +183,19 @@ class MUC(BaseModule):
             app.contacts.remove_gc_contact(self._account, contact)
             contact.name = properties.muc_user.nick
             app.contacts.add_gc_contact(self._account, contact)
-            log.info('Nickname changed: %s to %s',
-                     properties.jid,
-                     properties.muc_user.nick)
+            self._log.info('Nickname changed: %s to %s',
+                           properties.jid,
+                           properties.muc_user.nick)
             self._raise_muc_event('muc-nickname-changed', properties)
             return
 
         if contact is None and properties.type.is_available:
             self._add_new_muc_contact(properties)
             if properties.is_muc_self_presence:
-                log.info('Self presence: %s', properties.jid)
+                self._log.info('Self presence: %s', properties.jid)
                 self._raise_muc_event('muc-self-presence', properties)
             else:
-                log.info('User joined: %s', properties.jid)
+                self._log.info('User joined: %s', properties.jid)
                 self._raise_muc_event('muc-user-joined', properties)
             return
 
@@ -226,39 +226,39 @@ class MUC(BaseModule):
             if contact is None:
                 # If contact is None, its probably that a user left from a not
                 # insync MUC, can happen on older servers
-                log.warning('Unknown contact left groupchat: %s',
-                            properties.jid)
+                self._log.warning('Unknown contact left groupchat: %s',
+                                  properties.jid)
             else:
                 # We remove the contact from the MUC, but there could be
                 # a PrivateChatControl open, so we update the contacts presence
                 contact.presence = properties.type
                 app.contacts.remove_gc_contact(self._account, contact)
-            log.info('User %s left', properties.jid)
+            self._log.info('User %s left', properties.jid)
             self._raise_muc_event('muc-user-left', properties)
             return
 
         if contact.affiliation != properties.affiliation:
             contact.affiliation = properties.affiliation
-            log.info('Affiliation changed: %s %s',
-                     properties.jid,
-                     properties.affiliation)
+            self._log.info('Affiliation changed: %s %s',
+                           properties.jid,
+                           properties.affiliation)
             self._raise_muc_event('muc-user-affiliation-changed', properties)
 
         if contact.role != properties.role:
             contact.role = properties.role
-            log.info('Role changed: %s %s',
-                     properties.jid,
-                     properties.role)
+            self._log.info('Role changed: %s %s',
+                           properties.jid,
+                           properties.role)
             self._raise_muc_event('muc-user-role-changed', properties)
 
         if (contact.status != properties.status or
                 contact.show != properties.show):
             contact.status = properties.status
             contact.show = properties.show
-            log.info('Show/Status changed: %s %s %s',
-                     properties.jid,
-                     properties.status,
-                     properties.show)
+            self._log.info('Show/Status changed: %s %s %s',
+                           properties.jid,
+                           properties.status,
+                           properties.show)
             self._raise_muc_event('muc-user-status-show-changed', properties)
 
     def _raise_muc_event(self, event_name, properties):
@@ -364,7 +364,7 @@ class MUC(BaseModule):
         if contact is None:
             return
 
-        log.info('Captcha challenge received from %s', properties.jid)
+        self._log.info('Captcha challenge received from %s', properties.jid)
         store_bob_data(properties.captcha.bob_data)
 
         app.nec.push_incoming_event(
@@ -378,8 +378,8 @@ class MUC(BaseModule):
         if not properties.is_muc_config_change:
             return
 
-        log.info('Received config change: %s %s',
-                 properties.jid, properties.muc_status_codes)
+        self._log.info('Received config change: %s %s',
+                       properties.jid, properties.muc_status_codes)
         app.nec.push_incoming_event(
             NetworkEvent('muc-config-changed',
                          account=self._account,
@@ -393,8 +393,8 @@ class MUC(BaseModule):
             if helpers.ignore_contact(self._account, data.from_):
                 raise nbxmpp.NodeProcessed
 
-            log.info('Invite declined from: %s, reason: %s',
-                     data.from_, data.reason)
+            self._log.info('Invite declined from: %s, reason: %s',
+                           data.from_, data.reason)
 
             app.nec.push_incoming_event(
                 NetworkEvent('muc-decline',
@@ -407,11 +407,11 @@ class MUC(BaseModule):
             if helpers.ignore_contact(self._account, data.from_):
                 raise nbxmpp.NodeProcessed
 
-            log.info('Invite from: %s, to: %s', data.from_, data.muc)
+            self._log.info('Invite from: %s, to: %s', data.from_, data.muc)
 
             if app.in_groupchat(self._account, data.muc):
                 # We are already in groupchat. Ignore invitation
-                log.info('We are already in this room')
+                self._log.info('We are already in this room')
                 raise nbxmpp.NodeProcessed
 
             app.nec.push_incoming_event(
diff --git a/gajim/common/modules/pep.py b/gajim/common/modules/pep.py
index c34a191c10..d1325552d8 100644
--- a/gajim/common/modules/pep.py
+++ b/gajim/common/modules/pep.py
@@ -19,17 +19,12 @@ from typing import Dict
 from typing import List
 from typing import Tuple
 
-import logging
-
 import nbxmpp
 
 from gajim.common.types import ConnectionT
 from gajim.common.modules.base import BaseModule
 
 
-log = logging.getLogger('gajim.c.m.pep')
-
-
 class PEP(BaseModule):
     def __init__(self, con: ConnectionT) -> None:
         BaseModule.__init__(self, con)
@@ -45,7 +40,7 @@ class PEP(BaseModule):
         for identity in identities:
             if identity['category'] == 'pubsub':
                 if identity.get('type') == 'pep':
-                    log.info('Discovered PEP support: %s', from_)
+                    self._log.info('Discovered PEP support: %s', from_)
                     self.supported = True
 
 
diff --git a/gajim/common/modules/ping.py b/gajim/common/modules/ping.py
index 7d30ae095a..99a799689a 100644
--- a/gajim/common/modules/ping.py
+++ b/gajim/common/modules/ping.py
@@ -17,28 +17,29 @@
 from typing import Any
 from typing import Tuple
 
-import logging
 import time
 
 import nbxmpp
+from nbxmpp.structs import StanzaHandler
 from gi.repository import GLib
 
 from gajim.common import app
 from gajim.common.nec import NetworkIncomingEvent
 from gajim.common.types import ConnectionT
 from gajim.common.types import ContactsT
+from gajim.common.modules.base import BaseModule
 
-log = logging.getLogger('gajim.c.m.ping')
 
-
-class Ping:
+class Ping(BaseModule):
     def __init__(self, con: ConnectionT) -> None:
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
         self._timeout_id = None
 
         self.handlers = [
-            ('iq', self._answer_request, 'get', nbxmpp.NS_PING),
+            StanzaHandler(name='iq',
+                          callback=self._answer_request,
+                          typ='get',
+                          ns=nbxmpp.NS_PING),
         ]
 
     @staticmethod
@@ -51,7 +52,7 @@ class Ping:
         if not app.account_is_connected(self._account):
             return
 
-        log.info('Send keepalive')
+        self._log.info('Send keepalive')
 
         seconds = app.config.get_per('accounts', self._account,
                                      'time_for_ping_alive_answer')
@@ -62,14 +63,15 @@ class Ping:
                                                     self._keepalive_received)
 
     def _keepalive_received(self, _stanza: nbxmpp.Iq) -> None:
-        log.info('Received keepalive')
+        self._log.info('Received keepalive')
         self.remove_timeout()
 
     def _reconnect(self) -> None:
         if not app.account_is_connected(self._account):
             return
         # We haven't got the pong in time, disco and reconnect
-        log.warning('No reply received for keepalive ping. Reconnecting...')
+        self._log.warning('No reply received for keepalive ping. '
+                          'Reconnecting...')
         self._con.disconnect(immediately=True)
 
     def send_ping(self, contact: ContactsT) -> None:
@@ -79,7 +81,7 @@ class Ping:
         to = contact.get_full_jid()
         iq = self._get_ping_iq(to)
 
-        log.info('Send ping to %s', to)
+        self._log.info('Send ping to %s', to)
 
         self._con.connection.SendAndCallForResponse(
             iq, self._pong_received, {'ping_time': time.time(),
@@ -94,13 +96,13 @@ class Ping:
                        ping_time: int,
                        contact: ContactsT) -> None:
         if not nbxmpp.isResultNode(stanza):
-            log.info('Error: %s', stanza.getError())
+            self._log.info('Error: %s', stanza.getError())
             app.nec.push_incoming_event(
                 PingErrorEvent(None, conn=self._con, contact=contact))
             return
         diff = round(time.time() - ping_time, 2)
-        log.info('Received pong from %s after %s seconds',
-                 stanza.getFrom(), diff)
+        self._log.info('Received pong from %s after %s seconds',
+                       stanza.getFrom(), diff)
         app.nec.push_incoming_event(
             PingReplyEvent(None, conn=self._con,
                            contact=contact,
@@ -108,19 +110,20 @@ class Ping:
 
     def _answer_request(self,
                         _con: ConnectionT,
-                        stanza: nbxmpp.Iq) -> None:
+                        stanza: nbxmpp.Iq,
+                        _properties: Any) -> None:
         iq = stanza.buildReply('result')
         ping = iq.getTag('ping')
         if ping is not None:
             iq.delChild(ping)
         self._con.connection.send(iq)
-        log.info('Send pong to %s', stanza.getFrom())
+        self._log.info('Send pong to %s', stanza.getFrom())
         raise nbxmpp.NodeProcessed
 
     def remove_timeout(self) -> None:
         if self._timeout_id is None:
             return
-        log.info('Remove ping timeout')
+        self._log.info('Remove ping timeout')
         GLib.source_remove(self._timeout_id)
         self._timeout_id = None
 
diff --git a/gajim/common/modules/presence.py b/gajim/common/modules/presence.py
index 7773c6e7e4..260f56e9f0 100644
--- a/gajim/common/modules/presence.py
+++ b/gajim/common/modules/presence.py
@@ -14,7 +14,6 @@
 
 # Presence handler
 
-import logging
 import time
 
 import nbxmpp
@@ -27,14 +26,12 @@ from gajim.common.nec import NetworkEvent
 from gajim.common.const import KindConstant
 from gajim.common.const import ShowConstant
 from gajim.common.helpers import prepare_and_validate_gpg_keyID
+from gajim.common.modules.base import BaseModule
 
-log = logging.getLogger('gajim.c.m.presence')
 
-
-class Presence:
+class Presence(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
             StanzaHandler(name='presence',
@@ -70,10 +67,10 @@ class Presence:
             # Already handled in MUC module
             return
 
-        log.info('Received from %s', properties.jid)
+        self._log.info('Received from %s', properties.jid)
 
         if properties.type == PresenceType.ERROR:
-            log.info('Error: %s %s', properties.jid, properties.error)
+            self._log.info('Error: %s %s', properties.jid, properties.error)
             return
 
         if self._account == 'Local':
@@ -93,8 +90,8 @@ class Presence:
         contacts = app.contacts.get_jid_list(self._account)
         if properties.jid.getBare() not in contacts and not properties.is_self_bare:
             # Handle only presence from roster contacts
-            log.warning('Unknown presence received')
-            log.warning(stanza)
+            self._log.warning('Unknown presence received')
+            self._log.warning(stanza)
             return
 
         key_id = ''
@@ -151,7 +148,7 @@ class Presence:
         # Update contact
         contact_list = app.contacts.get_contacts(self._account, jid)
         if not contact_list:
-            log.warning('No contact found')
+            self._log.warning('No contact found')
             return
 
         event.contact_list = contact_list
@@ -162,7 +159,7 @@ class Presence:
         if contact is None:
             contact = app.contacts.get_first_contact_from_jid(self._account, jid)
             if contact is None:
-                log.warning('First contact not found')
+                self._log.warning('First contact not found')
                 return
 
             if self._is_resource_known(contact_list) and not app.jid_is_transport(jid):
@@ -255,9 +252,10 @@ class Presence:
         is_transport = app.jid_is_transport(fjid)
         auto_auth = app.config.get_per('accounts', self._account, 'autoauth')
 
-        log.info('Received Subscribe: %s, transport: %s, '
-                 'auto_auth: %s, user_nick: %s',
-                 properties.jid, is_transport, auto_auth, properties.nickname)
+        self._log.info('Received Subscribe: %s, transport: %s, '
+                       'auto_auth: %s, user_nick: %s',
+                       properties.jid, is_transport,
+                       auto_auth, properties.nickname)
 
         if is_transport and fjid in self._con.agent_registrations:
             self._con.agent_registrations[fjid]['sub_received'] = True
@@ -285,7 +283,7 @@ class Presence:
     def _subscribed_received(self, _con, _stanza, properties):
         jid = properties.jid.getBare()
         resource = properties.jid.getResource()
-        log.info('Received Subscribed: %s', properties.jid)
+        self._log.info('Received Subscribed: %s', properties.jid)
         if jid in self.automatically_added:
             self.automatically_added.remove(jid)
             raise nbxmpp.NodeProcessed
@@ -295,13 +293,12 @@ class Presence:
             conn=self._con, jid=jid, resource=resource))
         raise nbxmpp.NodeProcessed
 
-    @staticmethod
-    def _unsubscribe_received(_con, _stanza, properties):
-        log.info('Received Unsubscribe: %s', properties.jid)
+    def _unsubscribe_received(self, _con, _stanza, properties):
+        self._log.info('Received Unsubscribe: %s', properties.jid)
         raise nbxmpp.NodeProcessed
 
     def _unsubscribed_received(self, _con, _stanza, properties):
-        log.info('Received Unsubscribed: %s', properties.jid)
+        self._log.info('Received Unsubscribed: %s', properties.jid)
         app.nec.push_incoming_event(NetworkEvent(
             'unsubscribed-presence-received',
             conn=self._con, jid=properties.jid.getBare()))
@@ -310,13 +307,13 @@ class Presence:
     def subscribed(self, jid):
         if not app.account_is_connected(self._account):
             return
-        log.info('Subscribed: %s', jid)
+        self._log.info('Subscribed: %s', jid)
         self.send_presence(jid, 'subscribed')
 
     def unsubscribed(self, jid):
         if not app.account_is_connected(self._account):
             return
-        log.info('Unsubscribed: %s', jid)
+        self._log.info('Unsubscribed: %s', jid)
         self.send_presence(jid, 'unsubscribed')
 
     def unsubscribe(self, jid, remove_auth=True):
@@ -329,7 +326,7 @@ class Presence:
                 if j.startswith(jid):
                     app.config.del_per('contacts', j)
         else:
-            log.info('Unsubscribe from %s', jid)
+            self._log.info('Unsubscribe from %s', jid)
             self._con.getRoster().unsubscribe(jid)
             self._con.getRoster().set_item(jid)
 
@@ -339,7 +336,7 @@ class Presence:
         if groups is None:
             groups = []
 
-        log.info('Request Subscription to %s', jid)
+        self._log.info('Request Subscription to %s', jid)
 
         if auto_auth:
             self.jids_for_auto_auth.append(jid)
@@ -388,7 +385,7 @@ class Presence:
         if not app.account_is_connected(self._account):
             return
         presence = self.get_presence(*args, **kwargs)
-        log.debug('Send presence:\n%s', presence)
+        self._log.debug('Send presence:\n%s', presence)
         self._con.connection.send(presence)
 
 
diff --git a/gajim/common/modules/privacylists.py b/gajim/common/modules/privacylists.py
index c697467332..0e8088d669 100644
--- a/gajim/common/modules/privacylists.py
+++ b/gajim/common/modules/privacylists.py
@@ -14,24 +14,20 @@
 
 # XEP-0016: Privacy Lists
 
-import logging
-
 import nbxmpp
+from nbxmpp.structs import StanzaHandler
 
 from gajim.common import app
 from gajim.common import helpers
 from gajim.common.nec import NetworkEvent
 from gajim.common.nec import NetworkIncomingEvent
+from gajim.common.modules.base import BaseModule
 from gajim.common.connection_handlers_events import InformationEvent
 
 
-log = logging.getLogger('gajim.c.m.privacylists')
-
-
-class PrivacyLists:
+class PrivacyLists(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.default_list = None
         self.active_list = None
@@ -41,7 +37,10 @@ class PrivacyLists:
         self.blocked_all = False
 
         self.handlers = [
-            ('iq', self._list_push_received, 'set', nbxmpp.NS_PRIVACY)
+            StanzaHandler(name='iq',
+                          callback=self._list_push_received,
+                          typ='set',
+                          ns=nbxmpp.NS_PRIVACY),
         ]
 
         self.supported = False
@@ -51,14 +50,14 @@ class PrivacyLists:
             return
 
         self.supported = True
-        log.info('Discovered XEP-0016: Privacy Lists: %s', from_)
+        self._log.info('Discovered XEP-0016: Privacy Lists: %s', from_)
 
         app.nec.push_incoming_event(
             NetworkEvent('feature-discovered',
                          account=self._account,
                          feature=nbxmpp.NS_PRIVACY))
 
-    def _list_push_received(self, _con, stanza):
+    def _list_push_received(self, _con, stanza, _properties):
         result = stanza.buildReply('result')
         result.delChild(result.getTag('query'))
         self._con.connection.send(result)
@@ -66,13 +65,13 @@ class PrivacyLists:
         for list_ in stanza.getQueryPayload():
             if list_.getName() == 'list':
                 name = list_.getAttr('name')
-                log.info('Received Push: %s', name)
+                self._log.info('Received Push: %s', name)
                 self.get_privacy_list(name)
 
         raise nbxmpp.NodeProcessed
 
     def get_privacy_lists(self, callback=None):
-        log.info('Request lists')
+        self._log.info('Request lists')
         iq = nbxmpp.Iq('get', nbxmpp.NS_PRIVACY)
         self._con.connection.SendAndCallForResponse(
             iq, self._privacy_lists_received, {'callback': callback})
@@ -82,7 +81,7 @@ class PrivacyLists:
         new_default = None
         result = nbxmpp.isResultNode(stanza)
         if not result:
-            log.warning('List not available: %s', stanza.getError())
+            self._log.warning('List not available: %s', stanza.getError())
         else:
             for list_ in stanza.getQueryPayload():
                 name = list_.getAttr('name')
@@ -93,13 +92,13 @@ class PrivacyLists:
                 else:
                     lists.append(name)
 
-        log.info('Received lists: %s', lists)
+        self._log.info('Received lists: %s', lists)
 
         # Download default list if we dont have it
         if self.default_list != new_default:
             self.default_list = new_default
             if new_default is not None:
-                log.info('Found new default list: %s', new_default)
+                self._log.info('Found new default list: %s', new_default)
                 self.get_privacy_list(new_default)
 
         if callback:
@@ -113,7 +112,7 @@ class PrivacyLists:
                                           lists=lists))
 
     def get_privacy_list(self, name):
-        log.info('Request list: %s', name)
+        self._log.info('Request list: %s', name)
         list_ = nbxmpp.Node('list', {'name': name})
         iq = nbxmpp.Iq('get', nbxmpp.NS_PRIVACY, payload=[list_])
         self._con.connection.SendAndCallForResponse(
@@ -121,7 +120,7 @@ class PrivacyLists:
 
     def _privacy_list_received(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.warning('List not available: %s', stanza.getError())
+            self._log.warning('List not available: %s', stanza.getError())
             return
 
         rules = []
@@ -138,11 +137,11 @@ class PrivacyLists:
 
             item['child'] = childs
             if len(item) not in (3, 5):
-                log.warning('Wrong count of attrs: %s', stanza)
+                self._log.warning('Wrong count of attrs: %s', stanza)
                 continue
             rules.append(item)
 
-        log.info('Received list: %s', name)
+        self._log.info('Received list: %s', name)
 
         if name == self.default_list:
             self._default_list_received(rules)
@@ -151,11 +150,11 @@ class PrivacyLists:
             None, conn=self._con, list_name=name, rules=rules))
 
     def del_privacy_list(self, name):
-        log.info('Remove list: %s', name)
+        self._log.info('Remove list: %s', name)
 
         def _del_privacy_list_result(stanza):
             if not nbxmpp.isResultNode(stanza):
-                log.warning('List deletion failed: %s', stanza.getError())
+                self._log.warning('List deletion failed: %s', stanza.getError())
                 app.nec.push_incoming_event(InformationEvent(
                     None, dialog_name='privacy-list-error', args=name))
             else:
@@ -176,7 +175,7 @@ class PrivacyLists:
                 node.setTag(child)
             item.pop('child', None)
             node.setTag('item', item)
-        log.info('Update list: %s %s', name, rules)
+        self._log.info('Update list: %s %s', name, rules)
         self._con.connection.SendAndCallForResponse(
             iq, self._default_result_handler, {})
 
@@ -217,7 +216,7 @@ class PrivacyLists:
                     roster.draw_group(rule['value'], self._account)
 
     def set_active_list(self, name=None):
-        log.info('Set active list: %s', name)
+        self._log.info('Set active list: %s', name)
         attr = {}
         if name:
             attr['name'] = name
@@ -227,7 +226,7 @@ class PrivacyLists:
             iq, self._default_result_handler, {})
 
     def set_default_list(self, name=None):
-        log.info('Set default list: %s', name)
+        self._log.info('Set default list: %s', name)
         attr = {}
         if name:
             attr['name'] = name
@@ -236,10 +235,9 @@ class PrivacyLists:
         self._con.connection.SendAndCallForResponse(
             iq, self._default_result_handler, {})
 
-    @staticmethod
-    def _default_result_handler(_con, stanza):
+    def _default_result_handler(self, _con, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.warning('Operation failed: %s', stanza.getError())
+            self._log.warning('Operation failed: %s', stanza.getError())
 
     def _build_invisible_rule(self):
         node = nbxmpp.Node('list', {'name': 'invisible'})
@@ -267,7 +265,7 @@ class PrivacyLists:
         return iq
 
     def set_invisible_rule(self, callback=None, **kwargs):
-        log.info('Update invisible list')
+        self._log.info('Update invisible list')
         iq = self._build_invisible_rule()
         if callback is None:
             callback = self._default_result_handler
@@ -285,7 +283,7 @@ class PrivacyLists:
     def block_gc_contact(self, jid):
         if jid in self.blocked_contacts:
             return
-        log.info('Block GC contact: %s', jid)
+        self._log.info('Block GC contact: %s', jid)
 
         if self.default_list is None:
             self.default_list = 'block'
@@ -311,7 +309,7 @@ class PrivacyLists:
         if self.default_list is None:
             self.default_list = 'block'
         for contact in contact_list:
-            log.info('Block contacts: %s', contact.jid)
+            self._log.info('Block contacts: %s', contact.jid)
             contact.show = 'offline'
             self._con.send_custom_status('offline', message, contact.jid)
             max_order = self._get_max_blocked_list_order()
@@ -333,7 +331,7 @@ class PrivacyLists:
 
         self.blocked_contacts.remove(jid)
 
-        log.info('Unblock GC contact: %s', jid)
+        self._log.info('Unblock GC contact: %s', jid)
         for rule in self.blocked_list:
             if (rule['action'] != 'deny' or
                     rule['type'] != 'jid' or
@@ -358,7 +356,7 @@ class PrivacyLists:
         new_blocked_list = []
         to_unblock = []
         for contact in contact_list:
-            log.info('Unblock contacts: %s', contact.jid)
+            self._log.info('Unblock contacts: %s', contact.jid)
             to_unblock.append(contact.jid)
             if contact.jid in self.blocked_contacts:
                 self.blocked_contacts.remove(contact.jid)
@@ -393,7 +391,7 @@ class PrivacyLists:
             return
         self.blocked_groups.append(group)
 
-        log.info('Block group: %s', group)
+        self._log.info('Block group: %s', group)
 
         if self.default_list is None:
             self.default_list = 'block'
@@ -420,7 +418,7 @@ class PrivacyLists:
             return
         self.blocked_groups.remove(group)
 
-        log.info('Unblock group: %s', group)
+        self._log.info('Unblock group: %s', group)
         new_blocked_list = []
         for rule in self.blocked_list:
             if (rule['action'] != 'deny' or
@@ -446,7 +444,7 @@ class PrivacyLists:
             self._con.send_custom_status(show, self._con.status, contact.jid)
 
     def _presence_probe(self, jid):
-        log.info('Presence probe: %s', jid)
+        self._log.info('Presence probe: %s', jid)
         # Send a presence Probe to get the current Status
         probe = nbxmpp.Presence(jid, 'probe', frm=self._con.get_own_jid())
         self._con.connection.send(probe)
diff --git a/gajim/common/modules/pubsub.py b/gajim/common/modules/pubsub.py
index ec1722b3c1..43a12aaee9 100644
--- a/gajim/common/modules/pubsub.py
+++ b/gajim/common/modules/pubsub.py
@@ -20,23 +20,17 @@
 
 # XEP-0060: Publish-Subscribe
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
 from gajim.common.modules import dataforms
 from gajim.common.nec import NetworkIncomingEvent
-
-log = logging.getLogger('gajim.c.m.pubsub')
+from gajim.common.modules.base import BaseModule
 
 
-class PubSub:
+class PubSub(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
-
-        self.handlers = []
+        BaseModule.__init__(self, con)
 
         self.publish_options = False
 
@@ -45,7 +39,7 @@ class PubSub:
             # Remove stored bookmarks accessible to everyone.
             self._con.get_module('Bookmarks').purge_pubsub_bookmarks()
             return
-        log.info('Discovered Pubsub publish options: %s', from_)
+        self._log.info('Discovered Pubsub publish options: %s', from_)
         self.publish_options = True
 
     def send_pb_subscription_query(self, jid, cb, **kwargs):
@@ -209,7 +203,7 @@ class PubSub:
         configure = pubsub.addChild('configure', {'node': node})
         configure.addChild(node=form)
 
-        log.info('Send node config for %s', node)
+        self._log.info('Send node config for %s', node)
         self._con.connection.SendAndCallForResponse(query, cb, kwargs)
 
     def request_pb_configuration(self, jid, node):
@@ -220,46 +214,45 @@ class PubSub:
         pubsub = query.addChild('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
         pubsub.addChild('configure', {'node': node})
 
-        log.info('Request node config for %s', node)
+        self._log.info('Request node config for %s', node)
         self._con.connection.SendAndCallForResponse(
             query, self._received_pb_configuration, {'node': node})
 
     def _received_pb_configuration(self, _con, stanza, node):
         if not nbxmpp.isResultNode(stanza):
-            log.warning('Error: %s', stanza.getError())
+            self._log.warning('Error: %s', stanza.getError())
             return
 
         pubsub = stanza.getTag('pubsub', namespace=nbxmpp.NS_PUBSUB_OWNER)
         if pubsub is None:
-            log.warning('Malformed PubSub configure '
-                        'stanza (no pubsub node): %s', stanza)
+            self._log.warning('Malformed PubSub configure '
+                              'stanza (no pubsub node): %s', stanza)
             return
 
         configure = pubsub.getTag('configure')
         if configure is None:
-            log.warning('Malformed PubSub configure '
-                        'stanza (no configure node): %s', stanza)
+            self._log.warning('Malformed PubSub configure '
+                              'stanza (no configure node): %s', stanza)
             return
 
         if configure.getAttr('node') != node:
-            log.warning('Malformed PubSub configure '
-                        'stanza (wrong node): %s', stanza)
+            self._log.warning('Malformed PubSub configure '
+                              'stanza (wrong node): %s', stanza)
             return
 
         form = configure.getTag('x', namespace=nbxmpp.NS_DATA)
         if form is None:
-            log.warning('Malformed PubSub configure '
-                        'stanza (no form): %s', stanza)
+            self._log.warning('Malformed PubSub configure '
+                              'stanza (no form): %s', stanza)
             return
 
         app.nec.push_incoming_event(PubSubConfigReceivedEvent(
             None, conn=self._con, node=node,
             form=dataforms.extend_form(node=form)))
 
-    @staticmethod
-    def _default_callback(_con, stanza, *args, **kwargs):
+    def _default_callback(self, _con, stanza, *args, **kwargs):
         if not nbxmpp.isResultNode(stanza):
-            log.warning('Error: %s', stanza.getError())
+            self._log.warning('Error: %s', stanza.getError())
 
 
 class PubSubConfigReceivedEvent(NetworkIncomingEvent):
diff --git a/gajim/common/modules/receipts.py b/gajim/common/modules/receipts.py
index 74cbf587d3..1057b2e85d 100644
--- a/gajim/common/modules/receipts.py
+++ b/gajim/common/modules/receipts.py
@@ -14,22 +14,16 @@
 
 # XEP-0184: Message Delivery Receipts
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
 from gajim.common.nec import NetworkIncomingEvent
-
-log = logging.getLogger('gajim.c.m.receipts')
+from gajim.common.modules.base import BaseModule
 
 
-class Receipts:
+class Receipts(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
-
-        self.handlers = []
+        BaseModule.__init__(self, con)
 
     def delegate(self, event):
         request = event.stanza.getTag('request',
@@ -68,7 +62,7 @@ class Receipts:
             return
 
         receipt = self._build_answer_receipt(from_, receipt_id)
-        log.info('Answer %s', receipt_id)
+        self._log.info('Answer %s', receipt_id)
         self._con.connection.send(receipt)
 
     def _get_contact(self, event):
@@ -92,9 +86,9 @@ class Receipts:
     def _receipt_received(self, event, received):
         receipt_id = received.getAttr('id')
         if receipt_id is None:
-            log.warning('Receipt without ID: %s', event.stanza)
+            self._log.warning('Receipt without ID: %s', event.stanza)
             return
-        log.info('Received %s', receipt_id)
+        self._log.info('Received %s', receipt_id)
 
         jid = event.jid
         if event.muc_pm:
diff --git a/gajim/common/modules/register.py b/gajim/common/modules/register.py
index 45e3209ce5..1ebfa23d72 100644
--- a/gajim/common/modules/register.py
+++ b/gajim/common/modules/register.py
@@ -14,23 +14,18 @@
 
 # XEP-0077: In-Band Registration
 
-import logging
 import weakref
 
 import nbxmpp
 
 from gajim.common import app
+from gajim.common.modules.base import BaseModule
 from gajim.common.modules.bits_of_binary import parse_bob_data
 
-log = logging.getLogger('gajim.c.m.register')
 
-
-class Register:
+class Register(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
-
-        self.handlers = []
+        BaseModule.__init__(self, con)
 
         self.agent_registrations = {}
 
@@ -46,20 +41,19 @@ class Register:
 
         weak_success_cb = weakref.WeakMethod(success_cb)
         weak_error_cb = weakref.WeakMethod(error_cb)
-        log.info('Send password change')
+        self._log.info('Send password change')
         self._con.connection.SendAndCallForResponse(
             iq, self._change_password_response, {'success_cb': weak_success_cb,
                                                  'error_cb': weak_error_cb})
 
-    @staticmethod
-    def _change_password_response(_con, stanza, success_cb, error_cb):
+    def _change_password_response(self, _con, stanza, success_cb, error_cb):
         if not nbxmpp.isResultNode(stanza):
             error = stanza.getErrorMsg()
-            log.info('Error: %s', error)
+            self._log.info('Error: %s', error)
             if error_cb() is not None:
                 error_cb()(error)
         else:
-            log.info('Password changed')
+            self._log.info('Password changed')
             if success_cb() is not None:
                 success_cb()()
 
@@ -91,7 +85,7 @@ class Register:
                                  success_cb, error_cb):
         if not nbxmpp.isResultNode(stanza):
             error = stanza.getErrorMsg()
-            log.info('Error: %s', error)
+            self._log.info('Error: %s', error)
             if error_cb() is not None:
                 error_cb()(error)
             return
@@ -117,15 +111,14 @@ class Register:
             iq, self._register_info_response, {'success_cb': weak_success_cb,
                                                'error_cb': weak_error_cb})
 
-    @staticmethod
-    def _register_info_response(_con, stanza, success_cb, error_cb):
+    def _register_info_response(self, _con, stanza, success_cb, error_cb):
         if not nbxmpp.isResultNode(stanza):
             error = stanza.getErrorMsg()
-            log.info('Error: %s', error)
+            self._log.info('Error: %s', error)
             if error_cb() is not None:
                 error_cb()(error)
         else:
-            log.info('Register form received')
+            self._log.info('Register form received')
             parse_bob_data(stanza.getQuery())
             form = stanza.getQuery().getTag('x', namespace=nbxmpp.NS_DATA)
             is_form = form is not None
diff --git a/gajim/common/modules/roster.py b/gajim/common/modules/roster.py
index 4e44833621..41ecb49c9a 100644
--- a/gajim/common/modules/roster.py
+++ b/gajim/common/modules/roster.py
@@ -14,34 +14,36 @@
 
 # Roster
 
-import logging
 from collections import namedtuple
 
 import nbxmpp
+from nbxmpp.structs import StanzaHandler
 
 from gajim.common import app
 from gajim.common.nec import NetworkEvent
-
-log = logging.getLogger('gajim.c.m.roster')
+from gajim.common.modules.base import BaseModule
 
 RosterItem = namedtuple('RosterItem', 'jid data')
 
 
-class Roster:
+class Roster(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
-            ('iq', self._roster_push_received, 'set', nbxmpp.NS_ROSTER),
-            ('presence', self._presence_received)
+            StanzaHandler(name='iq',
+                          callback=self._roster_push_received,
+                          typ='set',
+                          ns=nbxmpp.NS_ROSTER),
+            StanzaHandler(name='presence',
+                          callback=self._presence_received),
         ]
 
         self._data = {}
         self._set = None
 
     def load_roster(self):
-        log.info('Load from database')
+        self._log.info('Load from database')
         account_jid = self._con.get_own_jid().getStripped()
         data = app.logger.get_roster(account_jid)
         if data:
@@ -57,7 +59,7 @@ class Roster:
                     groups=item['groups'],
                     avatar_sha=item['avatar_sha']))
         else:
-            log.info('Database empty, reset roster version')
+            self._log.info('Database empty, reset roster version')
             app.config.set_per(
                 'accounts', self._account, 'roster_version', '')
 
@@ -74,26 +76,26 @@ class Roster:
             version = app.config.get_per(
                 'accounts', self._account, 'roster_version')
 
-        log.info('Requested from server')
+        self._log.info('Requested from server')
         iq = nbxmpp.Iq('get', nbxmpp.NS_ROSTER)
         if version is not None:
             iq.setTagAttr('query', 'ver', version)
-        log.info('Request version: %s', version)
+        self._log.info('Request version: %s', version)
         self._con.connection.SendAndCallForResponse(
             iq, self._roster_received)
 
     def _roster_received(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.warning('Unable to retrive roster: %s', stanza.getError())
+            self._log.warning('Unable to retrive roster: %s', stanza.getError())
         else:
-            log.info('Received Roster')
+            self._log.info('Received Roster')
             received_from_server = False
             if stanza.getTag('query') is not None:
                 # clear Roster
                 self._data = {}
                 version = self._parse_roster(stanza)
 
-                log.info('New version: %s', version)
+                self._log.info('New version: %s', version)
                 app.logger.replace_roster(self._account, version, self._data)
 
                 received_from_server = True
@@ -106,13 +108,13 @@ class Roster:
 
         self._con.connect_machine()
 
-    def _roster_push_received(self, _con, stanza):
-        log.info('Push received')
+    def _roster_push_received(self, _con, stanza, _properties):
+        self._log.info('Push received')
 
         sender = stanza.getFrom()
         if sender is not None:
             if not self._con.get_own_jid().bareMatch(sender):
-                log.warning('Wrong JID %s', stanza.getFrom())
+                self._log.warning('Wrong JID %s', stanza.getFrom())
                 return
 
         push_items, version = self._parse_push(stanza)
@@ -135,7 +137,7 @@ class Roster:
                 account_jid, item.jid, attrs['name'],
                 attrs['subscription'], attrs['ask'], attrs['groups'])
 
-        log.info('New version: %s', version)
+        self._log.info('New version: %s', version)
         app.config.set_per(
             'accounts', self._account, 'roster_version', version)
 
@@ -148,7 +150,7 @@ class Roster:
         for item in query.getTags('item'):
             jid = item.getAttr('jid')
             self._data[jid] = self._get_item_attrs(item, update=False)
-            log.info('Item %s: %s', jid, self._data[jid])
+            self._log.info('Item %s: %s', jid, self._data[jid])
         return version
 
     @staticmethod
@@ -186,7 +188,7 @@ class Roster:
         for item in query.getTags('item'):
             push_items.append(self._update_roster_item(item))
         for item in push_items:
-            log.info('Push: %s', item)
+            self._log.info('Push: %s', item)
         return push_items, version
 
     def _update_roster_item(self, item):
@@ -211,7 +213,7 @@ class Roster:
                        attrs={'id': stanza.getID()})
         self._con.connection.send(iq)
 
-    def _presence_received(self, _con, pres):
+    def _presence_received(self, _con, pres, _properties):
         '''
         Add contacts that request subscription to our internal
         roster and also to the database. The contact is put into the
@@ -227,7 +229,7 @@ class Roster:
         if jid in self._data:
             return
 
-        log.info('Add Contact from presence %s', jid)
+        self._log.info('Add Contact from presence %s', jid)
         self._data[jid] = {'name': None,
                            'ask': None,
                            'subscription':
diff --git a/gajim/common/modules/roster_item_exchange.py b/gajim/common/modules/roster_item_exchange.py
index 0d19e121be..9043373d2e 100644
--- a/gajim/common/modules/roster_item_exchange.py
+++ b/gajim/common/modules/roster_item_exchange.py
@@ -14,32 +14,34 @@
 
 # XEP-0144: Roster Item Exchange
 
-import logging
-
 import nbxmpp
+from nbxmpp.structs import StanzaHandler
 
 from gajim.common import app
 from gajim.common import helpers
 from gajim.common.i18n import _
 from gajim.common.nec import NetworkIncomingEvent
-
-log = logging.getLogger('gajim.c.m.roster_item_exchange')
+from gajim.common.modules.base import BaseModule
 
 
-class RosterItemExchange:
+class RosterItemExchange(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
-            ('iq', self.received_item, 'set', nbxmpp.NS_ROSTERX),
-            ('message', self.received_item, '', nbxmpp.NS_ROSTERX)
+            StanzaHandler(name='iq',
+                          callback=self.received_item,
+                          typ='set',
+                          ns=nbxmpp.NS_ROSTERX),
+            StanzaHandler(name='message',
+                          callback=self.received_item,
+                          ns=nbxmpp.NS_ROSTERX),
         ]
 
-    def received_item(self, _con, stanza):
+    def received_item(self, _con, stanza, _properties):
         # stanza can be a message or a iq
 
-        log.info('Received roster items from %s', stanza.getFrom())
+        self._log.info('Received roster items from %s', stanza.getFrom())
 
         exchange_items_list = {}
         items_list = stanza.getTag(
@@ -55,8 +57,8 @@ class RosterItemExchange:
             try:
                 jid = helpers.parse_jid(item.getAttr('jid'))
             except helpers.InvalidFormat:
-                log.warning('Invalid JID: %s, ignoring it',
-                            item.getAttr('jid'))
+                self._log.warning('Invalid JID: %s, ignoring it',
+                                  item.getAttr('jid'))
                 continue
             name = item.getAttr('name')
             contact = app.contacts.get_contact(self._account, jid)
@@ -81,7 +83,7 @@ class RosterItemExchange:
         if not exchange_items_list:
             raise nbxmpp.NodeProcessed
 
-        log.info('Items: %s', exchange_items_list)
+        self._log.info('Items: %s', exchange_items_list)
 
         app.nec.push_incoming_event(RosterItemExchangeEvent(
             None, conn=self._con,
@@ -115,7 +117,7 @@ class RosterItemExchange:
             xdata.addChild(name='item', attrs={'action': 'add',
                                                'jid': contact.jid,
                                                'name': name})
-            log.info('Send contact: %s %s', contact.jid, name)
+            self._log.info('Send contact: %s %s', contact.jid, name)
         self._con.connection.send(stanza)
 
 
diff --git a/gajim/common/modules/search.py b/gajim/common/modules/search.py
index 5fba158e51..0b8885ca8a 100644
--- a/gajim/common/modules/search.py
+++ b/gajim/common/modules/search.py
@@ -14,25 +14,19 @@
 
 # XEP-0055: Jabber Search
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
 from gajim.common.nec import NetworkIncomingEvent
-
-log = logging.getLogger('gajim.c.m.search')
+from gajim.common.modules.base import BaseModule
 
 
-class Search:
+class Search(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
-
-        self.handlers = []
+        BaseModule.__init__(self, con)
 
     def request_search_fields(self, jid):
-        log.info('Request search fields from %s', jid)
+        self._log.info('Request search fields from %s', jid)
         iq = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_SEARCH)
         self._con.connection.SendAndCallForResponse(iq, self._fields_received)
 
@@ -41,10 +35,10 @@ class Search:
         is_dataform = False
 
         if nbxmpp.isResultNode(stanza):
-            log.info('Received search fields from %s', stanza.getFrom())
+            self._log.info('Received search fields from %s', stanza.getFrom())
             tag = stanza.getTag('query', namespace=nbxmpp.NS_SEARCH)
             if tag is None:
-                log.info('Invalid stanza: %s', stanza)
+                self._log.info('Invalid stanza: %s', stanza)
                 return
 
             data = tag.getTag('x', namespace=nbxmpp.NS_DATA)
@@ -55,7 +49,7 @@ class Search:
                 for i in stanza.getQueryPayload():
                     data[i.getName()] = i.getData()
         else:
-            log.info('Error: %s', stanza.getError())
+            self._log.info('Error: %s', stanza.getError())
 
         app.nec.push_incoming_event(
             SearchFormReceivedEvent(None, conn=self._con,
@@ -78,10 +72,10 @@ class Search:
         is_dataform = False
 
         if nbxmpp.isResultNode(stanza):
-            log.info('Received result from %s', stanza.getFrom())
+            self._log.info('Received result from %s', stanza.getFrom())
             tag = stanza.getTag('query', namespace=nbxmpp.NS_SEARCH)
             if tag is None:
-                log.info('Invalid stanza: %s', stanza)
+                self._log.info('Invalid stanza: %s', stanza)
                 return
 
             data = tag.getTag('x', namespace=nbxmpp.NS_DATA)
@@ -96,7 +90,7 @@ class Search:
                         field[i.getName()] = i.getData()
                     data.append(field)
         else:
-            log.info('Error: %s', stanza.getError())
+            self._log.info('Error: %s', stanza.getError())
 
         app.nec.push_incoming_event(
             SearchResultReceivedEvent(None, conn=self._con,
diff --git a/gajim/common/modules/security_labels.py b/gajim/common/modules/security_labels.py
index 37cd4ca51d..3f5648c7cb 100644
--- a/gajim/common/modules/security_labels.py
+++ b/gajim/common/modules/security_labels.py
@@ -14,22 +14,16 @@
 
 # XEP-0258: Security Labels in XMPP
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
 from gajim.common.nec import NetworkIncomingEvent
-
-log = logging.getLogger('gajim.c.m.security_labels')
+from gajim.common.modules.base import BaseModule
 
 
-class SecLabels:
+class SecLabels(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
-
-        self.handlers = []
+        BaseModule.__init__(self, con)
 
         self._catalogs = {}
         self.supported = False
@@ -39,7 +33,7 @@ class SecLabels:
             return
 
         self.supported = True
-        log.info('Discovered security labels: %s', from_)
+        self._log.info('Discovered security labels: %s', from_)
 
     def request_catalog(self, jid):
         server = app.get_jid_from_account(self._account).split("@")[1]
@@ -47,13 +41,13 @@ class SecLabels:
         iq.addChild(name='catalog',
                     namespace=nbxmpp.NS_SECLABEL_CATALOG,
                     attrs={'to': jid})
-        log.info('Request catalog: server: %s, to: %s', server, jid)
+        self._log.info('Request catalog: server: %s, to: %s', server, jid)
         self._con.connection.SendAndCallForResponse(
             iq, self._catalog_received)
 
     def _catalog_received(self, stanza):
         if not nbxmpp.isResultNode(stanza):
-            log.info('Error: %s', stanza.getError())
+            self._log.info('Error: %s', stanza.getError())
             return
 
         query = stanza.getTag('catalog', namespace=nbxmpp.NS_SECLABEL_CATALOG)
@@ -73,8 +67,8 @@ class SecLabels:
         catalog = (labels, label_list, default)
         self._catalogs[to] = catalog
 
-        log.info('Received catalog: %s', to)
-        log.debug(catalog)
+        self._log.info('Received catalog: %s', to)
+        self._log.debug(catalog)
 
         app.nec.push_incoming_event(SecLabelCatalog(
             None, account=self._account, jid=to, catalog=catalog))
diff --git a/gajim/common/modules/software_version.py b/gajim/common/modules/software_version.py
index e35b813953..56f891b8a7 100644
--- a/gajim/common/modules/software_version.py
+++ b/gajim/common/modules/software_version.py
@@ -14,24 +14,24 @@
 
 # XEP-0092: Software Version
 
-import logging
-
 import nbxmpp
+from nbxmpp.structs import StanzaHandler
 
 from gajim.common import app
 from gajim.common.helpers import get_os_info
 from gajim.common.nec import NetworkIncomingEvent
-
-log = logging.getLogger('gajim.c.m.software_version')
+from gajim.common.modules.base import BaseModule
 
 
-class SoftwareVersion:
+class SoftwareVersion(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
 
         self.handlers = [
-            ('iq', self._answer_request, 'get', nbxmpp.NS_VERSION),
+            StanzaHandler(name='iq',
+                          callback=self._answer_request,
+                          typ='get',
+                          ns=nbxmpp.NS_VERSION),
         ]
 
     def request_os_info(self, jid, resource):
@@ -45,23 +45,23 @@ class SoftwareVersion:
             jid += '/' + resource
         iq = nbxmpp.Iq(to=jid, typ='get', queryNS=nbxmpp.NS_VERSION)
 
-        log.info('Requested: %s', jid)
+        self._log.info('Requested: %s', jid)
 
         self._con.connection.SendAndCallForResponse(iq, self._result_received)
 
     def _result_received(self, stanza):
         client_info, os_info = None, None
         if not nbxmpp.isResultNode(stanza):
-            log.info('Error: %s', stanza.getError())
+            self._log.info('Error: %s', stanza.getError())
         else:
             try:
                 client_info, os_info = self._extract_info(stanza)
             except Exception:
-                log.exception('Error')
-                log.error(stanza)
+                self._log.exception('Error')
+                self._log.error(stanza)
 
-        log.info('Received: %s %s %s',
-                 stanza.getFrom(), client_info, os_info)
+        self._log.info('Received: %s %s %s',
+                       stanza.getFrom(), client_info, os_info)
 
         app.nec.push_incoming_event(
             VersionResultReceivedEvent(None, conn=self._con,
@@ -80,8 +80,8 @@ class SoftwareVersion:
             os_info = os_info.getData()
         return client_info, os_info
 
-    def _answer_request(self, _con, stanza):
-        log.info('%s asked for the software version', stanza.getFrom())
+    def _answer_request(self, _con, stanza, _properties):
+        self._log.info('%s asked for the software version', stanza.getFrom())
         if app.config.get_per('accounts', self._account, 'send_os_info'):
             os_info = get_os_info()
             iq = stanza.buildReply('result')
@@ -89,12 +89,12 @@ class SoftwareVersion:
             query.setTagData('name', 'Gajim')
             query.setTagData('version', app.version)
             query.setTagData('os', os_info)
-            log.info('Answer: Gajim %s %s', app.version, os_info)
+            self._log.info('Answer: Gajim %s %s', app.version, os_info)
         else:
             iq = stanza.buildReply('error')
             err = nbxmpp.ErrorNode(nbxmpp.ERR_SERVICE_UNAVAILABLE)
             iq.addChild(node=err)
-            log.info('Send service-unavailable')
+            self._log.info('Send service-unavailable')
         self._con.connection.send(iq)
         raise nbxmpp.NodeProcessed
 
diff --git a/gajim/common/modules/user_activity.py b/gajim/common/modules/user_activity.py
index e2ddea2fd7..4aa3a61763 100644
--- a/gajim/common/modules/user_activity.py
+++ b/gajim/common/modules/user_activity.py
@@ -17,8 +17,6 @@
 from typing import Any
 from typing import Tuple
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
@@ -28,8 +26,6 @@ from gajim.common.modules.util import event_node
 from gajim.common.modules.util import store_publish
 from gajim.common.const import PEPEventType
 
-log = logging.getLogger('gajim.c.m.user_activity')
-
 
 class UserActivity(BaseModule):
 
@@ -69,7 +65,7 @@ class UserActivity(BaseModule):
 
     @store_publish
     def set_activity(self, activity):
-        log.info('Send %s', activity)
+        self._log.info('Send %s', activity)
         self._nbxmpp('Activity').set_activity(activity)
 
 
diff --git a/gajim/common/modules/user_avatar.py b/gajim/common/modules/user_avatar.py
index 5a3787d7ca..bcc69630d7 100644
--- a/gajim/common/modules/user_avatar.py
+++ b/gajim/common/modules/user_avatar.py
@@ -14,8 +14,6 @@
 
 # XEP-0084: User Avatar
 
-import logging
-
 import nbxmpp
 from nbxmpp.util import is_error_result
 
@@ -23,8 +21,6 @@ from gajim.common import app
 from gajim.common.modules.base import BaseModule
 from gajim.common.modules.util import event_node
 
-log = logging.getLogger('gajim.c.m.user_avatar')
-
 
 class UserAvatar(BaseModule):
 
@@ -46,7 +42,7 @@ class UserAvatar(BaseModule):
 
         if empty:
             # Remove avatar
-            log.info('Remove: %s', jid)
+            self._log.info('Remove: %s', jid)
             app.contacts.set_avatar(self._account, jid, None)
             app.logger.set_avatar_sha(own_jid, jid, None)
             app.interface.update_avatar(self._account, jid)
@@ -58,19 +54,19 @@ class UserAvatar(BaseModule):
                 sha = app.contacts.get_avatar_sha(self._account, jid)
 
             if sha == data.id:
-                log.info('Avatar already known: %s %s', jid, data.id)
+                self._log.info('Avatar already known: %s %s', jid, data.id)
                 return
 
-            log.info('Request: %s %s', jid, data.id)
+            self._log.info('Request: %s %s', jid, data.id)
             self._nbxmpp('UserAvatar').request_avatar(
                 jid, data.id, callback=self._avatar_received)
 
     def _avatar_received(self, result):
         if is_error_result(result):
-            log.info('Error: %s', result)
+            self._log.info('Error: %s', result)
             return
 
-        log.info('Received Avatar: %s %s', result.jid, result.sha)
+        self._log.info('Received Avatar: %s %s', result.jid, result.sha)
         app.interface.save_avatar(result.data)
 
         if self._con.get_own_jid().bareMatch(result.jid):
diff --git a/gajim/common/modules/user_location.py b/gajim/common/modules/user_location.py
index 8e1b579625..0916ba31be 100644
--- a/gajim/common/modules/user_location.py
+++ b/gajim/common/modules/user_location.py
@@ -14,8 +14,6 @@
 
 # XEP-0080: User Location
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
@@ -25,8 +23,6 @@ from gajim.common.modules.util import event_node
 from gajim.common.modules.util import store_publish
 from gajim.common.const import PEPEventType
 
-log = logging.getLogger('gajim.c.m.user_location')
-
 
 class UserLocation(BaseModule):
 
@@ -66,7 +62,7 @@ class UserLocation(BaseModule):
 
     @store_publish
     def set_location(self, location):
-        log.info('Send %s', location)
+        self._log.info('Send %s', location)
         self._nbxmpp('Location').set_location(location)
 
 
diff --git a/gajim/common/modules/user_mood.py b/gajim/common/modules/user_mood.py
index 9842b108d8..56e9c26732 100644
--- a/gajim/common/modules/user_mood.py
+++ b/gajim/common/modules/user_mood.py
@@ -17,8 +17,6 @@
 from typing import Any
 from typing import Tuple
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
@@ -28,8 +26,6 @@ from gajim.common.modules.util import event_node
 from gajim.common.modules.util import store_publish
 from gajim.common.const import PEPEventType
 
-log = logging.getLogger('gajim.c.m.user_mood')
-
 
 class UserMood(BaseModule):
 
@@ -69,7 +65,7 @@ class UserMood(BaseModule):
 
     @store_publish
     def set_mood(self, mood):
-        log.info('Send %s', mood)
+        self._log.info('Send %s', mood)
         self._nbxmpp('Mood').set_mood(mood)
 
 
diff --git a/gajim/common/modules/user_nickname.py b/gajim/common/modules/user_nickname.py
index e25c41ee57..c6f415ba95 100644
--- a/gajim/common/modules/user_nickname.py
+++ b/gajim/common/modules/user_nickname.py
@@ -17,8 +17,6 @@
 from typing import Any
 from typing import Tuple
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
@@ -26,8 +24,6 @@ from gajim.common.nec import NetworkEvent
 from gajim.common.modules.base import BaseModule
 from gajim.common.modules.util import event_node
 
-log = logging.getLogger('gajim.c.m.user_nickname')
-
 
 class UserNickname(BaseModule):
 
diff --git a/gajim/common/modules/user_tune.py b/gajim/common/modules/user_tune.py
index 9186b8e759..8f450c3717 100644
--- a/gajim/common/modules/user_tune.py
+++ b/gajim/common/modules/user_tune.py
@@ -17,8 +17,6 @@
 from typing import Any
 from typing import Tuple
 
-import logging
-
 import nbxmpp
 
 from gajim.common import app
@@ -28,8 +26,6 @@ from gajim.common.modules.util import event_node
 from gajim.common.modules.util import store_publish
 from gajim.common.const import PEPEventType
 
-log = logging.getLogger('gajim.c.m.user_tune')
-
 
 class UserTune(BaseModule):
 
@@ -69,7 +65,7 @@ class UserTune(BaseModule):
 
     @store_publish
     def set_tune(self, tune):
-        log.info('Send %s', tune)
+        self._log.info('Send %s', tune)
         self._nbxmpp('Tune').set_tune(tune)
 
 
diff --git a/gajim/common/modules/vcard_avatars.py b/gajim/common/modules/vcard_avatars.py
index 747c3c1083..9520b2aeb2 100644
--- a/gajim/common/modules/vcard_avatars.py
+++ b/gajim/common/modules/vcard_avatars.py
@@ -15,7 +15,6 @@
 # XEP-0153: vCard-Based Avatars
 
 import os
-import logging
 from pathlib import Path
 
 import nbxmpp
@@ -26,14 +25,12 @@ from gajim.common import app
 from gajim.common import helpers
 from gajim.common import configpaths
 from gajim.common.const import RequestAvatar
+from gajim.common.modules.base import BaseModule
 
-log = logging.getLogger('gajim.c.m.vcard.avatars')
 
-
-class VCardAvatars:
+class VCardAvatars(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
+        BaseModule.__init__(self, con)
         self._requested_shas = []
 
         self.handlers = [
@@ -52,7 +49,7 @@ class VCardAvatars:
             return
         path = Path(configpaths.get('AVATAR')) / sha
         if not path.exists():
-            log.info('Missing own avatar, reset sha')
+            self._log.info('Missing own avatar, reset sha')
             app.config.set_per('accounts', self._account, 'avatar_sha', '')
 
     def _presence_received(self, _con, _stanza, properties):
@@ -87,13 +84,13 @@ class VCardAvatars:
         jid = properties.jid.getBare()
         if properties.avatar_state == AvatarState.EMPTY:
             # Empty <photo/> tag, means no avatar is advertised
-            log.info('%s has no avatar published', properties.jid)
+            self._log.info('%s has no avatar published', properties.jid)
             app.config.set_per('accounts', self._account, 'avatar_sha', '')
             app.contacts.set_avatar(self._account, jid, None)
             app.interface.update_avatar(self._account, jid)
             return
 
-        log.info('Update: %s %s', jid, properties.avatar_sha)
+        self._log.info('Update: %s %s', jid, properties.avatar_sha)
         current_sha = app.config.get_per(
             'accounts', self._account, 'avatar_sha')
 
@@ -107,21 +104,21 @@ class VCardAvatars:
                                         properties.avatar_sha)
                 app.interface.update_avatar(self._account, jid)
             else:
-                log.info('Request : %s', jid)
+                self._log.info('Request : %s', jid)
                 self._con.get_module('VCardTemp').request_vcard(
                     RequestAvatar.SELF)
         else:
-            log.info('Avatar already known: %s %s',
-                     jid, properties.avatar_sha)
+            self._log.info('Avatar already known: %s %s',
+                           jid, properties.avatar_sha)
 
     def _update_received(self, properties, room=False):
         jid = properties.jid.getBare()
         if properties.avatar_state == AvatarState.EMPTY:
             # Empty <photo/> tag, means no avatar is advertised
-            log.info('%s has no avatar published', properties.jid)
+            self._log.info('%s has no avatar published', properties.jid)
 
             # Remove avatar
-            log.debug('Remove: %s', jid)
+            self._log.debug('Remove: %s', jid)
             app.contacts.set_avatar(self._account, jid, None)
             acc_jid = self._con.get_own_jid().getStripped()
             if not room:
@@ -129,12 +126,13 @@ class VCardAvatars:
             app.interface.update_avatar(
                 self._account, jid, room_avatar=room)
         else:
-            log.info('Update: %s %s', properties.jid, properties.avatar_sha)
+            self._log.info('Update: %s %s',
+                           properties.jid, properties.avatar_sha)
             current_sha = app.contacts.get_avatar_sha(self._account, jid)
 
             if properties.avatar_sha == current_sha:
-                log.info('Avatar already known: %s %s',
-                         jid, properties.avatar_sha)
+                self._log.info('Avatar already known: %s %s',
+                               jid, properties.avatar_sha)
                 return
 
             if room:
@@ -164,17 +162,17 @@ class VCardAvatars:
             self._account, properties.jid.getBare(), nick)
 
         if gc_contact is None:
-            log.error('no gc contact found: %s', nick)
+            self._log.error('no gc contact found: %s', nick)
             return
 
         if properties.avatar_state == AvatarState.EMPTY:
             # Empty <photo/> tag, means no avatar is advertised, remove avatar
-            log.info('%s has no avatar published', nick)
-            log.debug('Remove: %s', nick)
+            self._log.info('%s has no avatar published', nick)
+            self._log.debug('Remove: %s', nick)
             gc_contact.avatar_sha = None
             app.interface.update_avatar(contact=gc_contact)
         else:
-            log.info('Update: %s %s', nick, properties.avatar_sha)
+            self._log.info('Update: %s %s', nick, properties.avatar_sha)
             path = os.path.join(configpaths.get('AVATAR'),
                                 properties.avatar_sha)
             if not os.path.isfile(path):
@@ -187,12 +185,12 @@ class VCardAvatars:
                 return
 
             if gc_contact.avatar_sha != properties.avatar_sha:
-                log.info('%s changed their Avatar: %s',
-                         nick, properties.avatar_sha)
+                self._log.info('%s changed their Avatar: %s',
+                               nick, properties.avatar_sha)
                 gc_contact.avatar_sha = properties.avatar_sha
                 app.interface.update_avatar(contact=gc_contact)
             else:
-                log.info('Avatar already known: %s', nick)
+                self._log.info('Avatar already known: %s', nick)
 
     def send_avatar_presence(self, force=False, after_publish=False):
         if self._con.avatar_conversion:
@@ -202,7 +200,7 @@ class VCardAvatars:
                 return
         else:
             if self.avatar_advertised and not force:
-                log.debug('Avatar already advertised')
+                self._log.debug('Avatar already advertised')
                 return
 
         show = helpers.get_xmpp_show(app.SHOW_LIST[self._con.connected])
@@ -219,8 +217,8 @@ class VCardAvatars:
         if self._con.get_module('VCardTemp').own_vcard_received:
             sha = app.config.get_per('accounts', self._account, 'avatar_sha')
             own_jid = self._con.get_own_jid()
-            log.info('Send avatar presence to: %s %s',
-                     node.getTo() or own_jid, sha or 'no sha advertised')
+            self._log.info('Send avatar presence to: %s %s',
+                           node.getTo() or own_jid, sha or 'no sha advertised')
             update.setTagData('photo', sha)
 
 
diff --git a/gajim/common/modules/vcard_temp.py b/gajim/common/modules/vcard_temp.py
index 929154850b..edf9f54aa7 100644
--- a/gajim/common/modules/vcard_temp.py
+++ b/gajim/common/modules/vcard_temp.py
@@ -17,7 +17,6 @@
 import hashlib
 import binascii
 import base64
-import logging
 
 import nbxmpp
 
@@ -25,17 +24,13 @@ from gajim.common import app
 from gajim.common.const import RequestAvatar
 from gajim.common.nec import NetworkEvent
 from gajim.common.nec import NetworkIncomingEvent
+from gajim.common.modules.base import BaseModule
 from gajim.common.connection_handlers_events import InformationEvent
 
-log = logging.getLogger('gajim.c.m.vcard')
 
-
-class VCardTemp:
+class VCardTemp(BaseModule):
     def __init__(self, con):
-        self._con = con
-        self._account = con.name
-
-        self.handlers = []
+        BaseModule.__init__(self, con)
 
         self._own_vcard = None
         self.own_vcard_received = False
@@ -47,7 +42,7 @@ class VCardTemp:
             return
 
         self.supported = True
-        log.info('Discovered vcard-temp: %s', from_)
+        self._log.info('Discovered vcard-temp: %s', from_)
 
         app.nec.push_incoming_event(NetworkEvent('feature-discovered',
                                                  account=self._account,
@@ -98,7 +93,7 @@ class VCardTemp:
         iq.setQuery('vCard').setNamespace(nbxmpp.NS_VCARD)
 
         own_jid = self._con.get_own_jid().getStripped()
-        log.info('Request: %s, expected sha: %s', jid or own_jid, sha)
+        self._log.info('Request: %s, expected sha: %s', jid or own_jid, sha)
 
         self._con.connection.SendAndCallForResponse(
             iq, self._parse_vcard, {'callback': callback, 'expected_sha': sha})
@@ -124,7 +119,7 @@ class VCardTemp:
             else:
                 iq2.addChild(i).setData(vcard[i])
 
-        log.info('Upload avatar: %s %s', self._account, sha)
+        self._log.info('Upload avatar: %s %s', self._account, sha)
 
         self._con.connection.SendAndCallForResponse(
             iq, self._avatar_publish_result, {'sha': sha})
@@ -136,7 +131,7 @@ class VCardTemp:
         photo.addChild('TYPE', payload='image/png')
         photo.addChild('BINVAL', payload=data)
 
-        log.info('Upload avatar: %s', room_jid)
+        self._log.info('Upload avatar: %s', room_jid)
         self._con.connection.SendAndCallForResponse(
             iq, self._upload_room_avatar_result)
 
@@ -162,7 +157,7 @@ class VCardTemp:
                     self._account, self._con.get_own_jid().getStripped())
                 self._con.get_module('VCardAvatars').send_avatar_presence(
                     after_publish=True)
-            log.info('%s: Published: %s', self._account, sha)
+            self._log.info('%s: Published: %s', self._account, sha)
             app.nec.push_incoming_event(
                 VcardPublishedEvent(None, conn=self._con))
 
@@ -170,8 +165,7 @@ class VCardTemp:
             app.nec.push_incoming_event(
                 VcardNotPublishedEvent(None, conn=self._con))
 
-    @staticmethod
-    def _get_vcard_photo(vcard, jid):
+    def _get_vcard_photo(self, vcard, jid):
         try:
             photo = vcard['PHOTO']['BINVAL']
         except (KeyError, AttributeError, TypeError):
@@ -185,7 +179,7 @@ class VCardTemp:
                 try:
                     photo_decoded = base64.b64decode(photo.encode('utf-8'))
                 except binascii.Error as error:
-                    log.warning('Invalid avatar for %s: %s', jid, error)
+                    self._log.warning('Invalid avatar for %s: %s', jid, error)
                     return None, None
                 avatar_sha = hashlib.sha1(photo_decoded).hexdigest()
 
@@ -205,14 +199,14 @@ class VCardTemp:
         stanza_error = stanza.getError()
         if stanza_error in ('service-unavailable', 'item-not-found',
                             'not-allowed'):
-            log.info('vCard not available: %s %s', frm_jid, stanza_error)
+            self._log.info('vCard not available: %s %s', frm_jid, stanza_error)
             callback(jid, resource, room, {}, expected_sha)
             return
 
         vcard_node = stanza.getTag('vCard', namespace=nbxmpp.NS_VCARD)
         if vcard_node is None:
-            log.info('vCard not available: %s', frm_jid)
-            log.debug(stanza)
+            self._log.info('vCard not available: %s', frm_jid)
+            self._log.debug(stanza)
             return
         vcard = self._node_to_dict(vcard_node)
 
@@ -232,14 +226,14 @@ class VCardTemp:
     def _on_own_avatar_received(self, jid, _resource, _room, vcard, *args):
         avatar_sha, photo_decoded = self._get_vcard_photo(vcard, jid)
 
-        log.info('Received own vcard, avatar sha is: %s', avatar_sha)
+        self._log.info('Received own vcard, avatar sha is: %s', avatar_sha)
 
         self._own_vcard = vcard
         self.own_vcard_received = True
 
         if avatar_sha is None:
             # No avatar found in vcard
-            log.info('No avatar found')
+            self._log.info('No avatar found')
             app.config.set_per('accounts', self._account, 'avatar_sha', '')
             app.contacts.set_avatar(self._account, jid, avatar_sha)
             self._con.get_module('VCardAvatars').send_avatar_presence(
@@ -265,12 +259,12 @@ class VCardTemp:
                                  expected_sha):
         avatar_sha, photo_decoded = self._get_vcard_photo(vcard, jid)
         if expected_sha != avatar_sha:
-            log.warning('Avatar mismatch: %s %s', jid, avatar_sha)
+            self._log.warning('Avatar mismatch: %s %s', jid, avatar_sha)
             return
 
         app.interface.save_avatar(photo_decoded)
 
-        log.info('Received: %s %s', jid, avatar_sha)
+        self._log.info('Received: %s %s', jid, avatar_sha)
         app.contacts.set_avatar(self._account, jid, avatar_sha)
         app.interface.update_avatar(self._account, jid, room_avatar=True)
 
@@ -281,21 +275,21 @@ class VCardTemp:
 
         avatar_sha, photo_decoded = self._get_vcard_photo(vcard, request_jid)
         if expected_sha != avatar_sha:
-            log.warning('Received: avatar mismatch: %s %s',
-                        request_jid, avatar_sha)
+            self._log.warning('Received: avatar mismatch: %s %s',
+                              request_jid, avatar_sha)
             return
 
         app.interface.save_avatar(photo_decoded)
 
         # Received vCard from a contact
         if room:
-            log.info('Received: %s %s', resource, avatar_sha)
+            self._log.info('Received: %s %s', resource, avatar_sha)
             contact = app.contacts.get_gc_contact(self._account, jid, resource)
             if contact is not None:
                 contact.avatar_sha = avatar_sha
                 app.interface.update_avatar(contact=contact)
         else:
-            log.info('Received: %s %s', jid, avatar_sha)
+            self._log.info('Received: %s %s', jid, avatar_sha)
             own_jid = self._con.get_own_jid().getStripped()
             app.logger.set_avatar_sha(own_jid, jid, avatar_sha)
             app.contacts.set_avatar(self._account, jid, avatar_sha)
-- 
GitLab