##// END OF EJS Templates
blackbox: optionally log event source
timeless -
r28305:f5ae291d default
parent child Browse files
Show More
@@ -1,233 +1,239
1 1 # blackbox.py - log repository events to a file for post-mortem debugging
2 2 #
3 3 # Copyright 2010 Nicolas Dumazet
4 4 # Copyright 2013 Facebook, Inc.
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 """log repository events to a blackbox for debugging
10 10
11 11 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
12 12 The events that get logged can be configured via the blackbox.track config key.
13 13
14 14 Examples::
15 15
16 16 [blackbox]
17 17 track = *
18 18 # dirty is *EXPENSIVE* (slow);
19 19 # each log entry indicates `+` if the repository is dirty, like :hg:`id`.
20 20 dirty = True
21 # record the source of log messages
22 logsource = True
21 23
22 24 [blackbox]
23 25 track = command, commandfinish, commandexception, exthook, pythonhook
24 26
25 27 [blackbox]
26 28 track = incoming
27 29
28 30 [blackbox]
29 31 # limit the size of a log file
30 32 maxsize = 1.5 MB
31 33 # rotate up to N log files when the current one gets too big
32 34 maxfiles = 3
33 35
34 36 """
35 37
36 38 from __future__ import absolute_import
37 39
38 40 import errno
39 41 import re
40 42
41 43 from mercurial.i18n import _
42 44 from mercurial.node import hex
43 45
44 46 from mercurial import (
45 47 cmdutil,
46 48 ui as uimod,
47 49 util,
48 50 )
49 51
50 52 cmdtable = {}
51 53 command = cmdutil.command(cmdtable)
52 54 # Note for extension authors: ONLY specify testedwith = 'internal' for
53 55 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
54 56 # be specifying the version(s) of Mercurial they are tested with, or
55 57 # leave the attribute unspecified.
56 58 testedwith = 'internal'
57 59 lastui = None
58 60
59 61 filehandles = {}
60 62
61 63 def _openlog(vfs):
62 64 path = vfs.join('blackbox.log')
63 65 if path in filehandles:
64 66 return filehandles[path]
65 67 filehandles[path] = fp = vfs('blackbox.log', 'a')
66 68 return fp
67 69
68 70 def _closelog(vfs):
69 71 path = vfs.join('blackbox.log')
70 72 fp = filehandles[path]
71 73 del filehandles[path]
72 74 fp.close()
73 75
74 76 def wrapui(ui):
75 77 class blackboxui(ui.__class__):
76 78 def __init__(self, src=None):
77 79 super(blackboxui, self).__init__(src)
78 80 if src is None:
79 81 self._partialinit()
80 82 else:
81 83 self._bbfp = src._bbfp
82 84 self._bbrepo = src._bbrepo
83 85 self._bbvfs = src._bbvfs
84 86
85 87 def _partialinit(self):
86 88 if util.safehasattr(self, '_bbvfs'):
87 89 return
88 90 self._bbfp = None
89 91 self._bbrepo = None
90 92 self._bbvfs = None
91 93
92 94 def copy(self):
93 95 self._partialinit()
94 96 return self.__class__(self)
95 97
96 98 @util.propertycache
97 99 def track(self):
98 100 return self.configlist('blackbox', 'track', ['*'])
99 101
100 102 def _openlogfile(self):
101 103 def rotate(oldpath, newpath):
102 104 try:
103 105 self._bbvfs.unlink(newpath)
104 106 except OSError as err:
105 107 if err.errno != errno.ENOENT:
106 108 self.debug("warning: cannot remove '%s': %s\n" %
107 109 (newpath, err.strerror))
108 110 try:
109 111 if newpath:
110 112 self._bbvfs.rename(oldpath, newpath)
111 113 except OSError as err:
112 114 if err.errno != errno.ENOENT:
113 115 self.debug("warning: cannot rename '%s' to '%s': %s\n" %
114 116 (newpath, oldpath, err.strerror))
115 117
116 118 fp = _openlog(self._bbvfs)
117 119 maxsize = self.configbytes('blackbox', 'maxsize', 1048576)
118 120 if maxsize > 0:
119 121 st = self._bbvfs.fstat(fp)
120 122 if st.st_size >= maxsize:
121 123 path = fp.name
122 124 _closelog(self._bbvfs)
123 125 maxfiles = self.configint('blackbox', 'maxfiles', 7)
124 126 for i in xrange(maxfiles - 1, 1, -1):
125 127 rotate(oldpath='%s.%d' % (path, i - 1),
126 128 newpath='%s.%d' % (path, i))
127 129 rotate(oldpath=path,
128 130 newpath=maxfiles > 0 and path + '.1')
129 131 fp = _openlog(self._bbvfs)
130 132 return fp
131 133
132 134 def _bbwrite(self, fmt, *args):
133 135 self._bbfp.write(fmt % args)
134 136 self._bbfp.flush()
135 137
136 138 def log(self, event, *msg, **opts):
137 139 global lastui
138 140 super(blackboxui, self).log(event, *msg, **opts)
139 141 self._partialinit()
140 142
141 143 if not '*' in self.track and not event in self.track:
142 144 return
143 145
144 146 if self._bbfp:
145 147 ui = self
146 148 elif self._bbvfs:
147 149 try:
148 150 self._bbfp = self._openlogfile()
149 151 except (IOError, OSError) as err:
150 152 self.debug('warning: cannot write to blackbox.log: %s\n' %
151 153 err.strerror)
152 154 del self._bbvfs
153 155 self._bbfp = None
154 156 ui = self
155 157 else:
156 158 # certain ui instances exist outside the context of
157 159 # a repo, so just default to the last blackbox that
158 160 # was seen.
159 161 ui = lastui
160 162
161 163 if ui and ui._bbfp:
162 164 date = util.datestr(None, '%Y/%m/%d %H:%M:%S')
163 165 user = util.getuser()
164 166 pid = str(util.getpid())
165 167 formattedmsg = msg[0] % msg[1:]
166 168 rev = '(unknown)'
167 169 changed = ''
168 170 if ui._bbrepo:
169 171 ctx = ui._bbrepo[None]
170 172 parents = ctx.parents()
171 173 rev = ('+'.join([hex(p.node()) for p in parents]))
172 174 if (ui.configbool('blackbox', 'dirty', False) and (
173 175 any(ui._bbrepo.status()) or
174 176 any(ctx.sub(s).dirty() for s in ctx.substate)
175 177 )):
176 178 changed = '+'
179 if ui.configbool('blackbox', 'logsource', False):
180 src = ' [%s]' % event
181 else:
182 src = ''
177 183 try:
178 ui._bbwrite('%s %s @%s%s (%s)> %s',
179 date, user, rev, changed, pid, formattedmsg)
184 ui._bbwrite('%s %s @%s%s (%s)%s> %s',
185 date, user, rev, changed, pid, src, formattedmsg)
180 186 except IOError as err:
181 187 self.debug('warning: cannot write to blackbox.log: %s\n' %
182 188 err.strerror)
183 189 if not lastui or ui._bbrepo:
184 190 lastui = ui
185 191
186 192 def setrepo(self, repo):
187 193 self._bbfp = None
188 194 self._bbrepo = repo
189 195 self._bbvfs = repo.vfs
190 196
191 197 ui.__class__ = blackboxui
192 198 uimod.ui = blackboxui
193 199
194 200 def uisetup(ui):
195 201 wrapui(ui)
196 202
197 203 def reposetup(ui, repo):
198 204 # During 'hg pull' a httppeer repo is created to represent the remote repo.
199 205 # It doesn't have a .hg directory to put a blackbox in, so we don't do
200 206 # the blackbox setup for it.
201 207 if not repo.local():
202 208 return
203 209
204 210 if util.safehasattr(ui, 'setrepo'):
205 211 ui.setrepo(repo)
206 212
207 213 @command('^blackbox',
208 214 [('l', 'limit', 10, _('the number of events to show')),
209 215 ],
210 216 _('hg blackbox [OPTION]...'))
211 217 def blackbox(ui, repo, *revs, **opts):
212 218 '''view the recent repository events
213 219 '''
214 220
215 221 if not repo.vfs.exists('blackbox.log'):
216 222 return
217 223
218 224 limit = opts.get('limit')
219 225 fp = repo.vfs('blackbox.log', 'r')
220 226 lines = fp.read().split('\n')
221 227
222 228 count = 0
223 229 output = []
224 230 for line in reversed(lines):
225 231 if count >= limit:
226 232 break
227 233
228 234 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
229 235 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
230 236 count += 1
231 237 output.append(line)
232 238
233 239 ui.status('\n'.join(reversed(output)))
General Comments 0
You need to be logged in to leave comments. Login now