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