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