Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2014 The Swarming Authors. All rights reserved. | 1 # Copyright 2015 The Swarming Authors. All rights reserved. |
| 2 # Use of this source code is governed by the Apache v2.0 license that can be | 2 # Use of this source code is governed under the Apache License, Version 2.0 that |
| 3 # found in the LICENSE file. | 3 # can be found in the LICENSE file. |
| 4 | 4 |
| 5 """Utility relating to logging. | 5 """Utility relating to logging.""" |
| 6 | 6 |
| 7 TODO(maruel): Merge buffering and output related code from client/utils/tools.py | 7 import argparse |
| 8 in a single file. | |
| 9 """ | |
| 10 | |
| 11 import codecs | 8 import codecs |
| 12 import logging | 9 import logging |
| 13 import logging.handlers | 10 import logging.handlers |
| 11 import optparse | |
| 14 import os | 12 import os |
| 15 import sys | 13 import sys |
| 16 import tempfile | 14 import tempfile |
| 17 import time | 15 import time |
| 18 | 16 |
| 19 | 17 |
| 20 # This works around file locking issue on Windows specifically in the case of | 18 # This works around file locking issue on Windows specifically in the case of |
| 21 # long lived child processes. | 19 # long lived child processes. |
| 22 # | 20 # |
| 23 # Python opens files with inheritable handle and without file sharing by | 21 # Python opens files with inheritable handle and without file sharing by |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 51 return os.fdopen(new_fd, self.mode) | 49 return os.fdopen(new_fd, self.mode) |
| 52 return codecs.open(self.baseFilename, self.mode, self.encoding) | 50 return codecs.open(self.baseFilename, self.mode, self.encoding) |
| 53 | 51 |
| 54 | 52 |
| 55 else: # Not Windows. | 53 else: # Not Windows. |
| 56 | 54 |
| 57 | 55 |
| 58 NoInheritRotatingFileHandler = logging.handlers.RotatingFileHandler | 56 NoInheritRotatingFileHandler = logging.handlers.RotatingFileHandler |
| 59 | 57 |
| 60 | 58 |
| 59 # Levels used for logging. | |
| 60 LEVELS = [logging.ERROR, logging.INFO, logging.DEBUG] | |
| 61 | |
| 62 | |
| 61 class CaptureLogs(object): | 63 class CaptureLogs(object): |
| 62 """Captures all the logs in a context.""" | 64 """Captures all the logs in a context.""" |
| 63 def __init__(self, prefix, root=None): | 65 def __init__(self, prefix, root=None): |
| 64 handle, self._path = tempfile.mkstemp(prefix=prefix, suffix='.log') | 66 handle, self._path = tempfile.mkstemp(prefix=prefix, suffix='.log') |
| 65 os.close(handle) | 67 os.close(handle) |
| 66 self._handler = logging.FileHandler(self._path, 'w') | 68 self._handler = logging.FileHandler(self._path, 'w') |
| 67 self._handler.setLevel(logging.DEBUG) | 69 self._handler.setLevel(logging.DEBUG) |
| 68 formatter = UTCFormatter( | 70 formatter = UTCFormatter( |
| 69 '%(process)d %(asctime)s: %(levelname)-5s %(message)s') | 71 '%(process)d %(asctime)s: %(levelname)-5s %(message)s') |
| 70 self._handler.setFormatter(formatter) | 72 self._handler.setFormatter(formatter) |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 127 if getattr(log, 'stream', None) is sys.stderr: | 129 if getattr(log, 'stream', None) is sys.stderr: |
| 128 return log | 130 return log |
| 129 | 131 |
| 130 | 132 |
| 131 def prepare_logging(filename, root=None): | 133 def prepare_logging(filename, root=None): |
| 132 """Prepare logging for scripts. | 134 """Prepare logging for scripts. |
| 133 | 135 |
| 134 Makes it log in UTC all the time. Prepare a rotating file based log. | 136 Makes it log in UTC all the time. Prepare a rotating file based log. |
| 135 """ | 137 """ |
| 136 assert not find_stderr(root) | 138 assert not find_stderr(root) |
| 139 # Other options are: | |
| 140 # '%(levelname)5s %(relativeCreated)6d %(module)15s(%(lineno)3d): ' | |
| 141 # '%(message)s' | |
| 142 # or: | |
| 143 # '%(asctime)s %(levelname)-8s %(module)15s(%(lineno)3d): %(message)s')) | |
| 137 formatter = UTCFormatter( | 144 formatter = UTCFormatter( |
| 138 '%(process)d %(asctime)s: %(levelname)-5s %(message)s') | 145 '%(process)d %(asctime)s: %(levelname)-5s %(message)s') |
|
Vadim Sh.
2015/07/15 18:46:38
consider also https://chromium.googlesource.com/in
| |
| 139 | 146 |
| 140 # It is a requirement that the root logger is set to DEBUG, so the messages | 147 # It is a requirement that the root logger is set to DEBUG, so the messages |
| 141 # are not lost. It defaults to WARNING otherwise. | 148 # are not lost. It defaults to WARNING otherwise. |
| 142 logger = root or logging.getLogger() | 149 logger = root or logging.getLogger() |
| 143 logger.setLevel(logging.DEBUG) | 150 logger.setLevel(logging.DEBUG) |
| 144 | 151 |
| 145 stderr = logging.StreamHandler() | 152 stderr = logging.StreamHandler() |
| 146 stderr.setFormatter(formatter) | 153 stderr.setFormatter(formatter) |
| 147 # Default to ERROR. | 154 # Default to ERROR. |
| 148 stderr.setLevel(logging.ERROR) | 155 stderr.setLevel(logging.ERROR) |
| 149 logger.addHandler(stderr) | 156 logger.addHandler(stderr) |
| 150 | 157 |
| 151 # Setup up logging to a constant file so we can debug issues where | 158 # Setup up logging to a constant file so we can debug issues where |
| 152 # the results aren't properly sent to the result URL. | 159 # the results aren't properly sent to the result URL. |
| 153 if filename: | 160 if filename: |
| 154 try: | 161 try: |
| 155 rotating_file = NoInheritRotatingFileHandler( | 162 rotating_file = NoInheritRotatingFileHandler( |
| 156 filename, maxBytes=10 * 1024 * 1024, backupCount=5) | 163 filename, maxBytes=10 * 1024 * 1024, backupCount=5, |
| 164 encoding='utf-8') | |
| 157 rotating_file.setLevel(logging.DEBUG) | 165 rotating_file.setLevel(logging.DEBUG) |
| 158 rotating_file.setFormatter(formatter) | 166 rotating_file.setFormatter(formatter) |
| 159 logger.addHandler(rotating_file) | 167 logger.addHandler(rotating_file) |
| 160 except Exception: | 168 except Exception: |
| 161 # May happen on cygwin. Do not crash. | 169 # May happen on cygwin. Do not crash. |
| 162 logging.exception('Failed to open %s', filename) | 170 logging.exception('Failed to open %s', filename) |
| 163 | 171 |
| 164 | 172 |
| 165 def set_console_level(level, root=None): | 173 def set_console_level(level, root=None): |
| 166 """Reset the console (stderr) logging level.""" | 174 """Reset the console (stderr) logging level.""" |
| 167 handler = find_stderr(root) | 175 handler = find_stderr(root) |
| 168 handler.setLevel(level) | 176 handler.setLevel(level) |
| 177 | |
| 178 | |
| 179 class OptionParserWithLogging(optparse.OptionParser): | |
| 180 """Adds --verbose option.""" | |
| 181 | |
| 182 # Set to True to enable --log-file options. | |
| 183 enable_log_file = True | |
| 184 | |
| 185 # Set in unit tests. | |
| 186 logger_root = None | |
| 187 | |
| 188 def __init__(self, verbose=0, log_file=None, **kwargs): | |
| 189 kwargs.setdefault('description', sys.modules['__main__'].__doc__) | |
| 190 optparse.OptionParser.__init__(self, **kwargs) | |
| 191 self.group_logging = optparse.OptionGroup(self, 'Logging') | |
| 192 self.group_logging.add_option( | |
| 193 '-v', '--verbose', | |
| 194 action='count', | |
| 195 default=verbose, | |
| 196 help='Use multiple times to increase verbosity') | |
| 197 if self.enable_log_file: | |
| 198 self.group_logging.add_option( | |
| 199 '-l', '--log-file', | |
| 200 default=log_file, | |
| 201 help='The name of the file to store rotating log details') | |
| 202 self.group_logging.add_option( | |
| 203 '--no-log', action='store_const', const='', dest='log_file', | |
| 204 help='Disable log file') | |
| 205 | |
| 206 def parse_args(self, *args, **kwargs): | |
| 207 # Make sure this group is always the last one. | |
| 208 self.add_option_group(self.group_logging) | |
| 209 | |
| 210 options, args = optparse.OptionParser.parse_args(self, *args, **kwargs) | |
| 211 prepare_logging(self.enable_log_file and options.log_file, self.logger_root) | |
| 212 set_console_level( | |
| 213 LEVELS[min(len(LEVELS) - 1, options.verbose)], self.logger_root) | |
| 214 return options, args | |
| 215 | |
| 216 | |
| 217 class ArgumentParserWithLogging(argparse.ArgumentParser): | |
| 218 """Adds --verbose option.""" | |
| 219 | |
| 220 # Set to True to enable --log-file options. | |
| 221 enable_log_file = True | |
| 222 | |
| 223 def __init__(self, verbose=0, log_file=None, **kwargs): | |
| 224 kwargs.setdefault('description', sys.modules['__main__'].__doc__) | |
| 225 kwargs.setdefault('conflict_handler', 'resolve') | |
| 226 self.__verbose = verbose | |
| 227 self.__log_file = log_file | |
| 228 super(ArgumentParserWithLogging, self).__init__(**kwargs) | |
| 229 | |
| 230 def _add_logging_group(self): | |
| 231 group = self.add_argument_group('Logging') | |
| 232 group.add_argument( | |
| 233 '-v', '--verbose', | |
| 234 action='count', | |
| 235 default=self.__verbose, | |
| 236 help='Use multiple times to increase verbosity') | |
| 237 if self.enable_log_file: | |
| 238 group.add_argument( | |
| 239 '-l', '--log-file', | |
| 240 default=self.__log_file, | |
| 241 help='The name of the file to store rotating log details') | |
| 242 group.add_argument( | |
| 243 '--no-log', action='store_const', const='', dest='log_file', | |
| 244 help='Disable log file') | |
| 245 | |
| 246 def parse_args(self, *args, **kwargs): | |
| 247 # Make sure this group is always the last one. | |
| 248 self._add_logging_group() | |
| 249 | |
| 250 args = super(ArgumentParserWithLogging, self).parse_args(*args, **kwargs) | |
| 251 prepare_logging(self.enable_log_file and args.log_file, self.logger_root) | |
| 252 set_console_level( | |
| 253 LEVELS[min(len(LEVELS) - 1, args.verbose)], self.logger_root) | |
| 254 return args | |
| OLD | NEW |