kiwi.logger

  1# Copyright (c) 2015 SUSE Linux GmbH.  All rights reserved.
  2#
  3# This file is part of kiwi.
  4#
  5# kiwi is free software: you can redistribute it and/or modify
  6# it under the terms of the GNU General Public License as published by
  7# the Free Software Foundation, either version 3 of the License, or
  8# (at your option) any later version.
  9#
 10# kiwi is distributed in the hope that it will be useful,
 11# but WITHOUT ANY WARRANTY; without even the implied warranty of
 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13# GNU General Public License for more details.
 14#
 15# You should have received a copy of the GNU General Public License
 16# along with kiwi.  If not, see <http://www.gnu.org/licenses/>
 17#
 18from typing import (
 19    Dict, Optional, List
 20)
 21import logging
 22from kiwi.logger_socket import PlainTextSocketHandler
 23import sys
 24
 25# project
 26from kiwi.logger_color_formatter import ColorFormatter
 27from kiwi.logger_filter import (
 28    LoggerSchedulerFilter,
 29    InfoFilter,
 30    DebugFilter,
 31    ErrorFilter,
 32    WarningFilter
 33)
 34
 35from kiwi.exceptions import (
 36    KiwiLogFileSetupFailed,
 37    KiwiLogSocketSetupFailed
 38)
 39
 40
 41class Logger(logging.Logger):
 42    """
 43    **Extended logging facility based on Python logging**
 44
 45    :param str name: name of the logger
 46    """
 47    def __init__(self, name: str):
 48        logging.Logger.__init__(self, name)
 49        self.log_handlers: Dict = {}
 50        self.logfile: Optional[str] = None
 51        # log INFO to stdout
 52        self._add_stream_handler(
 53            'info',
 54            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
 55            [InfoFilter(), LoggerSchedulerFilter()]
 56        )
 57        # log WARNING messages to stdout
 58        self._add_stream_handler(
 59            'warning',
 60            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
 61            [WarningFilter()]
 62        )
 63        # log DEBUG messages to stdout
 64        self._add_stream_handler(
 65            'debug',
 66            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
 67            [DebugFilter()]
 68        )
 69        # log ERROR messages to stderr
 70        self._add_stream_handler(
 71            'error',
 72            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
 73            [ErrorFilter()],
 74            sys.__stderr__
 75        )
 76        self.log_level = self.level
 77        self.log_flags: Dict[str, bool] = {}
 78
 79    def getLogLevel(self) -> int:
 80        """
 81        Return currently used log level
 82
 83        :return: log level number
 84
 85        :rtype: int
 86        """
 87        return self.log_level
 88
 89    def getLogFlags(self) -> Dict[str, bool]:
 90        """
 91        Return logging flags
 92
 93        :return:
 94            Dictionary with flags and their activation status
 95
 96        :rtype: dict
 97        """
 98        return self.log_flags
 99
