Ver código fonte

- Added new event types that are emitted when a subprocess sends data to
stdout or stderr that would be logged (i.e., data received when not in
the special capture mode used by the PROCESS_COMMUNICATION events).
Event listeners can subscribe to either PROCESS_LOG_STDOUT or
PROCESS_LOG_STDERR individually, or PROCESS_LOG for both.

Mike Naberezny 16 anos atrás
pai
commit
c26d64b9a3

+ 6 - 0
CHANGES.txt

@@ -1,5 +1,11 @@
 Next Release
 
+  - Added new event types that are emitted when a subprocess sends data to
+    stdout or stderr that would be logged (i.e., data received when not in 
+    the special capture mode used by the PROCESS_COMMUNICATION events).  
+    Event listeners can subscribe to either PROCESS_LOG_STDOUT or 
+    PROCESS_LOG_STDERR individually, or PROCESS_LOG for both.
+
   - Values for subprocess environment variables specified with environment=
     in supervisord.conf can now be optionally quoted, allowing them to 
     contain commas.  Patch by Tim Godfrey.

+ 7 - 0
src/supervisor/dispatchers.py

@@ -17,6 +17,8 @@ from asyncore import compact_traceback
 
 from supervisor.events import notify
 from supervisor.events import EventRejectedEvent
+from supervisor.events import ProcessLogStderrEvent
+from supervisor.events import ProcessLogStdoutEvent 
 from supervisor.states import EventListenerStates
 from supervisor import loggers
 
@@ -151,6 +153,11 @@ class POutputDispatcher(PDispatcher):
                 config.options.logger.log(
                     self.mainlog_level, msg, name=config.name,
                     channel=self.channel, data=data)
+            if self.channel == 'stderr':
+                event = ProcessLogStderrEvent
+            else:
+                event = ProcessLogStdoutEvent
+            notify(event(self.process, self.process.pid, data))
 
     def record_output(self):
         if self.capturelog is None:

+ 28 - 1
src/supervisor/events.py

@@ -31,6 +31,30 @@ class Event:
     """ Abstract event type """
     pass
 
+class ProcessLogEvent(Event):
+    """ Abstract """
+    def __init__(self, process, pid, data):
+        self.process = process
+        self.pid = pid
+        self.data = data
+
+    def __str__(self):
+        groupname = ''
+        if self.process.group is not None:
+            groupname = self.process.group.config.name
+        return 'processname:%s groupname:%s pid:%s channel:%s\n%s' % (
+            self.process.config.name,
+            groupname,
+            self.pid,
+            self.channel,
+            self.data)
+
+class ProcessLogStdoutEvent(ProcessLogEvent):
+    channel = 'stdout'
+
+class ProcessLogStderrEvent(ProcessLogEvent):
+    channel = 'stderr'
+
 class ProcessCommunicationEvent(Event):
     """ Abstract """
     # event mode tokens
@@ -77,7 +101,7 @@ class SupervisorRunningEvent(SupervisorStateChangeEvent):
 class SupervisorStoppingEvent(SupervisorStateChangeEvent):
     pass
 
-class EventRejectedEvent: # purposely does not subclass Event
+class EventRejectedEvent: # purposely does not subclass Event 
     def __init__(self, process, event):
         self.process = process
         self.event = event
@@ -175,6 +199,9 @@ class EventTypes:
     PROCESS_COMMUNICATION = ProcessCommunicationEvent # abstract
     PROCESS_COMMUNICATION_STDOUT = ProcessCommunicationStdoutEvent
     PROCESS_COMMUNICATION_STDERR = ProcessCommunicationStderrEvent
+    PROCESS_LOG = ProcessLogEvent
+    PROCESS_LOG_STDOUT = ProcessLogStdoutEvent
+    PROCESS_LOG_STDERR = ProcessLogStderrEvent     
     REMOTE_COMMUNICATION = RemoteCommunicationEvent
     SUPERVISOR_STATE_CHANGE = SupervisorStateChangeEvent # abstract
     SUPERVISOR_STATE_CHANGE_RUNNING = SupervisorRunningEvent

