Added core daemon function as flags.

Created three additional flags for the hass command:
-v -  Toggle verbose log file output
—pid-file -  Specify PID file path
—daemon -  Launch as daemon (nix only)

The core now binds to SIGQUIT on nix systems to trigger a clean
shutdown.

Modified HTTP server to write logging messages through the logging
module.
This commit is contained in:
Ryan Kraus 2015-09-01 02:12:00 -04:00
parent df4afa5025
commit ff470c8ffe
4 changed files with 119 additions and 31 deletions

View file

@ -77,8 +77,72 @@ def get_arguments():
'--open-ui', '--open-ui',
action='store_true', action='store_true',
help='Open the webinterface in a browser') help='Open the webinterface in a browser')
parser.add_argument(
'-v', '--verbose',
action='store_true',
help="Enable verbose logging to file.")
parser.add_argument(
'--pid-file',
metavar='path_to_pid_file',
default=None,
help='Path to PID file useful for running as daemon')
if os.name != "nt":
parser.add_argument(
'--daemon',
action='store_true',
help='Run Home Assistant as daemon')
return parser.parse_args() arguments = parser.parse_args()
if os.name == "nt":
arguments.daemon = False
return arguments
def daemonize():
""" Move current process to daemon process """
# create first fork
pid = os.fork()
if pid > 0:
sys.exit(0)
# decouple fork
os.setsid()
os.umask(0)
# create second fork
pid = os.fork()
if pid > 0:
sys.exit(0)
def check_pid(pid_file):
""" Check that HA is not already running """
# check pid file
if pid_file:
try:
pid = int(open(pid_file, 'r').readline())
except IOError:
pass
else:
try:
os.kill(pid, 0)
except OSError:
pass
else:
print('Fatal Error: HomeAssistant is already running.')
sys.exit(1)
def write_pid(pid_file):
""" Create PID File """
# store pid
if pid_file:
# write pid file
pid = os.getpid()
try:
open(pid_file, 'w').write(str(pid))
except IOError:
pass
def main(): def main():
@ -90,15 +154,22 @@ def main():
config_dir = os.path.join(os.getcwd(), args.config) config_dir = os.path.join(os.getcwd(), args.config)
ensure_config_path(config_dir) ensure_config_path(config_dir)
# daemon functions
check_pid(args.pid_file)
if args.daemon:
daemonize()
write_pid(args.pid_file)
if args.demo_mode: if args.demo_mode:
hass = bootstrap.from_config_dict({ hass = bootstrap.from_config_dict({
'frontend': {}, 'frontend': {},
'demo': {} 'demo': {}
}, config_dir=config_dir) }, config_dir=config_dir, daemon=args.daemon, verbose=args.verbose)
else: else:
config_file = ensure_config_file(config_dir) config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir) print('Config directory:', config_dir)
hass = bootstrap.from_config_file(config_file) hass = bootstrap.from_config_file(
config_file, daemon=args.daemon, verbose=args.verbose)
if args.open_ui: if args.open_ui:
def open_browser(event): def open_browser(event):

View file

