text: '''
  Logger object
  =============
  
  The Kivy `Logger` class provides a singleton logger instance. This instance
  exposes a standard Python
  `logger <https://docs.python.org/3/library/logging.html>`_ 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
  
      Logger.info('title: This is a info message.')
      Logger.debug('title: This is a debug message.')
  
      try:
          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. ::
  
      Logger.info('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
  
      Logger.setLevel(LOG_LEVELS["debug"])
  
  
  Features
  --------
  
  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::
  
      [kivy]
      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
  messages::
  
      from kivy.logger import LoggerHistory
  
      print(LoggerHistory.history)
  
  '''
  
  import logging
  import os
  import sys
  import copy
  from random import randint
  from functools import partial
  
  import kivy
  
  
  __all__ = (
      'Logger', 'LOG_LEVELS', 'COLORS', 'LoggerHistory', 'file_log_handler')
  
  try:
      PermissionError
  except NameError:  # Python 2
      PermissionError = OSError, IOError
  
  Logger = None
  
  BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = list(range(8))
  
  # These are the sequences need to get colored ouput
  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)
      else:
          message = message.replace("$RESET", "").replace("$BOLD", "")
      return message
  
  
  COLORS = {
      'TRACE': MAGENTA,
      'WARNING': YELLOW,
      'INFO': GREEN,
      'DEBUG': CYAN,
      'CRITICAL': RED,
      'ERROR': RED}
  
  logging.TRACE = 9
  LOG_LEVELS = {
      '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 log is called randomly to prevent the log directory from being
          filled by lots and lots of log files.
          You've a chance of 1 in 20 that purge log will be fired.
          '''
          if randint(0, 20) != 0:
              return
          if not self.log_dir:
              return
  
          from kivy.config import Config
          maxfiles = Config.getint('kivy', 'log_maxfiles')
  
          if maxfiles < 0:
              return
  
          Logger.info('Logger: Purge log fired. Analysing...')
          join = os.path.join
          unlink = os.unlink
  
          # search all log files
          lst = [join(self.log_dir, x) for x in os.listdir(self.log_dir)]
          if len(lst) > maxfiles:
              # get creation time on every files
              lst = [{'fn': x, 'ctime': os.path.getctime(x)} for x in lst]
  
              # sort by date
              lst = sorted(lst, key=lambda x: x['ctime'])
  
              # get the oldest (keep last maxfiles)
              lst = lst[:-maxfiles] if maxfiles else lst
              Logger.info('Logger: Purge %d log files' % len(lst))
  
              # now, unlink every file in the list
              for filename in lst:
                  try:
                      unlink(filename['fn'])
                  except PermissionError as e:
                      Logger.info('Logger: Skipped file {0}, {1}'.
                                  format(filename['fn'], e))
  
          Logger.info('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
          else:
              _dir = os.path.join(_dir, log_dir)
          if not os.path.exists(_dir):
              os.makedirs(_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):
                  break
              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:
              return
          FileHandler.filename = filename
          if FileHandler.fd is not None:
              FileHandler.fd.close()
          FileHandler.fd = open(filename, 'w', encoding=FileHandler.encoding)
          Logger.info('Logger: Record log in %s' % filename)
  
      def _write_message(self, record):
          if FileHandler.fd in (None, False):
              return
  
          msg = self.format(record)
          stream = FileHandler.fd
          fs = "%s\n"
          stream.write('[%-7s] ' % record.levelname)
          stream.write(fs % msg)
          stream.flush()
  
      def emit(self, message):
          # during the startup, store the message in the history
          if Logger.logfile_activated is None:
              FileHandler.history += [message]
              return
  
          # startup done, if the logfile is not activated, avoid history.
          if Logger.logfile_activated is False:
              FileHandler.history = []
              return
  
          if FileHandler.fd is None:
              try:
                  self._configure()
                  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...
                  FileHandler.fd = False
                  Logger.exception('Error while activating FileHandler logger')
                  return
              while FileHandler.history:
                  _message = FileHandler.history.pop()
                  self._write_message(_message)
  
          self._write_message(message)
  
  
  class LoggerHistory(logging.Handler):
  
      history = []
  
      def emit(self, message):
          LoggerHistory.history = [message] + LoggerHistory.history[:100]
  
      @classmethod
      def clear_history(cls):
          del cls.history[:]
  
      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
          self.channel = channel
          self.errors = ''
  
      def write(self, s):
          s = self.buffer + s
          self.flush()
          f = self.func
          channel = self.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)
  