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