blackbox: automatically rotate log files
authorBryan O'Sullivan <bryano@fb.com>
Thu, 18 Apr 2013 16:17:59 -0700
changeset 19066 2cad301a7f06
parent 19065 2c4cd1c42365
child 19067 292cd385856d
child 19115 55154cea5550
blackbox: automatically rotate log files If enabled, log rotation prevents the amount of space used by the blackbox log from growing without bound. This becomes important in cases where there are a lot of busy repositories managed by humans and automation on many machines. 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 move 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 * blackbox.maxfiles is the number of log files to maintain
hgext/blackbox.py
tests/test-blackbox.t
--- a/hgext/blackbox.py	Thu Apr 18 12:58:28 2013 -0700
+++ b/hgext/blackbox.py	Thu Apr 18 16:17:59 2013 -0700
@@ -21,11 +21,17 @@
   [blackbox]
   track = incoming
 
+  [blackbox]
+  # limit the size of a log file
+  maxsize = 1.5 MB
+  # 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 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 @@
                 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)
--- a/tests/test-blackbox.t	Thu Apr 18 12:58:28 2013 -0700
+++ b/tests/test-blackbox.t	Thu Apr 18 16:17:59 2013 -0700
@@ -131,5 +131,20 @@
   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 b' >> .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 ..