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
33 33 from trac.versioncontrol import RepositoryManager 34 34 from trac.web.href import Href 35 35 36 import logging 37 36 38 __all__ = ['Environment', 'IEnvironmentSetupParticipant', 'open_environment'] 37 39 38 40 … … 46 48 47 49 def environment_needs_upgrade(db): 48 50 """Called when Trac checks whether the environment needs to be upgraded. 49 51 50 52 Should return `True` if this participant needs an upgrade to be 51 53 performed, `False` otherwise. 52 54 """ 53 55 54 56 def upgrade_environment(db): 55 57 """Actually perform an environment upgrade. 56 58 57 59 Implementations of this method should not commit any database 58 60 transactions. This is done implicitly after all participants have 59 61 performed the upgrades they need without an error being raised. … … 69 71 * an SQLite database (stores tickets, wiki pages...) 70 72 * Project specific templates and plugins. 71 73 * wiki and ticket attachments. 72 """ 74 """ 73 75 setup_participants = ExtensionPoint(IEnvironmentSetupParticipant) 74 76 75 77 shared_plugins_dir = PathOption('inherit', 'plugins_dir', '', 76 78 """Path of the directory containing additional plugins. 77 79 78 80 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 80 82 precedence. 81 83 82 84 (''since 0.11'')""") 83 85 84 86 base_url = Option('trac', 'base_url', '', 85 87 """Reference URL for the Trac deployment. 86 88 87 89 This is the base URL that will be used when producing documents that 88 90 will be used outside of the web browsing context, like for example 89 91 when inserting URLs pointing to Trac resources in notification 90 92 e-mails.""") 91 93 92 94 base_url_for_redirect = BoolOption('trac', 'use_base_url_for_redirect', 93 False, 95 False, 94 96 """Optionally use `[trac] base_url` for redirects. 95 97 96 98 In some configurations, usually involving running Trac behind a HTTP 97 99 proxy, Trac can't automatically reconstruct the URL that is used to 98 100 access it. You may need to use this option to force Trac to use the … … 124 126 125 127 log_type = Option('logging', 'log_type', 'none', 126 128 """Logging facility to use. 127 129 128 130 Should be one of (`none`, `file`, `stderr`, `syslog`, `winlog`).""") 129 131 130 132 log_file = Option('logging', 'log_file', 'trac.log', … … 132 134 133 135 log_level = Option('logging', 'log_level', 'DEBUG', 134 136 """Level of verbosity in log. 135 137 136 138 Should be one of (`CRITICAL`, `ERROR`, `WARN`, `INFO`, `DEBUG`).""") 137 139 138 140 log_format = Option('logging', 'log_format', None, 139 141 """Custom logging format. 140 142 141 143 If nothing is set, the following will be used: 142 144 143 145 Trac[$(module)s] $(levelname)s: $(message)s 144 146 145 147 In addition to regular key names supported by the Python logger library … … 155 157 ($(thread)d) Trac[$(basename)s:$(module)s] $(levelname)s: $(message)s 156 158 157 159 (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)""") 158 179 159 180 def __init__(self, path, create=False, options=[]): 160 181 """Initialize the Trac environment. 161 182 162 183 @param path: the absolute path to the Trac environment 163 184 @param create: if `True`, the environment is created and populated with 164 185 default data; otherwise, the environment is expected to … … 195 216 196 217 def component_activated(self, component): 197 218 """Initialize additional member variables for components. 198 219 199 220 Every component activated through the `Environment` object gets three 200 221 member variables: `env` (the environment object), `config` (the 201 222 environment configuration) and `log` (a logger object).""" 202 223 component.env = self 203 224 component.config = self.config 204 component.log = self.log225 component.log = logging.getLogger(component.__class__.__module__) 205 226 206 227 def is_component_enabled(self, cls): 207 228 """Implemented to only allow activation of components that are not 208 229 disabled in the configuration. 209 230 210 231 This is called by the `ComponentManager` base class when a component is 211 232 about to be activated. If this method returns false, the component does 212 233 not get activated.""" … … 258 279 def get_repository(self, authname=None): 259 280 """Return the version control repository configured for this 260 281 environment. 261 282 262 283 @param authname: user name for authorization 263 284 """ 264 285 return RepositoryManager(self).get_repository(authname) … … 347 368 348 369 def setup_log(self): 349 370 """Initialize the logging sub-system.""" 350 from trac.log import logger_factory371 from trac.log import setup_logging 351 372 logtype = self.log_type 352 373 logfile = self.log_file 353 374 if logtype == 'file' and not os.path.isabs(logfile): … … 358 379 .replace('%(path)s', self.path) \ 359 380 .replace('%(basename)s', os.path.basename(self.path)) \ 360 381 .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__) 363 386 364 387 def get_known_users(self, cnx=None): 365 388 """Generator that yields information about all known users, i.e. users … … 413 436 414 437 def upgrade(self, backup=False, backup_dest=None): 415 438 """Upgrade database. 416 439 417 440 Each db version should have its own upgrade module, names 418 441 upgrades/dbN.py, where 'N' is the version number (int). 419 442 … … 551 574 env.log.info('Reloading environment due to configuration ' 552 575 'change') 553 576 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) 557 581 hdlr.close() 558 582 del env_cache[env_path] 559 583 env = None -
trac/admin/web_ui.py
211 211 log_level = self.env.log_level 212 212 log_file = self.env.log_file 213 213 log_dir = os.path.join(self.env.path, 'log') 214 log_filters = self.config.getlist('logging', 'log_filters') 214 215 215 216 log_types = [ 216 217 dict(name='', label=_('None'), selected=False, disabled=False), … … 229 230 230 231 if req.method == 'POST': 231 232 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: 248 239 raise TracError( 249 _('Unknown log level %(level)s', level=new_level),240 _('Unknown log level %(level)s',level=filter_log_level), 250 241 _('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)) 257 263 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 258 276 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') 263 289 changed = True 264 log_file = new_file265 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 = True271 290 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 272 303 if changed: 273 304 self.config.save() 274 305 req.redirect(req.href.admin(cat, page)) … … 276 307 data = { 277 308 'type': log_type, 'types': log_types, 278 309 'level': log_level, 'levels': log_levels, 279 'file': log_file, 'dir': log_dir 310 'file': log_file, 'dir': log_dir, 311 'filters': log_filters 280 312 } 281 313 return 'admin_logging.html', {'log': data} 282 314 … … 333 365 perm.grant_permission(subject, group) 334 366 req.redirect(req.href.admin(cat, page)) 335 367 else: 336 add_warning(req, 368 add_warning(req, 337 369 _('"%(subject)s" was already added to group ' 338 370 '"%(group)s"', subject=subject, group=group)) 339 371 … … 500 532 if v: 501 533 if k == 'home_page' or k == 'url': 502 534 k = 'home_page' 503 v = v.replace('$', '').replace('URL: ', '') 535 v = v.replace('$', '').replace('URL: ', '') 504 536 info[k] = v 505 537 # retrieve plugin version info 506 538 version = dist.version … … 508 540 version = (getattr(module, 'version', '') or 509 541 getattr(module, 'revision', '')) 510 542 # special handling for "$Rev$" strings 511 version = version.replace('$', '').replace('Rev: ', 'r') 543 version = version.replace('$', '').replace('Rev: ', 'r') 512 544 plugins[dist.project_name] = { 513 545 'name': dist.project_name, 'version': version, 514 546 'path': dist.location, 'description': description, -
trac/admin/templates/admin_logging.html
52 52 <div class="buttons"> 53 53 <input type="submit" value="${_('Apply changes')}"/> 54 54 </div> 55 <p><b><em>Changes require environment restart</em></b></p> 55 56 </fieldset> 56 57 </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"> </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 <tt>[logging]</tt><br/> 120 <tt>log_filters = trac:WARNING, trac.ticket:DEBUG</tt></p> 121 122 <p class="help">The above would translate to:<br/> 123 124 • 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 • 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 • 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 <tt>[logging]</tt><br/> 141 <tt>log_filters = trac:ERROR, my.plug.module:DEBUG</tt></p> 142 </form> 143 144 145 </fieldset> 57 146 </body> 58 147 59 148 </html> -
trac/log.py
3 3 # Copyright (C) 2003-2008 Edgewall Software 4 4 # Copyright (C) 2003-2005 Daniel Lundin <daniel@edgewall.com> 5 5 # Copyright (C) 2006 Christian Boos <cboos@neuf.fr> 6 # Copyright (C) 2008 Pedro Algarvio <ufs@ufsoft.org> 6 7 # All rights reserved. 7 8 # 8 9 # This software is licensed as described in the file COPYING, which … … 15 16 # 16 17 # Author: Daniel Lundin <daniel@edgewall.com> 17 18 19 import sys 18 20 import logging 19 21 import logging.handlers 20 import sys21 22 22 def logger_factory(logtype='syslog', logfile=None, level='WARNING', 23 logid='Trac', format=None): 24 logger = logging.getLogger(logid) 23 def _get_handler(logtype, logfile, logid): 25 24 logtype = logtype.lower() 26 25 if logtype == 'file': 27 26 hdlr = logging.FileHandler(logfile) … … 37 36 hdlr = logging.handlers.BufferingHandler(0) 38 37 # Note: this _really_ throws away log events, as a `MemoryHandler` 39 38 # would keep _all_ records in case there's no target handler (a bug?) 39 return hdlr 40 41 42 def setup_logging(logtype='syslog', logfile=None, level='WARNING', 43 logid='Trac', format=None, filters=None): 40 44 41 45 if not format: 42 format = 'Trac[%( module)s] %(levelname)s: %(message)s'46 format = 'Trac[%(name)s] %(levelname)s: %(message)s' 43 47 if logtype in ('file', 'stderr'): 44 48 format = '%(asctime)s ' + format 45 49 datefmt = '' 46 50 if logtype == 'stderr': 47 51 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)59 52 formatter = logging.Formatter(format, datefmt) 60 hdlr.setFormatter(formatter)61 logger.addHandler(hdlr)62 53 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() 65 116 66 return logger 117 __all__ = ['setup_logging']
