1#
2# CDDL HEADER START
3#
4# The contents of this file are subject to the terms of the
5# Common Development and Distribution License (the "License").
6# You may not use this file except in compliance with the License.
7#
8# See LICENSE.txt included in this distribution for the specific
9# language governing permissions and limitations under the License.
10#
11# When distributing Covered Code, include this CDDL HEADER in each
12# file and include the License file at LICENSE.txt.
13# If applicable, add the following below this CDDL HEADER, with the
14# fields enclosed by brackets "[]" replaced with your own identifying
15# information: Portions Copyright [yyyy] [name of copyright owner]
16#
17# CDDL HEADER END
18#
19
20#
21# Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
22#
23
24import logging
25import sys
26import os
27import argparse
28from logging.handlers import RotatingFileHandler
29from .exitvals import (
30    FAILURE_EXITVAL,
31)
32
33
34def fatal(msg, exit=True):
35    """
36    Print message to standard error output and exit
37    unless the exit parameter is False
38    :param msg: message
39    :param exit
40    """
41    print(msg, file=sys.stderr)
42    if exit:
43        sys.exit(FAILURE_EXITVAL)
44    else:
45        return FAILURE_EXITVAL
46
47
48def add_log_level_argument(parser):
49    parser.add_argument('-l', '--loglevel', action=LogLevelAction,
50                        help='Set log level (e.g. \"ERROR\")',
51                        default=logging.INFO)
52
53
54class LogLevelAction(argparse.Action):
55    """
56    This class is supposed to be used as action for argparse.
57    The action is handled by trying to find the option argument as attribute
58    in the logging module. On success, its numeric value is stored in the
59    namespace, otherwise ValueError exception is thrown.
60    """
61    def __init__(self, option_strings, dest, nargs=None, **kwargs):
62        if nargs is not None:
63            raise ValueError("nargs not allowed")
64        super(LogLevelAction, self).__init__(option_strings, dest, **kwargs)
65
66    def __call__(self, parser, namespace, values, option_string=None):
67        # print('%r %r %r' % (namespace, values, option_string))
68        val = get_log_level(values)
69        if val:
70            setattr(namespace, self.dest, val)
71        else:
72            raise ValueError("invalid log level '{}'".format(values))
73
74
75def get_log_level(level):
76    """
77    :param level: expressed in string (upper or lower case) or integer
78    :return: integer representation of the log level or None
79    """
80    if type(level) is int:
81        return level
82
83    # This could be a string storing a number.
84    try:
85        return int(level)
86    except ValueError:
87        pass
88
89    # Look up the name in the logging module.
90    try:
91        value = getattr(logging, level.upper())
92        if type(value) is int:
93            return value
94        else:
95            return None
96    except AttributeError:
97        return None
98
99
100def get_class_basename():
101    return __name__.split('.')[0]
102
103
104def get_console_logger(name=__name__, level=logging.INFO,
105                       format='%(message)s'):
106    """
107    Get logger that logs logging.ERROR and higher to stderr, the rest
108    to stdout. For logging.DEBUG level more verbose format is used.
109
110    :param name: name of the logger
111    :param level: base logging level
112    :param format: format string to use
113    :return: logger
114    """
115    if level is None:
116        level = logging.INFO
117
118    if level == logging.DEBUG:
119        format = '%(asctime)s %(levelname)8s %(name)s | %(message)s'
120
121    formatter = logging.Formatter(format)
122
123    stderr_handler = logging.StreamHandler(stream=sys.stderr)
124    stderr_handler.setFormatter(formatter)
125
126    stdout_handler = logging.StreamHandler(stream=sys.stdout)
127    stdout_handler.setFormatter(formatter)
128
129    stderr_handler.addFilter(lambda rec: rec.levelno >= logging.ERROR)
130    stdout_handler.addFilter(lambda rec: rec.levelno < logging.ERROR)
131
132    logger = logging.getLogger(name)
133    logger.setLevel(level)
134    logger.propagate = False
135    logger.handlers = []
136    logger.addHandler(stdout_handler)
137    logger.addHandler(stderr_handler)
138
139    return logger
140
141
142def get_batch_logger(logdir, project_name, loglevel, backupcount,
143                     name=__name__):
144    """
145    Get rotating file logger for storing logs of mirroring of given project.
146    :param logdir: log directory
147    :param project_name: name of the project
148    :param loglevel: logging level
149    :param backupcount count of log files to keep around
150    :param name name of the logger
151    :return logger
152    """
153
154    logger = logging.getLogger(name)
155
156    logfile = os.path.join(logdir, project_name + ".log")
157
158    handler = RotatingFileHandler(logfile, maxBytes=0, mode='a',
159                                  backupCount=backupcount)
160    formatter = logging.Formatter("%(asctime)s - %(levelname)s: "
161                                  "%(message)s", '%m/%d/%Y %I:%M:%S %p')
162    handler.setFormatter(formatter)
163    handler.doRollover()
164
165    logger.setLevel(loglevel)
166    logger.propagate = False
167    logger.handlers = []
168    logger.addHandler(handler)
169
170    return logger
171