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