pid 3.0.4
Logging to file is also possible when using PidFile with a daemon context manager (e.g. python-daemon). This requires some care in handling the open files when the daemon starts to avoid closing them, which causes problems with the logging. In particular, the open handlers should be preserved:
import sys import logging import logging.config import daemon from pid impor PidFile logging.config.fileConfig(fname="logging.conf", disable_existing_loggers=False) log = logging.getLogger(__name__) PIDNAME = "/tmp/mydaemon.pid" def get_logging_handles(logger): handles = [] for handler in logger.handlers: handles.append(handler.stream.fileno()) if logger.parent: handles += get_logging_handles(logger.parent) return handles def daemonize(): file_preserve = get_logging_handles(logging.root) pid_file = PidFile(pidname=PIDNAME) with daemon.DaemonContext(stdout=sys.stdout, stderr=sys.stderr, stdin=sys.stdin, pidfile=_pid_file, files_preserve=files_preserve): run_daemon_job() print("DONE!") if __name__ == "__main__": daemonize()
This assumes a logging.conf file has been created, see e.g. basic tutorial for logging.
Decorator
PidFile can also be used a a decorator:
from pid.decorator import pidfile @pidfile() def main(): pass if __name__ == "__main__": main()
Exception Order
In default mode PidFile will try to acquire a file lock before anything else. This means that normally you get a PidFileAlreadyLockedError instead of the PidFileAlreadyRunningError when running a program twice.
If you just want to know if a program is already running its easiest to catch just PidFileError since it will capture all possible PidFile exceptions.
Behaviour
Changes in version 2.0.0 and going forward:
- pid is now friendly with daemon context managers such as python-daemon where the PidFile context manager is passed as a parameter. The new corrected behaviour will ensure the process environment is determined at the time of acquiring/checking the lock. Prior behaviour would determine the process environment when instancing the class which may result in incorrect determination of the PID in the case of a process forking after instancing PidFile.
- Cleanup of pidfile on termination is done using atexit module. The default SIGTERM handler doesn’t cleanly exit and therefore the atexit registered functions will not execute. A custom handler which triggers the atexit registered functions for cleanup will override the default SIGTERM handler. If a prior signal handler has been configured, then it will not be overridden.
Легко настраиваемый python daemon
Дальше я попробую описать логику работы всех трех.
Сразу скажу, что все есть на Гитхабе. Потому как если вы легко читаете питон — читать мой весьма неумелый текст может оказаться гораздо сложнее.
Собственно в первом файлике описывать толком нечего: Это почти неизменные три класса, взятые из этой статьи. Из изменений там только то, что к самому демону был прикреплен класс обработчик сигналов, и добавление сигналов в список обрабатываемых было вкручено в собственно процедуру демонизации.
Вторая часть будет чуть интереснее. Там присутствует три класса:
1) SigFunctionsCon — содержит реакцию на сигналы. При инициализации получает экземпляр демона, чтобы уметь обращаться к его методам. Каждый метод должен соответствовать сигналу, который он обрабатывает названием. Например так:
def SIGTERM(self): sys.stderr.write("BB!\n") sys.exit(0)
Внутренние методы и поля могут быть какими угодно.
2)ReactFunctionCon — содержит реакцию на консольные команды. При инициализации так же получает демона. Каждый метод по названию должен соответствовать команде на которую он будет реагировать и может принимать аргументы (то, что собственно идет за командой в командной строке). Например:
def stmess(self,message): print message self.__ourdaemon.start()
3)StatCon — содержит всякие статические настройки демона. На данный момент выглядит так:
class StatCon: Help = "Autmation has be applied to distribution sistem feeder for a long time, aspecially as related to protection and the restoration of some parts of the feeder." def run(self): while(True): time.sleep(1) PidFile = "/tmp/daemon-naprimer.pid" Inputter = "/dev/null" Outputter = "/dev/null" Errorer = "/home/espresso/lid"
Соответственно —
Хелп строка, выводимая при неправильной передаче аргументов в какую-либо функцию (Возможно следует сделать команду хелп по умолчанию, которая выводит это сообщение?).
Метод run — собственно то, для чего все затевалось — то, что демон делает.
Адрес pid файла — для хранения процесса и все такое.
Ввод, вывод, ошибки — логгирование и прочее. По умолчанию отсылается в /dev/null
Центровой скрипт представляет интерес исключительно кодом. В общем говоря он наследует класс демона, собирает все настройки с предыдущего файла и раскладывает их по демону, и принимает команды.
Ну и собственно вопросы:
Что не так, что не очень так?
Как по вашему следует ли как-то приписывать к этому GPL, или не стоит больгеносить, и все это слишком несерьезно?
Достаточно ли адекватно я указал предыдущих авторов?
Linux Follies
How-to’s and technical news about Linux and open computing, with a sprinkling of Python.
2016-11-10
Linux daemon using Python daemon with PID file and logging
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)
daemonize¶
daemonize is a library for writing system daemons in Python. It is distributed under MIT license. Latest version can be downloaded from PyPI. Full documentation can be found at ReadTheDocs.
Dependencies¶
It is tested under following Python versions:
Installation¶
You can install it from Python Package Index (PyPI):
Usage¶
from time import sleep from daemonize import Daemonize pid = "/tmp/test.pid" def main(): while True: sleep(5) daemon = Daemonize(app="test_app", pid=pid, action=main) daemon.start()
File descriptors¶
Daemonize object’s constructor understands the optional argument keep_fds which contains a list of FDs which should not be closed. For example:
import logging from daemonize import Daemonize pid = "/tmp/test.pid" logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) logger.propagate = False fh = logging.FileHandler("/tmp/test.log", "w") fh.setLevel(logging.DEBUG) logger.addHandler(fh) keep_fds = [fh.stream.fileno()] def main(): logger.debug("Test") daemon = Daemonize(app="test_app", pid=pid, action=main, keep_fds=keep_fds) daemon.start()
API¶
class daemonize. Daemonize ( app, pid, action, keep_fds=None, auto_close_fds=True, privileged_action=None, user=None, group=None, verbose=False, logger=None, foreground=False, chdir=’/’ ) [source] ¶
Object constructor expects three arguments.
- app – contains the application name which will be sent to syslog.
- pid – path to the pidfile.
- action – your custom function which will be executed after daemonization.
- keep_fds – optional list of fds which should not be closed.
- auto_close_fds – optional parameter to not close opened fds.
- privileged_action – action that will be executed before drop privileges if user or group parameter is provided. If you want to transfer anything from privileged_action to action, such as opened privileged file descriptor, you should return it from privileged_action function and catch it inside action function.
- user – drop privileges to this user if provided.
- group – drop privileges to this group if provided.
- verbose – send debug messages to logger if provided.
- logger – use this logger object instead of creating new one, if provided.
- foreground – stay in foreground; do not fork (for debugging)
- chdir – change working directory if provided or /
These actions will be done after SIGTERM.
Start daemonization process.