100    def setLogLevel(
101        self, level: int, except_for: List[str] = [], only_for: List[str] = []
102    ) -> None:
103        """
104        Set custom log level for all console handlers
105
106        :param int level: log level number
107        :param list except_for:
108            set log level to all handlers except for the given list
109        :param list only_for:
110            set log level to the given handlers only
111
112        if both except_for and only_for handlers are specified,
113        the except_for list will be ignored
114        """
115        self.log_level = level
116        if only_for:
117            for handler_type in self.log_handlers:
118                if handler_type in only_for:
119                    self.log_handlers[handler_type].setLevel(level)
120            return
121        for handler_type in self.log_handlers:
122            if handler_type not in except_for:
123                self.log_handlers[handler_type].setLevel(level)
124
125    def setLogFlag(self, flag: str, value: bool = True) -> None:
126        """
127        Set logging flag for further properties of the logging facility
128        Available flags are:
129
130        * run-scripts-in-screen
131
132        :param str flag: name
133        """
134        self.log_flags[flag] = value
135
136    def set_color_format(self) -> None:
137        """
138        Set color format for all console handlers
139        """
140        for handler_type in self.log_handlers:
141            message_format = None
142            if handler_type == 'debug':
143                message_format = \
144                    '$LIGHTCOLOR[ %(levelname)-8s]: %(asctime)-8s | %(message)s'
145            elif handler_type == 'warning' or handler_type == 'error':
146                message_format = \
147                    '$COLOR[ %(levelname)-8s]: %(asctime)-8s | %(message)s'
148
149            if message_format:
150                self.log_handlers[handler_type].setFormatter(
151                    ColorFormatter(message_format, '%H:%M:%S')
152                )
153
154    def set_logfile(self, filename: str) -> None:
155        """
156        Set logfile handler
157
158        :param str filename: logfile file path
159        """
160        try:
161            if filename == 'stdout':
162                # special case, log usual log file contents to stdout
163                handler = logging.StreamHandler(sys.__stdout__)
164            else:
165                handler = logging.FileHandler(
166                    filename=filename, encoding='utf-8'
167                )
168                self.logfile = filename
169            handler.setFormatter(
170                logging.Formatter(
171                    '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
172                    '%H:%M:%S'
173                )
174            )
175            handler.addFilter(LoggerSchedulerFilter())
176            self.addHandler(handler)
177            self.log_handlers['file'] = handler
178        except Exception as e:
179            raise KiwiLogFileSetupFailed(
180                f'{type(e).__name__}: {format(e)}'
181            )
182
183    def set_log_socket(self, filename: str) -> None:
184        """
185        Set log socket handler
186
187        :param str filename:
188            UDS socket file path. Note if there is no server
189            listening on the socket the log handler setup
190            will fail
191        """
192        try:
193            handler = PlainTextSocketHandler(filename, None)
194            handler.makeSocket()
195            handler.setFormatter(
196                logging.Formatter(
197                    '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
198                    '%H:%M:%S'
199                )
200            )
201            handler.addFilter(LoggerSchedulerFilter())
202            self.addHandler(handler)
203            self.log_handlers['socket'] = handler
204        except Exception as e:
205            raise KiwiLogSocketSetupFailed(
206                'UDS socket: {0}:{1}: {2}'.format(filename, type(e).__name__, e)
207            )
208
209    def get_logfile(self) -> Optional[str]:
210        """
211        Return file path name of logfile
212
213        :return: file path
214
215        :rtype: str
216        """
217        return self.logfile
218
219    @staticmethod
220    def progress(
221        current: int, total: int, prefix: str, bar_length: int = 40
222    ) -> None:
223        """
224        Custom progress log information. progress information is
225        intentionally only logged to stdout and will bypass any
226        handlers. We don't want this information to show up in
227        the log file
228
229        :param int current: current item
230        :param int total: total number of items
231        :param string prefix: prefix name
232        :param int bar_length: length of progress bar
233        """
234        try:
235            percent = float(current) / total
236        except Exception:
237            # we don't want the progress to raise an exception
238            # In case of any error e.g division by zero the current
239            # way out is to skip the progress update
240            return
241        hashes = '#' * int(round(percent * bar_length))
242        spaces = ' ' * (bar_length - len(hashes))
243        sys.stdout.write('\r{0}: [{1}] {2}%'.format(
244            prefix, hashes + spaces, int(round(percent * 100))
245        ))
246        if current == 100:
247            sys.stdout.write('\n')
248        sys.stdout.flush()
249
250    def _add_stream_handler(
251        self, handler_type, message_format, message_filter,
252        channel=sys.__stdout__
253    ):
254        handler = logging.StreamHandler(channel)
255        handler.setFormatter(
256            logging.Formatter(message_format, '%H:%M:%S')
257        )
258        for rule in message_filter:
259            handler.addFilter(rule)
260        self.addHandler(handler)
261        self.log_handlers[handler_type] = handler
class Logger(logging.Logger):
 42class Logger(logging.Logger):
 43    """
 44    **Extended logging facility based on Python logging**
 45
 46    :param str name: name of the logger
 47    """
 48    def __init__(self, name: str):
 49        logging.Logger.__init__(self, name)
 50        self.log_handlers: Dict = {}
 51        self.logfile: Optional[str] = None
 52        # log INFO to stdout
 53        self._add_stream_handler(
 54            'info',
 55            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
 56            [InfoFilter(), LoggerSchedulerFilter()]
 57        )
 58        # log WARNING messages to stdout
 59        self._add_stream_handler(
 60            'warning',
 61            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
 62            [WarningFilter()]
 63        )
 64        # log DEBUG messages to stdout
 65        self._add_stream_handler(
 66            'debug',
 67            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
 68            [DebugFilter()]
 69        )
 70        # log ERROR messages to stderr
 71        self._add_stream_handler(
 72            'error',
 73            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
 74            [ErrorFilter()],
 75            sys.__stderr__
 76        )
 77        self.log_level = self.level
 78        self.log_flags: Dict[str, bool] = {}
 79
 80    def getLogLevel(self) -> int:
 81        """
 82        Return currently used log level
 83
 84        :return: log level number
 85
 86        :rtype: int
 87        """
 88        return self.log_level
 89
 90    def getLogFlags(self) -> Dict[str, bool]:
 91        """
 92        Return logging flags
 93
 94        :return:
 95            Dictionary with flags and their activation status
 96
 97        :rtype: dict
 98        """
 99        return self.log_flags
