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