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