100
101    def setLogLevel(
102        self, level: int, except_for: List[str] = [], only_for: List[str] = []
103    ) -> None:
104        """
105        Set custom log level for all console handlers
106
107        :param int level: log level number
108        :param list except_for:
109            set log level to all handlers except for the given list
110        :param list only_for:
111            set log level to the given handlers only
112
113        if both except_for and only_for handlers are specified,
114        the except_for list will be ignored
115        """
116        self.log_level = level
117        if only_for:
118            for handler_type in self.log_handlers:
119                if handler_type in only_for:
120                    self.log_handlers[handler_type].setLevel(level)
121            return
122        for handler_type in self.log_handlers:
123            if handler_type not in except_for:
124                self.log_handlers[handler_type].setLevel(level)
125
126    def setLogFlag(self, flag: str, value: bool = True) -> None:
127        """
128        Set logging flag for further properties of the logging facility
129        Available flags are:
130
131        * run-scripts-in-screen
132
133        :param str flag: name
134        """
135        self.log_flags[flag] = value
136
137    def set_color_format(self) -> None:
138        """
139        Set color format for all console handlers
140        """
141        for handler_type in self.log_handlers:
142            message_format = None
143            if handler_type == 'debug':
144                message_format = \
145                    '$LIGHTCOLOR[ %(levelname)-8s]: %(asctime)-8s | %(message)s'
146            elif handler_type == 'warning' or handler_type == 'error':
147                message_format = \
148                    '$COLOR[ %(levelname)-8s]: %(asctime)-8s | %(message)s'
149
150            if message_format:
151                self.log_handlers[handler_type].setFormatter(
152                    ColorFormatter(message_format, '%H:%M:%S')
153                )
154
155    def set_logfile(self, filename: str) -> None:
156        """
157        Set logfile handler
158
159        :param str filename: logfile file path
160        """
161        try:
162            if filename == 'stdout':
163                # special case, log usual log file contents to stdout
164                handler = logging.StreamHandler(sys.__stdout__)
165            else:
166                handler = logging.FileHandler(
167                    filename=filename, encoding='utf-8'
168                )
169                self.logfile = filename
170            handler.setFormatter(
171                logging.Formatter(
172                    '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
173                    '%H:%M:%S'
174                )
175            )
176            handler.addFilter(LoggerSchedulerFilter())
177            self.addHandler(handler)
178            self.log_handlers['file'] = handler
179        except Exception as e:
180            raise KiwiLogFileSetupFailed(
181                f'{type(e).__name__}: {format(e)}'
182            )
183
184    def set_log_socket(self, filename: str) -> None:
185        """
186        Set log socket handler
187
188        :param str filename:
189            UDS socket file path. Note if there is no server
190            listening on the socket the log handler setup
191            will fail
192        """
193        try:
194            handler = PlainTextSocketHandler(filename, None)
195            handler.makeSocket()
196            handler.setFormatter(
197                logging.Formatter(
198                    '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
199                    '%H:%M:%S'
200                )
201            )
202            handler.addFilter(LoggerSchedulerFilter())
203            self.addHandler(handler)
204            self.log_handlers['socket'] = handler
205        except Exception as e:
206            raise KiwiLogSocketSetupFailed(
207                'UDS socket: {0}:{1}: {2}'.format(filename, type(e).__name__, e)
208            )
209
210    def get_logfile(self) -> Optional[str]:
211        """
212        Return file path name of logfile
213
214        :return: file path
215
216        :rtype: str
217        """
218        return self.logfile
219
220    @staticmethod
221    def progress(
222        current: int, total: int, prefix: str, bar_length: int = 40
223    ) -> None:
224        """
225        Custom progress log information. progress information is
226        intentionally only logged to stdout and will bypass any
227        handlers. We don't want this information to show up in
228        the log file
229
230        :param int current: current item
231        :param int total: total number of items
232        :param string prefix: prefix name
233        :param int bar_length: length of progress bar
234        """
235        try:
236            percent = float(current) / total
237        except Exception:
238            # we don't want the progress to raise an exception
239            # In case of any error e.g division by zero the current
240            # way out is to skip the progress update
241            return
242        hashes = '#' * int(round(percent * bar_length))
243        spaces = ' ' * (bar_length - len(hashes))
244        sys.stdout.write('\r{0}: [{1}] {2}%'.format(
245            prefix, hashes + spaces, int(round(percent * 100))
246        ))
247        if current == 100:
248            sys.stdout.write('\n')
249        sys.stdout.flush()
250
251    def _add_stream_handler(
252        self, handler_type, message_format, message_filter,
253        channel=sys.__stdout__
254    ):
255        handler = logging.StreamHandler(channel)
256        handler.setFormatter(
257            logging.Formatter(message_format, '%H:%M:%S')
258        )
259        for rule in message_filter:
260            handler.addFilter(rule)
261        self.addHandler(handler)
262        self.log_handlers[handler_type] = handler

