From patchwork Fri Apr 19 18:59:27 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [3,of,3,v4] blackbox: automatically rotate log files From: Bryan O'Sullivan X-Patchwork-Id: 1459 Message-Id: <555a784013830b33b10d.1366397967@australite.local> To: mercurial-devel@selenic.com Date: Fri, 19 Apr 2013 11:59:27 -0700 # HG changeset patch # User Bryan O'Sullivan # Date 1366397932 25200 # Fri Apr 19 11:58:52 2013 -0700 # Node ID 555a784013830b33b10d53878b204c278d4967bf # Parent 1a7d9ff79aad0ada89da06d1c9f33d8fb1046c91 blackbox: automatically rotate log files When enabled, log rotation bounds the amount of space consumed by the blackbox log. This becomes important in cases where there are lots of busy repositories managed by humans and automation on many machines. This is enabled by default, with limits of 1MB (about 15,000 lines of output) per log file and 7 log files (1 current, 6 historical). (In large deployments, we cannot reasonably track all the repos where blackbox logs need to be managed, so it is safer to have blackbox manage its own logs than to delegate responsibility to an external tool such as logrotate.) This change adds two configuration keys: * blackbox.maxsize is the maximum allowable size of the current log (zero or negative for no limit) * blackbox.maxfiles is the number of log files to maintain diff --git a/hgext/blackbox.py b/hgext/blackbox.py --- a/hgext/blackbox.py +++ b/hgext/blackbox.py @@ -21,11 +21,17 @@ Examples: [blackbox] track = incoming + [blackbox] + # limit the size of a log file + maxsize = 1.5MB + # rotate up to N log files when the current one gets too big + maxfiles = 3 + """ from mercurial import util, cmdutil from mercurial.i18n import _ -import os, re +import errno, os, re cmdtable = {} command = cmdutil.command(cmdtable) @@ -38,6 +44,38 @@ def wrapui(ui): def track(self): return self.configlist('blackbox', 'track', ['*']) + def _openlogfile(self): + def rotate(oldpath, newpath): + try: + os.unlink(newpath) + except OSError, err: + if err.errno != errno.ENOENT: + self.debug("warning: cannot remove '%s': %s\n" % + (newpath, err.strerror)) + try: + if newpath: + os.rename(oldpath, newpath) + except OSError, err: + if err.errno != errno.ENOENT: + self.debug("warning: cannot rename '%s' to '%s': %s\n" % + (newpath, oldpath, err.strerror)) + + fp = self._bbopener('blackbox.log', 'a') + maxsize = self.configbytes('blackbox', 'maxsize', 1048576) + if maxsize > 0: + st = os.fstat(fp.fileno()) + if st.st_size >= maxsize: + path = fp.name + fp.close() + maxfiles = self.configint('blackbox', 'maxfiles', 7) + for i in xrange(maxfiles - 1, 1, -1): + rotate(oldpath='%s.%d' % (path, i - 1), + newpath='%s.%d' % (path, i)) + rotate(oldpath=path, + newpath=maxfiles > 0 and path + '.1') + fp = self._bbopener('blackbox.log', 'a') + return fp + def log(self, event, *msg, **opts): global lastblackbox super(blackboxui, self).log(event, *msg, **opts) @@ -49,7 +87,7 @@ def wrapui(ui): blackbox = self._blackbox elif util.safehasattr(self, '_bbopener'): try: - self._blackbox = self._bbopener('blackbox.log', 'a') + self._blackbox = self._openlogfile() except (IOError, OSError), err: self.debug('warning: cannot write to blackbox.log: %s\n' % err.strerror) diff --git a/tests/test-blackbox.t b/tests/test-blackbox.t --- a/tests/test-blackbox.t +++ b/tests/test-blackbox.t @@ -131,5 +131,20 @@ extension and python hooks - use the eol 1970/01/01 00:00:00 bob> exthook-update: echo hooked finished in * seconds (glob) 1970/01/01 00:00:00 bob> update exited False after * seconds (glob) +log rotation + + $ echo '[blackbox]' >> .hg/hgrc + $ echo 'maxsize = 20' >> .hg/hgrc + $ echo 'maxfiles = 3' >> .hg/hgrc + $ hg status + $ hg status + $ hg status + $ hg tip -q + 2:d02f48003e62 + $ ls .hg/blackbox.log* + .hg/blackbox.log + .hg/blackbox.log.1 + .hg/blackbox.log.2 + cleanup $ cd ..