Edgewall Software

Ticket #7286: enhanced_logging.patch

File enhanced_logging.patch, 23.2 KB (added by Pedro Algarvio, aka, s0undt3ch <ufs@…>, 4 years ago)
  • trac/env.py

     
    3333from trac.versioncontrol import RepositoryManager 
    3434from trac.web.href import Href 
    3535 
     36import logging 
     37 
    3638__all__ = ['Environment', 'IEnvironmentSetupParticipant', 'open_environment'] 
    3739 
    3840 
     
    4648 
    4749    def environment_needs_upgrade(db): 
    4850        """Called when Trac checks whether the environment needs to be upgraded. 
    49          
     51 
    5052        Should return `True` if this participant needs an upgrade to be 
    5153        performed, `False` otherwise. 
    5254        """ 
    5355 
    5456    def upgrade_environment(db): 
    5557        """Actually perform an environment upgrade. 
    56          
     58 
    5759        Implementations of this method should not commit any database 
    5860        transactions. This is done implicitly after all participants have 
    5961        performed the upgrades they need without an error being raised. 
     
    6971     * an SQLite database (stores tickets, wiki pages...) 
    7072     * Project specific templates and plugins. 
    7173     * wiki and ticket attachments. 
    72     """    
     74    """ 
    7375    setup_participants = ExtensionPoint(IEnvironmentSetupParticipant) 
    7476 
    7577    shared_plugins_dir = PathOption('inherit', 'plugins_dir', '', 
    7678        """Path of the directory containing additional plugins. 
    77          
     79 
    7880        Plugins in that directory are loaded in addition to those in the 
    79         directory of the environment `plugins`, with this one taking  
     81        directory of the environment `plugins`, with this one taking 
    8082        precedence. 
    81          
     83 
    8284        (''since 0.11'')""") 
    8385 
    8486    base_url = Option('trac', 'base_url', '', 
    8587        """Reference URL for the Trac deployment. 
    86          
     88 
    8789        This is the base URL that will be used when producing documents that 
    8890        will be used outside of the web browsing context, like for example 
    8991        when inserting URLs pointing to Trac resources in notification 
    9092        e-mails.""") 
    9193 
    9294    base_url_for_redirect = BoolOption('trac', 'use_base_url_for_redirect', 
    93             False,  
     95            False, 
    9496        """Optionally use `[trac] base_url` for redirects. 
    95          
     97 
    9698        In some configurations, usually involving running Trac behind a HTTP 
    9799        proxy, Trac can't automatically reconstruct the URL that is used to 
    98100        access it. You may need to use this option to force Trac to use the 
     
    124126 
    125127    log_type = Option('logging', 'log_type', 'none', 
    126128        """Logging facility to use. 
    127          
     129 
    128130        Should be one of (`none`, `file`, `stderr`, `syslog`, `winlog`).""") 
    129131 
    130132    log_file = Option('logging', 'log_file', 'trac.log', 
     
    132134 
    133135    log_level = Option('logging', 'log_level', 'DEBUG', 
    134136        """Level of verbosity in log. 
    135          
     137 
    136138        Should be one of (`CRITICAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`).""") 
    137139 
    138140    log_format = Option('logging', 'log_format', None, 
    139141        """Custom logging format. 
    140142 
    141143        If nothing is set, the following will be used: 
    142          
     144 
    143145        Trac[$(module)s] $(levelname)s: $(message)s 
    144146 
    145147        In addition to regular key names supported by the Python logger library 
     
    155157         ($(thread)d) Trac[$(basename)s:$(module)s] $(levelname)s: $(message)s 
    156158 
    157159         (since 0.10.5)""") 
     160    log_filters = ListOption('logging', 'log_filters', [], doc= 
     161        """Custom logging handlers. 
     162 
     163        If nothing set, logging will be as it was, nothing is changed. 
     164 
     165        Example usage is: 
     166        log_filters = trac:WARNING, trac.ticket:DEBUG 
     167 
     168        The above would translate to: 
     169            * all messages who's module name starts with `trac` and log level 
     170            is higher than `WARNING` are logged 
     171            * all messages who's module name starts with `trac.ticket` and log 
     172            level is higher than `DEBUG` are logged 
     173 
     174        This way you can narrow the debugging messages to the modules you 
     175        wish to. The same applies to a plugin you're coding: 
     176        log_filters = trac:ERROR, my.plug.module:DEBUG 
     177 
     178        (since 0.12)""") 
    158179 
    159180    def __init__(self, path, create=False, options=[]): 
    160181        """Initialize the Trac environment. 
    161          
     182 
    162183        @param path:   the absolute path to the Trac environment 
    163184        @param create: if `True`, the environment is created and populated with 
    164185                       default data; otherwise, the environment is expected to 
     
    195216 
    196217    def component_activated(self, component): 
    197218        """Initialize additional member variables for components. 
    198          
     219 
    199220        Every component activated through the `Environment` object gets three 
    200221        member variables: `env` (the environment object), `config` (the 
    201222        environment configuration) and `log` (a logger object).""" 
    202223        component.env = self 
    203224        component.config = self.config 
    204         component.log = self.log 
     225        component.log = logging.getLogger(component.__class__.__module__) 
    205226 
    206227    def is_component_enabled(self, cls): 
    207228        """Implemented to only allow activation of components that are not 
    208229        disabled in the configuration. 
    209          
     230 
    210231        This is called by the `ComponentManager` base class when a component is 
    211232        about to be activated. If this method returns false, the component does 
    212233        not get activated.""" 
     
    258279    def get_repository(self, authname=None): 
    259280        """Return the version control repository configured for this 
    260281        environment. 
    261          
     282 
    262283        @param authname: user name for authorization 
    263284        """ 
    264285        return RepositoryManager(self).get_repository(authname) 
     
    347368 
    348369    def setup_log(self): 
    349370        """Initialize the logging sub-system.""" 
    350         from trac.log import logger_factory 
     371        from trac.log import setup_logging 
    351372        logtype = self.log_type 
    352373        logfile = self.log_file 
    353374        if logtype == 'file' and not os.path.isabs(logfile): 
     
    358379                     .replace('%(path)s', self.path) \ 
    359380                     .replace('%(basename)s', os.path.basename(self.path)) \ 
    360381                     .replace('%(project)s', self.project_name) 
    361         self.log = logger_factory(logtype, logfile, self.log_level, self.path, 
    362                                   format=format) 
     382 
     383        setup_logging(logtype, logfile, self.log_level, self.path, 
     384                      format, self.log_filters) 
     385        self.log = logging.getLogger(__name__) 
    363386 
    364387    def get_known_users(self, cnx=None): 
    365388        """Generator that yields information about all known users, i.e. users 
     
    413436 
    414437    def upgrade(self, backup=False, backup_dest=None): 
    415438        """Upgrade database. 
    416          
     439 
    417440        Each db version should have its own upgrade module, names 
    418441        upgrades/dbN.py, where 'N' is the version number (int). 
    419442 
     
    551574                env.log.info('Reloading environment due to configuration ' 
    552575                             'change') 
    553576                env.shutdown() 
    554                 if hasattr(env.log, '_trac_handler'): 
    555                     hdlr = env.log._trac_handler 
    556                     env.log.removeHandler(hdlr) 
     577 
     578                root = logging.root 
     579                for hdlr in root.handlers[:]: 
     580                    root.removeHandler(hdlr) 
    557581                    hdlr.close() 
    558582                del env_cache[env_path] 
    559583                env = None 
  • trac/admin/web_ui.py

     
    211211        log_level = self.env.log_level 
    212212        log_file = self.env.log_file 
    213213        log_dir = os.path.join(self.env.path, 'log') 
     214        log_filters = self.config.getlist('logging', 'log_filters') 
    214215 
    215216        log_types = [ 
    216217            dict(name='', label=_('None'), selected=False, disabled=False), 
     
    229230 
    230231        if req.method == 'POST': 
    231232            changed = False 
    232  
    233             new_type = req.args.get('log_type') 
    234             if new_type and new_type not in ('stderr', 'file', 'syslog', 
    235                                              'eventlog'): 
    236                 raise TracError( 
    237                     _('Unknown log type %(type)s', type=new_type), 
    238                     _('Invalid log type') 
    239                 ) 
    240             if new_type != log_type: 
    241                 self.config.set('logging', 'log_type', new_type or 'none') 
    242                 changed = True 
    243                 log_type = new_type 
    244  
    245             if log_type: 
    246                 new_level = req.args.get('log_level') 
    247                 if new_level and new_level not in log_levels: 
     233            if 'add_filter' in req.args: 
     234                filter_module_name = req.args.get('filter_modname') 
     235                if not filter_module_name: 
     236                    raise TracError(_("Filter module name must not be empty")) 
     237                filter_log_level = req.args.get('filter_loglevel') 
     238                if filter_log_level and filter_log_level not in log_levels: 
    248239                    raise TracError( 
    249                         _('Unknown log level %(level)s', level=new_level), 
     240                        _('Unknown log level %(level)s',level=filter_log_level), 
    250241                        _('Invalid log level')) 
    251                 if new_level and new_level != log_level: 
    252                     self.config.set('logging', 'log_level', new_level) 
    253                     changed = True 
    254                     log_evel = new_level 
    255             else: 
    256                 self.config.remove('logging', 'log_level') 
     242                for idx, filter in enumerate(log_filters): 
     243                    if filter.split(':')[0] == filter_module_name: 
     244                        raise TracError( 
     245                            _("A filter for module '%(module)s' already exists." 
     246                              " Remove that one first.", 
     247                              module=filter_module_name), 
     248                            _("Filter already exists")) 
     249                new_log_filter = "%s:%s" % (filter_module_name, 
     250                                            filter_log_level) 
     251                log_filters.append(new_log_filter) 
     252                self.log.debug("Adding new filter '%s' to log_filters", 
     253                               new_log_filter) 
     254                self.config.set('logging', 'log_filters',', '.join(log_filters)) 
     255                changed = True 
     256            elif 'delete_filters' in req.args: 
     257                selected = req.args.getlist('sel') 
     258                for filter in selected: 
     259                    self.log.debug("Removing filter '%s' from log_filters", 
     260                                   filter) 
     261                    log_filters.pop(log_filters.index(filter)) 
     262                self.config.set('logging', 'log_filters',', '.join(log_filters)) 
    257263                changed = True 
     264            else: 
     265                new_type = req.args.get('log_type') 
     266                if new_type and new_type not in ('stderr', 'file', 'syslog', 
     267                                                 'eventlog'): 
     268                    raise TracError( 
     269                        _('Unknown log type %(type)s', type=new_type), 
     270                        _('Invalid log type') 
     271                    ) 
     272                if new_type != log_type: 
     273                    self.config.set('logging', 'log_type', new_type or 'none') 
     274                    changed = True 
     275                    log_type = new_type 
    258276 
    259             if log_type == 'file': 
    260                 new_file = req.args.get('log_file', 'trac.log') 
    261                 if new_file != log_file: 
    262                     self.config.set('logging', 'log_file', new_file or '') 
     277                if log_type: 
     278                    new_level = req.args.get('log_level') 
     279                    if new_level and new_level not in log_levels: 
     280                        raise TracError( 
     281                            _('Unknown log level %(level)s', level=new_level), 
     282                            _('Invalid log level')) 
     283                    if new_level and new_level != log_level: 
     284                        self.config.set('logging', 'log_level', new_level) 
     285                        changed = True 
     286                        log_evel = new_level 
     287                else: 
     288                    self.config.remove('logging', 'log_level') 
    263289                    changed = True 
    264                     log_file = new_file 
    265                 if log_type == 'file' and not log_file: 
    266                     raise TracError(_('You must specify a log file'), 
    267                                     _('Missing field')) 
    268             else: 
    269                 self.config.remove('logging', 'log_file') 
    270                 changed = True 
    271290 
     291                if log_type == 'file': 
     292                    new_file = req.args.get('log_file', 'trac.log') 
     293                    if new_file != log_file: 
     294                        self.config.set('logging', 'log_file', new_file or '') 
     295                        changed = True 
     296                        log_file = new_file 
     297                    if log_type == 'file' and not log_file: 
     298                        raise TracError(_('You must specify a log file'), 
     299                                        _('Missing field')) 
     300                else: 
     301                    self.config.remove('logging', 'log_file') 
     302                    changed = True 
    272303            if changed: 
    273304                self.config.save() 
    274305            req.redirect(req.href.admin(cat, page)) 
     
    276307        data = { 
    277308            'type': log_type, 'types': log_types, 
    278309            'level': log_level, 'levels': log_levels, 
    279             'file': log_file, 'dir': log_dir 
     310            'file': log_file, 'dir': log_dir, 
     311            'filters': log_filters 
    280312        } 
    281313        return 'admin_logging.html', {'log': data} 
    282314 
     
    333365                    perm.grant_permission(subject, group) 
    334366                    req.redirect(req.href.admin(cat, page)) 
    335367                else: 
    336                     add_warning(req,  
     368                    add_warning(req, 
    337369                                _('"%(subject)s" was already added to group ' 
    338370                                '"%(group)s"', subject=subject, group=group)) 
    339371 
     
    500532                        if v: 
    501533                            if k == 'home_page' or k == 'url': 
    502534                                k = 'home_page' 
    503                                 v = v.replace('$', '').replace('URL: ', '')  
     535                                v = v.replace('$', '').replace('URL: ', '') 
    504536                            info[k] = v 
    505537                # retrieve plugin version info 
    506538                version = dist.version 
     
    508540                    version = (getattr(module, 'version', '') or 
    509541                               getattr(module, 'revision', '')) 
    510542                    # special handling for "$Rev$" strings 
    511                     version = version.replace('$', '').replace('Rev: ', 'r')  
     543                    version = version.replace('$', '').replace('Rev: ', 'r') 
    512544                plugins[dist.project_name] = { 
    513545                    'name': dist.project_name, 'version': version, 
    514546                    'path': dist.location, 'description': description, 
  • trac/admin/templates/admin_logging.html

     
    5252        <div class="buttons"> 
    5353          <input type="submit" value="${_('Apply changes')}"/> 
    5454        </div> 
     55        <p><b><em>Changes require environment restart</em></b></p> 
    5556      </fieldset> 
    5657    </form> 
     58 
     59    <fieldset> 
     60      <legend>Logging Filters</legend> 
     61 
     62      <form class="addnew" id="newlog_filters" name="newlog_filters" method="post"> 
     63        <fieldset> 
     64          <legend>Add New Logging Filter</legend> 
     65          <table> 
     66            <tr class="field"> 
     67              <th><label for="filter_modname">Module:</label></th> 
     68              <td><input type="text" id="filter_modname" name="filter_modname"/></td> 
     69            </tr> 
     70            <tr class="field"> 
     71              <th><label for="filter_loglevel">Log level:</label></th> 
     72              <td> 
     73                <select id="filter_loglevel" name="filter_loglevel"> 
     74                  <option py:for="level in log.levels">$level</option> 
     75                </select> 
     76              </td> 
     77            </tr> 
     78          </table> 
     79        <div class="buttons"> 
     80          <input type="submit" name="add_filter" value="${_('Add Filter')}"/> 
     81        </div> 
     82        </fieldset> 
     83      </form> 
     84 
     85      <p class="help">If nothing set, logging will be as it was, nothing is 
     86      changed.</p> 
     87 
     88      <form class="mod" id="log_filters" name="log_filters" method="post"> 
     89        <div class="field"> 
     90          <table class="listing" id="filters_table"> 
     91            <thead> 
     92              <tr> 
     93                <th class="sel">&nbsp;</th> 
     94                <th>Module</th> 
     95                <th>Log level</th> 
     96              </tr> 
     97            </thead> 
     98            <tbody py:if="log.filters"> 
     99              <tr py:for="mod, level in [f.split(':') for f in log.filters]"> 
     100                <td class="sel"><input type="checkbox" name="sel" value="$mod:$level"/></td> 
     101                <td>$mod</td> 
     102                <td>$level</td> 
     103              </tr> 
     104            </tbody> 
     105            <tbody py:if="not log.filters"> 
     106              <tr><td colspan="3"> 
     107                <center><b>No Filters Available</b></center> 
     108              </td></tr> 
     109            </tbody> 
     110          </table> 
     111        </div> 
     112        <div class="buttons" py:if="log.filters"> 
     113          <input type="submit" name="delete_filters" value="${_('Delete Selected Filters')}"/> 
     114        </div> 
     115 
     116        <p><b><em>Adding new filters require environment restart</em></b></p> 
     117 
     118        <p class="help">Example usage is:<br/> 
     119        &nbsp;<tt>[logging]</tt><br/> 
     120        &nbsp;<tt>log_filters = trac:WARNING, trac.ticket:DEBUG</tt></p> 
     121 
     122        <p class="help">The above would translate to:<br/> 
     123 
     124          &nbsp;&bull; all messages who's module name starts with 
     125          <b><tt>trac</tt></b> and log level is higher than <b><tt>WARNING</tt></b> 
     126          are logged;<br/> 
     127 
     128          &nbsp;&bull; all messages who's module name starts with 
     129          <b><tt>trac.ticket</tt></b> and log level is higher than 
     130          <b><tt>DEBUG</tt></b> are logged;<br/> 
     131 
     132          &nbsp;&bull; all other messages who's module name <b>does not</b> 
     133          start with either <b><tt>trac</tt></b> or <b><tt>trac.ticket</tt></b> 
     134          will be logged if their level is higher than the default 
     135          <b><tt>log_level</tt></b>;</p> 
     136 
     137        <p class="help">This way you can narrow the debugging messages to the 
     138        modules you wish to.<br/> 
     139        The same applies to a plugin you're coding:<br/> 
     140        &nbsp;<tt>[logging]</tt><br/> 
     141        &nbsp;<tt>log_filters = trac:ERROR, my.plug.module:DEBUG</tt></p> 
     142      </form> 
     143 
     144 
     145    </fieldset> 
    57146  </body> 
    58147 
    59148</html> 
  • trac/log.py

     
    33# Copyright (C) 2003-2008 Edgewall Software 
    44# Copyright (C) 2003-2005 Daniel Lundin <daniel@edgewall.com> 
    55# Copyright (C) 2006 Christian Boos <cboos@neuf.fr> 
     6# Copyright (C) 2008 Pedro Algarvio <ufs@ufsoft.org> 
    67# All rights reserved. 
    78# 
    89# This software is licensed as described in the file COPYING, which 
     
    1516# 
    1617# Author: Daniel Lundin <daniel@edgewall.com> 
    1718 
     19import sys 
    1820import logging 
    1921import logging.handlers 
    20 import sys 
    2122 
    22 def logger_factory(logtype='syslog', logfile=None, level='WARNING', 
    23                    logid='Trac', format=None): 
    24     logger = logging.getLogger(logid) 
     23def _get_handler(logtype, logfile, logid): 
    2524    logtype = logtype.lower() 
    2625    if logtype == 'file': 
    2726        hdlr = logging.FileHandler(logfile) 
     
    3736        hdlr = logging.handlers.BufferingHandler(0) 
    3837        # Note: this _really_ throws away log events, as a `MemoryHandler` 
    3938        # would keep _all_ records in case there's no target handler (a bug?) 
     39    return hdlr 
     40 
     41 
     42def setup_logging(logtype='syslog', logfile=None, level='WARNING', 
     43                   logid='Trac', format=None, filters=None): 
    4044 
    4145    if not format: 
    42         format = 'Trac[%(module)s] %(levelname)s: %(message)s' 
     46        format = 'Trac[%(name)s] %(levelname)s: %(message)s' 
    4347        if logtype in ('file', 'stderr'): 
    4448            format = '%(asctime)s ' + format 
    4549    datefmt = '' 
    4650    if logtype == 'stderr': 
    4751        datefmt = '%X' 
    48     level = level.upper() 
    49     if level in ('DEBUG', 'ALL'): 
    50         logger.setLevel(logging.DEBUG) 
    51     elif level == 'INFO': 
    52         logger.setLevel(logging.INFO) 
    53     elif level == 'ERROR': 
    54         logger.setLevel(logging.ERROR) 
    55     elif level == 'CRITICAL': 
    56         logger.setLevel(logging.CRITICAL) 
    57     else: 
    58         logger.setLevel(logging.WARNING) 
    5952    formatter = logging.Formatter(format, datefmt) 
    60     hdlr.setFormatter(formatter) 
    61     logger.addHandler(hdlr) 
    6253 
    63     # Remember our handler so that we can remove it later 
    64     logger._trac_handler = hdlr  
     54    level = logging._levelNames[level.upper()] 
     55 
     56    logging._acquireLock() 
     57    try: 
     58        logging._handlers.clear() 
     59        if hasattr(logging, '_handlerList'): 
     60            del logging._handlerList[:] 
     61 
     62        # Setup Handlers 
     63        handlers = [] 
     64        if isinstance(filters, basestring): 
     65            filters = filters.split(',') 
     66        for hdl in filters: 
     67            hl = hdl.strip().split(':') 
     68            try: 
     69                qn, lvl = hl[0].rstrip('*'), logging._levelNames[hl[1]] 
     70            except IndexError: 
     71                qn, lvl = hl[0].rstrip('*'), level 
     72            handler = _get_handler(logtype, logfile, logid) 
     73            handler.setLevel(lvl) 
     74            handler.setFormatter(formatter) 
     75            handlers.append((qn, handler, lvl)) 
     76 
     77        # Install Loggers 
     78        root = logging.root 
     79        root.setLevel(level) 
     80        for h in root.handlers[:]: 
     81            root.removeHandler(h) 
     82 
     83        root_handler = _get_handler(logtype, logfile, logid) 
     84        root_handler.setFormatter(formatter) 
     85        root.addHandler(root_handler) 
     86 
     87        #and now the others... 
     88        #we don't want to lose the existing loggers, 
     89        #since other threads may have pointers to them. 
     90        #existing is set to contain all existing loggers, 
     91        #and as we go through the new configuration we 
     92        #remove any which are configured. At the end, 
     93        #what's left in existing is the set of loggers 
     94        #which were in the previous configuration but 
     95        #which are not in the new configuration. 
     96        existing = root.manager.loggerDict.keys() 
     97        #now set up the new ones... 
     98        for qn, handler, lvl in handlers: 
     99            logger = logging.getLogger(qn) 
     100            if qn in existing: 
     101                existing.remove(qn) 
     102            logger.setLevel(lvl) 
     103            for h in logger.handlers[:]: 
     104                logger.removeHandler(h) 
     105            logger.disabled = 0 
     106            logger.addHandler(handler) 
     107 
     108        #Disable any old loggers. There's no point deleting 
     109        #them as other threads may continue to hold references 
     110        #and by disabling them, you stop them doing any logging. 
     111        # Next time their called, they will use our setup. 
     112        for log in existing: 
     113            root.manager.loggerDict[log].disabled = 1 
     114    finally: 
     115        logging._releaseLock() 
    65116 
    66     return logger 
     117__all__ = ['setup_logging']