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