logger.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. """logging facilities
  2. The way to use this is as follows:
  3. * each module declares its own logger, using:
  4. from .logger import create_logger
  5. logger = create_logger()
  6. * then each module uses logger.info/warning/debug/etc according to the
  7. level it believes is appropriate:
  8. logger.debug('debugging info for developers or power users')
  9. logger.info('normal, informational output')
  10. logger.warning('warn about a non-fatal error or sth else')
  11. logger.error('a fatal error')
  12. ... and so on. see the `logging documentation
  13. <https://docs.python.org/3/howto/logging.html#when-to-use-logging>`_
  14. for more information
  15. * console interaction happens on stderr, that includes interactive
  16. reporting functions like `help`, `info` and `list`
  17. * ...except ``input()`` is special, because we can't control the
  18. stream it is using, unfortunately. we assume that it won't clutter
  19. stdout, because interaction would be broken then anyways
  20. * what is output on INFO level is additionally controlled by commandline
  21. flags
  22. """
  23. import inspect
  24. import logging
  25. import logging.config
  26. import logging.handlers # needed for handlers defined there being configurable in logging.conf file
  27. import os
  28. import warnings
  29. configured = False
  30. # use something like this to ignore warnings:
  31. # warnings.filterwarnings('ignore', r'... regex for warning message to ignore ...')
  32. def _log_warning(message, category, filename, lineno, file=None, line=None):
  33. # for warnings, we just want to use the logging system, not stderr or other files
  34. msg = "{0}:{1}: {2}: {3}".format(filename, lineno, category.__name__, message)
  35. logger = create_logger(__name__)
  36. # Note: the warning will look like coming from here,
  37. # but msg contains info about where it really comes from
  38. logger.warning(msg)
  39. def setup_logging(stream=None, conf_fname=None, env_var='BORG_LOGGING_CONF'):
  40. """setup logging module according to the arguments provided
  41. if conf_fname is given (or the config file name can be determined via
  42. the env_var, if given): load this logging configuration.
  43. otherwise, set up a stream handler logger on stderr (by default, if no
  44. stream is provided).
  45. """
  46. global configured
  47. err_msg = None
  48. if env_var:
  49. conf_fname = os.environ.get(env_var, conf_fname)
  50. if conf_fname:
  51. try:
  52. conf_fname = os.path.abspath(conf_fname)
  53. # we open the conf file here to be able to give a reasonable
  54. # error message in case of failure (if we give the filename to
  55. # fileConfig(), it silently ignores unreadable files and gives
  56. # unhelpful error msgs like "No section: 'formatters'"):
  57. with open(conf_fname) as f:
  58. logging.config.fileConfig(f)
  59. configured = True
  60. logger = logging.getLogger(__name__)
  61. logger.debug('using logging configuration read from "{0}"'.format(conf_fname))
  62. warnings.showwarning = _log_warning
  63. return None
  64. except Exception as err: # XXX be more precise
  65. err_msg = str(err)
  66. # if we did not / not successfully load a logging configuration, fallback to this:
  67. logger = logging.getLogger('')
  68. handler = logging.StreamHandler(stream)
  69. # other formatters will probably want this, but let's remove clutter on stderr
  70. # example:
  71. # handler.setFormatter(logging.Formatter('%(name)s: %(message)s'))
  72. logger.addHandler(handler)
  73. logger.setLevel(logging.INFO)
  74. configured = True
  75. logger = logging.getLogger(__name__)
  76. if err_msg:
  77. logger.warning('setup_logging for "{0}" failed with "{1}".'.format(conf_fname, err_msg))
  78. logger.debug('using builtin fallback logging configuration')
  79. warnings.showwarning = _log_warning
  80. return handler
  81. def find_parent_module():
  82. """find the name of a the first module calling this module
  83. if we cannot find it, we return the current module's name
  84. (__name__) instead.
  85. """
  86. try:
  87. frame = inspect.currentframe().f_back
  88. module = inspect.getmodule(frame)
  89. while module is None or module.__name__ == __name__:
  90. frame = frame.f_back
  91. module = inspect.getmodule(frame)
  92. return module.__name__
  93. except AttributeError:
  94. # somehow we failed to find our module
  95. # return the logger module name by default
  96. return __name__
  97. def create_logger(name=None):
  98. """lazily create a Logger object with the proper path, which is returned by
  99. find_parent_module() by default, or is provided via the commandline
  100. this is really a shortcut for:
  101. logger = logging.getLogger(__name__)
  102. we use it to avoid errors and provide a more standard API.
  103. We must create the logger lazily, because this is usually called from
  104. module level (and thus executed at import time - BEFORE setup_logging()
  105. was called). By doing it lazily we can do the setup first, we just have to
  106. be careful not to call any logger methods before the setup_logging() call.
  107. If you try, you'll get an exception.
  108. """
  109. class LazyLogger:
  110. def __init__(self, name=None):
  111. self.__name = name or find_parent_module()
  112. self.__real_logger = None
  113. @property
  114. def __logger(self):
  115. if self.__real_logger is None:
  116. if not configured:
  117. raise Exception("tried to call a logger before setup_logging() was called")
  118. self.__real_logger = logging.getLogger(self.__name)
  119. return self.__real_logger
  120. def setLevel(self, *args, **kw):
  121. return self.__logger.setLevel(*args, **kw)
  122. def log(self, *args, **kw):
  123. return self.__logger.log(*args, **kw)
  124. def exception(self, *args, **kw):
  125. return self.__logger.exception(*args, **kw)
  126. def debug(self, *args, **kw):
  127. return self.__logger.debug(*args, **kw)
  128. def info(self, *args, **kw):
  129. return self.__logger.info(*args, **kw)
  130. def warning(self, *args, **kw):
  131. return self.__logger.warning(*args, **kw)
  132. def error(self, *args, **kw):
  133. return self.__logger.error(*args, **kw)
  134. def critical(self, *args, **kw):
  135. return self.__logger.critical(*args, **kw)
  136. return LazyLogger(name)