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