Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(164)

Side by Side Diff: appengine/swarming/swarming_bot/logging_utils.py

Issue 1233303003: Migrate and merge logging_utils from swarming_bot into client/utils. (Closed) Base URL: git@github.com:luci/luci-py.git@master
Patch Set: Changed formatter Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 # Copyright 2014 The Swarming Authors. All rights reserved.
2 # Use of this source code is governed by the Apache v2.0 license that can be
3 # found in the LICENSE file.
4
5 """Utility relating to logging.
6
7 TODO(maruel): Merge buffering and output related code from client/utils/tools.py
8 in a single file.
9 """
10
11 import codecs
12 import logging
13 import logging.handlers
14 import os
15 import sys
16 import tempfile
17 import time
18
19
20 # This works around file locking issue on Windows specifically in the case of
21 # long lived child processes.
22 #
23 # Python opens files with inheritable handle and without file sharing by
24 # default. This causes the RotatingFileHandler file handle to be duplicated in
25 # the subprocesses even if the log file is not used in it. Because of this
26 # handle in the child process, when the RotatingFileHandler tries to os.rename()
27 # the file in the parent process, it fails with:
28 # WindowsError: [Error 32] The process cannot access the file because
29 # it is being used by another process
30 if sys.platform == 'win32':
31 import msvcrt # pylint: disable=F0401
32 import _subprocess # pylint: disable=F0401
33
34 # TODO(maruel): Make it work in cygwin too if necessary. This would have to
35 # use ctypes.cdll.kernel32 instead of _subprocess and msvcrt.
36
37 def _duplicate(handle):
38 target_process = _subprocess.GetCurrentProcess()
39 return _subprocess.DuplicateHandle(
40 _subprocess.GetCurrentProcess(), handle, target_process,
41 0, False, _subprocess.DUPLICATE_SAME_ACCESS).Detach()
42
43
44 class NoInheritRotatingFileHandler(logging.handlers.RotatingFileHandler):
45 def _open(self):
46 """Opens the current file without handle inheritance."""
47 if self.encoding is None:
48 with open(self.baseFilename, self.mode) as stream:
49 newosf = _duplicate(msvcrt.get_osfhandle(stream.fileno()))
50 new_fd = msvcrt.open_osfhandle(newosf, os.O_APPEND)
51 return os.fdopen(new_fd, self.mode)
52 return codecs.open(self.baseFilename, self.mode, self.encoding)
53
54
55 else: # Not Windows.
56
57
58 NoInheritRotatingFileHandler = logging.handlers.RotatingFileHandler
59
60
61 class CaptureLogs(object):
62 """Captures all the logs in a context."""
63 def __init__(self, prefix, root=None):
64 handle, self._path = tempfile.mkstemp(prefix=prefix, suffix='.log')
65 os.close(handle)
66 self._handler = logging.FileHandler(self._path, 'w')
67 self._handler.setLevel(logging.DEBUG)
68 formatter = UTCFormatter(
69 '%(process)d %(asctime)s: %(levelname)-5s %(message)s')
70 self._handler.setFormatter(formatter)
71 self._root = root or logging.getLogger()
72 self._root.addHandler(self._handler)
73 assert self._root.isEnabledFor(logging.DEBUG)
74
75 def read(self):
76 """Returns the current content of the logs.
77
78 This also closes the log capture so future logs will not be captured.
79 """
80 self._disconnect()
81 assert self._path
82 try:
83 with open(self._path, 'rb') as f:
84 return f.read()
85 except IOError as e:
86 return 'Failed to read %s: %s' % (self._path, e)
87
88 def close(self):
89 """Closes and delete the log."""
90 self._disconnect()
91 if self._path:
92 try:
93 os.remove(self._path)
94 except OSError as e:
95 logging.error('Failed to delete log file %s: %s', self._path, e)
96 self._path = None
97
98 def __enter__(self):
99 return self
100
101 def __exit__(self, _exc_type, _exc_value, _traceback):
102 self.close()
103
104 def _disconnect(self):
105 if self._handler:
106 self._root.removeHandler(self._handler)
107 self._handler.close()
108 self._handler = None
109
110
111 class UTCFormatter(logging.Formatter):
112 converter = time.gmtime
113
114 def formatTime(self, record, datefmt=None):
115 """Change is ',' to '.'."""
116 ct = self.converter(record.created)
117 if datefmt:
118 return time.strftime(datefmt, ct)
119 else:
120 t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
121 return "%s.%03d" % (t, record.msecs)
122
123
124 def find_stderr(root=None):
125 """Returns the logging.handler streaming to stderr, if any."""
126 for log in (root or logging.getLogger()).handlers:
127 if getattr(log, 'stream', None) is sys.stderr:
128 return log
129
130
131 def prepare_logging(filename, root=None):
132 """Prepare logging for scripts.
133
134 Makes it log in UTC all the time. Prepare a rotating file based log.
135 """
136 assert not find_stderr(root)
137 formatter = UTCFormatter(
138 '%(process)d %(asctime)s: %(levelname)-5s %(message)s')
139
140 # It is a requirement that the root logger is set to DEBUG, so the messages
141 # are not lost. It defaults to WARNING otherwise.
142 logger = root or logging.getLogger()
143 logger.setLevel(logging.DEBUG)
144
145 stderr = logging.StreamHandler()
146 stderr.setFormatter(formatter)
147 # Default to ERROR.
148 stderr.setLevel(logging.ERROR)
149 logger.addHandler(stderr)
150
151 # Setup up logging to a constant file so we can debug issues where
152 # the results aren't properly sent to the result URL.
153 if filename:
154 try:
155 rotating_file = NoInheritRotatingFileHandler(
156 filename, maxBytes=10 * 1024 * 1024, backupCount=5)
157 rotating_file.setLevel(logging.DEBUG)
158 rotating_file.setFormatter(formatter)
159 logger.addHandler(rotating_file)
160 except Exception:
161 # May happen on cygwin. Do not crash.
162 logging.exception('Failed to open %s', filename)
163
164
165 def set_console_level(level, root=None):
166 """Reset the console (stderr) logging level."""
167 handler = find_stderr(root)
168 handler.setLevel(level)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698