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 |