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): _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org http://mail.zope.org/mailman/listinfo/zope-checkins