Patchwork [3,of,3,v4] blackbox: automatically rotate log files

login
register
mail settings
Submitter Bryan O'Sullivan
Date April 19, 2013, 6:59 p.m.
Message ID <555a784013830b33b10d.1366397967@australite.local>
Download mbox | patch
Permalink /patch/1459/
State Rejected
Headers show

Comments

Bryan O'Sullivan - April 19, 2013, 6:59 p.m.
# HG changeset patch
# User Bryan O'Sullivan <bryano@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

Patch

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 ..