+ 45 - 5
src/supervisor/tests/test_dispatchers.py

@@ -21,10 +21,12 @@ class POutputDispatcherTests(unittest.TestCase):
         from supervisor.dispatchers import POutputDispatcher
         return POutputDispatcher
 
-    def _makeOne(self, process):
-        from supervisor.events import ProcessCommunicationStdoutEvent
-        return self._getTargetClass()(process,
-                                      ProcessCommunicationStdoutEvent, 0)
+    def _makeOne(self, process, channel='stdout'):
+        from supervisor import events
+        events = {'stdout': events.ProcessCommunicationStdoutEvent,
+                  'stderr': events.ProcessCommunicationStderrEvent}
+        # dispatcher derives its channel from event class
+        return self._getTargetClass()(process, events[channel], 0)
 
     def test_writable(self):
         options = DummyOptions()
@@ -124,7 +126,7 @@ class POutputDispatcherTests(unittest.TestCase):
         self.assertEqual(dispatcher.childlog.handlers[0].reopened, True)
         self.assertEqual(dispatcher.mainlog.handlers[0].reopened, True)
 
-    def test_record_output_non_capturemode(self):
+    def test_record_output_log_non_capturemode(self):
         # stdout/stderr goes to the process log and the main log,
         # in non-capturemode, the data length doesn't matter
         options = DummyOptions()
@@ -141,6 +143,44 @@ class POutputDispatcherTests(unittest.TestCase):
              "'process1' stdout output:\na")
         self.assertEqual(dispatcher.output_buffer, '')
 
+    def test_record_output_emits_stdout_event(self):
+        options = DummyOptions()
+        config = DummyPConfig(options, 'process1', '/bin/process1')
+        process = DummyProcess(config)
+        dispatcher = self._makeOne(process, 'stdout')
+        dispatcher.output_buffer = 'hello from stdout'
+
+        L = []
+        def doit(event):
+            L.append(event)
+        from supervisor import events
+        events.subscribe(events.EventTypes.PROCESS_LOG_STDOUT, doit)
+        dispatcher.record_output()
+
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.process, process)
+        self.assertEqual(event.data, 'hello from stdout') 
+
+    def test_record_output_emits_stderr_event(self):
+        options = DummyOptions()
+        config = DummyPConfig(options, 'process1', '/bin/process1')
+        process = DummyProcess(config)
+        dispatcher = self._makeOne(process, 'stderr')
+        dispatcher.output_buffer = 'hello from stderr'
+
+        L = []
+        def doit(event):
+            L.append(event)
+        from supervisor import events
+        events.subscribe(events.EventTypes.PROCESS_LOG_STDERR, doit)
+        dispatcher.record_output()
+
+        self.assertEqual(len(L), 1)
+        event = L[0]
+        self.assertEqual(event.process, process)
+        self.assertEqual(event.data, 'hello from stderr') 
+
     def test_record_output_capturemode_string_longer_than_token(self):
         # stdout/stderr goes to the process log and the main log,
         # in capturemode, the length of the data needs to be longer

+ 90 - 1
src/supervisor/tests/test_events.py

@@ -65,6 +65,50 @@ class EventSubscriptionNotificationTests(unittest.TestCase):
         
 
 class TestEventTypes(unittest.TestCase):
