Browse Source

- supervisorctl 'tail' command now accepts a trailing specifier:
'stderr' or 'stdout', which respectively, allow a user to tail the
stderr or stdout of the named process. When this specifier is not
provided, tail defaults to stdout.

- supervisor 'clear' command now clears both stderr and stdout logs
for the given process.

Chris McDonough 18 years ago
parent
commit
5b0edde6e8

+ 8 - 0
CHANGES.txt

@@ -16,6 +16,14 @@ Next Release
   - When a listener process exits (unexpectedly) before transitioning
     from the BUSY state, rebuffer the event that was being processed.
 
+  - supervisorctl 'tail' command now accepts a trailing specifier:
+    'stderr' or 'stdout', which respectively, allow a user to tail the
+    stderr or stdout of the named process.  When this specifier is not
+    provided, tail defaults to stdout.
+
+  - supervisor 'clear' command now clears both stderr and stdout logs
+    for the given process.
+
 3.0a2
 
   - Fixed the README.txt example for defining the supervisor RPC

+ 9 - 2
src/supervisor/http.py

@@ -675,7 +675,14 @@ class logtail_handler:
         while path and path[0] == '/':
             path = path[1:]
 
-        path, process_name = path.split('/', 1)
+        path, process_name_and_channel = path.split('/', 1)
+
+        try:
+            process_name, channel = process_name_and_channel.split('/', 1)
+        except ValueError:
+            # no channel specified, default channel to stdout
+            process_name = process_name_and_channel
+            channel = 'stdout'
 
         from supervisor.options import split_namespec
         group_name, process_name = split_namespec(process_name)
@@ -690,7 +697,7 @@ class logtail_handler:
             request.error(404) # not found
             return
 
-        logfile = process.config.stdout_logfile
+        logfile = getattr(process.config, '%s_logfile' % channel, None)
 
         if logfile is None or not os.path.exists(logfile):
             # XXX problematic: processes that don't start won't have a log

+ 1 - 1
src/supervisor/rpcinterface.py

