##// END OF EJS Templates
blackbox: properly replace ui class...
timeless -
r28248:851c41a2 default
parent child Browse files
Show More
@@ -1,220 +1,243 b''
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 If you want to record whether the repository is dirty (a `+` sign, as you'd
15 15 get when using :hg:`id`), you can set the blackbox.dirty config key.
16 16
17 17 Examples::
18 18
19 19 [blackbox]
20 20 track = *
21 21 dirty = True
22 22
23 23 [blackbox]
24 24 track = command, commandfinish, commandexception, exthook, pythonhook
25 25
26 26 [blackbox]
27 27 track = incoming
28 28
29 29 [blackbox]
30 30 # limit the size of a log file
31 31 maxsize = 1.5 MB
32 32 # rotate up to N log files when the current one gets too big
33 33 maxfiles = 3
34 34
35 35 """
36 36
37 37 from __future__ import absolute_import
38 38
39 39 import errno
40 40 import re
41 41
42 42 from mercurial.i18n import _
43 43 from mercurial.node import hex
44 44
45 45 from mercurial import (
46 46 cmdutil,
47 ui as uimod,
47 48 util,
48 49 )
49 50
50 51 cmdtable = {}
51 52 command = cmdutil.command(cmdtable)
52 53 # Note for extension authors: ONLY specify testedwith = 'internal' for
53 54 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
54 55 # be specifying the version(s) of Mercurial they are tested with, or
55 56 # leave the attribute unspecified.
56 57 testedwith = 'internal'
57 58 lastui = None
58 59
59 60 filehandles = {}
60 61
61 62 def _openlog(vfs):
62 63 path = vfs.join('blackbox.log')
63 64 if path in filehandles:
64 65 return filehandles[path]
65 66 filehandles[path] = fp = vfs('blackbox.log', 'a')
66 67 return fp
67 68
68 69 def _closelog(vfs):
69 70 path = vfs.join('blackbox.log')
70 71 fp = filehandles[path]
71 72 del filehandles[path]
72 73 fp.close()
73 74
74 75 def hexfn(node):
75 76 if node is None:
76 77 return None
77 78 else:
78 79 return hex(node)
79 80
80 81 def wrapui(ui):
81 82 class blackboxui(ui.__class__):
83 def __init__(self, src=None):
84 super(blackboxui, self).__init__(src)
85 if src is None:
86 self._partialinit()
87 else:
88 self._bbfp = src._bbfp
89 self._bbrepo = src._bbrepo
90 self._bbvfs = src._bbvfs
91
92 def _partialinit(self):
93 if util.safehasattr(self, '_bbvfs'):
94 return
95 self._bbfp = None
96 self._bbrepo = None
97 self._bbvfs = None
98
99 def copy(self):
100 self._partialinit()
101 return self.__class__(self)
102
82 103 @util.propertycache
83 104 def track(self):
84 105 return self.configlist('blackbox', 'track', ['*'])
85 106
86 107 def _openlogfile(self):
87 108 def rotate(oldpath, newpath):
88 109 try:
89 110 self._bbvfs.unlink(newpath)
90 111 except OSError as err:
91 112 if err.errno != errno.ENOENT:
92 113 self.debug("warning: cannot remove '%s': %s\n" %
93 114 (newpath, err.strerror))
94 115 try:
95 116 if newpath:
96 117 self._bbvfs.rename(oldpath, newpath)
97 118 except OSError as err:
98 119 if err.errno != errno.ENOENT:
99 120 self.debug("warning: cannot rename '%s' to '%s': %s\n" %
100 121 (newpath, oldpath, err.strerror))
101 122
102 123 fp = _openlog(self._bbvfs)
103 124 maxsize = self.configbytes('blackbox', 'maxsize', 1048576)
104 125 if maxsize > 0:
105 126 st = self._bbvfs.fstat(fp)
106 127 if st.st_size >= maxsize:
107 128 path = fp.name
108 129 _closelog(self._bbvfs)
109 130 maxfiles = self.configint('blackbox', 'maxfiles', 7)
110 131 for i in xrange(maxfiles - 1, 1, -1):
111 132 rotate(oldpath='%s.%d' % (path, i - 1),
112 133 newpath='%s.%d' % (path, i))
113 134 rotate(oldpath=path,
114 135 newpath=maxfiles > 0 and path + '.1')
115 136 fp = _openlog(self._bbvfs)
116 137 return fp
117 138
118 139 def _bbwrite(self, fmt, *args):
119 140 self._bbfp.write(fmt % args)
120 141 self._bbfp.flush()
121 142
122 143 def log(self, event, *msg, **opts):
123 144 global lastui
124 145 super(blackboxui, self).log(event, *msg, **opts)
146 self._partialinit()
125 147
126 148 if not '*' in self.track and not event in self.track:
127 149 return
128 150
129 if util.safehasattr(self, '_bbfp'):
151 if self._bbfp:
130 152 ui = self
131 elif util.safehasattr(self, '_bbvfs'):
153 elif self._bbvfs:
132 154 try:
133 155 self._bbfp = self._openlogfile()
134 156 except (IOError, OSError) as err:
135 157 self.debug('warning: cannot write to blackbox.log: %s\n' %
136 158 err.strerror)
137 159 del self._bbvfs
138 160 self._bbfp = None
139 161 ui = self
140 162 else:
141 163 # certain ui instances exist outside the context of
142 164 # a repo, so just default to the last blackbox that
143 165 # was seen.
144 166 ui = lastui
145 167
146 if (util.safehasattr(ui, '_bbfp') and
147 ui._bbfp is not None):
168 if ui and ui._bbfp:
148 169 date = util.datestr(None, '%Y/%m/%d %H:%M:%S')
149 170 user = util.getuser()
150 171 pid = str(util.getpid())
151 172 formattedmsg = msg[0] % msg[1:]
152 173 rev = '(unknown)'
153 174 changed = ''
154 if util.safehasattr(ui, '_bbrepo'):
175 if ui._bbrepo:
155 176 ctx = ui._bbrepo[None]
156 177 if ctx.rev() is not None:
157 178 rev = hexfn(ctx.node())
158 179 else:
159 180 parents = ctx.parents()
160 181 rev = ('+'.join([hexfn(p.node()) for p in parents]))
161 182 if (ui.configbool('blackbox', 'dirty', False) and (
162 183 any(ui._bbrepo.status()) or
163 184 any(ctx.sub(s).dirty() for s in ctx.substate)
164 185 )):
165 186 changed = '+'
166 187 try:
167 188 ui._bbwrite('%s %s @%s%s (%s)> %s',
168 189 date, user, rev, changed, pid, formattedmsg)
169 190 except IOError as err:
170 191 self.debug('warning: cannot write to blackbox.log: %s\n' %
171 192 err.strerror)
172 if not lastui or util.safehasattr(ui, '_bbrepo'):
193 if not lastui or ui._bbrepo:
173 194 lastui = ui
174 195
175 196 def setrepo(self, repo):
197 self._bbfp = None
198 self._bbrepo = repo
176 199 self._bbvfs = repo.vfs
177 self._bbrepo = repo
178 200
179 201 ui.__class__ = blackboxui
202 uimod.ui = blackboxui
180 203
181 204 def uisetup(ui):
182 205 wrapui(ui)
183 206
184 207 def reposetup(ui, repo):
185 208 # During 'hg pull' a httppeer repo is created to represent the remote repo.
186 209 # It doesn't have a .hg directory to put a blackbox in, so we don't do
187 210 # the blackbox setup for it.
188 211 if not repo.local():
189 212 return
190 213
191 214 if util.safehasattr(ui, 'setrepo'):
192 215 ui.setrepo(repo)
193 216
194 217 @command('^blackbox',
195 218 [('l', 'limit', 10, _('the number of events to show')),
196 219 ],
197 220 _('hg blackbox [OPTION]...'))
198 221 def blackbox(ui, repo, *revs, **opts):
199 222 '''view the recent repository events
200 223 '''
201 224
202 225 if not repo.vfs.exists('blackbox.log'):
203 226 return
204 227
205 228 limit = opts.get('limit')
206 229 fp = repo.vfs('blackbox.log', 'r')
207 230 lines = fp.read().split('\n')
208 231
209 232 count = 0
210 233 output = []
211 234 for line in reversed(lines):
212 235 if count >= limit:
213 236 break
214 237
215 238 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
216 239 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
217 240 count += 1
218 241 output.append(line)
219 242
220 243 ui.status('\n'.join(reversed(output)))
@@ -1,186 +1,193 b''
1 1 setup
2 2 $ cat >> $HGRCPATH <<EOF
3 3 > [extensions]
4 4 > blackbox=
5 5 > mock=$TESTDIR/mockblackbox.py
6 6 > mq=
7 7 > EOF
8 8 $ hg init blackboxtest
9 9 $ cd blackboxtest
10 10
11 11 command, exit codes, and duration
12 12
13 13 $ echo a > a
14 14 $ hg add a
15 15 $ hg blackbox --config blackbox.dirty=True
16 16 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
17 17 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob)
18 18 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox
19 19
20 20 incoming change tracking
21 21
22 22 create two heads to verify that we only see one change in the log later
23 23 $ hg commit -ma
24 24 $ hg up null
25 25 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
26 26 $ echo b > b
27 27 $ hg commit -Amb
28 28 adding b
29 29 created new head
30 30
31 31 clone, commit, pull
32 32 $ hg clone . ../blackboxtest2
33 33 updating to branch default
34 34 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
35 35 $ echo c > c
36 36 $ hg commit -Amc
37 37 adding c
38 38 $ cd ../blackboxtest2
39 39 $ hg pull
40 40 pulling from $TESTTMP/blackboxtest (glob)
41 41 searching for changes
42 42 adding changesets
43 43 adding manifests
44 44 adding file changes
45 45 added 1 changesets with 1 changes to 1 files
46 46 (run 'hg update' to get a working copy)
47 47 $ hg blackbox -l 6
48 48 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull
49 49 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated served branch cache in * seconds (glob)
50 50 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote served branch cache with 1 labels and 2 nodes
51 51 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> 1 incoming changes - new heads: d02f48003e62
52 52 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull exited 0 after * seconds (glob)
53 53 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6
54 54
55 55 we must not cause a failure if we cannot write to the log
56 56
57 57 $ hg rollback
58 58 repository tip rolled back to revision 1 (undo pull)
59 59
60 60 $ mv .hg/blackbox.log .hg/blackbox.log-
61 61 $ mkdir .hg/blackbox.log
62 62 $ hg --debug incoming
63 63 warning: cannot write to blackbox.log: * (glob)
64 64 comparing with $TESTTMP/blackboxtest (glob)
65 65 query 1; heads
66 66 searching for changes
67 67 all local heads known remotely
68 68 changeset: 2:d02f48003e62c24e2659d97d30f2a83abe5d5d51
69 69 tag: tip
70 70 phase: draft
71 71 parent: 1:6563da9dcf87b1949716e38ff3e3dfaa3198eb06
72 72 parent: -1:0000000000000000000000000000000000000000
73 73 manifest: 2:ab9d46b053ebf45b7996f2922b9893ff4b63d892
74 74 user: test
75 75 date: Thu Jan 01 00:00:00 1970 +0000
76 76 files+: c
77 77 extra: branch=default
78 78 description:
79 79 c
80 80
81 81
82 82 $ hg pull
83 83 pulling from $TESTTMP/blackboxtest (glob)
84 84 searching for changes
85 85 adding changesets
86 86 adding manifests
87 87 adding file changes
88 88 added 1 changesets with 1 changes to 1 files
89 89 (run 'hg update' to get a working copy)
90 90
91 91 a failure reading from the log is fatal
92 92
93 93 $ hg blackbox -l 3
94 94 abort: *$TESTTMP/blackboxtest2/.hg/blackbox.log* (glob)
95 95 [255]
96 96
97 97 $ rmdir .hg/blackbox.log
98 98 $ mv .hg/blackbox.log- .hg/blackbox.log
99 99
100 100 backup bundles get logged
101 101
102 102 $ touch d
103 103 $ hg commit -Amd
104 104 adding d
105 105 created new head
106 106 $ hg strip tip
107 107 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
108 108 saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/*-backup.hg (glob)
109 109 $ hg blackbox -l 6
110 110 1970/01/01 00:00:00 bob @73f6ee326b27d820b0472f1a825e3a50f3dc489b (5000)> strip tip
111 111 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/73f6ee326b27-7612e004-backup.hg
112 112 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> updated base branch cache in * seconds (glob)
113 113 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> wrote base branch cache with 1 labels and 2 nodes
114 114 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> strip tip exited 0 after * seconds (glob)
115 115 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> blackbox -l 6
116 116
117 117 extension and python hooks - use the eol extension for a pythonhook
118 118
119 119 $ echo '[extensions]' >> .hg/hgrc
120 120 $ echo 'eol=' >> .hg/hgrc
121 121 $ echo '[hooks]' >> .hg/hgrc
122 122 $ echo 'update = echo hooked' >> .hg/hgrc
123 123 $ hg update
124 124 hooked
125 125 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
126 126 1 other heads for branch "default"
127 127 $ hg blackbox -l 6
128 128 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update
129 129 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> writing .hg/cache/tags2-visible with 0 tags
130 130 1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob)
131 131 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> exthook-update: echo hooked finished in * seconds (glob)
132 132 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> update exited 0 after * seconds (glob)
133 133 1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> blackbox -l 6
134 134
135 135 log rotation
136 136
137 137 $ echo '[blackbox]' >> .hg/hgrc
138 138 $ echo 'maxsize = 20 b' >> .hg/hgrc
139 139 $ echo 'maxfiles = 3' >> .hg/hgrc
140 140 $ hg status
141 141 $ hg status
142 142 $ hg status
143 143 $ hg tip -q
144 144 2:d02f48003e62
145 145 $ ls .hg/blackbox.log*
146 146 .hg/blackbox.log
147 147 .hg/blackbox.log.1
148 148 .hg/blackbox.log.2
149 149 $ cd ..
150 150
151 151 $ hg init blackboxtest3
152 152 $ cd blackboxtest3
153 153 $ hg blackbox
154 154 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
155 155 $ mv .hg/blackbox.log .hg/blackbox.log-
156 156 $ mkdir .hg/blackbox.log
157 157 $ sed -e 's/\(.*test1.*\)/#\1/; s#\(.*commit2.*\)#os.rmdir(".hg/blackbox.log")\nos.rename(".hg/blackbox.log-", ".hg/blackbox.log")\n\1#' $TESTDIR/test-dispatch.py > ../test-dispatch.py
158 158 $ python ../test-dispatch.py
159 159 running: add foo
160 160 result: 0
161 161 running: commit -m commit1 -d 2000-01-01 foo
162 162 result: None
163 163 running: commit -m commit2 -d 2000-01-02 foo
164 164 result: None
165 165 running: log -r 0
166 166 changeset: 0:0e4634943879
167 167 user: test
168 168 date: Sat Jan 01 00:00:00 2000 +0000
169 169 summary: commit1
170 170
171 171 result: None
172 172 running: log -r tip
173 173 changeset: 1:45589e459b2e
174 174 tag: tip
175 175 user: test
176 176 date: Sun Jan 02 00:00:00 2000 +0000
177 177 summary: commit2
178 178
179 179 result: None
180 180 $ hg blackbox
181 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
182 1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox exited 0 after * seconds (glob)
181 1970/01/01 00:00:00 bob @0e46349438790c460c5c9f7546bfcd39b267bbd2 (5000)> commit -m commit2 -d 2000-01-02 foo
182 1970/01/01 00:00:00 bob @0e46349438790c460c5c9f7546bfcd39b267bbd2 (5000)> updated served branch cache in * seconds (glob)
183 1970/01/01 00:00:00 bob @0e46349438790c460c5c9f7546bfcd39b267bbd2 (5000)> wrote served branch cache with 1 labels and 1 nodes
184 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> commit -m commit2 -d 2000-01-02 foo exited 0 after * seconds (glob)
185 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r 0
186 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> writing .hg/cache/tags2-visible with 0 tags
187 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r 0 exited 0 after * seconds (glob)
188 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r tip
189 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> log -r tip exited 0 after * seconds (glob)
183 190 1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> blackbox
184 191
185 192 cleanup
186 193 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now