[Zope-Checkins] SVN: zdaemon/branches/jim-env/src/zdaemon/ Added log-rotation (reopening) for the transctipt log. Unfortunately,

Jim Fulton jim at zope.com
Mon Jan 8 16:16:11 EST 2007


Log message for revision 71832:
  Added log-rotation (reopening) for the transctipt log. Unfortunately,
  this required redoing the transacript log implementation a fair bit.
  

Changed:
  U   zdaemon/branches/jim-env/src/zdaemon/README.txt
  U   zdaemon/branches/jim-env/src/zdaemon/zdctl.py
  U   zdaemon/branches/jim-env/src/zdaemon/zdrun.py

-=-
Modified: zdaemon/branches/jim-env/src/zdaemon/README.txt
===================================================================
--- zdaemon/branches/jim-env/src/zdaemon/README.txt	2007-01-08 20:59:25 UTC (rev 71831)
+++ zdaemon/branches/jim-env/src/zdaemon/README.txt	2007-01-08 21:16:10 UTC (rev 71832)
@@ -174,6 +174,84 @@
     EDITOR=emacs
     LD_LIBRARY_PATH=/home/foo/lib
 
+Transcript log
+---------------
+
+When zdaemon run a program in daemon mode, it disconnects the
+program's standard input, standard output, and standard error from the
+controlling terminal.  It can optionally redirect the output to
+standard error and standard output to a file.  This is done with the
+transcript option.  This is, of course, useful for logging output from
+long-running applications.  
+
+Let's look at an example. We'll have a long-running process that
+simple tails a data file:
+
+    >>> f = open('data', 'w', 0)
+    >>> import os
+    >>> f.write('rec 1\n'); os.fsync(f.fileno())
+
+    >>> open('conf', 'w').write(
+    ... '''
+    ... <runner>
+    ...   program tail -f data
+    ...   transcript log
+    ... </runner>
+    ... ''')
+
+    >>> system("./zdaemon -Cconf start")
+    . daemon process started, pid=7963
+
+.. Wait a little bit to make sure tail has a chance to work
+
+    >>> import time
+    >>> time.sleep(0.1)
+
+Now, if we look at the log file, it contains the tail output:
+
+    >>> open('log').read()
+    'rec 1\n'
+    
+We can rotate the transcript log by renaming it and telling zdaemon to
+reopen it:
+
+    >>> import os
+    >>> os.rename('log', 'log.1')
+
+If we generate more output:
+
+    >>> f.write('rec 2\n'); os.fsync(f.fileno())
+
+.. Wait a little bit to make sure tail has a chance to work
+
+    >>> time.sleep(1)
+
+The output will appear in the old file, because zdaemon still has it
+open:
+
+    >>> open('log.1').read()
+    'rec 1\nrec 2\n'
+
+Now, if we tell zdaemon to reopen the file:
+
+    >>> system("./zdaemon -Cconf reopen_transcript")
+
+and generate some output:
+
+    >>> f.write('rec 3\n'); os.fsync(f.fileno())
+
+.. Wait a little bit to make sure tail has a chance to work
+
+    >>> time.sleep(1)
+
+the output will show up in the new file, not the old:
+
+    >>> open('log').read()
+    'rec 3\n'
+
+    >>> open('log.1').read()
+    'rec 1\nrec 2\n'
+
 Reference Documentation
 -----------------------
 

Modified: zdaemon/branches/jim-env/src/zdaemon/zdctl.py
===================================================================
--- zdaemon/branches/jim-env/src/zdaemon/zdctl.py	2007-01-08 20:59:25 UTC (rev 71831)
+++ zdaemon/branches/jim-env/src/zdaemon/zdctl.py	2007-01-08 21:16:10 UTC (rev 71832)
@@ -293,6 +293,14 @@
             self.send_action("stop")
             self.awhile(lambda: not self.zd_pid, "daemon process stopped")
 
+    def do_reopen_transcript(self, arg):
+        if not self.zd_up:
+            print "daemon manager not running"
+        elif not self.zd_pid:
+            print "daemon process not running"
+        else:
+            self.send_action("reopen_transcript")
+
     def help_stop(self):
         print "stop -- Stop the daemon process."
         print "        If it is not running, do nothing."

Modified: zdaemon/branches/jim-env/src/zdaemon/zdrun.py
===================================================================
--- zdaemon/branches/jim-env/src/zdaemon/zdrun.py	2007-01-08 20:59:25 UTC (rev 71831)
+++ zdaemon/branches/jim-env/src/zdaemon/zdrun.py	2007-01-08 21:16:10 UTC (rev 71832)
@@ -74,6 +74,7 @@
 import socket
 import select
 import signal
+import threading
 from stat import ST_MODE
 
 if __name__ == "__main__":
@@ -349,6 +350,8 @@
         if pid:
             self.waitstatus = pid, sts
 
+    transcript = None
+
     def daemonize(self):
 
         # To daemonize, we need to become the leader of our own session
@@ -390,10 +393,7 @@
                                  % self.options.directory)
         os.close(0)
         sys.stdin = sys.__stdin__ = open("/dev/null")
-        os.close(1)
-        sys.stdout = sys.__stdout__ = open(self.options.transcript, "a", 0)
-        os.close(2)
-        sys.stderr = sys.__stderr__ = open(self.options.transcript, "a", 0)
+        self.transcript = Transcript(self.options.transcript)
         os.setsid()
         os.umask(self.options.umask)
         # XXX Stevens, in his Advanced Unix book, section 13.3 (page
@@ -626,6 +626,10 @@
                        "filename=%r\n" % self.proc.filename +
                        "args=%r\n" % self.proc.args)
 
+    def cmd_reopen_transcript(self, args):
+        if self.transcript is not None:
+            self.transcript.reopen()
+
     def cmd_help(self, args):
         self.sendreply(
             "Available commands:\n"
@@ -655,6 +659,40 @@
             self.logger.warn("Error sending reply: %s" % str(msg))
 
 
+class Transcript:
+
+    def __init__(self, filename):
+        self.read_from, w = os.pipe()
+        os.dup2(w, 1)
+        sys.stdout = sys.__stdout__ = os.fdopen(1, "a", 0)
+        os.dup2(w, 2)
+        sys.stderr = sys.__stderr__ = os.fdopen(2, "a", 0)
+        self.filename = filename
+        self.file = open(filename, 'a', 0)
+        self.write = self.file.write
+        self.lock = threading.Lock()
+        thread = threading.Thread(target=self.copy)
+        thread.setDaemon(True)
+        thread.start()
+
+    def copy(self):
+        lock = self.lock
+        i = [self.read_from]
+        o = e = []
+        while 1:
+            ii, oo, ee = select.select(i, o, e)
+            lock.acquire()
+            for fd in ii:
+                self.write(os.read(fd, 8192))
+            lock.release()
+            
+    def reopen(self):
+        self.lock.acquire()
+        self.file.close()
+        self.file = open(self.filename, 'a', 0)
+        self.write = self.file.write
+        self.lock.release()
+
 # Helpers for dealing with signals and exit status
 
 def decode_wait_status(sts):



More information about the Zope-Checkins mailing list