@ -150,7 +150,8 @@ def mount_local_lib_path(config_dir):
# pylint: disable=too-many-branches, too-many-statements # pylint: disable=too-many-branches, too-many-statements
def from_config_dict(config, hass=None, config_dir=None, enable_log=True): def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
verbose=False, daemon=False):
""" """
Tries to configure Home Assistant from a config dict. Tries to configure Home Assistant from a config dict.
@ -166,7 +167,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True):
process_ha_core_config(hass, config.get(core.DOMAIN, {})) process_ha_core_config(hass, config.get(core.DOMAIN, {}))
if enable_log: if enable_log:
enable_logging(hass) enable_logging(hass, verbose, daemon)
_ensure_loader_prepared(hass) _ensure_loader_prepared(hass)
@ -195,7 +196,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True):
return hass return hass
def from_config_file(config_path, hass=None): def from_config_file(config_path, hass=None, verbose=False, daemon=False):
""" """
Reads the configuration file and tries to start all the required Reads the configuration file and tries to start all the required
functionality. Will add functionality to 'hass' parameter if given, functionality. Will add functionality to 'hass' parameter if given,
@ -209,35 +210,36 @@ def from_config_file(config_path, hass=None):
hass.config.config_dir = config_dir hass.config.config_dir = config_dir
mount_local_lib_path(config_dir) mount_local_lib_path(config_dir)
enable_logging(hass) enable_logging(hass, verbose, daemon)
config_dict = config_util.load_config_file(config_path) config_dict = config_util.load_config_file(config_path)
return from_config_dict(config_dict, hass, enable_log=False) return from_config_dict(config_dict, hass, enable_log=False)
def enable_logging(hass): def enable_logging(hass, verbose=False, daemon=False):
""" Setup the logging for home assistant. """ """ Setup the logging for home assistant. """
logging.basicConfig(level=logging.INFO) if not daemon:
fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) " logging.basicConfig(level=logging.INFO)
"[%(name)s] %(message)s%(reset)s") fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) "
try: "[%(name)s] %(message)s%(reset)s")
from colorlog import ColoredFormatter try:
logging.getLogger().handlers[0].setFormatter(ColoredFormatter( from colorlog import ColoredFormatter
fmt, logging.getLogger().handlers[0].setFormatter(ColoredFormatter(
datefmt='%y-%m-%d %H:%M:%S', fmt,
reset=True, datefmt='%y-%m-%d %H:%M:%S',
log_colors={ reset=True,
'DEBUG': 'cyan', log_colors={
'INFO': 'green', 'DEBUG': 'cyan',
'WARNING': 'yellow', 'INFO': 'green',
'ERROR': 'red', 'WARNING': 'yellow',
'CRITICAL': 'red', 'ERROR': 'red',
} 'CRITICAL': 'red',
)) }
except ImportError: ))
_LOGGER.warning( except ImportError:
"Colorlog package not found, console coloring disabled") _LOGGER.warning(
"Colorlog package not found, console coloring disabled")
# Log errors to a file if we have write access to file or config dir # Log errors to a file if we have write access to file or config dir
err_log_path = hass.config.path('home-assistant.log') err_log_path = hass.config.path('home-assistant.log')
@ -251,11 +253,13 @@ def enable_logging(hass):
err_handler = logging.FileHandler( err_handler = logging.FileHandler(
err_log_path, mode='w', delay=True) err_log_path, mode='w', delay=True)
err_handler.setLevel(logging.WARNING) err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
err_handler.setFormatter( err_handler.setFormatter(
logging.Formatter('%(asctime)s %(name)s: %(message)s', logging.Formatter('%(asctime)s %(name)s: %(message)s',
datefmt='%y-%m-%d %H:%M:%S')) datefmt='%y-%m-%d %H:%M:%S'))
logging.getLogger('').addHandler(err_handler) logger = logging.getLogger('')
logger.addHandler(err_handler)
logger.setLevel(logging.INFO) # this sets the minimum log level
else: else:
_LOGGER.error( _LOGGER.error(

View file

@ -208,6 +208,11 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer):
""" Registers a path wit the server. """ """ Registers a path wit the server. """
self.paths.append((method, url, callback, require_auth)) self.paths.append((method, url, callback, require_auth))
def log_message(self, fmt, *args):
""" Redirect built-in log to HA logging """
# pylint: disable=no-self-use
_LOGGER.info(fmt, *args)
# pylint: disable=too-many-public-methods,too-many-locals # pylint: disable=too-many-public-methods,too-many-locals
class RequestHandler(SimpleHTTPRequestHandler): class RequestHandler(SimpleHTTPRequestHandler):
@ -225,6 +230,10 @@ class RequestHandler(SimpleHTTPRequestHandler):
self._session = None self._session = None
SimpleHTTPRequestHandler.__init__(self, req, client_addr, server) SimpleHTTPRequestHandler.__init__(self, req, client_addr, server)
def log_message(self, fmt, *arguments):
""" Redirect built-in log to HA logging """
_LOGGER.info(fmt, *arguments)
def _handle_request(self, method): # pylint: disable=too-many-branches def _handle_request(self, method): # pylint: disable=too-many-branches
""" Does some common checks and calls appropriate method. """ """ Does some common checks and calls appropriate method. """
url = urlparse(self.path) url = urlparse(self.path)

View file

@ -9,6 +9,7 @@ of entities and react to changes.
import os import os
import time import time
import logging import logging
import signal
import threading import threading
import enum import enum
import re import re
@ -73,13 +74,16 @@ class HomeAssistant(object):
will block until called. """ will block until called. """
request_shutdown = threading.Event() request_shutdown = threading.Event()
def stop_homeassistant(service): def stop_homeassistant(*args):
""" Stops Home Assistant. """ """ Stops Home Assistant. """
request_shutdown.set() request_shutdown.set()
self.services.register( self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant) DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant)
if os.name != "nt":
signal.signal(signal.SIGQUIT, stop_homeassistant)
while not request_shutdown.isSet(): while not request_shutdown.isSet():
try: try:
time.sleep(1) time.sleep(1)