Ticket #7286: corrected_enhanced_logging.patch
| File corrected_enhanced_logging.patch, 19.5 KB (added by Pedro Algarvio, aka, s0undt3ch <ufs@…>, 4 years ago) |
|---|
-
trac/env.py
2 2 # 3 3 # Copyright (C) 2003-2008 Edgewall Software 4 4 # Copyright (C) 2003-2007 Jonas Borgström <jonas@edgewall.com> 5 # Copyright (C) 2008 Pedro Algarvio <ufs@ufsoft.org> 5 6 # All rights reserved. 6 7 # 7 8 # This software is licensed as described in the file COPYING, which … … 14 15 # 15 16 # Author: Jonas Borgström <jonas@edgewall.com> 16 17 18 import logging 17 19 import os 18 20 try: 19 21 import threading … … 147 149 - $(path)s the path for the current environment 148 150 - $(basename)s the last path component of the current environment 149 151 - $(project)s the project name 150 151 Note the usage of `$(...)s` instead of `%(...)s` as the latter form152 would be interpreted by the ConfigParser itself.153 154 Example:152 153 Note the usage of `$(...)s` instead of `%(...)s` as the latter form 154 would be interpreted by the ConfigParser itself. 155 156 Example: 155 157 ($(thread)d) Trac[$(basename)s:$(module)s] $(levelname)s: $(message)s 156 157 (since 0.10.5)""") 158 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 * all other messages who's module name does not start with any of 174 the above and for which their level is higher than the default 175 `log_level` will be logged; 176 177 This way you can narrow the debugging messages to the modules you 178 wish to. The same applies to a plugin you're coding: 179 log_filters = trac:ERROR, my.plug.module:DEBUG 180 181 (since 0.12)""") 158 182 159 183 def __init__(self, path, create=False, options=[]): 160 184 """Initialize the Trac environment. … … 201 225 environment configuration) and `log` (a logger object).""" 202 226 component.env = self 203 227 component.config = self.config 204 component.log = self.log 228 component.log = logging.getLogger( 229 "%s.%s" % (self.path, component.__class__.__module__) 230 ) 205 231 206 232 def is_component_enabled(self, cls): 207 233 """Implemented to only allow activation of components that are not … … 347 373 348 374 def setup_log(self): 349 375 """Initialize the logging sub-system.""" 350 from trac.log import logger_factory376 from trac.log import setup_logging 351 377 logtype = self.log_type 352 378 logfile = self.log_file 353 379 if logtype == 'file' and not os.path.isabs(logfile): … … 358 384 .replace('%(path)s', self.path) \ 359 385 .replace('%(basename)s', os.path.basename(self.path)) \ 360 386 .replace('%(project)s', self.project_name) 361 self.log = logger_factory(logtype, logfile, self.log_level, self.path, 362 format=format) 387 setup_logging(logtype, logfile, self.log_level, self.path, 388 format, self.log_filters) 389 self.log = logging.getLogger("%s.%s" % (self.path, __name__)) 363 390 364 391 def get_known_users(self, cnx=None): 365 392 """Generator that yields information about all known users, i.e. users -
trac/htdocs/css/admin.css
21 21 22 22 #tabcontent { padding: 0.4em 2em; margin-left: 12em; min-height: 300px; } 23 23 #tabcontent h2 { color: #333; margin-top: 0; } 24 p.help { color: #666; font-size: 90%; margin: 1em .5em .5em; }24 div.help, p.help { color: #666; font-size: 90%; margin: 1em .5em .5em; } 25 25 26 26 #enumlist tbody td { vertical-align: middle; } 27 27 -
trac/admin/web_ui.py
2 2 # 3 3 # Copyright (C) 2005-2008 Edgewall Software 4 4 # Copyright (C) 2005 Jonas Borgström <jonas@edgewall.com> 5 # Copyright (C) 2008 Pedro Algarvio <ufs@ufsoft.org> 5 6 # All rights reserved. 6 7 # 7 8 # This software is licensed as described in the file COPYING, which … … 211 212 log_level = self.env.log_level 212 213 log_file = self.env.log_file 213 214 log_dir = os.path.join(self.env.path, 'log') 215 log_filters = self.config.getlist('logging', 'log_filters') 216 for idx, filter in enumerate(log_filters): 217 filter = filter.split(':') 218 if not (len(filter) > 1 and filter[1]): 219 log_filters[idx] = "%s:%s" % (filter[0], log_level) 214 220 215 221 log_types = [ 216 222 dict(name='', label=_('None'), selected=False, disabled=False), … … 229 235 230 236 if req.method == 'POST': 231 237 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') 238 if 'add_filter' in req.args: 239 filter_module_name = req.args.get('filter_modname') 240 if not filter_module_name: 241 raise TracError(_("Filter module name must not be empty")) 242 filter_log_level = req.args.get('filter_loglevel') 243 if filter_log_level and filter_log_level not in log_levels: 244 raise TracError(_('Unknown log level %(level)s', 245 level=filter_log_level), 246 _('Invalid log level')) 247 for filter in log_filters: 248 if filter.split(':')[0] == filter_module_name or \ 249 filter.split(':')[0].rstrip('.*') == filter_module_name: 250 raise TracError( 251 _("A filter for module '%(module)s' already exists." 252 " Remove that one first.", 253 module=filter_module_name), 254 _("Filter already exists")) 255 new_log_filter = "%s:%s" % (filter_module_name.rstrip('.*'), 256 filter_log_level) 257 log_filters.append(new_log_filter) 258 self.log.debug("Adding new filter '%s' to log_filters", 259 new_log_filter) 260 self.config.set('logging', 'log_filters',', '.join(log_filters)) 242 261 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: 262 elif 'delete_filters' in req.args: 263 selected = req.args.getlist('sel') 264 for filter in selected: 265 self.log.debug("Removing filter '%s' from log_filters", 266 filter) 267 log_filters.pop(log_filters.index(filter)) 268 self.config.set('logging', 'log_filters',', '.join(log_filters)) 269 changed = True 270 else: 271 new_type = req.args.get('log_type') 272 if new_type and new_type not in ('stderr', 'file', 'syslog', 273 'eventlog'): 248 274 raise TracError( 249 _('Unknown log level %(level)s', level=new_level), 250 _('Invalid log level')) 251 if new_level and new_level != log_level: 252 self.config.set('logging', 'log_level', new_level) 275 _('Unknown log type %(type)s', type=new_type), 276 _('Invalid log type') 277 ) 278 if new_type != log_type: 279 self.config.set('logging', 'log_type', new_type or 'none') 253 280 changed = True 254 log_evel = new_level 255 else: 256 self.config.remove('logging', 'log_level') 257 changed = True 281 log_type = new_type 258 282 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 '') 283 if log_type: 284 new_level = req.args.get('log_level') 285 if new_level and new_level not in log_levels: 286 raise TracError( 287 _('Unknown log level %(level)s', level=new_level), 288 _('Invalid log level')) 289 if new_level and new_level != log_level: 290 self.config.set('logging', 'log_level', new_level) 291 changed = True 292 log_evel = new_level 293 else: 294 self.config.remove('logging', 'log_level') 263 295 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 296 297 if log_type == 'file': 298 new_file = req.args.get('log_file', 'trac.log') 299 if new_file != log_file: 300 self.config.set('logging', 'log_file', new_file or '') 301 changed = True 302 log_file = new_file 303 if log_type == 'file' and not log_file: 304 raise TracError(_('You must specify a log file'), 305 _('Missing field')) 306 else: 307 self.config.remove('logging', 'log_file') 308 changed = True 272 309 if changed: 273 310 self.config.save() 274 311 req.redirect(req.href.admin(cat, page)) … … 276 313 data = { 277 314 'type': log_type, 'types': log_types, 278 315 'level': log_level, 'levels': log_levels, 279 'file': log_file, 'dir': log_dir 316 'file': log_file, 'dir': log_dir, 317 'filters': log_filters 280 318 } 281 319 return 'admin_logging.html', {'log': data} 282 320 -
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 <div class="help"> 119 <p>Example usage is:</p> 120 <pre> 121 [logging] 122 log_filters = trac:WARNING, trac.ticket:DEBUG 123 </pre> 124 <p>The above would translate to:</p> 125 <ul> 126 <li>all messages who's module name starts with <b><tt>trac</tt></b> 127 and log level is higher than <b><tt>WARNING</tt></b> are logged;</li> 128 129 <li>all messages who's module name starts with <b><tt>trac.ticket</tt></b> 130 and log level is higher than <b><tt>DEBUG</tt></b> are logged;</li> 131 132 <li>all other messages who's module name <b>does not</b> start with either 133 <b><tt>trac</tt></b> or <b><tt>trac.ticket</tt></b> will be logged if 134 their level is higher than the default <b><tt>log_level</tt></b>;</li> 135 </ul> 136 137 <p>This way you can narrow the debugging messages to the modules you wish to.</p> 138 <p>The same applies to a plugin you're coding:</p> 139 <pre> 140 [logging] 141 log_filters = trac:ERROR, my.plug.module:DEBUG 142 </pre> 143 </div> 144 </form> 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 … … 19 20 import logging.handlers 20 21 import sys 21 22 22 def logger_factory(logtype='syslog', logfile=None, level='WARNING', 23 logid='Trac', format=None): 24 logger = logging.getLogger(logid) 23 from trac.util.compat import sorted 24 from trac.util.translation import _ 25 26 27 def setup_logging(logtype='syslog', logfile=None, level='WARNING', logid='Trac', 28 format=None, filters=()): 29 env_root_logger = logging.getLogger(logid) 25 30 logtype = logtype.lower() 26 31 if logtype == 'file': 27 32 hdlr = logging.FileHandler(logfile) 28 33 elif logtype in ('winlog', 'eventlog', 'nteventlog'): 29 34 # Requires win32 extensions 30 hdlr = logging.handlers.NTEventLogHandler(logid, 31 logtype='Application') 35 hdlr = logging.handlers.NTEventLogHandler(logid, logtype='Application') 32 36 elif logtype in ('syslog', 'unix'): 33 37 hdlr = logging.handlers.SysLogHandler('/dev/log') 34 38 elif logtype in ('stderr'): … … 47 51 datefmt = '%X' 48 52 level = level.upper() 49 53 if level in ('DEBUG', 'ALL'): 50 logger.setLevel(logging.DEBUG)54 env_root_logger.setLevel(logging.DEBUG) 51 55 elif level == 'INFO': 52 logger.setLevel(logging.INFO)56 env_root_logger.setLevel(logging.INFO) 53 57 elif level == 'ERROR': 54 logger.setLevel(logging.ERROR)58 env_root_logger.setLevel(logging.ERROR) 55 59 elif level == 'CRITICAL': 56 logger.setLevel(logging.CRITICAL)60 env_root_logger.setLevel(logging.CRITICAL) 57 61 else: 58 logger.setLevel(logging.WARNING) 59 formatter = logging.Formatter(format, datefmt) 60 hdlr.setFormatter(formatter) 61 logger.addHandler(hdlr) 62 env_root_logger.setLevel(logging.WARNING) 62 63 64 hdlr.setFormatter(TracFormatter(logid, format, datefmt)) 65 # Assign handler right away to be able to log filter errors 66 env_root_logger.addHandler(hdlr) 67 68 if filters: 69 hdlr.addFilter(TracFilter(filters, level, logid)) 70 63 71 # Remember our handler so that we can remove it later 64 logger._trac_handler = hdlr 72 env_root_logger._trac_handler = hdlr 73 74 75 class TracFilter(logging.Filter): 76 def __init__(self, trac_filters=(), default_level='DEBUG', name=''): 77 self.qns = [] 78 for filter in trac_filters: 79 filter = filter.split(':') 80 if len(filter) > 1 and filter[1]: 81 qn, lvl = filter[0].rstrip('.*'), filter[1] 82 else: 83 qn, lvl = filter[0].rstrip('.*'), default_level 84 path_and_qn = "%s.%s" % (name, qn) 85 86 if lvl == 'ALL': 87 lvl = 'DEBUG' 88 89 if lvl not in logging._levelNames: 90 logging.getLogger("%s.%s" % (name, __name__)).warning( 91 _("Level '%(level)s' for filter '%(filter)s' not know. " 92 "Ignoring filter.", 93 level=lvl.upper(), filter=':'.join(filter))) 94 continue 95 96 self.qns.append((path_and_qn, logging.getLevelName(lvl.upper()))) 97 98 self.qns = sorted(self.qns, key=lambda x: len(x[0]), reverse=True) 99 100 logging.Filter.__init__(self, name) 101 102 def filter(self, record): 103 for qn, level in self.qns: 104 if record.name.startswith("%s." % qn) or record.name == qn: 105 # Match trac, trac.web but not tracforge 106 if level <= record.levelno: 107 return 1 108 return 0 109 # No point returning `logging.Filter.filter(self, record)` 110 # All logging messages will arrive to the filter with self.name at 111 # least equal to environment path, ie, self.name 112 return 1 113 114 115 class TracFormatter(logging.Formatter): 116 117 def __init__(self, env_path, fmt, datefmt): 118 self.env_path = env_path 119 # Calculate strip length at init time, no need to keep calculating it 120 self.strip_length = len(env_path)+1 121 logging.Formatter.__init__(self, fmt, datefmt) 65 122 66 return logger 123 def format(self, record): 124 # get full dotted module name and stick that under record.module 125 if record.name.startswith(self.env_path): 126 record.module = record.name[self.strip_length:] 127 return logging.Formatter.format(self, record)
