##// END OF EJS Templates
blackbox: resurrect recursion guard...
Yuya Nishihara -
r41029:49d48489 default
parent child Browse files
Show More
@@ -1,185 +1,198
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 [blackbox]
36 [blackbox]
37 # Include nanoseconds in log entries with %f (see Python function
37 # Include nanoseconds in log entries with %f (see Python function
38 # datetime.datetime.strftime)
38 # datetime.datetime.strftime)
39 date-format = '%Y-%m-%d @ %H:%M:%S.%f'
39 date-format = '%Y-%m-%d @ %H:%M:%S.%f'
40
40
41 """
41 """
42
42
43 from __future__ import absolute_import
43 from __future__ import absolute_import
44
44
45 import re
45 import re
46
46
47 from mercurial.i18n import _
47 from mercurial.i18n import _
48 from mercurial.node import hex
48 from mercurial.node import hex
49
49
50 from mercurial import (
50 from mercurial import (
51 encoding,
51 encoding,
52 loggingutil,
52 loggingutil,
53 registrar,
53 registrar,
54 )
54 )
55 from mercurial.utils import (
55 from mercurial.utils import (
56 dateutil,
56 dateutil,
57 procutil,
57 procutil,
58 )
58 )
59
59
60 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
60 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
61 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
61 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
62 # be specifying the version(s) of Mercurial they are tested with, or
62 # be specifying the version(s) of Mercurial they are tested with, or
63 # leave the attribute unspecified.
63 # leave the attribute unspecified.
64 testedwith = 'ships-with-hg-core'
64 testedwith = 'ships-with-hg-core'
65
65
66 cmdtable = {}
66 cmdtable = {}
67 command = registrar.command(cmdtable)
67 command = registrar.command(cmdtable)
68
68
69 configtable = {}
69 configtable = {}
70 configitem = registrar.configitem(configtable)
70 configitem = registrar.configitem(configtable)
71
71
72 configitem('blackbox', 'dirty',
72 configitem('blackbox', 'dirty',
73 default=False,
73 default=False,
74 )
74 )
75 configitem('blackbox', 'maxsize',
75 configitem('blackbox', 'maxsize',
76 default='1 MB',
76 default='1 MB',
77 )
77 )
78 configitem('blackbox', 'logsource',
78 configitem('blackbox', 'logsource',
79 default=False,
79 default=False,
80 )
80 )
81 configitem('blackbox', 'maxfiles',
81 configitem('blackbox', 'maxfiles',
82 default=7,
82 default=7,
83 )
83 )
84 configitem('blackbox', 'track',
84 configitem('blackbox', 'track',
85 default=lambda: ['*'],
85 default=lambda: ['*'],
86 )
86 )
87 configitem('blackbox', 'date-format',
87 configitem('blackbox', 'date-format',
88 default='%Y/%m/%d %H:%M:%S',
88 default='%Y/%m/%d %H:%M:%S',
89 )
89 )
90
90
91 _lastlogger = loggingutil.proxylogger()
91 _lastlogger = loggingutil.proxylogger()
92
92
93 class blackboxlogger(object):
93 class blackboxlogger(object):
94 def __init__(self, ui, repo):
94 def __init__(self, ui, repo):
95 self._repo = repo
95 self._repo = repo
96 self._trackedevents = set(ui.configlist('blackbox', 'track'))
96 self._trackedevents = set(ui.configlist('blackbox', 'track'))
97 self._maxfiles = ui.configint('blackbox', 'maxfiles')
97 self._maxfiles = ui.configint('blackbox', 'maxfiles')
98 self._maxsize = ui.configbytes('blackbox', 'maxsize')
98 self._maxsize = ui.configbytes('blackbox', 'maxsize')
99 self._inlog = False
99
100
100 def tracked(self, event):
101 def tracked(self, event):
101 return b'*' in self._trackedevents or event in self._trackedevents
102 return b'*' in self._trackedevents or event in self._trackedevents
102
103
103 def log(self, ui, event, msg, opts):
104 def log(self, ui, event, msg, opts):
105 # self._log() -> ctx.dirty() may create new subrepo instance, which
106 # ui is derived from baseui. So the recursion guard in ui.log()
107 # doesn't work as it's local to the ui instance.
108 if self._inlog:
109 return
110 self._inlog = True
111 try:
112 self._log(ui, event, msg, opts)
113 finally:
114 self._inlog = False
115
116 def _log(self, ui, event, msg, opts):
104 default = ui.configdate('devel', 'default-date')
117 default = ui.configdate('devel', 'default-date')
105 date = dateutil.datestr(default, ui.config('blackbox', 'date-format'))
118 date = dateutil.datestr(default, ui.config('blackbox', 'date-format'))
106 user = procutil.getuser()
119 user = procutil.getuser()
107 pid = '%d' % procutil.getpid()
120 pid = '%d' % procutil.getpid()
108 rev = '(unknown)'
121 rev = '(unknown)'
109 changed = ''
122 changed = ''
110 ctx = self._repo[None]
123 ctx = self._repo[None]
111 parents = ctx.parents()
124 parents = ctx.parents()
112 rev = ('+'.join([hex(p.node()) for p in parents]))
125 rev = ('+'.join([hex(p.node()) for p in parents]))
113 if (ui.configbool('blackbox', 'dirty') and
126 if (ui.configbool('blackbox', 'dirty') and
114 ctx.dirty(missing=True, merge=False, branch=False)):
127 ctx.dirty(missing=True, merge=False, branch=False)):
115 changed = '+'
128 changed = '+'
116 if ui.configbool('blackbox', 'logsource'):
129 if ui.configbool('blackbox', 'logsource'):
117 src = ' [%s]' % event
130 src = ' [%s]' % event
118 else:
131 else:
119 src = ''
132 src = ''
120 try:
133 try:
121 fmt = '%s %s @%s%s (%s)%s> %s'
134 fmt = '%s %s @%s%s (%s)%s> %s'
122 args = (date, user, rev, changed, pid, src, msg)
135 args = (date, user, rev, changed, pid, src, msg)
123 with loggingutil.openlogfile(
136 with loggingutil.openlogfile(
124 ui, self._repo.vfs, name='blackbox.log',
137 ui, self._repo.vfs, name='blackbox.log',
125 maxfiles=self._maxfiles, maxsize=self._maxsize) as fp:
138 maxfiles=self._maxfiles, maxsize=self._maxsize) as fp:
126 fp.write(fmt % args)
139 fp.write(fmt % args)
127 except (IOError, OSError) as err:
140 except (IOError, OSError) as err:
128 # deactivate this to avoid failed logging again
141 # deactivate this to avoid failed logging again
129 self._trackedevents.clear()
142 self._trackedevents.clear()
130 ui.debug('warning: cannot write to blackbox.log: %s\n' %
143 ui.debug('warning: cannot write to blackbox.log: %s\n' %
131 encoding.strtolocal(err.strerror))
144 encoding.strtolocal(err.strerror))
132 return
145 return
133 _lastlogger.logger = self
146 _lastlogger.logger = self
134
147
135 def uipopulate(ui):
148 def uipopulate(ui):
136 ui.setlogger(b'blackbox', _lastlogger)
149 ui.setlogger(b'blackbox', _lastlogger)
137
150
138 def reposetup(ui, repo):
151 def reposetup(ui, repo):
139 # During 'hg pull' a httppeer repo is created to represent the remote repo.
152 # During 'hg pull' a httppeer repo is created to represent the remote repo.
140 # It doesn't have a .hg directory to put a blackbox in, so we don't do
153 # It doesn't have a .hg directory to put a blackbox in, so we don't do
141 # the blackbox setup for it.
154 # the blackbox setup for it.
142 if not repo.local():
155 if not repo.local():
143 return
156 return
144
157
145 # Since blackbox.log is stored in the repo directory, the logger should be
158 # Since blackbox.log is stored in the repo directory, the logger should be
146 # instantiated per repository.
159 # instantiated per repository.
147 logger = blackboxlogger(ui, repo)
160 logger = blackboxlogger(ui, repo)
148 ui.setlogger(b'blackbox', logger)
161 ui.setlogger(b'blackbox', logger)
149
162
150 # Set _lastlogger even if ui.log is not called. This gives blackbox a
163 # Set _lastlogger even if ui.log is not called. This gives blackbox a
151 # fallback place to log
164 # fallback place to log
152 if _lastlogger.logger is None:
165 if _lastlogger.logger is None:
153 _lastlogger.logger = logger
166 _lastlogger.logger = logger
154
167
155 repo._wlockfreeprefix.add('blackbox.log')
168 repo._wlockfreeprefix.add('blackbox.log')
156
169
157 @command('blackbox',
170 @command('blackbox',
158 [('l', 'limit', 10, _('the number of events to show')),
171 [('l', 'limit', 10, _('the number of events to show')),
159 ],
172 ],
160 _('hg blackbox [OPTION]...'),
173 _('hg blackbox [OPTION]...'),
161 helpcategory=command.CATEGORY_MAINTENANCE,
174 helpcategory=command.CATEGORY_MAINTENANCE,
162 helpbasic=True)
175 helpbasic=True)
163 def blackbox(ui, repo, *revs, **opts):
176 def blackbox(ui, repo, *revs, **opts):
164 '''view the recent repository events
177 '''view the recent repository events
165 '''
178 '''
166
179
167 if not repo.vfs.exists('blackbox.log'):
180 if not repo.vfs.exists('blackbox.log'):
168 return
181 return
169
182
170 limit = opts.get(r'limit')
183 limit = opts.get(r'limit')
171 fp = repo.vfs('blackbox.log', 'r')
184 fp = repo.vfs('blackbox.log', 'r')
172 lines = fp.read().split('\n')
185 lines = fp.read().split('\n')
173
186
174 count = 0
187 count = 0
175 output = []
188 output = []
176 for line in reversed(lines):
189 for line in reversed(lines):
177 if count >= limit:
190 if count >= limit:
178 break
191 break
179
192
180 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
193 # count the commands by matching lines like: 2013/01/23 19:13:36 root>
181 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
194 if re.match('^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} .*> .*', line):
182 count += 1
195 count += 1
183 output.append(line)
196 output.append(line)
184
197
185 ui.status('\n'.join(reversed(output)))
198 ui.status('\n'.join(reversed(output)))
General Comments 0
You need to be logged in to leave comments. Login now