浏览代码

Merge Mike's strip_ansi code.

Chris McDonough 18 年之前
父节点
当前提交
73f757b5f6
共有 5 个文件被更改,包括 83 次插入2 次删除
  1. 4 0
      CHANGES.txt
  2. 1 0
      sample.conf
  3. 33 2
      src/supervisor/options.py
  4. 3 0
      src/supervisor/supervisord.py
  5. 42 0
      src/supervisor/tests.py

+ 4 - 0
CHANGES.txt

@@ -7,6 +7,10 @@ Next Release
   - Make note of subprocess environment behavior in README.txt.
     Thanks to Christoph Zwerschke.
 
+  - New "strip_ansi" config file option attempts to strip ANSI escape
+    sequences from logs for smaller/more readable logs (submitted by
+    Mike Naberezny).
+
 2.2b1
 
   - Individual program configuration sections can now specify an

+ 1 - 0
sample.conf

@@ -21,6 +21,7 @@ minprocs=200                ; (min. avail process descriptors;default 200)
 ;user=chrism                 ; (default is current user, required if root)
 ;directory=/tmp              ; (default is not to cd during start)
 ;environment=KEY=value       ; (key value pairs to add to environment)
+;strip_ansi=false            ; (strip ansi escape codes from stdout; def. false)
 
 [supervisorctl]
 serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket

+ 33 - 2
src/supervisor/options.py

@@ -18,11 +18,11 @@ import grp
 import resource
 import stat
 
-VERSION = '2.2b1'
-
 from fcntl import fcntl
 from fcntl import F_SETFL, F_GETFL
 
+VERSION = '2.2b1'
+
 class FileHandler(logging.StreamHandler):
     """File handler which supports reopening of logs.
 
@@ -442,6 +442,10 @@ class ServerOptions(Options):
     AUTOMATIC = []
     TRACE = 5
     
+    ANSI_ESCAPE_BEGIN = '\x1b['
+    ANSI_TERMINATORS = ('H', 'f', 'A', 'B', 'C', 'D', 'R', 's', 'u', 'J', 
+                        'K', 'h', 'l', 'p', 'm')    
+    
     def __init__(self):
         Options.__init__(self)
         self.configroot = Dummy()
@@ -481,6 +485,8 @@ class ServerOptions(Options):
                  "", "minprocs=", int, default=200)
         self.add("nocleanup", "supervisord.nocleanup",
                  "k", "nocleanup", flag=1, default=0)
+        self.add("strip_ansi", "supervisord.strip_ansi",
+                 "t", "strip_ansi", flag=1, default=0)
         self.add("sockchmod", "supervisord.sockchmod", "p:", "socket-mode=",
                  datatypes.octal_type, default=0700)
         self.add("sockchown", "supervisord.sockchown", "o:", "socket-owner=",
@@ -642,6 +648,9 @@ class ServerOptions(Options):
         nocleanup = config.getdefault('nocleanup', 'false')
         section.nocleanup = datatypes.boolean(nocleanup)
 
+        strip_ansi = config.getdefault('strip_ansi', 'false')
+        section.strip_ansi = datatypes.boolean(strip_ansi)
+        
         sockchown = config.getdefault('sockchown', None)
         if sockchown is None:
             section.sockchown = (-1, -1)
@@ -943,6 +952,28 @@ class ServerOptions(Options):
             return 'Could not set group id of effective user'
         os.setuid(uid)
 
+    def stripEscapes(self, string):
+        """
+        Remove all ANSI color escapes from the given string.
+        """
+        result = ''
+        show = 1
+        i = 0
+        L = len(string)
+        while i < L:
+            if show == 0 and string[i] in self.ANSI_TERMINATORS:
+                show = 1
+            elif show:
+                n = string.find(self.ANSI_ESCAPE_BEGIN, i)
+                if n == -1:
+                    return result + string[i:]
+                else:
+                    result = result + string[i:n]
+                    i = n
+                    show = 0
+            i = i + 1
+        return result
+
     def waitpid(self):
         # need pthread_sigmask here to avoid concurrent sigchild, but
         # Python doesn't offer it as it's not standard across UNIX versions.

+ 3 - 0
src/supervisor/supervisord.py

@@ -36,6 +36,7 @@ Options:
 -g/--http_username STR -- the username for HTTP auth
 -r/--http_password STR -- the password for HTTP auth
 -a/--minfds NUM -- the minimum number of file descriptors for start success
+-t/--strip_ansi -- strip ansi escape codes from output
 --minprocs NUM  -- the minimum number of processes available for start success
 """
 
@@ -135,6 +136,8 @@ class Subprocess:
         if self.logbuffer:
             data, self.logbuffer = self.logbuffer, ''
             if self.childlog:
+                if self.options.strip_ansi:
+                    data = self.options.stripEscapes(data)
                 self.childlog.info(data)
             msg = '%s output:\n%s' % (self.config.name, data)
             self.options.logger.log(self.options.TRACE, msg)

+ 42 - 0
src/supervisor/tests.py

@@ -1816,6 +1816,42 @@ class XMLRPCHandlerTests(unittest.TestCase):
     
     def _makeOne(self, supervisord):
         return self._getTargetClass()(supervisord)
+    def test_strip_ansi(self):
+        executable = '/bin/cat'
+        options = DummyOptions()
+        from options import getLogger
+        options.getLogger = getLogger
+        options.strip_ansi = True
+        config = DummyPConfig('output', executable, logfile='/tmp/foo')
+
+        ansi = '\x1b[34mHello world!\x1b[0m'
+        noansi = 'Hello world!'
+
+        try:
+            instance = self._makeOne(options, config)
+            instance.logbuffer = ansi
+            instance.log_output()
+            [ x.flush() for x in instance.childlog.handlers ]
+            self.assertEqual(open(instance.config.logfile, 'r').read(), noansi)
+        finally:
+            try:
+                os.remove(instance.config.logfile)
+            except (OSError, IOError):
+                pass
+
+        try:
+            options.strip_ansi = False
+            instance = self._makeOne(options, config)
+            instance.logbuffer = ansi
+            instance.log_output()
+            [ x.flush() for x in instance.childlog.handlers ]
+            self.assertEqual(open(instance.config.logfile, 'r').read(), ansi)
+        finally:
+            try:
+                os.remove(instance.config.logfile)
+            except (OSError, IOError):
+                pass
+
 
     def test_ctor(self):
         supervisor = DummySupervisor()
@@ -2764,6 +2800,7 @@ class DummyOptions:
         self.make_logger_messages = None
         self.autochildlogs_created = False
         self.autochildlogdir_cleared = False
+        self.strip_ansi = False
         self.cleaned_up = False
         self.pidfile_written = False
         self.directory = None
@@ -2928,6 +2965,11 @@ _TIMEFORMAT = '%b %d %I:%M %p'
 
 class DummySupervisorRPCNamespace:
     _restartable = True
+    def stripEscapes(self, data):
+        from options import ServerOptions
+        o = ServerOptions()
+        return o.stripEscapes(data)
+
     _restarted = False
     _shutdown = False