Extended logging facility based on Python logging

Parameters
  • str name: name of the logger
Logger(name: str)
48    def __init__(self, name: str):
49        logging.Logger.__init__(self, name)
50        self.log_handlers: Dict = {}
51        self.logfile: Optional[str] = None
52        # log INFO to stdout
53        self._add_stream_handler(
54            'info',
55            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
56            [InfoFilter(), LoggerSchedulerFilter()]
57        )
58        # log WARNING messages to stdout
59        self._add_stream_handler(
60            'warning',
61            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
62            [WarningFilter()]
63        )
64        # log DEBUG messages to stdout
65        self._add_stream_handler(
66            'debug',
67            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
68            [DebugFilter()]
69        )
70        # log ERROR messages to stderr
71        self._add_stream_handler(
72            'error',
73            '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
74            [ErrorFilter()],
75            sys.__stderr__
76        )
77        self.log_level = self.level
78        self.log_flags: Dict[str, bool] = {}

Initialize the logger with a name and an optional level.

log_handlers: Dict
logfile: Optional[str]
log_level
log_flags: Dict[str, bool]
def getLogLevel(self) -> int:
80    def getLogLevel(self) -> int:
81        """
82        Return currently used log level
83
84        :return: log level number
85
86        :rtype: int
87        """
88        return self.log_level

Return currently used log level

Returns

log level number

def getLogFlags(self) -> Dict[str, bool]:
90    def getLogFlags(self) -> Dict[str, bool]:
91        """
92        Return logging flags
93
94        :return:
95            Dictionary with flags and their activation status
96
97        :rtype: dict
98        """
99        return self.log_flags

Return logging flags

Returns
Dictionary with flags and their activation status
def setLogLevel( self, level: int, except_for: List[str] = [], only_for: List[str] = []) -> None:
101    def setLogLevel(
102        self, level: int, except_for: List[str] = [], only_for: List[str] = []
103    ) -> None:
104        """
105        Set custom log level for all console handlers
106
107        :param int level: log level number
108        :param list except_for:
109            set log level to all handlers except for the given list
110        :param list only_for:
111            set log level to the given handlers only
112
113        if both except_for and only_for handlers are specified,
114        the except_for list will be ignored
115        """
116        self.log_level = level
117        if only_for:
118            for handler_type in self.log_handlers:
119                if handler_type in only_for:
120                    self.log_handlers[handler_type].setLevel(level)
121            return
122        for handler_type in self.log_handlers:
123            if handler_type not in except_for:
124                self.log_handlers[handler_type].setLevel(level)

Set custom log level for all console handlers

Parameters
  • int level: log level number
  • list except_for: set log level to all handlers except for the given list
  • list only_for: set log level to the given handlers only

if both except_for and only_for handlers are specified, the except_for list will be ignored

def setLogFlag(self, flag: str, value: bool = True) -> None:
126    def setLogFlag(self, flag: str, value: bool = True) -> None:
127        """
128        Set logging flag for further properties of the logging facility
129        Available flags are:
130
131        * run-scripts-in-screen
132
133        :param str flag: name
134        """
135        self.log_flags[flag] = value

Set logging flag for further properties of the logging facility Available flags are:

  • run-scripts-in-screen
