Quick search

Table Of Contents

Source code for kivy.logger

Logger object

The Kivy `Logger` class provides a singleton logger instance. This instance
exposes a standard Python
`logger <>`_ object but adds
some convenient features.

All the standard logging levels are available : `trace`, `debug`, `info`,
`warning`, `error` and `critical`.

Example Usage

Use the `Logger` as you would a standard Python logger. ::

    from kivy.logger import Logger'title: This is a info message.')
    Logger.debug('title: This is a debug message.')

        raise Exception('bleh')
    except Exception:
        Logger.exception('Something happened!')

The message passed to the logger is split into two parts separated by a colon
(:). The first part is used as a title and the second part is used as the
message. This way, you can "categorize" your messages easily. ::'Application: This is a test')

    # will appear as

    [INFO   ] [Application ] This is a test

You can change the logging level at any time using the `setLevel` method. ::

    from kivy.logger import Logger, LOG_LEVELS



Although you are free to use standard python loggers, the Kivy `Logger` offers
some solid benefits and useful features. These include:

* simplied usage (single instance, simple configuration, works by default)
* color-coded output
* output to `stdout` by default
* message categorization via colon separation
* access to log history even if logging is disabled
* built-in handling of various cross-platform considerations

Kivys' logger was designed to be used with kivy apps and makes logging from
Kivy apps more convenient.

Logger Configuration

The Logger can be controlled via the Kivy configuration file::

    log_level = info
    log_enable = 1
    log_dir = logs
    log_name = kivy_%y-%m-%d_%_.txt
    log_maxfiles = 100

More information about the allowed values are described in the
:mod:`kivy.config` module.

Logger History

Even if the logger is not enabled, you still have access to the last 100

    from kivy.logger import LoggerHistory



import logging
import os
import sys
import copy
from random import randint
from functools import partial
import pathlib

import kivy

__all__ = (
    'Logger', 'LOG_LEVELS', 'COLORS', 'LoggerHistory', 'file_log_handler')

except NameError:  # Python 2
    PermissionError = OSError, IOError

Logger = None


# These are the sequences need to get colored output
RESET_SEQ = "\033[0m"
COLOR_SEQ = "\033[1;%dm"
BOLD_SEQ = "\033[1m"

previous_stderr = sys.stderr

def formatter_message(message, use_color=True):
    if use_color:
        message = message.replace("$RESET", RESET_SEQ)
        message = message.replace("$BOLD", BOLD_SEQ)
        message = message.replace("$RESET", "").replace("$BOLD", "")
    return message

    'INFO': GREEN,
    'DEBUG': CYAN,
    'ERROR': RED}

logging.TRACE = 9
    'trace': logging.TRACE,
    'debug': logging.DEBUG,
    'info': logging.INFO,
    'warning': logging.WARNING,
    'error': logging.ERROR,
    'critical': logging.CRITICAL}