+    def test_ProcessLogEvent_attributes(self):
+        from supervisor.events import ProcessLogEvent
+        inst = ProcessLogEvent(1, 2, 3)
+        self.assertEqual(inst.process, 1)
+        self.assertEqual(inst.pid, 2)
+        self.assertEqual(inst.data, 3)
+
+    def test_ProcessLogEvent_inheritence(self):
+        from supervisor.events import ProcessLogEvent
+        from supervisor.events import Event
+        self.assertTrue(
+            issubclass(ProcessLogEvent, Event)
+        )
+
+    def test_ProcessLogStdoutEvent_attributes(self):
+        from supervisor.events import ProcessLogStdoutEvent
+        inst = ProcessLogStdoutEvent(1, 2, 3)
+        self.assertEqual(inst.process, 1)
+        self.assertEqual(inst.pid, 2)
+        self.assertEqual(inst.data, 3)
+        self.assertEqual(inst.channel, 'stdout')
+
+    def test_ProcessLogStdoutEvent_inheritence(self):
+        from supervisor.events import ProcessLogStdoutEvent
+        from supervisor.events import ProcessLogEvent
+        self.assertTrue(
+            issubclass(ProcessLogStdoutEvent, ProcessLogEvent)
+        )
+
+    def test_ProcessLogStderrEvent_attributes(self):
+        from supervisor.events import ProcessLogStderrEvent
+        inst = ProcessLogStderrEvent(1, 2, 3)
+        self.assertEqual(inst.process, 1)
+        self.assertEqual(inst.pid, 2)
+        self.assertEqual(inst.data, 3)
+        self.assertEqual(inst.channel, 'stderr')
+
+    def test_ProcessLogStderrEvent_inheritence(self):
+        from supervisor.events import ProcessLogStderrEvent
+        from supervisor.events import ProcessLogEvent
+        self.assertTrue(
+            issubclass(ProcessLogStderrEvent, ProcessLogEvent)
+        )
+
     def test_ProcessCommunicationEvent_attributes(self):
         from supervisor.events import ProcessCommunicationEvent
         inst = ProcessCommunicationEvent(1, 2, 3)
@@ -215,7 +259,52 @@ class TestSerializations(unittest.TestCase):
                 raise AssertionError('headerdata %r could not be deserialized' %
                                      headerdata)
         return headers, payload
-    
+
+    def test_plog_stdout_event(self):
+        options = DummyOptions()
+        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
+        process1 = DummyProcess(pconfig1)
+        from supervisor.events import ProcessLogStdoutEvent
+        class DummyGroup:
+            config = pconfig1
+        process1.group = DummyGroup
+        event = ProcessLogStdoutEvent(process1, 1, 'yo')
+        headers, payload = self._deserialize(str(event))
+        self.assertEqual(headers['processname'], 'process1', headers)
+        self.assertEqual(headers['groupname'], 'process1', headers)
+        self.assertEqual(headers['pid'], '1', headers)
+        self.assertEqual(payload, 'yo')
+
+    def test_plog_stderr_event(self):
+        options = DummyOptions()
+        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
+        process1 = DummyProcess(pconfig1)
+        from supervisor.events import ProcessLogStderrEvent
+        class DummyGroup:
+            config = pconfig1
+        process1.group = DummyGroup
+        event = ProcessLogStderrEvent(process1, 1, 'yo')
+        headers, payload = self._deserialize(str(event))
+        self.assertEqual(headers['processname'], 'process1', headers)
+        self.assertEqual(headers['groupname'], 'process1', headers)
+        self.assertEqual(headers['pid'], '1', headers)
+        self.assertEqual(payload, 'yo')
+            
+    def test_pcomm_stdout_event(self):
+        options = DummyOptions()
+        pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')
+        process1 = DummyProcess(pconfig1)
+        from supervisor.events import ProcessCommunicationStdoutEvent
+        class DummyGroup:
+            config = pconfig1
+        process1.group = DummyGroup
+        event = ProcessCommunicationStdoutEvent(process1, 1, 'yo')
+        headers, payload = self._deserialize(str(event))
+        self.assertEqual(headers['processname'], 'process1', headers)
+        self.assertEqual(headers['groupname'], 'process1', headers)
+        self.assertEqual(headers['pid'], '1', headers)
+        self.assertEqual(payload, 'yo')
+            
     def test_pcomm_stdout_event(self):
         options = DummyOptions()
         pconfig1 = DummyPConfig(options, 'process1', 'process1','/bin/process1')