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