[Zope3-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 Zope3-Checkins
mailing list