Parameters
  • str flag: name
def set_color_format(self) -> None:
137    def set_color_format(self) -> None:
138        """
139        Set color format for all console handlers
140        """
141        for handler_type in self.log_handlers:
142            message_format = None
143            if handler_type == 'debug':
144                message_format = \
145                    '$LIGHTCOLOR[ %(levelname)-8s]: %(asctime)-8s | %(message)s'
146            elif handler_type == 'warning' or handler_type == 'error':
147                message_format = \
148                    '$COLOR[ %(levelname)-8s]: %(asctime)-8s | %(message)s'
149
150            if message_format:
151                self.log_handlers[handler_type].setFormatter(
152                    ColorFormatter(message_format, '%H:%M:%S')
153                )

Set color format for all console handlers

def set_logfile(self, filename: str) -> None:
155    def set_logfile(self, filename: str) -> None:
156        """
157        Set logfile handler
158
159        :param str filename: logfile file path
160        """
161        try:
162            if filename == 'stdout':
163                # special case, log usual log file contents to stdout
164                handler = logging.StreamHandler(sys.__stdout__)
165            else:
166                handler = logging.FileHandler(
167                    filename=filename, encoding='utf-8'
168                )
169                self.logfile = filename
170            handler.setFormatter(
171                logging.Formatter(
172                    '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
173                    '%H:%M:%S'
174                )
175            )
176            handler.addFilter(LoggerSchedulerFilter())
177            self.addHandler(handler)
178            self.log_handlers['file'] = handler
179        except Exception as e:
180            raise KiwiLogFileSetupFailed(
181                f'{type(e).__name__}: {format(e)}'
182            )

Set logfile handler

Parameters
  • str filename: logfile file path
def set_log_socket(self, filename: str) -> None:
184    def set_log_socket(self, filename: str) -> None:
185        """
186        Set log socket handler
187
188        :param str filename:
189            UDS socket file path. Note if there is no server
190            listening on the socket the log handler setup
191            will fail
192        """
193        try:
194            handler = PlainTextSocketHandler(filename, None)
195            handler.makeSocket()
196            handler.setFormatter(
197                logging.Formatter(
198                    '[ %(levelname)-8s]: %(asctime)-8s | %(message)s',
199                    '%H:%M:%S'
200                )
201            )
202            handler.addFilter(LoggerSchedulerFilter())
203            self.addHandler(handler)
204            self.log_handlers['socket'] = handler
205        except Exception as e:
206            raise KiwiLogSocketSetupFailed(
207                'UDS socket: {0}:{1}: {2}'.format(filename, type(e).__name__, e)
208            )

Set log socket handler

Parameters
  • str filename: UDS socket file path. Note if there is no server listening on the socket the log handler setup will fail
def get_logfile(self) -> Optional[str]:
210    def get_logfile(self) -> Optional[str]:
211        """
212        Return file path name of logfile
213
214        :return: file path
215
216        :rtype: str
217        """
218        return self.logfile

Return file path name of logfile

Returns

file path

@staticmethod
def progress(current: int, total: int, prefix: str, bar_length: int = 40) -> None:
220    @staticmethod
221    def progress(
222        current: int, total: int, prefix: str, bar_length: int = 40
223    ) -> None:
224        """
225        Custom progress log information. progress information is
226        intentionally only logged to stdout and will bypass any
227        handlers. We don't want this information to show up in
228        the log file
229
230        :param int current: current item
231        :param int total: total number of items
232        :param string prefix: prefix name
233        :param int bar_length: length of progress bar
234        """
235        try:
236            percent = float(current) / total
237        except Exception:
238            # we don't want the progress to raise an exception
239            # In case of any error e.g division by zero the current
240            # way out is to skip the progress update
241            return
242        hashes = '#' * int(round(percent * bar_length))
243        spaces = ' ' * (bar_length - len(hashes))
244        sys.stdout.write('\r{0}: [{1}] {2}%'.format(
245            prefix, hashes + spaces, int(round(percent * 100))
246        ))
247        if current == 100:
248            sys.stdout.write('\n')
249        sys.stdout.flush()

Custom progress log information. progress information is intentionally only logged to stdout and will bypass any handlers. We don't want this information to show up in the log file

Parameters
  • int current: current item
  • int total: total number of items
  • string prefix: prefix name
  • int bar_length: length of progress bar