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