##// END OF EJS Templates
blackbox: do not assume self._bb{vfs,repo,fp} are set in blackboxui.__init__...
Jun Wu -
r28552:999e1acc default
parent child Browse files
Show More
@@ -1,250 +1,250 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 cmdutil,
47 cmdutil,
48 ui as uimod,
48 ui as uimod,
49 util,
49 util,
50 )
50 )
51
51
52 cmdtable = {}
52 cmdtable = {}
53 command = cmdutil.command(cmdtable)
53 command = cmdutil.command(cmdtable)
54 # Note for extension authors: ONLY specify testedwith = 'internal' for
54 # Note for extension authors: ONLY specify testedwith = 'internal' for
55 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
55 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
56 # be specifying the version(s) of Mercurial they are tested with, or
56 # be specifying the version(s) of Mercurial they are tested with, or
57 # leave the attribute unspecified.
57 # leave the attribute unspecified.
58 testedwith = 'internal'
58 testedwith = 'internal'
59 lastui = None
59 lastui = None
60
60
61 filehandles = {}
61 filehandles = {}
62
62
63 def _openlog(vfs):
63 def _openlog(vfs):
64 path = vfs.join('blackbox.log')
64 path = vfs.join('blackbox.log')
65 if path in filehandles:
65 if path in filehandles:
66 return filehandles[path]
66 return filehandles[path]
67 filehandles[path] = fp = vfs('blackbox.log', 'a')
67 filehandles[path] = fp = vfs('blackbox.log', 'a')
68 return fp
68 return fp
69
69
70 def _closelog(vfs):
70 def _closelog(vfs):
71 path = vfs.join('blackbox.log')
71 path = vfs.join('blackbox.log')
72 fp = filehandles[path]
72 fp = filehandles[path]
73 del filehandles[path]
73 del filehandles[path]
74 fp.close()
74 fp.close()
75
75
76 def wrapui(ui):
76 def wrapui(ui):
77 class blackboxui(ui.__class__):
77 class blackboxui(ui.__class__):
78 def __init__(self, src=None):
78 def __init__(self, src=None):
79 super(blackboxui, self).__init__(src)
79 super(blackboxui, self).__init__(src)
80 if src is None:
80 if src is None:
81 self._partialinit()
81 self._partialinit()
82 else:
82 else:
83 self._bbfp = src._bbfp
83 self._bbfp = getattr(src, '_bbfp', None)
84 self._bbinlog = False
84 self._bbinlog = False
85 self._bbrepo = src._bbrepo
85 self._bbrepo = getattr(src, '_bbrepo', None)
86 self._bbvfs = src._bbvfs
86 self._bbvfs = getattr(src, '_bbvfs', None)
87
87
88 def _partialinit(self):
88 def _partialinit(self):
89 if util.safehasattr(self, '_bbvfs'):
89 if util.safehasattr(self, '_bbvfs'):
90 return
90 return
91 self._bbfp = None
91 self._bbfp = None
92 self._bbinlog = False
92 self._bbinlog = False
93 self._bbrepo = None
93 self._bbrepo = None
94 self._bbvfs = None
94 self._bbvfs = None
95
95
96 def copy(self):
96 def copy(self):
97 self._partialinit()
97 self._partialinit()
98 return self.__class__(self)
98 return self.__class__(self)
99
99
100 @util.propertycache
100 @util.propertycache
101 def track(self):
101 def track(self):
102 return self.configlist('blackbox', 'track', ['*'])
102 return self.configlist('blackbox', 'track', ['*'])
103
103
104 def _openlogfile(self):
104 def _openlogfile(self):
105 def rotate(oldpath, newpath):
105 def rotate(oldpath, newpath):
106 try:
106 try:
107 self._bbvfs.unlink(newpath)
107 self._bbvfs.unlink(newpath)
108 except OSError as err:
108 except OSError as err:
109 if err.errno != errno.ENOENT:
109 if err.errno != errno.ENOENT:
110 self.debug("warning: cannot remove '%s': %s\n" %
110 self.debug("warning: cannot remove '%s': %s\n" %
111 (newpath, err.strerror))
111 (newpath, err.strerror))
112 try:
112 try:
113 if newpath:
113 if newpath:
114 self._bbvfs.rename(oldpath, newpath)
114 self._bbvfs.rename(oldpath, newpath)
115 except OSError as err:
115 except OSError as err:
116 if err.errno != errno.ENOENT:
116 if err.errno != errno.ENOENT:
117 self.debug("warning: cannot rename '%s' to '%s': %s\n" %
117 self.debug("warning: cannot rename '%s' to '%s': %s\n" %
118 (newpath, oldpath, err.strerror))
118 (newpath, oldpath, err.strerror))
119
119
120 fp = _openlog(self._bbvfs)
120 fp = _openlog(self._bbvfs)
121 maxsize = self.configbytes('blackbox', 'maxsize', 1048576)
121 maxsize = self.configbytes('blackbox', 'maxsize', 1048576)
122 if maxsize > 0:
122 if maxsize > 0:
123 st = self._bbvfs.fstat(fp)
123 st = self._bbvfs.fstat(fp)
124 if st.st_size >= maxsize:
124 if st.st_size >= maxsize:
125 path = fp.name
125 path = fp.name
126 _closelog(self._bbvfs)
126 _closelog(self._bbvfs)
127 maxfiles = self.configint('blackbox', 'maxfiles', 7)
127 maxfiles = self.configint('blackbox', 'maxfiles', 7)
128 for i in xrange(maxfiles - 1, 1, -1):
128 for i in xrange(maxfiles - 1, 1, -1):
129 rotate(oldpath='%s.%d' % (path, i - 1),
129 rotate(oldpath='%s.%d' % (path, i - 1),
130 newpath='%s.%d' % (path, i))
130 newpath='%s.%d' % (path, i))
131 rotate(oldpath=path,
131 rotate(oldpath=path,
132 newpath=maxfiles > 0 and path + '.1')
132 newpath=maxfiles > 0 and path + '.1')
133 fp = _openlog(self._bbvfs)
133 fp = _openlog(self._bbvfs)
134 return fp
134 return fp
135
135
136 def _bbwrite(self, fmt, *args):
136 def _bbwrite(self, fmt, *args):
137 self._bbfp.write(fmt % args)
137 self._bbfp.write(fmt % args)
138 self._bbfp.flush()
138 self._bbfp.flush()
139
139
140 def log(self, event, *msg, **opts):
140 def log(self, event, *msg, **opts):
141 global lastui
141 global lastui
142 super(blackboxui, self).log(event, *msg, **opts)
142 super(blackboxui, self).log(event, *msg, **opts)
143 self._partialinit()
143 self._partialinit()
144
144
145 if not '*' in self.track and not event in self.track:
145 if not '*' in self.track and not event in self.track:
146 return
146 return
147
147
148 if self._bbfp:
148 if self._bbfp:
149 ui = self
149 ui = self
150 elif self._bbvfs:
150 elif self._bbvfs:
151 try:
151 try:
152 self._bbfp = self._openlogfile()
152 self._bbfp = self._openlogfile()
153 except (IOError, OSError) as err:
153 except (IOError, OSError) as err:
154 self.debug('warning: cannot write to blackbox.log: %s\n' %
154 self.debug('warning: cannot write to blackbox.log: %s\n' %
155 err.strerror)
155 err.strerror)
156 del self._bbvfs
156 del self._bbvfs
157 self._bbfp = None
157 self._bbfp = None
158 ui = self
158 ui = self
159 else:
159 else:
160 # certain ui instances exist outside the context of
160 # certain ui instances exist outside the context of
161 # a repo, so just default to the last blackbox that
161 # a repo, so just default to the last blackbox that
162 # was seen.
162 # was seen.
163 ui = lastui
163 ui = lastui
164
164
165 if not ui or not ui._bbfp:
165 if not ui or not ui._bbfp:
166 return
166 return
167 if not lastui or ui._bbrepo:
167 if not lastui or ui._bbrepo:
168 lastui = ui
168 lastui = ui
169 if ui._bbinlog:
169 if ui._bbinlog:
170 # recursion guard
170 # recursion guard
171 return
171 return
172 try:
172 try:
173 ui._bbinlog = True
173 ui._bbinlog = True
174 date = util.datestr(None, '%Y/%m/%d %H:%M:%S')
174 date = util.datestr(None, '%Y/%m/%d %H:%M:%S')
175 user = util.getuser()
175 user = util.getuser()
176 pid = str(util.getpid())
176 pid = str(util.getpid())
177 formattedmsg = msg[0] % msg[1:]
177 formattedmsg = msg[0] % msg[1:]
178 rev = '(unknown)'
178 rev = '(unknown)'
179 changed = ''
179 changed = ''
180 if ui._bbrepo:
180 if ui._bbrepo:
181 ctx = ui._bbrepo[None]
181 ctx = ui._bbrepo[None]
182 parents = ctx.parents()
182 parents = ctx.parents()
183 rev = ('+'.join([hex(p.node()) for p in parents]))
183 rev = ('+'.join([hex(p.node()) for p in parents]))
184 if (ui.configbool('blackbox', 'dirty', False) and (
184 if (ui.configbool('blackbox', 'dirty', False) and (
185 any(ui._bbrepo.status()) or
185 any(ui._bbrepo.status()) or
186 any(ctx.sub(s).dirty() for s in ctx.substate)
186 any(ctx.sub(s).dirty() for s in ctx.substate)
187 )):
187 )):
188 changed = '+'
188 changed = '+'
189 if ui.configbool('blackbox', 'logsource', False):
189 if ui.configbool('blackbox', 'logsource', False):
190 src = ' [%s]' % event
190 src = ' [%s]' % event
191 else:
191 else:
192 src = ''
192 src = ''
193 try:
193 try:
194 ui._bbwrite('%s %s @%s%s (%s)%s> %s',
194 ui._bbwrite('%s %s @%s%s (%s)%s> %s',
195 date, user, rev, changed, pid, src, formattedmsg)
195 date, user, rev, changed, pid, src, formattedmsg)
196 except IOError as err:
196 except IOError as err:
197 self.debug('warning: cannot write to blackbox.log: %s\n' %
197 self.debug('warning: cannot write to blackbox.log: %s\n' %
198 err.strerror)
198 err.strerror)
199 finally:
199 finally:
200 ui._bbinlog = False
200 ui._bbinlog = False
201
201
202 def setrepo(self, repo):
202 def setrepo(self, repo):
203 self._bbfp = None
203 self._bbfp = None
204 self._bbinlog = False
204 self._bbinlog = False
205 self._bbrepo = repo
205 self._bbrepo = repo
206 self._bbvfs = repo.vfs
206 self._bbvfs = repo.vfs
207
207
208 ui.__class__ = blackboxui
208 ui.__class__ = blackboxui
209 uimod.ui = blackboxui
209 uimod.ui = blackboxui
210
210
211 def uisetup(ui):
211 def uisetup(ui):
212 wrapui(ui)
212 wrapui(ui)
213
213
214 def reposetup(ui, repo):
214 def reposetup(ui, repo):
215 # During 'hg pull' a httppeer repo is created to represent the remote repo.
215 # During 'hg pull' a httppeer repo is created to represent the remote repo.
216 # It doesn't have a .hg directory to put a blackbox in, so we don't do
216 # It doesn't have a .hg directory to put a blackbox in, so we don't do
217 # the blackbox setup for it.
217 # the blackbox setup for it.
218 if not repo.local():
218 if not repo.local():
219 return
219 return
220
220
221 if util.safehasattr(ui, 'setrepo'):
221 if util.safehasattr(ui, 'setrepo'):
222 ui.setrepo(repo)
222 ui.setrepo(repo)
223
223
224 @command('^blackbox',
224 @command('^blackbox',
225 [('l', 'limit', 10, _('the number of events to show')),
225 [('l', 'limit', 10, _('the number of events to show')),
226 ],
226 ],
227 _('hg blackbox [OPTION]...'))
227 _('hg blackbox [OPTION]...'))
228 def blackbox(ui, repo, *revs, **opts):
228 def blackbox(ui, repo, *revs, **opts):
229 '''view the recent repository events
229 '''view the recent repository events
230 '''
230 '''
231
231
232 if not repo.vfs.exists('blackbox.log'):
232 if not repo.vfs.exists('blackbox.log'):
233 return
233 return
234
234
235 limit = opts.get('limit')
235 limit = opts.get('limit')
236 fp = repo.vfs('blackbox.log', 'r')
236 fp = repo.vfs('blackbox.log', 'r')
237 lines = fp.read().split('\n')
237 lines = fp.read().split('\n')
238
238
239 count = 0
239 count = 0
240 output = []
240 output = []
241 for line in reversed(lines):
241 for line in reversed(lines):
242 if count >= limit:
242 if count >= limit:
243 break
243 break
244
244
245 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
245 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
246 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
246 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
247 count += 1
247 count += 1
248 output.append(line)
248 output.append(line)
249
249
250 ui.status('\n'.join(reversed(output)))
250 ui.status('\n'.join(reversed(output)))
General Comments 0
You need to be logged in to leave comments. Login now