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