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