[PATCH 3 of 3 v4] blackbox: automatically rotate log files

Bryan O'Sullivan bos at serpentine.com
Fri Apr 19 13:59:27 CDT 2013


# HG changeset patch
# User Bryan O'Sullivan <bryano at fb.com>
# 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 ..


More information about the Mercurial-devel mailing list