I would like to create a python daemon, completely detaching from the terminal or parent process, and yet retaining any log handlers through the python logging module. There is a wonderful example at cookbook-278731 of a well-behaved daemon, and see PEP 3143.
Borrowing from these examples, here is a simple daemonize function that will retain thread-safe logging handlers that were setup through the logging module. This is also available via github.
# vim: set tabstop=4 shiftwidth=4 autoindent smartindent:
'''
Daemon (python daemonize function)
Detach process through double-fork (to avoid zombie process), close all
file descriptors, and set working directory to root (to avoid umount problems)
'''
import os, resource
import logging
# target environment
UMASK = 0
WORKINGDIR = '/'
MAXFD = 1024
if (hasattr(os, "devnull")):
REDIRECT_TO = os.devnull
else:
REDIRECT_TO = "/dev/null"
def daemonize():
'''Detach this process and run it as a daemon'''
try:
pid = os.fork() #first fork
except OSError, e:
raise Exception, "%s [%d]" % (e.strerror, e.errno)
if (pid == 0): #first child
os.setsid()
try:
pid = os.fork() #second fork
except OSError, e:
raise Exception, "%s [%d]" % (e.strerror, e.errno)
if (pid == 0): #second child
os.chdir(WORKINGDIR)
os.umask(UMASK)
else:
os._exit(0)
else:
os._exit(0)
#close all file descriptors except from non-console logging handlers
maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
if (maxfd == resource.RLIM_INFINITY):
maxfd = MAXFD
filenos = []
for handler in logging.root.handlers:
if hasattr(handler, 'stream') and hasattr(handler.stream, 'fileno') and handler.stream.fileno() > 2:
filenos.append( handler.stream.fileno() )
for fd in range(0, maxfd):
try:
if fd not in filenos:
os.close(fd)
except OSError:
pass
#redirect stdin, stdout, stderr to null
os.open(REDIRECT_TO, os.O_RDWR)
os.dup2(0, 1)
os.dup2(0, 2)
return(0)
Simply call the daemonize() function within your application, e.g.,
import logging
import Daemon
logging.info('daemonize?')
Daemon.daemonize():
logging.info('daemonized! we are now safely detached')
From a shell, console logging will only appear before daemonize() was called, e.g.,
# python test/daemon.py INFO:daemon.py:4 -- daemonize? #
And the logfile output:
# cat test.log 2011-09-27 13:26:02,712 INFO:daemon.py:4 -- daemonize? 2011-09-27 13:26:02,717 INFO:daemon.py:6 -- daemonized! we are now safely detached #