Skip to content
This repository was archived by the owner on Jan 19, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 0 additions & 41 deletions atomicapp/__init__.py
Original file line number Diff line number Diff line change
@@ -1,41 +0,0 @@
"""
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 <http://www.gnu.org/licenses/>.
"""

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
152 changes: 152 additions & 0 deletions atomicapp/applogging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""
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 <http://www.gnu.org/licenses/>.
"""

import sys
import logging

from atomicapp.constants import (LOGGER_COCKPIT,
LOGGER_DEFAULT)


class colorizeOutputFormatter(logging.Formatter):
"""
A class to colorize the log msgs based on log level
"""

def format(self, record):
# Call the parent class to do formatting.
msg = super(colorizeOutputFormatter, self).format(record)

# Now post process and colorize if needed
if record.levelno == logging.DEBUG:
msg = self._colorize(msg, 'cyan')
elif record.levelno == logging.WARNING:
msg = self._colorize(msg, 'yellow')
elif record.levelno == logging.INFO:
msg = self._colorize(msg, 'white')
elif record.levelno == logging.ERROR:
msg = self._colorize(msg, 'red')
else:
raise Exception("Invalid logging level {}".format(record.levelno))
return self._make_unicode(msg)

def _colorize(self, text, color):
"""
Colorize based upon the color codes indicated.
"""
# Console color codes
colorCodes = {
'white': '0', 'bright white': '1;37',
'blue': '0;34', 'bright blue': '1;34',
'green': '0;32', 'bright green': '1;32',
'cyan': '0;36', 'bright cyan': '1;36',
'red': '0;31', 'bright red': '1;31',
'purple': '0;35', 'bright purple': '1;35',
'yellow': '0;33', 'bright yellow': '1;33',
}
return "\033[" + colorCodes[color] + "m" + text + "\033[0m"

def _make_unicode(self, input):
"""
Convert all input to utf-8 for multi language support
"""
if type(input) != unicode:
input = input.decode('utf-8')
return input


class Logging:

@staticmethod
def setup_logging(verbose=None, quiet=None, logtype=None):
"""
This function sets up logging based on the logtype requested.
The 'none' level outputs no logs at all
The 'cockpit' level outputs just logs for the cockpit logger
The 'nocolor' level prints out normal log msgs (no cockpit) without color
The 'color' level prints out normal log msgs (no cockpit) with color
"""

# If no logtype was set then let's have a sane default
# If connected to a tty, then default to color, else, no color
if not logtype:
if sys.stdout.isatty():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for this code

logtype = 'color'
else:
logtype = 'nocolor'

# Determine what logging level we should use
if verbose:
logging_level = logging.DEBUG
elif quiet:
logging_level = logging.WARNING
else:
logging_level = logging.INFO

# Get the loggers and clear out the handlers (allows this function
# to be ran more than once)
logger = logging.getLogger(LOGGER_DEFAULT)
logger.handlers = []
cockpit_logger = logging.getLogger(LOGGER_COCKPIT)
cockpit_logger.handlers = []

if logtype == 'none':
# blank out both loggers
logger.addHandler(logging.NullHandler())
cockpit_logger.addHandler(logging.NullHandler())
return

if logtype == 'cockpit':
# blank out normal log messages
logger.addHandler(logging.NullHandler())

# configure cockpit logger
handler = logging.StreamHandler(stream=sys.stdout)
formatter = logging.Formatter('atomicapp.status.%(levelname)s.message=%(message)s')
handler.setFormatter(formatter)
cockpit_logger.addHandler(handler)
cockpit_logger.setLevel(logging_level)
return

if logtype == 'nocolor':
# blank out cockpit log messages
cockpit_logger.addHandler(logging.NullHandler())

# configure logger for basic no color printing to stdout
handler = logging.StreamHandler(stream=sys.stdout)
formatter = logging.Formatter('%(asctime)s - [%(levelname)s] - %(filename)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging_level)
return

if logtype == 'color':
# blank out cockpit log messages
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we not need to add
cockpit_logger = logging.getLogger(LOGGER_COCKPIT) as well? or no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, it gets created befores these if statements. It was a mistake to have cockpit_logger = logging.getLogger(LOGGER_COCKPIT) in the if statement above. removing it now.

cockpit_logger.addHandler(logging.NullHandler())

# configure logger for color printing to stdout
handler = logging.StreamHandler(stream=sys.stdout)
formatter = colorizeOutputFormatter('%(asctime)s - [%(levelname)s] - %(filename)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging_level)
return

# If we made it here then there is an error
raise Exception("Invalid logging output type: {}".format(logtype))
39 changes: 24 additions & 15 deletions atomicapp/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from lockfile import LockFile
from lockfile import AlreadyLocked

from atomicapp import set_logging
from atomicapp.applogging import Logging
from atomicapp.constants import (__ATOMICAPPVERSION__,
__NULECULESPECVERSION__,
ANSWERS_FILE,
Expand All @@ -34,12 +34,13 @@
CACHE_DIR,
HOST_DIR,
LOCK_FILE,
LOGGER_DEFAULT,
PROVIDERS)
from atomicapp.nulecule import NuleculeManager
from atomicapp.nulecule.exceptions import NuleculeException
from atomicapp.utils import Utils

logger = logging.getLogger(__name__)
logger = logging.getLogger(LOGGER_DEFAULT)


def print_app_location(app_path):
Expand Down Expand Up @@ -218,7 +219,7 @@ def create_parser(self):
action="store_true",
help=(
"Don't actually call provider. The commands that should be "
"run will be sent to stdout but not run."))
"run will be logged but not run."))
globals_parser.add_argument(
"--answers-format",
dest="answers_format",
Expand Down Expand Up @@ -249,6 +250,18 @@ def create_parser(self):
"--providerapi",
dest="providerapi",
help='Value for providerapi answers option.')
globals_parser.add_argument(
"--logtype",
dest="logtype",
choices=['cockpit', 'color', 'nocolor', 'none'],
help="""
Override the default logging output. The options are:
nocolor: we will only log to stdout;
color: log to stdout with color;
cockpit: used with cockpit integration;
none: atomicapp will disable any logging.
If nothing is set and logging to file then 'nocolor' by default.
If nothing is set and logging to tty then 'color' by default.""")

# === "run" SUBPARSER ===
run_subparser = toplevel_subparsers.add_parser(
Expand Down Expand Up @@ -367,6 +380,9 @@ def create_parser(self):
def run(self):
cmdline = sys.argv[1:] # Grab args from cmdline

# Initial setup of logging (to allow for a few early debug statements)
Logging.setup_logging(verbose=True, quiet=False)

# If we are running in an openshift pod (via `oc new-app`) then
# there is no cmdline but we want to default to "atomicapp run".
if Utils.running_on_openshift():
Expand Down Expand Up @@ -399,11 +415,15 @@ def run(self):
if args.mode:
args.action = args.mode # Allow mode to override 'action'
cmdline.insert(0, args.action) # Place 'action' at front
logger.info("Action/Mode Selected is: %s" % args.action)

# Finally, parse args and give error if necessary
args = self.parser.parse_args(cmdline)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needs to be moved up before setup_logging, passing -v doesn't work since we haven't parsed args yet

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm. so you are saying the logger.debug() statement needs to be moved before setup_logging?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I see what you are saying now. You are saying that self.parser.parse_args() needs to be moved above the setup_logging() call. I don't think this is true because the call to self.parser.parse_known_args() should have captured --verbose from the cmdline. I'll run some tests to see if it changes things.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving it down did make the test pass. Now just need to figure out what part of my logic is flawed in the first part.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dustymabe Yeah, I'm not able to receive any debug information at all by passing -v or --verbose


# Setup logging (now with arguments from cmdline) and log a few msgs
Logging.setup_logging(args.verbose, args.quiet, args.logtype)
logger.info("Action/Mode Selected is: %s" % args.action)
logger.debug("Final parsed cmdline: {}".format(' '.join(cmdline)))

# In the case of Atomic CLI we want to allow the user to specify
# a directory if they want to for "run". For that reason we won't
# default the RUN label for Atomic App to provide an app_spec argument.
Expand All @@ -425,17 +445,6 @@ def run(self):
if hasattr(args, item) and getattr(args, item) is not None:
args.cli_answers[item] = getattr(args, item)

# Set logging level
if args.verbose:
set_logging(level=logging.DEBUG)
elif args.quiet:
set_logging(level=logging.WARNING)
else:
set_logging(level=logging.INFO)

# Now that we have set the logging level let's print out the cmdline
logger.debug("Final parsed cmdline: {}".format(' '.join(cmdline)))

lock = LockFile(os.path.join(Utils.getRoot(), LOCK_FILE))
try:
lock.acquire(timeout=-1)
Expand Down
4 changes: 4 additions & 0 deletions atomicapp/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
ANSWERS_FILE_SAMPLE_FORMAT = 'ini'
WORKDIR = ".workdir"
LOCK_FILE = "/run/lock/atomicapp.lock"

LOGGER_DEFAULT = "atomicapp"
LOGGER_COCKPIT = "cockpit"

HOST_DIR = "/host"

DEFAULT_PROVIDER = "kubernetes"
Expand Down
10 changes: 9 additions & 1 deletion atomicapp/nulecule/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from atomicapp.constants import (APP_ENT_PATH,
EXTERNAL_APP_DIR,
GLOBAL_CONF,
LOGGER_COCKPIT,
LOGGER_DEFAULT,
MAIN_FILE,
RESOURCE_KEY,
PARAMS_KEY,
Expand All @@ -27,7 +29,8 @@

from jsonpointer import resolve_pointer, set_pointer, JsonPointerException

logger = logging.getLogger(__name__)
cockpit_logger = logging.getLogger(LOGGER_COCKPIT)
logger = logging.getLogger(LOGGER_DEFAULT)


class Nulecule(NuleculeBase):
Expand Down Expand Up @@ -100,6 +103,7 @@ def unpack(cls, image, dest, config=None, namespace=GLOBAL_CONF,
docker_handler = DockerHandler(dryrun=dryrun)
docker_handler.pull(image)
docker_handler.extract(image, APP_ENT_PATH, dest, update)
cockpit_logger.info("All dependencies installed successfully.")
return cls.load_from_path(
dest, config=config, namespace=namespace, nodeps=nodeps,
dryrun=dryrun, update=update)
Expand Down Expand Up @@ -159,6 +163,7 @@ def run(self, provider_key=None, dryrun=False):
# Process components
for component in self.components:
component.run(provider_key, dryrun)
cockpit_logger.info("Component %s installed successfully" % provider_key)

def stop(self, provider_key=None, dryrun=False):
"""
Expand Down Expand Up @@ -271,6 +276,7 @@ def load(self, nodeps=False, dryrun=False):
"""
Load external application of the Nulecule component.
"""
cockpit_logger.info("Loading app %s ." % self.name)
if self.source:
if nodeps:
logger.info(
Expand All @@ -282,6 +288,7 @@ def run(self, provider_key, dryrun=False):
"""
Run the Nulecule component with the specified provider,
"""
cockpit_logger.info("Deploying component %s ..." % self.name)
if self._app:
self._app.run(provider_key, dryrun)
return
Expand Down Expand Up @@ -346,6 +353,7 @@ def load_external_application(self, dryrun=False, update=False):
update=update
)
self._app = nulecule
cockpit_logger.info("Copied app successfully.")

@property
def components(self):
Expand Down
Loading