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