There are several questions about it on StackOverflow, going back a few years: 2009, 2011, 2012, and 2015. Some refer to the included runner.py as an example, which is being deprecated.
So, I decided to figure it out myself. I wanted to use the PID lockfile mechanism provided by python-daemon, and also the Python logging module. The inline documentation for python-daemon mention the files_preserve parameter, a list of file handles which should be held open when the daemon process is forked off. However, there wasn't an explicit example, and one StackOverflow solution for logging under python-daemon mentions that the file handle for logging objects may not be obvious:
- for a StreamHandler, it's logging.root.handlers[0].stream.fileno()
- for a SyslogHandler, it's logging.root.handlers[1].socket.fileno()
After a bunch of experiments, I think I have sorted it out to my own satisfaction. My example code is in GitHub: prehensilecode/python-daemon-example. It also has a SysV init script.
The daemon itself is straigtforward, doing nothing but logging timestamps to the logfile. The full code is pasted here:
#!/usr/bin/env python3.5 import sys import os import time import argparse import logging import daemon from daemon import pidfile debug_p = False def do_something(logf): ### This does the "work" of the daemon logger = logging.getLogger('eg_daemon') logger.setLevel(logging.INFO) fh = logging.FileHandler(logf) fh.setLevel(logging.INFO) formatstr = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' formatter = logging.Formatter(formatstr) fh.setFormatter(formatter) logger.addHandler(fh) while True: logger.debug("this is an DEBUG message") logger.info("this is an INFO message") logger.error("this is an ERROR message") time.sleep(5) def start_daemon(pidf, logf): ### This launches the daemon in its context global debug_p if debug_p: print("eg_daemon: entered run()") print("eg_daemon: pidf = {} logf = {}".format(pidf, logf)) print("eg_daemon: about to start daemonization") ### XXX pidfile is a context with daemon.DaemonContext( working_directory='/var/lib/eg_daemon', umask=0o002, pidfile=pidfile.TimeoutPIDLockFile(pidf), ) as context: do_something(logf) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Example daemon in Python") parser.add_argument('-p', '--pid-file', default='/var/run/eg_daemon.pid') parser.add_argument('-l', '--log-file', default='/var/log/eg_daemon.log') args = parser.parse_args() start_daemon(pidf=args.pid_file, logf=args.log_file)