diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index d7e81eee13c..e0502da705d 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -77,8 +77,72 @@ def get_arguments(): '--open-ui', action='store_true', 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(): @@ -90,15 +154,22 @@ def main(): config_dir = os.path.join(os.getcwd(), args.config) 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: hass = bootstrap.from_config_dict({ 'frontend': {}, 'demo': {} - }, config_dir=config_dir) + }, config_dir=config_dir, daemon=args.daemon, verbose=args.verbose) else: config_file = ensure_config_file(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: def open_browser(event): diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 98c7ae63a8b..74b831d58c1 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -150,7 +150,8 @@ def mount_local_lib_path(config_dir): # 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. @@ -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, {})) if enable_log: - enable_logging(hass) + enable_logging(hass, verbose, daemon) _ensure_loader_prepared(hass) @@ -195,7 +196,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True): 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 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 mount_local_lib_path(config_dir) - enable_logging(hass) + enable_logging(hass, verbose, daemon) config_dict = config_util.load_config_file(config_path) 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. """ - logging.basicConfig(level=logging.INFO) - fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) " - "[%(name)s] %(message)s%(reset)s") - try: - from colorlog import ColoredFormatter - logging.getLogger().handlers[0].setFormatter(ColoredFormatter( - fmt, - datefmt='%y-%m-%d %H:%M:%S', - reset=True, - log_colors={ - 'DEBUG': 'cyan', - 'INFO': 'green', - 'WARNING': 'yellow', - 'ERROR': 'red', - 'CRITICAL': 'red', - } - )) - except ImportError: - _LOGGER.warning( - "Colorlog package not found, console coloring disabled") + if not daemon: + logging.basicConfig(level=logging.INFO) + fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) " + "[%(name)s] %(message)s%(reset)s") + try: + from colorlog import ColoredFormatter + logging.getLogger().handlers[0].setFormatter(ColoredFormatter( + fmt, + datefmt='%y-%m-%d %H:%M:%S', + reset=True, + log_colors={ + 'DEBUG': 'cyan', + 'INFO': 'green', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'red', + } + )) + except ImportError: + _LOGGER.warning( + "Colorlog package not found, console coloring disabled") # Log errors to a file if we have write access to file or config dir err_log_path = hass.config.path('home-assistant.log') @@ -251,11 +253,13 @@ def enable_logging(hass): err_handler = logging.FileHandler( 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( logging.Formatter('%(asctime)s %(name)s: %(message)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: _LOGGER.error( diff --git a/homeassistant/components/http.py b/homeassistant/components/http.py index a28def8e7ba..0b4f6165bed 100644 --- a/homeassistant/components/http.py +++ b/homeassistant/components/http.py @@ -208,6 +208,11 @@ class HomeAssistantHTTPServer(ThreadingMixIn, HTTPServer): """ Registers a path wit the server. """ 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 class RequestHandler(SimpleHTTPRequestHandler): @@ -225,6 +230,10 @@ class RequestHandler(SimpleHTTPRequestHandler): self._session = None 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 """ Does some common checks and calls appropriate method. """ url = urlparse(self.path) diff --git a/homeassistant/core.py b/homeassistant/core.py index c04e9a9ab63..1e48fce8029 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -9,6 +9,7 @@ of entities and react to changes. import os import time import logging +import signal import threading import enum import re @@ -73,13 +74,16 @@ class HomeAssistant(object): will block until called. """ request_shutdown = threading.Event() - def stop_homeassistant(service): + def stop_homeassistant(*args): """ Stops Home Assistant. """ request_shutdown.set() self.services.register( DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant) + if os.name != "nt": + signal.signal(signal.SIGQUIT, stop_homeassistant) + while not request_shutdown.isSet(): try: time.sleep(1)