|
@@ -32,28 +32,70 @@ The way to use this is as follows:
|
|
|
|
|
|
import inspect
|
|
|
import logging
|
|
|
-
|
|
|
-# make it easy for PyInstaller (it does not discover the dependency on this
|
|
|
-# module automatically, because it is lazy-loaded by logging, see #218):
|
|
|
import logging.config
|
|
|
+import logging.handlers # needed for handlers defined there being configurable in logging.conf file
|
|
|
+import os
|
|
|
+import warnings
|
|
|
+
|
|
|
+configured = False
|
|
|
+
|
|
|
+# use something like this to ignore warnings:
|
|
|
+# warnings.filterwarnings('ignore', r'... regex for warning message to ignore ...')
|
|
|
+
|
|
|
+
|
|
|
+def _log_warning(message, category, filename, lineno, file=None, line=None):
|
|
|
+ # for warnings, we just want to use the logging system, not stderr or other files
|
|
|
+ msg = "{0}:{1}: {2}: {3}".format(filename, lineno, category.__name__, message)
|
|
|
+ logger = create_logger(__name__)
|
|
|
+ # Note: the warning will look like coming from here,
|
|
|
+ # but msg contains info about where it really comes from
|
|
|
+ logger.warning(msg)
|
|
|
|
|
|
|
|
|
-def setup_logging(stream=None):
|
|
|
+def setup_logging(stream=None, conf_fname=None, env_var='BORG_LOGGING_CONF'):
|
|
|
"""setup logging module according to the arguments provided
|
|
|
|
|
|
- this sets up a stream handler logger on stderr (by default, if no
|
|
|
+ if conf_fname is given (or the config file name can be determined via
|
|
|
+ the env_var, if given): load this logging configuration.
|
|
|
+
|
|
|
+ otherwise, set up a stream handler logger on stderr (by default, if no
|
|
|
stream is provided).
|
|
|
"""
|
|
|
- logging.raiseExceptions = False
|
|
|
- l = logging.getLogger('')
|
|
|
- sh = logging.StreamHandler(stream)
|
|
|
- # other formatters will probably want this, but let's remove
|
|
|
- # clutter on stderr
|
|
|
+ global configured
|
|
|
+ err_msg = None
|
|
|
+ if env_var:
|
|
|
+ conf_fname = os.environ.get(env_var, conf_fname)
|
|
|
+ if conf_fname:
|
|
|
+ try:
|
|
|
+ conf_fname = os.path.abspath(conf_fname)
|
|
|
+ # we open the conf file here to be able to give a reasonable
|
|
|
+ # error message in case of failure (if we give the filename to
|
|
|
+ # fileConfig(), it silently ignores unreadable files and gives
|
|
|
+ # unhelpful error msgs like "No section: 'formatters'"):
|
|
|
+ with open(conf_fname) as f:
|
|
|
+ logging.config.fileConfig(f)
|
|
|
+ configured = True
|
|
|
+ logger = logging.getLogger(__name__)
|
|
|
+ logger.debug('using logging configuration read from "{0}"'.format(conf_fname))
|
|
|
+ warnings.showwarning = _log_warning
|
|
|
+ return None
|
|
|
+ except Exception as err: # XXX be more precise
|
|
|
+ err_msg = str(err)
|
|
|
+ # if we did not / not successfully load a logging configuration, fallback to this:
|
|
|
+ logger = logging.getLogger('')
|
|
|
+ handler = logging.StreamHandler(stream)
|
|
|
+ # other formatters will probably want this, but let's remove clutter on stderr
|
|
|
# example:
|
|
|
- # sh.setFormatter(logging.Formatter('%(name)s: %(message)s'))
|
|
|
- l.addHandler(sh)
|
|
|
- l.setLevel(logging.INFO)
|
|
|
- return sh
|
|
|
+ # handler.setFormatter(logging.Formatter('%(name)s: %(message)s'))
|
|
|
+ logger.addHandler(handler)
|
|
|
+ logger.setLevel(logging.INFO)
|
|
|
+ configured = True
|
|
|
+ logger = logging.getLogger(__name__)
|
|
|
+ if err_msg:
|
|
|
+ logger.warning('setup_logging for "{0}" failed with "{1}".'.format(conf_fname, err_msg))
|
|
|
+ logger.debug('using builtin fallback logging configuration')
|
|
|
+ warnings.showwarning = _log_warning
|
|
|
+ return handler
|
|
|
|
|
|
|
|
|
def find_parent_module():
|
|
@@ -76,7 +118,7 @@ def find_parent_module():
|
|
|
|
|
|
|
|
|
def create_logger(name=None):
|
|
|
- """create a Logger object with the proper path, which is returned by
|
|
|
+ """lazily create a Logger object with the proper path, which is returned by
|
|
|
find_parent_module() by default, or is provided via the commandline
|
|
|
|
|
|
this is really a shortcut for:
|
|
@@ -84,5 +126,48 @@ def create_logger(name=None):
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
we use it to avoid errors and provide a more standard API.
|
|
|
+
|
|
|
+ We must create the logger lazily, because this is usually called from
|
|
|
+ module level (and thus executed at import time - BEFORE setup_logging()
|
|
|
+ was called). By doing it lazily we can do the setup first, we just have to
|
|
|
+ be careful not to call any logger methods before the setup_logging() call.
|
|
|
+ If you try, you'll get an exception.
|
|
|
"""
|
|
|
- return logging.getLogger(name or find_parent_module())
|
|
|
+ class LazyLogger:
|
|
|
+ def __init__(self, name=None):
|
|
|
+ self.__name = name or find_parent_module()
|
|
|
+ self.__real_logger = None
|
|
|
+
|
|
|
+ @property
|
|
|
+ def __logger(self):
|
|
|
+ if self.__real_logger is None:
|
|
|
+ if not configured:
|
|
|
+ raise Exception("tried to call a logger before setup_logging() was called")
|
|
|
+ self.__real_logger = logging.getLogger(self.__name)
|
|
|
+ return self.__real_logger
|
|
|
+
|
|
|
+ def setLevel(self, *args, **kw):
|
|
|
+ return self.__logger.setLevel(*args, **kw)
|
|
|
+
|
|
|
+ def log(self, *args, **kw):
|
|
|
+ return self.__logger.log(*args, **kw)
|
|
|
+
|
|
|
+ def exception(self, *args, **kw):
|
|
|
+ return self.__logger.exception(*args, **kw)
|
|
|
+
|
|
|
+ def debug(self, *args, **kw):
|
|
|
+ return self.__logger.debug(*args, **kw)
|
|
|
+
|
|
|
+ def info(self, *args, **kw):
|
|
|
+ return self.__logger.info(*args, **kw)
|
|
|
+
|
|
|
+ def warning(self, *args, **kw):
|
|
|
+ return self.__logger.warning(*args, **kw)
|
|
|
+
|
|
|
+ def error(self, *args, **kw):
|
|
|
+ return self.__logger.error(*args, **kw)
|
|
|
+
|
|
|
+ def critical(self, *args, **kw):
|
|
|
+ return self.__logger.critical(*args, **kw)
|
|
|
+
|
|
|
+ return LazyLogger(name)
|