Index: trac/env.py
===================================================================
--- trac/env.py	(revision 7181)
+++ trac/env.py	(working copy)
@@ -33,6 +33,8 @@
 from trac.versioncontrol import RepositoryManager
 from trac.web.href import Href
 
+import logging
+
 __all__ = ['Environment', 'IEnvironmentSetupParticipant', 'open_environment']
 
 
@@ -46,14 +48,14 @@
 
     def environment_needs_upgrade(db):
         """Called when Trac checks whether the environment needs to be upgraded.
-        
+
         Should return `True` if this participant needs an upgrade to be
         performed, `False` otherwise.
         """
 
     def upgrade_environment(db):
         """Actually perform an environment upgrade.
-        
+
         Implementations of this method should not commit any database
         transactions. This is done implicitly after all participants have
         performed the upgrades they need without an error being raised.
@@ -69,30 +71,30 @@
      * an SQLite database (stores tickets, wiki pages...)
      * Project specific templates and plugins.
      * wiki and ticket attachments.
-    """   
+    """
     setup_participants = ExtensionPoint(IEnvironmentSetupParticipant)
 
     shared_plugins_dir = PathOption('inherit', 'plugins_dir', '',
         """Path of the directory containing additional plugins.
-        
+
         Plugins in that directory are loaded in addition to those in the
-        directory of the environment `plugins`, with this one taking 
+        directory of the environment `plugins`, with this one taking
         precedence.
-        
+
         (''since 0.11'')""")
 
     base_url = Option('trac', 'base_url', '',
         """Reference URL for the Trac deployment.
-        
+
         This is the base URL that will be used when producing documents that
         will be used outside of the web browsing context, like for example
         when inserting URLs pointing to Trac resources in notification
         e-mails.""")
 
     base_url_for_redirect = BoolOption('trac', 'use_base_url_for_redirect',
-            False, 
+            False,
         """Optionally use `[trac] base_url` for redirects.
-        
+
         In some configurations, usually involving running Trac behind a HTTP
         proxy, Trac can't automatically reconstruct the URL that is used to
         access it. You may need to use this option to force Trac to use the
