##// END OF EJS Templates
blackbox: minor code reordering...
marmoute -
r33129:7dc090fa default
parent child Browse files
Show More
@@ -1,251 +1,253 b''
1 # blackbox.py - log repository events to a file for post-mortem debugging
1 # blackbox.py - log repository events to a file for post-mortem debugging
2 #
2 #
3 # Copyright 2010 Nicolas Dumazet
3 # Copyright 2010 Nicolas Dumazet
4 # Copyright 2013 Facebook, Inc.
4 # Copyright 2013 Facebook, Inc.
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 """log repository events to a blackbox for debugging
9 """log repository events to a blackbox for debugging
10
10
11 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
11 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
12 The events that get logged can be configured via the blackbox.track config key.
12 The events that get logged can be configured via the blackbox.track config key.
13
13
14 Examples::
14 Examples::
15
15
16 [blackbox]
16 [blackbox]
17 track = *
17 track = *
18 # dirty is *EXPENSIVE* (slow);
18 # dirty is *EXPENSIVE* (slow);
19 # each log entry indicates `+` if the repository is dirty, like :hg:`id`.
19 # each log entry indicates `+` if the repository is dirty, like :hg:`id`.
20 dirty = True
20 dirty = True
21 # record the source of log messages
21 # record the source of log messages
22 logsource = True
22 logsource = True
23
23
24 [blackbox]
24 [blackbox]
25 track = command, commandfinish, commandexception, exthook, pythonhook
25 track = command, commandfinish, commandexception, exthook, pythonhook
26
26
27 [blackbox]
27 [blackbox]
28 track = incoming
28 track = incoming
29
29
30 [blackbox]
30 [blackbox]
31 # limit the size of a log file
31 # limit the size of a log file
32 maxsize = 1.5 MB
32 maxsize = 1.5 MB
33 # rotate up to N log files when the current one gets too big
33 # rotate up to N log files when the current one gets too big
34 maxfiles = 3
34 maxfiles = 3
35
35
36 """
36 """
37
37
38 from __future__ import absolute_import
38 from __future__ import absolute_import
39
39
40 import errno
40 import errno
41 import re
41 import re
42
42
43 from mercurial.i18n import _
43 from mercurial.i18n import _
44 from mercurial.node import hex
44 from mercurial.node import hex
45
45
46 from mercurial import (
46 from mercurial import (
47 registrar,
47 registrar,
48 ui as uimod,
48 ui as uimod,
49 util,
49 util,
50 )
50 )
51
51
52 cmdtable = {}
53 command = registrar.command(cmdtable)
54 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
52 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
55 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
53 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
56 # be specifying the version(s) of Mercurial they are tested with, or
54 # be specifying the version(s) of Mercurial they are tested with, or
57 # leave the attribute unspecified.
55 # leave the attribute unspecified.
58 testedwith = 'ships-with-hg-core'
56 testedwith = 'ships-with-hg-core'
57
58 cmdtable = {}
59 command = registrar.command(cmdtable)
60
59 lastui = None
61 lastui = None
60
62
61 filehandles = {}
63 filehandles = {}
62
64
63 def _openlog(vfs):
65 def _openlog(vfs):
64 path = vfs.join('blackbox.log')
66 path = vfs.join('blackbox.log')
65 if path in filehandles:
67 if path in filehandles:
66 return filehandles[path]
68 return filehandles[path]
67 filehandles[path] = fp = vfs('blackbox.log', 'a')
69 filehandles[path] = fp = vfs('blackbox.log', 'a')
68 return fp
70 return fp
69
71
70 def _closelog(vfs):
72 def _closelog(vfs):
71 path = vfs.join('blackbox.log')
73 path = vfs.join('blackbox.log')
72 fp = filehandles[path]
74 fp = filehandles[path]
73 del filehandles[path]
75 del filehandles[path]
74 fp.close()
76 fp.close()
75
77
76 def wrapui(ui):
78 def wrapui(ui):
77 class blackboxui(ui.__class__):
79 class blackboxui(ui.__class__):
78 def __init__(self, src=None):
80 def __init__(self, src=None):
79 super(blackboxui, self).__init__(src)
81 super(blackboxui, self).__init__(src)
80 if src is None:
82 if src is None:
81 self._partialinit()
83 self._partialinit()
82 else:
84 else:
83 self._bbfp = getattr(src, '_bbfp', None)
85 self._bbfp = getattr(src, '_bbfp', None)
84 self._bbinlog = False
86 self._bbinlog = False
85 self._bbrepo = getattr(src, '_bbrepo', None)
87 self._bbrepo = getattr(src, '_bbrepo', None)
86 self._bbvfs = getattr(src, '_bbvfs', None)
88 self._bbvfs = getattr(src, '_bbvfs', None)
87
89
88 def _partialinit(self):
90 def _partialinit(self):
89 if util.safehasattr(self, '_bbvfs'):
91 if util.safehasattr(self, '_bbvfs'):
90 return
92 return
91 self._bbfp = None
93 self._bbfp = None
92 self._bbinlog = False
94 self._bbinlog = False
93 self._bbrepo = None
95 self._bbrepo = None
94 self._bbvfs = None
96 self._bbvfs = None
95
97
96 def copy(self):
98 def copy(self):
97 self._partialinit()
99 self._partialinit()
98 return self.__class__(self)
100 return self.__class__(self)
99
101
100 @util.propertycache
102 @util.propertycache
101 def track(self):
103 def track(self):
102 return self.configlist('blackbox', 'track', ['*'])
104 return self.configlist('blackbox', 'track', ['*'])
103
105
104 def _openlogfile(self):
106 def _openlogfile(self):
105 def rotate(oldpath, newpath):
107 def rotate(oldpath, newpath):
106 try:
108 try:
107 self._bbvfs.unlink(newpath)
109 self._bbvfs.unlink(newpath)
108 except OSError as err:
110 except OSError as err:
109 if err.errno != errno.ENOENT:
111 if err.errno != errno.ENOENT:
110 self.debug("warning: cannot remove '%s': %s\n" %
112 self.debug("warning: cannot remove '%s': %s\n" %
111 (newpath, err.strerror))
113 (newpath, err.strerror))
112 try:
114 try:
113 if newpath:
115 if newpath:
114 self._bbvfs.rename(oldpath, newpath)
116 self._bbvfs.rename(oldpath, newpath)
115 except OSError as err:
117 except OSError as err:
116 if err.errno != errno.ENOENT:
118 if err.errno != errno.ENOENT:
117 self.debug("warning: cannot rename '%s' to '%s': %s\n" %
119 self.debug("warning: cannot rename '%s' to '%s': %s\n" %
118 (newpath, oldpath, err.strerror))
120 (newpath, oldpath, err.strerror))
119
121
120 fp = _openlog(self._bbvfs)
122 fp = _openlog(self._bbvfs)
121 maxsize = self.configbytes('blackbox', 'maxsize', 1048576)
123 maxsize = self.configbytes('blackbox', 'maxsize', 1048576)
122 if maxsize > 0:
124 if maxsize > 0:
123 st = self._bbvfs.fstat(fp)
125 st = self._bbvfs.fstat(fp)
124 if st.st_size >= maxsize:
126 if st.st_size >= maxsize:
125 path = fp.name
127 path = fp.name
126 _closelog(self._bbvfs)
128 _closelog(self._bbvfs)
127 maxfiles = self.configint('blackbox', 'maxfiles', 7)
129 maxfiles = self.configint('blackbox', 'maxfiles', 7)
128 for i in xrange(maxfiles - 1, 1, -1):
130 for i in xrange(maxfiles - 1, 1, -1):
129 rotate(oldpath='%s.%d' % (path, i - 1),
131 rotate(oldpath='%s.%d' % (path, i - 1),
130 newpath='%s.%d' % (path, i))
132 newpath='%s.%d' % (path, i))
131 rotate(oldpath=path,
133 rotate(oldpath=path,
132 newpath=maxfiles > 0 and path + '.1')
134 newpath=maxfiles > 0 and path + '.1')
133 fp = _openlog(self._bbvfs)
135 fp = _openlog(self._bbvfs)
134 return fp
136 return fp
135
137
136 def _bbwrite(self, fmt, *args):
138 def _bbwrite(self, fmt, *args):
137 self._bbfp.write(fmt % args)
139 self._bbfp.write(fmt % args)
138 self._bbfp.flush()
140 self._bbfp.flush()
139
141
140 def log(self, event, *msg, **opts):
142 def log(self, event, *msg, **opts):
141 global lastui
143 global lastui
142 super(blackboxui, self).log(event, *msg, **opts)
144 super(blackboxui, self).log(event, *msg, **opts)
143 self._partialinit()
145 self._partialinit()
144
146
145 if not '*' in self.track and not event in self.track:
147 if not '*' in self.track and not event in self.track:
146 return
148 return
147
149
148 if self._bbfp:
150 if self._bbfp:
149 ui = self
151 ui = self
150 elif self._bbvfs:
152 elif self._bbvfs:
151 try:
153 try:
152 self._bbfp = self._openlogfile()
154 self._bbfp = self._openlogfile()
153 except (IOError, OSError) as err:
155 except (IOError, OSError) as err:
154 self.debug('warning: cannot write to blackbox.log: %s\n' %
156 self.debug('warning: cannot write to blackbox.log: %s\n' %
155 err.strerror)
157 err.strerror)
156 del self._bbvfs
158 del self._bbvfs
157 self._bbfp = None
159 self._bbfp = None
158 ui = self
160 ui = self
159 else:
161 else:
160 # certain ui instances exist outside the context of
162 # certain ui instances exist outside the context of
161 # a repo, so just default to the last blackbox that
163 # a repo, so just default to the last blackbox that
162 # was seen.
164 # was seen.
163 ui = lastui
165 ui = lastui
164
166
165 if not ui or not ui._bbfp:
167 if not ui or not ui._bbfp:
166 return
168 return
167 if not lastui or ui._bbrepo:
169 if not lastui or ui._bbrepo:
168 lastui = ui
170 lastui = ui
169 if ui._bbinlog:
171 if ui._bbinlog:
170 # recursion guard
172 # recursion guard
171 return
173 return
172 try:
174 try:
173 ui._bbinlog = True
175 ui._bbinlog = True
174 default = self.configdate('devel', 'default-date')
176 default = self.configdate('devel', 'default-date')
175 date = util.datestr(default, '%Y/%m/%d %H:%M:%S')
177 date = util.datestr(default, '%Y/%m/%d %H:%M:%S')
176 user = util.getuser()
178 user = util.getuser()
177 pid = '%d' % util.getpid()
179 pid = '%d' % util.getpid()
178 formattedmsg = msg[0] % msg[1:]
180 formattedmsg = msg[0] % msg[1:]
179 rev = '(unknown)'
181 rev = '(unknown)'
180 changed = ''
182 changed = ''
181 if ui._bbrepo:
183 if ui._bbrepo:
182 ctx = ui._bbrepo[None]
184 ctx = ui._bbrepo[None]
183 parents = ctx.parents()
185 parents = ctx.parents()
184 rev = ('+'.join([hex(p.node()) for p in parents]))
186 rev = ('+'.join([hex(p.node()) for p in parents]))
185 if (ui.configbool('blackbox', 'dirty', False) and (
187 if (ui.configbool('blackbox', 'dirty', False) and (
186 any(ui._bbrepo.status()) or
188 any(ui._bbrepo.status()) or
187 any(ctx.sub(s).dirty() for s in ctx.substate)
189 any(ctx.sub(s).dirty() for s in ctx.substate)
188 )):
190 )):
189 changed = '+'
191 changed = '+'
190 if ui.configbool('blackbox', 'logsource', False):
192 if ui.configbool('blackbox', 'logsource', False):
191 src = ' [%s]' % event
193 src = ' [%s]' % event
192 else:
194 else:
193 src = ''
195 src = ''
194 try:
196 try:
195 ui._bbwrite('%s %s @%s%s (%s)%s> %s',
197 ui._bbwrite('%s %s @%s%s (%s)%s> %s',
196 date, user, rev, changed, pid, src, formattedmsg)
198 date, user, rev, changed, pid, src, formattedmsg)
197 except IOError as err:
199 except IOError as err:
198 self.debug('warning: cannot write to blackbox.log: %s\n' %
200 self.debug('warning: cannot write to blackbox.log: %s\n' %
199 err.strerror)
201 err.strerror)
200 finally:
202 finally:
201 ui._bbinlog = False
203 ui._bbinlog = False
202
204
203 def setrepo(self, repo):
205 def setrepo(self, repo):
204 self._bbfp = None
206 self._bbfp = None
205 self._bbinlog = False
207 self._bbinlog = False
206 self._bbrepo = repo
208 self._bbrepo = repo
207 self._bbvfs = repo.vfs
209 self._bbvfs = repo.vfs
208
210
209 ui.__class__ = blackboxui
211 ui.__class__ = blackboxui
210 uimod.ui = blackboxui
212 uimod.ui = blackboxui
211
213
212 def uisetup(ui):
214 def uisetup(ui):
213 wrapui(ui)
215 wrapui(ui)
214
216
215 def reposetup(ui, repo):
217 def reposetup(ui, repo):
216 # During 'hg pull' a httppeer repo is created to represent the remote repo.
218 # During 'hg pull' a httppeer repo is created to represent the remote repo.
217 # It doesn't have a .hg directory to put a blackbox in, so we don't do
219 # It doesn't have a .hg directory to put a blackbox in, so we don't do
218 # the blackbox setup for it.
220 # the blackbox setup for it.
219 if not repo.local():
221 if not repo.local():
220 return
222 return
221
223
222 if util.safehasattr(ui, 'setrepo'):
224 if util.safehasattr(ui, 'setrepo'):
223 ui.setrepo(repo)
225 ui.setrepo(repo)
224
226
225 @command('^blackbox',
227 @command('^blackbox',
226 [('l', 'limit', 10, _('the number of events to show')),
228 [('l', 'limit', 10, _('the number of events to show')),
227 ],
229 ],
228 _('hg blackbox [OPTION]...'))
230 _('hg blackbox [OPTION]...'))
229 def blackbox(ui, repo, *revs, **opts):
231 def blackbox(ui, repo, *revs, **opts):
230 '''view the recent repository events
232 '''view the recent repository events
231 '''
233 '''
232
234
233 if not repo.vfs.exists('blackbox.log'):
235 if not repo.vfs.exists('blackbox.log'):
234 return
236 return
235
237
236 limit = opts.get('limit')
238 limit = opts.get('limit')
237 fp = repo.vfs('blackbox.log', 'r')
239 fp = repo.vfs('blackbox.log', 'r')
238 lines = fp.read().split('\n')
240 lines = fp.read().split('\n')
239
241
240 count = 0
242 count = 0
241 output = []
243 output = []
242 for line in reversed(lines):
244 for line in reversed(lines):
243 if count >= limit:
245 if count >= limit:
244 break
246 break
245
247
246 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
248 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
247 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
249 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
248 count += 1
250 count += 1
249 output.append(line)
251 output.append(line)
250
252
251 ui.status('\n'.join(reversed(output)))
253 ui.status('\n'.join(reversed(output)))
General Comments 0
You need to be logged in to leave comments. Login now