@@ -791,7 +791,7 @@ class SupervisorNamespaceRPCInterface:
         @param string name   The name of the process (or 'group:name')
         @return boolean result      Always True unless error
         """
-        self._update('clearProcessLog')
+        self._update('clearProcessLogs')
 
         group, process = self._getGroupAndProcess(name)
 

+ 56 - 37
src/supervisor/supervisorctl.py

@@ -185,54 +185,73 @@ class Controller(cmd.Cmd):
             self.help_tail()
             return
 
-        elif len(args) > 2:
+        elif len(args) > 3:
             self._output('Error: too many arguments')
             self.help_tail()
             return
 
-        elif len(args) == 2:
-            if args[0].startswith('-'):
-                what = args[0][1:]
-                if what == 'f':
-                    path = '/logtail/' + args[1]
-                    return self._tailf(path)
+        modifier = None
+
+        if args[0].startswith('-'):
+            modifier = args.pop(0)
+
+        if len(args) == 1:
+            processname = args[-1]
+            channel = 'stdout'
+        else:
+            processname = args[0]
+            channel = args[-1].lower()
+            if channel not in ('stderr', 'stdout'):
+                self._output('Error: bad channel %r' % channel)
+                return
+
+        bytes = 1600
+
+        if modifier is not None:
+            what = modifier[1:]
+            if what == 'f':
+                bytes = None
+            else:
                 try:
-                    what = int(what)
+                    bytes = int(what)
                 except:
-                    self._output('Error: bad argument %s' % args[0])
+                    self._output('Error: bad argument %s' % modifier)
                     return
-                else:
-                    bytes = what
-            else:
-                self._output('Error: bad argument %s' % args[0])
-                
-        else:
-            bytes = 1600
 
-        processname = args[-1]
-        
         supervisor = self._get_supervisor()
 
-        try:
-            output = supervisor.readProcessLog(processname, -bytes, 0)
-        except xmlrpclib.Fault, e:
-            template = '%s: ERROR (%s)'
-            if e.faultCode == xmlrpc.Faults.NO_FILE:
-                self._output(template % (processname, 'no log file'))
-            elif e.faultCode == xmlrpc.Faults.FAILED:
-                self._output(template % (processname,
-                                         'unknown error reading log'))
-            elif e.faultCode == xmlrpc.Faults.BAD_NAME:
-                self._output(template % (processname, 'no such process name'))
+        if bytes is None:
+            return self._tailf('/logtail/%s/%s' % (processname, channel))
+
         else:
-            self._output(output)
+            try:
+                if channel is 'stdout':
+                    output = supervisor.readProcessStdoutLog(processname,
+                                                             -bytes, 0)
+                else: # if channel is 'stderr'
+                    output = supervisor.readProcessStderrLog(processname,
+                                                             -bytes, 0)
+            except xmlrpclib.Fault, e:
+                template = '%s: ERROR (%s)'
+                if e.faultCode == xmlrpc.Faults.NO_FILE:
+                    self._output(template % (processname, 'no log file'))
+                elif e.faultCode == xmlrpc.Faults.FAILED:
+                    self._output(template % (processname,
+                                             'unknown error reading log'))
+                elif e.faultCode == xmlrpc.Faults.BAD_NAME:
+                    self._output(template % (processname,
+                                             'no such process name'))
+            else:
+                self._output(output)
 
     def help_tail(self):
         self._output(
-            "tail -f <processname>\tContinuous tail of named process stdout,\n"
+            "tail [-f] <processname> [stdin|stdout] (default stdout)"
+            "Ex:\n"
+            "tail -f <processname>\tContinuous tail of named process stdout\n"
             "\t\t\tCtrl-C to exit.\n"
-            "tail -100 <processname>\tlast 100 *bytes* of process log file\n"
-            "tail <processname>\tlast 1600 *bytes* of process log file\n"
+            "tail -100 <processname>\tlast 100 *bytes* of process stdout\n"
+            "tail <processname> stderr\tlast 1600 *bytes* of process stderr\n"
             )
 
     def do_maintail(self, arg):
@@ -570,7 +589,7 @@ class Controller(cmd.Cmd):
 
             for processname in processnames:
                 try:
-                    result = supervisor.clearProcessLog(processname)
+                    result = supervisor.clearProcessLogs(processname)
                 except xmlrpclib.Fault, e:
                     error = self._clearresult(e.faultCode, processname)
                     if error is not None:
@@ -584,10 +603,10 @@ class Controller(cmd.Cmd):
                         raise # assertion
 
     def help_clear(self):
-        self._output("clear <processname>\t\t\tClear a process' log file.")
+        self._output("clear <processname>\t\t\tClear a process' log files.")
         self._output("clear <processname> <processname>\tclear multiple "
-                     "process log files")
-        self._output("clear all\t\t\t\tClear all process log files")
+                     "process' log files")
+        self._output("clear all\t\t\t\tClear all process' log files")
 
     def do_open(self, arg):
         url = arg.strip()

+ 9 - 2
src/supervisor/tests/base.py

@@ -555,7 +555,7 @@ class DummySupervisorRPCNamespace:
 
     getVersion = getAPIVersion # deprecated
 
-    def readProcessLog(self, name, offset, length):
+    def readProcessStdoutLog(self, name, offset, length):
         from supervisor import xmlrpc
         import xmlrpclib
         if name == 'BAD_NAME':
@@ -567,6 +567,9 @@ class DummySupervisorRPCNamespace:
         a = 'output line\n' * 10
         return a[offset:]
 
+    readProcessLog = readProcessStdoutLog
+    readProcessStderrLog = readProcessStdoutLog
+
     def getAllProcessInfo(self):
         from supervisor.process import ProcessStates
         return [
@@ -690,13 +693,17 @@ class DummySupervisorRPCNamespace:
         from supervisor import xmlrpc
         raise Fault(xmlrpc.Faults.SHUTDOWN_STATE, '')
 
-    def clearProcessLog(self, name):
+    def clearProcessStdoutLog(self, name):
         from xmlrpclib import Fault
         from supervisor import xmlrpc
         if name == 'BAD_NAME':
             raise Fault(xmlrpc.Faults.BAD_NAME, 'BAD_NAME')
         return True
 
+    clearProcessLog = clearProcessStdoutLog
+    clearProcessStderrLog = clearProcessStdoutLog
+    clearProcessLogs = clearProcessStdoutLog
+
     def clearAllProcessLogs(self):
         from supervisor import xmlrpc
         return [

+ 32 - 4
src/supervisor/tests/test_supervisorctl.py

@@ -63,7 +63,7 @@ class ControllerTests(unittest.TestCase):
             controller.stdout.getvalue().find('Documented commands') != -1
             )
 
-    def test_tail_noname(self):
+    def test_tail_toofewargs(self):
         options = DummyClientOptions()
         controller = self._makeOne(options)
         controller.stdout = StringIO()
@@ -76,12 +76,12 @@ class ControllerTests(unittest.TestCase):
         options = DummyClientOptions()
         controller = self._makeOne(options)
         controller.stdout = StringIO()
-        result = controller.do_tail('one two three')
+        result = controller.do_tail('one two three four')
         self.assertEqual(result, None)
         lines = controller.stdout.getvalue().split('\n')
         self.assertEqual(lines[0], 'Error: too many arguments')
 
-    def test_tail_onearg(self):
+    def test_tail_defaults(self):
         options = DummyClientOptions()
         controller = self._makeOne(options)
         controller.stdout = StringIO()
@@ -118,7 +118,7 @@ class ControllerTests(unittest.TestCase):
         self.assertEqual(len(lines), 2)
         self.assertEqual(lines[0], 'BAD_NAME: ERROR (no such process name)')
 
-    def test_tail_twoargs(self):
+    def test_tail_bytesmodifier(self):
         options = DummyClientOptions()
         controller = self._makeOne(options)
         controller.stdout = StringIO()
@@ -128,6 +128,34 @@ class ControllerTests(unittest.TestCase):
         self.assertEqual(len(lines), 3)
         self.assertEqual(lines[0], 'tput line')
 
+    def test_tail_explicit_channel_stdout_nomodifier(self):
+        options = DummyClientOptions()
+        controller = self._makeOne(options)
+        controller.stdout = StringIO()
+        result = controller.do_tail('foo stdout')
+        self.assertEqual(result, None)
+        lines = controller.stdout.getvalue().split('\n')
+        self.assertEqual(len(lines), 12)
+        self.assertEqual(lines[0], 'output line')
+
+    def test_tail_explicit_channel_stderr_nomodifier(self):
+        options = DummyClientOptions()
+        controller = self._makeOne(options)
+        controller.stdout = StringIO()
+        result = controller.do_tail('foo stderr')
+        lines = controller.stdout.getvalue().split('\n')
+        self.assertEqual(len(lines), 12)
+        self.assertEqual(lines[0], 'output line')
+
+    def test_tail_explicit_channel_unrecognized(self):
+        options = DummyClientOptions()
+        controller = self._makeOne(options)
+        controller.stdout = StringIO()
+        result = controller.do_tail('foo fudge')
+        self.assertEqual(result, None)
+        value = controller.stdout.getvalue().strip()
+        self.assertEqual(value, "Error: bad channel 'fudge'")
+
     def test_status_oneprocess(self):
         options = DummyClientOptions()
         controller = self._makeOne(options)