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