diff --git a/atomicapp/__init__.py b/atomicapp/__init__.py
index 24dd79f8..e0c998d3 100644
--- a/atomicapp/__init__.py
+++ b/atomicapp/__init__.py
@@ -16,26 +16,25 @@
You should have received a copy of the GNU Lesser General Public License
along with Atomic App. If not, see .
"""
-
import logging
-
-
-def set_logging(name="atomicapp", level=logging.DEBUG):
- # create logger
- logger = logging.getLogger()
- logger.handlers = []
- logger.setLevel(level)
-
- # create console handler
- ch = logging.StreamHandler()
-
- # create formatter
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-
- # add formatter to ch
- ch.setFormatter(formatter)
-
- # add ch to logger
- logger.addHandler(ch)
-
-set_logging(level=logging.DEBUG) # override this however you want
+import os
+from atomicapp.constants import LOG_FILE, LOG_NAME
+
+# Let's make sure logging works upon first startup
+path = LOG_FILE
+
+# Touch if it doesn't exist
+if not (os.path.exists(path)):
+ try:
+ os.utime(path, None)
+ except:
+ open(path, 'a').close()
+
+if not (os.access(path, os.W_OK)):
+ raise RuntimeError("%s is not writeable" % path)
+
+# Setup log handling
+handler = logging.FileHandler(path)
+formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+handler.setFormatter(formatter)
+logging.getLogger(LOG_NAME).addHandler(handler)
diff --git a/atomicapp/cli/main.py b/atomicapp/cli/main.py
index a66ecd3b..69c563c0 100644
--- a/atomicapp/cli/main.py
+++ b/atomicapp/cli/main.py
@@ -210,6 +210,15 @@ def create_parser(self):
default=ANSWERS_FILE_SAMPLE_FORMAT,
choices=['ini', 'json', 'xml', 'yaml'],
help="The format for the answers.conf.sample file. Default: %s" % ANSWERS_FILE_SAMPLE_FORMAT)
+ globals.parser.add_argument(
+ "--logging-output",
+ dest="logging_output",
+ choices=['cockpit', 'stdout', 'syslog', 'none'],
+ help="Override the default logging output."
+ "stdout: We will only log to stdout/stderr"
+ "cockpit: Used with cockpit integration"
+ "syslog: Outputs to OS default syslog daemon"
+ "none: atomicapp will disable any logging")
# === "run" SUBPARSER ===
run_subparser = toplevel_subparsers.add_parser(
diff --git a/atomicapp/constants.py b/atomicapp/constants.py
index 2d21ec88..3aebd53b 100644
--- a/atomicapp/constants.py
+++ b/atomicapp/constants.py
@@ -48,6 +48,24 @@
ANSWERS_FILE_SAMPLE_FORMAT = 'ini'
WORKDIR = ".workdir"
LOCK_FILE = "/run/lock/atomicapp.lock"
+
+LOG_FILE = "/var/log/atomicapp.log"
+LOG_SYSLOG = "/dev/log"
+LOG_NAME = "atomicapp"
+LOG_CUSTOM_NAME = "atomicapp-custom"
+LOG_LEVELS = {
+ "notset": 0,
+ "debug": 10,
+ "info": 20,
+ "warning": 30,
+ "error": 40,
+ "critical": 50,
+ "default": 90,
+ "cockpit": 91,
+ "stdout": 92,
+ "syslog": 93,
+ "none": 94
+}
HOST_DIR = "/host"
DEFAULT_PROVIDER = "kubernetes"
diff --git a/atomicapp/display.py b/atomicapp/display.py
new file mode 100644
index 00000000..1975311d
--- /dev/null
+++ b/atomicapp/display.py
@@ -0,0 +1,221 @@
+"""
+ Copyright 2015 Red Hat, Inc.
+
+ This file is part of Atomic App.
+
+ Atomic App is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Atomic App is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with Atomic App. If not, see .
+"""
+
+import os
+import sys
+import time
+import logging
+import logging.handlers
+from atomicapp.constants import LOG_NAME, LOG_LEVELS, LOG_CUSTOM_NAME, LOG_SYSLOG
+
+
+class Display:
+
+ '''
+
+ In order to effectively use logging within Python, we manipulate the level
+ codes by adding our own custom ones. By doing so, we are able to have different
+ log legs effectively span all Python files.
+
+ Upon initialization, Atomic App checks to see if --logging-ouput= is set during
+ initalization and set's the appropriate log level based on either the default
+ or custom level value.
+
+ Default python level codes
+
+ NOTSET 0
+ DEBUG 10
+ INFO 20
+ WARNING 30
+ ERROR 40
+ CRITICAL 50
+
+ Custom log levels in constants.py
+
+ LOG_LEVELS = {
+ "default": 90,
+ "cockpit": 91,
+ "stdout": 92,
+ "syslog": 93,
+ "none": 94
+ }
+
+ '''
+
+ # Console colour codes
+ codeCodes = {
+ 'black': '0;30', 'bright gray': '0;37',
+ 'blue': '0;34', 'white': '1;37',
+ 'green': '0;32', 'bright blue': '1;34',
+ 'cyan': '0;36', 'bright green': '1;32',
+ 'red': '0;31', 'bright cyan': '1;36',
+ 'purple': '0;35', 'bright red': '1;31',
+ 'yellow': '0;33', 'bright purple': '1;35',
+ 'dark gray': '1;30', 'bright yellow': '1;33',
+ 'normal': '0'
+ }
+
+ # Upon initialization we grab the log level value and verbose level (-v or not)
+ def __init__(self):
+ self.logger = logging.getLogger(LOG_NAME)
+ self.verbose_level = self.logger.getEffectiveLevel()
+ self.log_level = logging.getLogger(LOG_CUSTOM_NAME).getEffectiveLevel()
+
+ def debug(self, msg, only=None):
+ self.display(msg, 10, 'cyan', only)
+
+ def verbose(self, msg, only=None):
+ self.display(msg, 10, 'cyan', only)
+
+ def info(self, msg, only=None):
+ self.display(msg, 20, 'green', only)
+
+ def warning(self, msg, only=None):
+ self.display(msg, 30, 'yellow', only)
+
+ def error(self, msg, only=None):
+ self.display(msg, 40, 'red', only)
+
+ # Display checks to see what log_level is being matched to and passes it along to
+ # the logging provider
+ def display(self, msg, code, color, only):
+ if self.log_level == LOG_LEVELS['cockpit']:
+ self._cockpit(msg, code, only)
+ elif self.log_level == LOG_LEVELS['stdout']:
+ self._stdout(msg, code)
+ elif self.log_level == LOG_LEVELS['syslog']:
+ self._syslog(msg, code)
+ elif self.log_level == LOG_LEVELS['none']:
+ return
+ else:
+ self._default(msg, code, color)
+
+ # Make sure that we're outputting stdout or stderr each time
+ def _sysflush(self):
+ if self.verbose_level is LOG_LEVELS['error'] or self.verbose_level is LOG_LEVELS['warning']:
+ sys.stderr.flush()
+ else:
+ sys.stdout.flush()
+
+ # Due to cockpit logging requirements, we will ONLY output logging that is designed as
+ # display.info("foo bar", "cockpit")
+ def _cockpit(self, msg, code, only):
+ if only is not "cockpit":
+ return
+
+ if self.verbose_level is not LOG_LEVELS['debug'] and code is LOG_LEVELS['debug']:
+ return
+
+ if code == LOG_LEVELS['debug']:
+ msg = "atomicapp.status.debug.message=%s" % msg
+ elif code == LOG_LEVELS['warning']:
+ msg = "atomicapp.status.warning.message=%s" % msg
+ elif code == LOG_LEVELS['error']:
+ msg = "atomicapp.status.error.message=%s" % msg
+ else:
+ msg = "atomicapp.status.info.message=%s" % msg
+
+ self._sysflush()
+ print(self._make_unicode(msg))
+
+ def _syslog(self, msg, code):
+ if self.verbose_level is not LOG_LEVELS['debug'] and code is LOG_LEVELS['debug']:
+ return
+
+ # Don't output anything, but everything into the syslog (/var/log/atomicapp.log)
+ handler = logging.handlers.SysLogHandler(address=LOG_SYSLOG)
+ self.logger.handlers = []
+ self.logger.addHandler(handler)
+
+ if code == LOG_LEVELS['debug']:
+ self.logger.info(msg)
+ elif code == LOG_LEVELS['warning']:
+ self.logger.warning(msg)
+ elif code == LOG_LEVELS['error']:
+ self.logger.error(msg)
+ else:
+ self.logger.info(msg)
+
+ def _default(self, msg, code, color):
+ if self.verbose_level is not LOG_LEVELS['debug'] and code is LOG_LEVELS['debug']:
+ return
+
+ if code == LOG_LEVELS['debug']:
+ self.logger.info(msg)
+ msg = "[DEBUG] %6d %0.2f %s" % (os.getpid(), time.time(), msg)
+ elif code == LOG_LEVELS['warning']:
+ self.logger.warning(msg)
+ msg = "[WARNING] %s" % msg
+ elif code == LOG_LEVELS['error']:
+ self.logger.error(msg)
+ msg = "[ERROR] %s" % msg
+ else:
+ self.logger.info(msg)
+ msg = "[INFO] %s" % msg
+
+ self._sysflush()
+ print(self._colorize(self._make_unicode(msg), color))
+
+ def _stdout(self, msg, code):
+ if self.verbose_level is not LOG_LEVELS['debug'] and code is LOG_LEVELS['debug']:
+ return
+
+ if code == LOG_LEVELS['debug']:
+ msg = "[DEBUG] %6d %0.2f %s" % (os.getpid(), time.time(), msg)
+ elif code == LOG_LEVELS['warning']:
+ msg = "[WARNING] %s" % msg
+ elif code == LOG_LEVELS['error']:
+ msg = "[ERROR] %s" % msg
+ else:
+ msg = "[INFO] %s" % msg
+
+ self._sysflush()
+ print(self._make_unicode(msg))
+
+ # Colors!
+ def _colorize(self, text, color):
+ return "\033[" + self.codeCodes[color] + "m" + text + "\033[0m"
+
+ # Convert all those pesky log messages to utf-8
+ def _make_unicode(self, input):
+ if type(input) != unicode:
+ input = input.decode('utf-8')
+ return input
+ else:
+ return input
+
+
+def set_logging(args):
+ if args.verbose:
+ level = logging.DEBUG
+ elif args.quiet:
+ level = logging.WARNING
+ else:
+ level = logging.INFO
+
+ # Let's check to see if any of our choices match the LOG_LEVELS constant! --logging-output
+ for k in LOG_LEVELS:
+ if args.logging_output == k:
+ custom_level = LOG_LEVELS[args.logging_output]
+ break
+ else:
+ custom_level = LOG_LEVELS['default']
+
+ logging.getLogger(LOG_NAME).setLevel(level)
+ logging.getLogger(LOG_CUSTOM_NAME).setLevel(custom_level)