class FileHandler(logging.Handler):
    history = []
    filename = 'log.txt'
    fd = None
    log_dir = ''
    encoding = 'utf-8'

    def purge_logs(self):
        """Purge logs which exceed the maximum amount of log files,
        starting with the oldest creation timestamp (or edit-timestamp on Linux)

        if not self.log_dir:

        from kivy.config import Config
        maxfiles = Config.getint("kivy", "log_maxfiles")

        # Get path to log directory
        log_dir = pathlib.Path(self.log_dir)

        if maxfiles < 0:  # No log file limit set
            return"Logger: Purge log fired. Processing...")

        # Get all files from log directory and corresponding creation timestamps
        files = [(item, item.stat().st_ctime)
                 for item in log_dir.iterdir() if item.is_file()]
        # Sort files by ascending timestamp
        files.sort(key=lambda x: x[1])

        for file, _ in files[:(-maxfiles or len(files))]:
            # More log files than allowed maximum,
            # delete files, starting with oldest creation timestamp
            # (or edit-timestamp on Linux)
            except (PermissionError, FileNotFoundError) as e:
      "Logger: Skipped file {file}, {repr(e)}")"Logger: Purge finished!")

    def _configure(self, *largs, **kwargs):
        from time import strftime
        from kivy.config import Config
        log_dir = Config.get('kivy', 'log_dir')
        log_name = Config.get('kivy', 'log_name')

        _dir = kivy.kivy_home_dir
        if log_dir and os.path.isabs(log_dir):
            _dir = log_dir
            _dir = os.path.join(_dir, log_dir)
        if not os.path.exists(_dir):
        self.log_dir = _dir

        pattern = log_name.replace('%_', '@@NUMBER@@')
        pattern = os.path.join(_dir, strftime(pattern))
        n = 0
        while True:
            filename = pattern.replace('@@NUMBER@@', str(n))
            if not os.path.exists(filename):
            n += 1
            if n > 10000:  # prevent maybe flooding ?
                raise Exception('Too many logfile, remove them')

        if FileHandler.filename == filename and FileHandler.fd is not None:

        FileHandler.filename = filename
        if FileHandler.fd not in (None, False):
        FileHandler.fd = open(filename, 'w', encoding=FileHandler.encoding)'Logger: Record log in %s' % filename)

    def _write_message(self, record):
        if FileHandler.fd in (None, False):

        msg = self.format(record)
        stream = FileHandler.fd
        fs = "%s\n"
        stream.write('[%-7s] ' % record.levelname)
        stream.write(fs % msg)

    def emit(self, message):
        # during the startup, store the message in the history
        if Logger.logfile_activated is None:
            FileHandler.history += [message]

        # startup done, if the logfile is not activated, avoid history.
        if Logger.logfile_activated is False:
            FileHandler.history = []

        if FileHandler.fd is None:
                from kivy.config import Config
                Config.add_callback(self._configure, 'kivy', 'log_dir')
                Config.add_callback(self._configure, 'kivy', 'log_name')
            except Exception:
                # deactivate filehandler...
                if FileHandler.fd not in (None, False):
                FileHandler.fd = False
                Logger.exception('Error while activating FileHandler logger')
            while FileHandler.history:
                _message = FileHandler.history.pop()


[docs]class LoggerHistory(logging.Handler): history = []
[docs] def emit(self, message): LoggerHistory.history = [message] + LoggerHistory.history[:100]
@classmethod def clear_history(cls): del cls.history[:]
[docs] def flush(self): super(LoggerHistory, self).flush() self.clear_history()
class ColoredFormatter(logging.Formatter): def __init__(self, msg, use_color=True): logging.Formatter.__init__(self, msg) self.use_color = use_color def format(self, record): """Apply terminal color code to the record""" # deepcopy so we do not mess up the record for other formatters record = copy.deepcopy(record) try: msg = record.msg.split(':', 1) if len(msg) == 2: record.msg = '[%-12s]%s' % (msg[0], msg[1]) except: pass levelname = record.levelname if record.levelno == logging.TRACE: levelname = 'TRACE' record.levelname = levelname if self.use_color and levelname in COLORS: levelname_color = ( COLOR_SEQ % (30 + COLORS[levelname]) + levelname + RESET_SEQ) record.levelname = levelname_color return logging.Formatter.format(self, record) class ConsoleHandler(logging.StreamHandler): def filter(self, record): try: msg = record.msg k = msg.split(':', 1) if k[0] == 'stderr' and len(k) == 2: previous_stderr.write(k[1] + '\n') return False except: pass return True class LogFile(object): def __init__(self, channel, func): self.buffer = '' self.func = func = channel self.errors = '' def write(self, s): s = self.buffer + s self.flush() f = self.func channel = lines = s.split('\n') for l in lines[:-1]: f('%s: %s' % (channel, l)) self.buffer = lines[-1] def flush(self): return def isatty(self): return False def logger_config_update(section, key, value): if LOG_LEVELS.get(value) is None: raise AttributeError('Loglevel {0!r} doesn\'t exists'.format(value)) Logger.setLevel(level=LOG_LEVELS.get(value)) #: Kivy default logger instance Logger = logging.getLogger('kivy') Logger.logfile_activated = None Logger.trace = partial(Logger.log, logging.TRACE) # set the Kivy logger as the default logging.root = Logger # add default kivy logger Logger.addHandler(LoggerHistory()) file_log_handler = None if 'KIVY_NO_FILELOG' not in os.environ: file_log_handler = FileHandler() Logger.addHandler(file_log_handler) # Use the custom handler instead of streaming one. if 'KIVY_NO_CONSOLELOG' not in os.environ: if hasattr(sys, '_kivy_logging_handler'): Logger.addHandler(getattr(sys, '_kivy_logging_handler')) else: use_color = ( ( os.environ.get("WT_SESSION") or os.environ.get("COLORTERM") == 'truecolor' or os.environ.get('PYCHARM_HOSTED') == '1' or os.environ.get('TERM') in ( 'rxvt', 'rxvt-256color', 'rxvt-unicode', 'rxvt-unicode-256color', 'xterm', 'xterm-256color', ) ) and os.environ.get('KIVY_BUILD') not in ('android', 'ios') ) if not use_color: # No additional control characters will be inserted inside the # levelname field, 7 chars will fit "WARNING" color_fmt = formatter_message( '[%(levelname)-7s] %(message)s', use_color) else: # levelname field width need to take into account the length of the # color control codes (7+4 chars for bold+color, and reset) color_fmt = formatter_message( '[%(levelname)-18s] %(message)s', use_color) formatter = ColoredFormatter(color_fmt, use_color=use_color) console = ConsoleHandler() console.setFormatter(formatter) Logger.addHandler(console) # install stderr handlers sys.stderr = LogFile('stderr', Logger.warning)