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