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