##// END OF EJS Templates
blackbox: automatically rotate log files...
Bryan O'Sullivan -
r19066:2cad301a default
parent child Browse files
Show More
@@ -1,119 +1,157 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 Examples:
13 Examples:
14
14
15 [blackbox]
15 [blackbox]
16 track = *
16 track = *
17
17
18 [blackbox]
18 [blackbox]
19 track = command, commandfinish, commandexception, exthook, pythonhook
19 track = command, commandfinish, commandexception, exthook, pythonhook
20
20
21 [blackbox]
21 [blackbox]
22 track = incoming
22 track = incoming
23
23
24 [blackbox]
25 # limit the size of a log file
26 maxsize = 1.5 MB
27 # rotate up to N log files when the current one gets too big
28 maxfiles = 3
29
24 """
30 """
25
31
26 from mercurial import util, cmdutil
32 from mercurial import util, cmdutil
27 from mercurial.i18n import _
33 from mercurial.i18n import _
28 import os, re
34 import errno, os, re
29
35
30 cmdtable = {}
36 cmdtable = {}
31 command = cmdutil.command(cmdtable)
37 command = cmdutil.command(cmdtable)
32 testedwith = 'internal'
38 testedwith = 'internal'
33 lastblackbox = None
39 lastblackbox = None
34
40
35 def wrapui(ui):
41 def wrapui(ui):
36 class blackboxui(ui.__class__):
42 class blackboxui(ui.__class__):
37 @util.propertycache
43 @util.propertycache
38 def track(self):
44 def track(self):
39 return self.configlist('blackbox', 'track', ['*'])
45 return self.configlist('blackbox', 'track', ['*'])
40
46
47 def _openlogfile(self):
48 def rotate(oldpath, newpath):
49 try:
50 os.unlink(newpath)
51 except OSError, err:
52 if err.errno != errno.ENOENT:
53 self.debug("warning: cannot remove '%s': %s\n" %
54 (newpath, err.strerror))
55 try:
56 if newpath:
57 os.rename(oldpath, newpath)
58 except OSError, err:
59 if err.errno != errno.ENOENT:
60 self.debug("warning: cannot rename '%s' to '%s': %s\n" %
61 (newpath, oldpath, err.strerror))
62
63 fp = self._bbopener('blackbox.log', 'a')
64 maxsize = self.configbytes('blackbox', 'maxsize', 1048576)
65 if maxsize > 0:
66 st = os.fstat(fp.fileno())
67 if st.st_size >= maxsize:
68 path = fp.name
69 fp.close()
70 maxfiles = self.configint('blackbox', 'maxfiles', 7)
71 for i in xrange(maxfiles - 1, 1, -1):
72 rotate(oldpath='%s.%d' % (path, i - 1),
73 newpath='%s.%d' % (path, i))
74 rotate(oldpath=path,
75 newpath=maxfiles > 0 and path + '.1')
76 fp = self._bbopener('blackbox.log', 'a')
77 return fp
78
41 def log(self, event, *msg, **opts):
79 def log(self, event, *msg, **opts):
42 global lastblackbox
80 global lastblackbox
43 super(blackboxui, self).log(event, *msg, **opts)
81 super(blackboxui, self).log(event, *msg, **opts)
44
82
45 if not '*' in self.track and not event in self.track:
83 if not '*' in self.track and not event in self.track:
46 return
84 return
47
85
48 if util.safehasattr(self, '_blackbox'):
86 if util.safehasattr(self, '_blackbox'):
49 blackbox = self._blackbox
87 blackbox = self._blackbox
50 elif util.safehasattr(self, '_bbopener'):
88 elif util.safehasattr(self, '_bbopener'):
51 try:
89 try:
52 self._blackbox = self._bbopener('blackbox.log', 'a')
90 self._blackbox = self._openlogfile()
53 except (IOError, OSError), err:
91 except (IOError, OSError), err:
54 self.debug('warning: cannot write to blackbox.log: %s\n' %
92 self.debug('warning: cannot write to blackbox.log: %s\n' %
55 err.strerror)
93 err.strerror)
56 del self._bbopener
94 del self._bbopener
57 self._blackbox = None
95 self._blackbox = None
58 blackbox = self._blackbox
96 blackbox = self._blackbox
59 else:
97 else:
60 # certain ui instances exist outside the context of
98 # certain ui instances exist outside the context of
61 # a repo, so just default to the last blackbox that
99 # a repo, so just default to the last blackbox that
62 # was seen.
100 # was seen.
63 blackbox = lastblackbox
101 blackbox = lastblackbox
64
102
65 if blackbox:
103 if blackbox:
66 date = util.datestr(None, '%Y/%m/%d %H:%M:%S')
104 date = util.datestr(None, '%Y/%m/%d %H:%M:%S')
67 user = util.getuser()
105 user = util.getuser()
68 formattedmsg = msg[0] % msg[1:]
106 formattedmsg = msg[0] % msg[1:]
69 try:
107 try:
70 blackbox.write('%s %s> %s' % (date, user, formattedmsg))
108 blackbox.write('%s %s> %s' % (date, user, formattedmsg))
71 except IOError, err:
109 except IOError, err:
72 self.debug('warning: cannot write to blackbox.log: %s\n' %
110 self.debug('warning: cannot write to blackbox.log: %s\n' %
73 err.strerror)
111 err.strerror)
74 lastblackbox = blackbox
112 lastblackbox = blackbox
75
113
76 def setrepo(self, repo):
114 def setrepo(self, repo):
77 self._bbopener = repo.opener
115 self._bbopener = repo.opener
78
116
79 ui.__class__ = blackboxui
117 ui.__class__ = blackboxui
80
118
81 def uisetup(ui):
119 def uisetup(ui):
82 wrapui(ui)
120 wrapui(ui)
83
121
84 def reposetup(ui, repo):
122 def reposetup(ui, repo):
85 # During 'hg pull' a httppeer repo is created to represent the remote repo.
123 # During 'hg pull' a httppeer repo is created to represent the remote repo.
86 # It doesn't have a .hg directory to put a blackbox in, so we don't do
124 # It doesn't have a .hg directory to put a blackbox in, so we don't do
87 # the blackbox setup for it.
125 # the blackbox setup for it.
88 if not repo.local():
126 if not repo.local():
89 return
127 return
90
128
91 ui.setrepo(repo)
129 ui.setrepo(repo)
92
130
93 @command('^blackbox',
131 @command('^blackbox',
94 [('l', 'limit', 10, _('the number of events to show')),
132 [('l', 'limit', 10, _('the number of events to show')),
95 ],
133 ],
96 _('hg blackbox [OPTION]...'))
134 _('hg blackbox [OPTION]...'))
97 def blackbox(ui, repo, *revs, **opts):
135 def blackbox(ui, repo, *revs, **opts):
98 '''view the recent repository events
136 '''view the recent repository events
99 '''
137 '''
100
138
101 if not os.path.exists(repo.join('blackbox.log')):
139 if not os.path.exists(repo.join('blackbox.log')):
102 return
140 return
103
141
104 limit = opts.get('limit')
142 limit = opts.get('limit')
105 blackbox = repo.opener('blackbox.log', 'r')
143 blackbox = repo.opener('blackbox.log', 'r')
106 lines = blackbox.read().split('\n')
144 lines = blackbox.read().split('\n')
107
145
108 count = 0
146 count = 0
109 output = []
147 output = []
110 for line in reversed(lines):
148 for line in reversed(lines):
111 if count >= limit:
149 if count >= limit:
112 break
150 break
113
151
114 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
152 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
115 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
153 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
116 count += 1
154 count += 1
117 output.append(line)
155 output.append(line)
118
156
119 ui.status('\n'.join(reversed(output)))
157 ui.status('\n'.join(reversed(output)))
@@ -1,135 +1,150 b''
1 setup
1 setup
2 $ cat > mock.py <<EOF
2 $ cat > mock.py <<EOF
3 > from mercurial import util
3 > from mercurial import util
4 >
4 >
5 > def makedate():
5 > def makedate():
6 > return 0, 0
6 > return 0, 0
7 > def getuser():
7 > def getuser():
8 > return 'bob'
8 > return 'bob'
9 > # mock the date and user apis so the output is always the same
9 > # mock the date and user apis so the output is always the same
10 > def uisetup(ui):
10 > def uisetup(ui):
11 > util.makedate = makedate
11 > util.makedate = makedate
12 > util.getuser = getuser
12 > util.getuser = getuser
13 > EOF
13 > EOF
14 $ cat >> $HGRCPATH <<EOF
14 $ cat >> $HGRCPATH <<EOF
15 > [extensions]
15 > [extensions]
16 > blackbox=
16 > blackbox=
17 > mock=`pwd`/mock.py
17 > mock=`pwd`/mock.py
18 > mq=
18 > mq=
19 > EOF
19 > EOF
20 $ hg init blackboxtest
20 $ hg init blackboxtest
21 $ cd blackboxtest
21 $ cd blackboxtest
22
22
23 command, exit codes, and duration
23 command, exit codes, and duration
24
24
25 $ echo a > a
25 $ echo a > a
26 $ hg add a
26 $ hg add a
27 $ hg blackbox
27 $ hg blackbox
28 1970/01/01 00:00:00 bob> add a
28 1970/01/01 00:00:00 bob> add a
29 1970/01/01 00:00:00 bob> add exited 0 after * seconds (glob)
29 1970/01/01 00:00:00 bob> add exited 0 after * seconds (glob)
30
30
31 incoming change tracking
31 incoming change tracking
32
32
33 create two heads to verify that we only see one change in the log later
33 create two heads to verify that we only see one change in the log later
34 $ hg commit -ma
34 $ hg commit -ma
35 $ hg up null
35 $ hg up null
36 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
36 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
37 $ echo b > b
37 $ echo b > b
38 $ hg commit -Amb
38 $ hg commit -Amb
39 adding b
39 adding b
40 created new head
40 created new head
41
41
42 clone, commit, pull
42 clone, commit, pull
43 $ hg clone . ../blackboxtest2
43 $ hg clone . ../blackboxtest2
44 updating to branch default
44 updating to branch default
45 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 $ echo c > c
46 $ echo c > c
47 $ hg commit -Amc
47 $ hg commit -Amc
48 adding c
48 adding c
49 $ cd ../blackboxtest2
49 $ cd ../blackboxtest2
50 $ hg pull
50 $ hg pull
51 pulling from $TESTTMP/blackboxtest (glob)
51 pulling from $TESTTMP/blackboxtest (glob)
52 searching for changes
52 searching for changes
53 adding changesets
53 adding changesets
54 adding manifests
54 adding manifests
55 adding file changes
55 adding file changes
56 added 1 changesets with 1 changes to 1 files
56 added 1 changesets with 1 changes to 1 files
57 (run 'hg update' to get a working copy)
57 (run 'hg update' to get a working copy)
58 $ hg blackbox -l 3
58 $ hg blackbox -l 3
59 1970/01/01 00:00:00 bob> pull
59 1970/01/01 00:00:00 bob> pull
60 1970/01/01 00:00:00 bob> 1 incoming changes - new heads: d02f48003e62
60 1970/01/01 00:00:00 bob> 1 incoming changes - new heads: d02f48003e62
61 1970/01/01 00:00:00 bob> pull exited None after * seconds (glob)
61 1970/01/01 00:00:00 bob> pull exited None after * seconds (glob)
62
62
63 we must not cause a failure if we cannot write to the log
63 we must not cause a failure if we cannot write to the log
64
64
65 $ hg rollback
65 $ hg rollback
66 repository tip rolled back to revision 1 (undo pull)
66 repository tip rolled back to revision 1 (undo pull)
67 $ chmod 000 .hg/blackbox.log
67 $ chmod 000 .hg/blackbox.log
68 $ hg --debug incoming
68 $ hg --debug incoming
69 warning: cannot write to blackbox.log: Permission denied
69 warning: cannot write to blackbox.log: Permission denied
70 comparing with $TESTTMP/blackboxtest (glob)
70 comparing with $TESTTMP/blackboxtest (glob)
71 query 1; heads
71 query 1; heads
72 searching for changes
72 searching for changes
73 all local heads known remotely
73 all local heads known remotely
74 changeset: 2:d02f48003e62c24e2659d97d30f2a83abe5d5d51
74 changeset: 2:d02f48003e62c24e2659d97d30f2a83abe5d5d51
75 tag: tip
75 tag: tip
76 phase: draft
76 phase: draft
77 parent: 1:6563da9dcf87b1949716e38ff3e3dfaa3198eb06
77 parent: 1:6563da9dcf87b1949716e38ff3e3dfaa3198eb06
78 parent: -1:0000000000000000000000000000000000000000
78 parent: -1:0000000000000000000000000000000000000000
79 manifest: 2:ab9d46b053ebf45b7996f2922b9893ff4b63d892
79 manifest: 2:ab9d46b053ebf45b7996f2922b9893ff4b63d892
80 user: test
80 user: test
81 date: Thu Jan 01 00:00:00 1970 +0000
81 date: Thu Jan 01 00:00:00 1970 +0000
82 files+: c
82 files+: c
83 extra: branch=default
83 extra: branch=default
84 description:
84 description:
85 c
85 c
86
86
87
87
88 $ hg pull
88 $ hg pull
89 pulling from $TESTTMP/blackboxtest (glob)
89 pulling from $TESTTMP/blackboxtest (glob)
90 searching for changes
90 searching for changes
91 adding changesets
91 adding changesets
92 adding manifests
92 adding manifests
93 adding file changes
93 adding file changes
94 added 1 changesets with 1 changes to 1 files
94 added 1 changesets with 1 changes to 1 files
95 (run 'hg update' to get a working copy)
95 (run 'hg update' to get a working copy)
96
96
97 a failure reading from the log is fine
97 a failure reading from the log is fine
98
98
99 $ hg blackbox -l 3
99 $ hg blackbox -l 3
100 abort: Permission denied: $TESTTMP/blackboxtest2/.hg/blackbox.log
100 abort: Permission denied: $TESTTMP/blackboxtest2/.hg/blackbox.log
101 [255]
101 [255]
102
102
103 $ chmod 600 .hg/blackbox.log
103 $ chmod 600 .hg/blackbox.log
104
104
105 backup bundles get logged
105 backup bundles get logged
106
106
107 $ touch d
107 $ touch d
108 $ hg commit -Amd
108 $ hg commit -Amd
109 adding d
109 adding d
110 created new head
110 created new head
111 $ hg strip tip
111 $ hg strip tip
112 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
112 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
113 saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/*-backup.hg (glob)
113 saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/*-backup.hg (glob)
114 $ hg blackbox -l 3
114 $ hg blackbox -l 3
115 1970/01/01 00:00:00 bob> strip tip
115 1970/01/01 00:00:00 bob> strip tip
116 1970/01/01 00:00:00 bob> saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/*-backup.hg (glob)
116 1970/01/01 00:00:00 bob> saved backup bundle to $TESTTMP/blackboxtest2/.hg/strip-backup/*-backup.hg (glob)
117 1970/01/01 00:00:00 bob> strip exited 0 after * seconds (glob)
117 1970/01/01 00:00:00 bob> strip exited 0 after * seconds (glob)
118
118
119 extension and python hooks - use the eol extension for a pythonhook
119 extension and python hooks - use the eol extension for a pythonhook
120
120
121 $ echo '[extensions]' >> .hg/hgrc
121 $ echo '[extensions]' >> .hg/hgrc
122 $ echo 'eol=' >> .hg/hgrc
122 $ echo 'eol=' >> .hg/hgrc
123 $ echo '[hooks]' >> .hg/hgrc
123 $ echo '[hooks]' >> .hg/hgrc
124 $ echo 'update = echo hooked' >> .hg/hgrc
124 $ echo 'update = echo hooked' >> .hg/hgrc
125 $ hg update
125 $ hg update
126 hooked
126 hooked
127 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
127 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
128 $ hg blackbox -l 4
128 $ hg blackbox -l 4
129 1970/01/01 00:00:00 bob> update
129 1970/01/01 00:00:00 bob> update
130 1970/01/01 00:00:00 bob> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob)
130 1970/01/01 00:00:00 bob> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob)
131 1970/01/01 00:00:00 bob> exthook-update: echo hooked finished in * seconds (glob)
131 1970/01/01 00:00:00 bob> exthook-update: echo hooked finished in * seconds (glob)
132 1970/01/01 00:00:00 bob> update exited False after * seconds (glob)
132 1970/01/01 00:00:00 bob> update exited False after * seconds (glob)
133
133
134 log rotation
135
136 $ echo '[blackbox]' >> .hg/hgrc
137 $ echo 'maxsize = 20 b' >> .hg/hgrc
138 $ echo 'maxfiles = 3' >> .hg/hgrc
139 $ hg status
140 $ hg status
141 $ hg status
142 $ hg tip -q
143 2:d02f48003e62
144 $ ls .hg/blackbox.log*
145 .hg/blackbox.log
146 .hg/blackbox.log.1
147 .hg/blackbox.log.2
148
134 cleanup
149 cleanup
135 $ cd ..
150 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now