@@ -124,7 +126,7 @@
 
     log_type = Option('logging', 'log_type', 'none',
         """Logging facility to use.
-        
+
         Should be one of (`none`, `file`, `stderr`, `syslog`, `winlog`).""")
 
     log_file = Option('logging', 'log_file', 'trac.log',
@@ -132,14 +134,14 @@
 
     log_level = Option('logging', 'log_level', 'DEBUG',
         """Level of verbosity in log.
-        
+
         Should be one of (`CRITICAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`).""")
 
     log_format = Option('logging', 'log_format', None,
         """Custom logging format.
 
         If nothing is set, the following will be used:
-        
+
         Trac[$(module)s] $(levelname)s: $(message)s
 
         In addition to regular key names supported by the Python logger library
@@ -155,10 +157,29 @@
          ($(thread)d) Trac[$(basename)s:$(module)s] $(levelname)s: $(message)s
 
          (since 0.10.5)""")
+    log_filters = ListOption('logging', 'log_filters', [], doc=
+        """Custom logging handlers.
+
+        If nothing set, logging will be as it was, nothing is changed.
+
+        Example usage is:
+        log_filters = trac:WARNING, trac.ticket:DEBUG
+
+        The above would translate to:
+            * all messages who's module name starts with `trac` and log level
+            is higher than `WARNING` are logged
+            * all messages who's module name starts with `trac.ticket` and log
+            level is higher than `DEBUG` are logged
+
+        This way you can narrow the debugging messages to the modules you
+        wish to. The same applies to a plugin you're coding:
+        log_filters = trac:ERROR, my.plug.module:DEBUG
+
+        (since 0.12)""")
 
     def __init__(self, path, create=False, options=[]):
         """Initialize the Trac environment.
-        
+
         @param path:   the absolute path to the Trac environment
         @param create: if `True`, the environment is created and populated with
                        default data; otherwise, the environment is expected to
@@ -195,18 +216,18 @@
 
     def component_activated(self, component):
         """Initialize additional member variables for components.
-        
+
         Every component activated through the `Environment` object gets three
         member variables: `env` (the environment object), `config` (the
         environment configuration) and `log` (a logger object)."""
         component.env = self
         component.config = self.config
-        component.log = self.log
+        component.log = logging.getLogger(component.__class__.__module__)
 
     def is_component_enabled(self, cls):
         """Implemented to only allow activation of components that are not
         disabled in the configuration.
-        
+
         This is called by the `ComponentManager` base class when a component is
         about to be activated. If this method returns false, the component does
         not get activated."""
@@ -258,7 +279,7 @@
     def get_repository(self, authname=None):
         """Return the version control repository configured for this
         environment.
-        
+
         @param authname: user name for authorization
         """
         return RepositoryManager(self).get_repository(authname)
@@ -347,7 +368,7 @@
 
     def setup_log(self):
         """Initialize the logging sub-system."""
-        from trac.log import logger_factory
+        from trac.log import setup_logging
         logtype = self.log_type
         logfile = self.log_file
         if logtype == 'file' and not os.path.isabs(logfile):
@@ -358,8 +379,10 @@
                      .replace('%(path)s', self.path) \
                      .replace('%(basename)s', os.path.basename(self.path)) \
                      .replace('%(project)s', self.project_name)
-        self.log = logger_factory(logtype, logfile, self.log_level, self.path,
-                                  format=format)
+
+        setup_logging(logtype, logfile, self.log_level, self.path,
+                      format, self.log_filters)
+        self.log = logging.getLogger(__name__)
 
     def get_known_users(self, cnx=None):
         """Generator that yields information about all known users, i.e. users
@@ -413,7 +436,7 @@
 
     def upgrade(self, backup=False, backup_dest=None):
         """Upgrade database.
-        
+
         Each db version should have its own upgrade module, names
         upgrades/dbN.py, where 'N' is the version number (int).
 
@@ -551,9 +574,10 @@
                 env.log.info('Reloading environment due to configuration '
                              'change')
                 env.shutdown()
-                if hasattr(env.log, '_trac_handler'):
-                    hdlr = env.log._trac_handler
-                    env.log.removeHandler(hdlr)
+
+                root = logging.root
+                for hdlr in root.handlers[:]:
+                    root.removeHandler(hdlr)
                     hdlr.close()
                 del env_cache[env_path]
                 env = None
Index: trac/admin/web_ui.py
===================================================================
--- trac/admin/web_ui.py	(revision 7181)
+++ trac/admin/web_ui.py	(working copy)
@@ -211,6 +211,7 @@
         log_level = self.env.log_level
         log_file = self.env.log_file
         log_dir = os.path.join(self.env.path, 'log')
+        log_filters = self.config.getlist('logging', 'log_filters')
 
         log_types = [
             dict(name='', label=_('None'), selected=False, disabled=False),
@@ -229,46 +230,76 @@
 
         if req.method == 'POST':
             changed = False
-
-            new_type = req.args.get('log_type')
-            if new_type and new_type not in ('stderr', 'file', 'syslog',
-                                             'eventlog'):
-                raise TracError(
-                    _('Unknown log type %(type)s', type=new_type),
-                    _('Invalid log type')
-                )
-            if new_type != log_type:
-                self.config.set('logging', 'log_type', new_type or 'none')
-                changed = True
-                log_type = new_type
-
-            if log_type:
-                new_level = req.args.get('log_level')
-                if new_level and new_level not in log_levels:
+            if 'add_filter' in req.args:
+                filter_module_name = req.args.get('filter_modname')
+                if not filter_module_name:
+                    raise TracError(_("Filter module name must not be empty"))
+                filter_log_level = req.args.get('filter_loglevel')
+                if filter_log_level and filter_log_level not in log_levels:
                     raise TracError(
-                        _('Unknown log level %(level)s', level=new_level),
+                        _('Unknown log level %(level)s',level=filter_log_level),
                         _('Invalid log level'))
-                if new_level and new_level != log_level:
-                    self.config.set('logging', 'log_level', new_level)
-                    changed = True
-                    log_evel = new_level
-            else:
-                self.config.remove('logging', 'log_level')
+                for idx, filter in enumerate(log_filters):
+                    if filter.split(':')[0] == filter_module_name:
+                        raise TracError(
+                            _("A filter for module '%(module)s' already exists."
+                              " Remove that one first.",
+                              module=filter_module_name),
+                            _("Filter already exists"))
+                new_log_filter = "%s:%s" % (filter_module_name,
+                                            filter_log_level)
+                log_filters.append(new_log_filter)
+                self.log.debug("Adding new filter '%s' to log_filters",
+                               new_log_filter)
+                self.config.set('logging', 'log_filters',', '.join(log_filters))
+                changed = True
+            elif 'delete_filters' in req.args:
+                selected = req.args.getlist('sel')
+                for filter in selected:
+                    self.log.debug("Removing filter '%s' from log_filters",
+                                   filter)
+                    log_filters.pop(log_filters.index(filter))
+                self.config.set('logging', 'log_filters',', '.join(log_filters))
                 changed = True
+            else:
+                new_type = req.args.get('log_type')
+                if new_type and new_type not in ('stderr', 'file', 'syslog',
+                                                 'eventlog'):
+                    raise TracError(
+                        _('Unknown log type %(type)s', type=new_type),
+                        _('Invalid log type')
+                    )
+                if new_type != log_type:
+                    self.config.set('logging', 'log_type', new_type or 'none')
+                    changed = True
+                    log_type = new_type
 
-            if log_type == 'file':
-                new_file = req.args.get('log_file', 'trac.log')
-                if new_file != log_file:
-                    self.config.set('logging', 'log_file', new_file or '')
+                if log_type:
+                    new_level = req.args.get('log_level')
+                    if new_level and new_level not in log_levels:
+                        raise TracError(
+                            _('Unknown log level %(level)s', level=new_level),
+                            _('Invalid log level'))
+                    if new_level and new_level != log_level:
+                        self.config.set('logging', 'log_level', new_level)
+                        changed = True
+                        log_evel = new_level
+                else:
+                    self.config.remove('logging', 'log_level')
                     changed = True
-                    log_file = new_file
-                if log_type == 'file' and not log_file:
-                    raise TracError(_('You must specify a log file'),
-                                    _('Missing field'))
-            else:
-                self.config.remove('logging', 'log_file')
-                changed = True
 
+                if log_type == 'file':
+                    new_file = req.args.get('log_file', 'trac.log')
+                    if new_file != log_file:
+                        self.config.set('logging', 'log_file', new_file or '')
+                        changed = True
+                        log_file = new_file
+                    if log_type == 'file' and not log_file:
+                        raise TracError(_('You must specify a log file'),
+                                        _('Missing field'))
+                else:
+                    self.config.remove('logging', 'log_file')
+                    changed = True
             if changed:
                 self.config.save()
             req.redirect(req.href.admin(cat, page))
@@ -276,7 +307,8 @@
         data = {
             'type': log_type, 'types': log_types,
             'level': log_level, 'levels': log_levels,
-            'file': log_file, 'dir': log_dir
+            'file': log_file, 'dir': log_dir,
+            'filters': log_filters
         }
         return 'admin_logging.html', {'log': data}
 
@@ -333,7 +365,7 @@
                     perm.grant_permission(subject, group)
                     req.redirect(req.href.admin(cat, page))
                 else:
-                    add_warning(req, 
+                    add_warning(req,
                                 _('"%(subject)s" was already added to group '
                                 '"%(group)s"', subject=subject, group=group))
 
@@ -500,7 +532,7 @@
                         if v:
                             if k == 'home_page' or k == 'url':
                                 k = 'home_page'
-                                v = v.replace('$', '').replace('URL: ', '') 
+                                v = v.replace('$', '').replace('URL: ', '')
                             info[k] = v
                 # retrieve plugin version info
                 version = dist.version
@@ -508,7 +540,7 @@
                     version = (getattr(module, 'version', '') or
                                getattr(module, 'revision', ''))
                     # special handling for "$Rev$" strings
-                    version = version.replace('$', '').replace('Rev: ', 'r') 
+                    version = version.replace('$', '').replace('Rev: ', 'r')
                 plugins[dist.project_name] = {
                     'name': dist.project_name, 'version': version,
                     'path': dist.location, 'description': description,
Index: trac/admin/templates/admin_logging.html
===================================================================
--- trac/admin/templates/admin_logging.html	(revision 7181)
+++ trac/admin/templates/admin_logging.html	(working copy)
@@ -52,8 +52,97 @@
         <div class="buttons">
           <input type="submit" value="${_('Apply changes')}"/>
         </div>
+        <p><b><em>Changes require environment restart</em></b></p>
       </fieldset>
     </form>
+
+    <fieldset>
+      <legend>Logging Filters</legend>
+
+      <form class="addnew" id="newlog_filters" name="newlog_filters" method="post">
+        <fieldset>
+          <legend>Add New Logging Filter</legend>
+          <table>
+            <tr class="field">
+              <th><label for="filter_modname">Module:</label></th>
+              <td><input type="text" id="filter_modname" name="filter_modname"/></td>
+            </tr>
+            <tr class="field">
+              <th><label for="filter_loglevel">Log level:</label></th>
+              <td>
+                <select id="filter_loglevel" name="filter_loglevel">
+                  <option py:for="level in log.levels">$level</option>
+                </select>
+              </td>
+            </tr>
+          </table>
+        <div class="buttons">
+          <input type="submit" name="add_filter" value="${_('Add Filter')}"/>
+        </div>
+        </fieldset>
+      </form>
+
+      <p class="help">If nothing set, logging will be as it was, nothing is
+      changed.</p>
+
+      <form class="mod" id="log_filters" name="log_filters" method="post">
+        <div class="field">
+          <table class="listing" id="filters_table">
+            <thead>
+              <tr>
+                <th class="sel">&nbsp;</th>
+                <th>Module</th>
+                <th>Log level</th>
+              </tr>
+            </thead>
+            <tbody py:if="log.filters">
+              <tr py:for="mod, level in [f.split(':') for f in log.filters]">
+                <td class="sel"><input type="checkbox" name="sel" value="$mod:$level"/></td>
+                <td>$mod</td>
+                <td>$level</td>
+              </tr>
+            </tbody>
+            <tbody py:if="not log.filters">
+              <tr><td colspan="3">
+                <center><b>No Filters Available</b></center>
+              </td></tr>
+            </tbody>
+          </table>
+        </div>
+        <div class="buttons" py:if="log.filters">
+          <input type="submit" name="delete_filters" value="${_('Delete Selected Filters')}"/>
+        </div>
+
+        <p><b><em>Adding new filters require environment restart</em></b></p>
+
+        <p class="help">Example usage is:<br/>
+        &nbsp;<tt>[logging]</tt><br/>
+        &nbsp;<tt>log_filters = trac:WARNING, trac.ticket:DEBUG</tt></p>
+
+        <p class="help">The above would translate to:<br/>
+
+          &nbsp;&bull; all messages who's module name starts with
+          <b><tt>trac</tt></b> and log level is higher than <b><tt>WARNING</tt></b>
+          are logged;<br/>
+
+          &nbsp;&bull; all messages who's module name starts with
+          <b><tt>trac.ticket</tt></b> and log level is higher than
+          <b><tt>DEBUG</tt></b> are logged;<br/>
+
+          &nbsp;&bull; all other messages who's module name <b>does not</b>
+          start with either <b><tt>trac</tt></b> or <b><tt>trac.ticket</tt></b>
+          will be logged if their level is higher than the default
+          <b><tt>log_level</tt></b>;</p>
+
+        <p class="help">This way you can narrow the debugging messages to the
+        modules you wish to.<br/>
+        The same applies to a plugin you're coding:<br/>
+        &nbsp;<tt>[logging]</tt><br/>
+        &nbsp;<tt>log_filters = trac:ERROR, my.plug.module:DEBUG</tt></p>
+      </form>
+
+
+    </fieldset>
   </body>
 
 </html>
Index: trac/log.py
===================================================================
--- trac/log.py	(revision 7181)
+++ trac/log.py	(working copy)
@@ -3,6 +3,7 @@
 # Copyright (C) 2003-2008 Edgewall Software
 # Copyright (C) 2003-2005 Daniel Lundin <daniel@edgewall.com>
 # Copyright (C) 2006 Christian Boos <cboos@neuf.fr>
+# Copyright (C) 2008 Pedro Algarvio <ufs@ufsoft.org>
 # All rights reserved.
 #
 # This software is licensed as described in the file COPYING, which
@@ -15,13 +16,11 @@
 #
 # Author: Daniel Lundin <daniel@edgewall.com>
 
+import sys
 import logging
 import logging.handlers
-import sys
 
-def logger_factory(logtype='syslog', logfile=None, level='WARNING',
-                   logid='Trac', format=None):
-    logger = logging.getLogger(logid)
+def _get_handler(logtype, logfile, logid):
     logtype = logtype.lower()
     if logtype == 'file':
         hdlr = logging.FileHandler(logfile)
@@ -37,30 +36,82 @@
         hdlr = logging.handlers.BufferingHandler(0)
         # Note: this _really_ throws away log events, as a `MemoryHandler`
         # would keep _all_ records in case there's no target handler (a bug?)
+    return hdlr
+
+
+def setup_logging(logtype='syslog', logfile=None, level='WARNING',
+                   logid='Trac', format=None, filters=None):
 
     if not format:
-        format = 'Trac[%(module)s] %(levelname)s: %(message)s'
+        format = 'Trac[%(name)s] %(levelname)s: %(message)s'
         if logtype in ('file', 'stderr'):
             format = '%(asctime)s ' + format
     datefmt = ''
     if logtype == 'stderr':
         datefmt = '%X'
-    level = level.upper()
-    if level in ('DEBUG', 'ALL'):
-        logger.setLevel(logging.DEBUG)
-    elif level == 'INFO':
-        logger.setLevel(logging.INFO)
-    elif level == 'ERROR':
-        logger.setLevel(logging.ERROR)
-    elif level == 'CRITICAL':
-        logger.setLevel(logging.CRITICAL)
-    else:
-        logger.setLevel(logging.WARNING)
     formatter = logging.Formatter(format, datefmt)
-    hdlr.setFormatter(formatter)
-    logger.addHandler(hdlr)
 
-    # Remember our handler so that we can remove it later
-    logger._trac_handler = hdlr 
+    level = logging._levelNames[level.upper()]
+
+    logging._acquireLock()
+    try:
+        logging._handlers.clear()
+        if hasattr(logging, '_handlerList'):
+            del logging._handlerList[:]
+
+        # Setup Handlers
+        handlers = []
+        if isinstance(filters, basestring):
+            filters = filters.split(',')
+        for hdl in filters:
+            hl = hdl.strip().split(':')
+            try:
+                qn, lvl = hl[0].rstrip('*'), logging._levelNames[hl[1]]
+            except IndexError:
+                qn, lvl = hl[0].rstrip('*'), level
+            handler = _get_handler(logtype, logfile, logid)
+            handler.setLevel(lvl)
+            handler.setFormatter(formatter)
+            handlers.append((qn, handler, lvl))
+
+        # Install Loggers
+        root = logging.root
+        root.setLevel(level)
+        for h in root.handlers[:]:
+            root.removeHandler(h)
+
+        root_handler = _get_handler(logtype, logfile, logid)
+        root_handler.setFormatter(formatter)
+        root.addHandler(root_handler)
+
+        #and now the others...
+        #we don't want to lose the existing loggers,
+        #since other threads may have pointers to them.
+        #existing is set to contain all existing loggers,
+        #and as we go through the new configuration we
+        #remove any which are configured. At the end,
+        #what's left in existing is the set of loggers
+        #which were in the previous configuration but
+        #which are not in the new configuration.
+        existing = root.manager.loggerDict.keys()
+        #now set up the new ones...
+        for qn, handler, lvl in handlers:
+            logger = logging.getLogger(qn)
+            if qn in existing:
+                existing.remove(qn)
+            logger.setLevel(lvl)
+            for h in logger.handlers[:]:
+                logger.removeHandler(h)
+            logger.disabled = 0
+            logger.addHandler(handler)
+
+        #Disable any old loggers. There's no point deleting
+        #them as other threads may continue to hold references
+        #and by disabling them, you stop them doing any logging.
+        # Next time their called, they will use our setup.
+        for log in existing:
+            root.manager.loggerDict[log].disabled = 1
+    finally:
+        logging._releaseLock()
 
-    return logger
+__all__ = ['setup_logging']
