The python-daemon package (
PyPI listing,
Pagure repo) is very useful. However, I feel it has suffered a bit from sparse documentation, and the inclusion of a "runner" example, which is
in the process of being deprecated as of 2 weeks ago (2016-10-26).
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)