##// END OF EJS Templates
loggingutil: extract openlogfile() and proxylogger to new module...
Yuya Nishihara -
r40830:03127e58 default
parent child Browse files
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 maxsize=self._maxsize) as fp:
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 mercurial.i18n import _
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