logger.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import logging
  2. import os
  3. import sys
  4. import colorama
  5. def to_bool(arg):
  6. '''
  7. Return a boolean value based on `arg`.
  8. '''
  9. if arg is None or isinstance(arg, bool):
  10. return arg
  11. if isinstance(arg, str):
  12. arg = arg.lower()
  13. if arg in ('yes', 'on', '1', 'true', 1):
  14. return True
  15. return False
  16. def interactive_console():
  17. '''
  18. Return whether the current console is "interactive". Meaning: Capable of
  19. user input and not just something like a cron job.
  20. '''
  21. return sys.stdout.isatty() and os.environ.get('TERM') != 'dumb'
  22. def should_do_markup(no_color, configs):
  23. '''
  24. Given the value of the command-line no-color argument, and a dict of configuration filename to
  25. corresponding parsed configuration, determine if we should enable colorama marking up.
  26. '''
  27. if no_color:
  28. return False
  29. if any(config.get('output', {}).get('color') is False for config in configs.values()):
  30. return False
  31. py_colors = os.environ.get('PY_COLORS', None)
  32. if py_colors is not None:
  33. return to_bool(py_colors)
  34. return interactive_console()
  35. LOG_LEVEL_TO_COLOR = {
  36. logging.CRITICAL: colorama.Fore.RED,
  37. logging.ERROR: colorama.Fore.RED,
  38. logging.WARN: colorama.Fore.YELLOW,
  39. logging.INFO: colorama.Fore.GREEN,
  40. logging.DEBUG: colorama.Fore.CYAN,
  41. }
  42. class Console_color_formatter(logging.Formatter):
  43. def format(self, record):
  44. color = LOG_LEVEL_TO_COLOR.get(record.levelno)
  45. return color_text(color, record.msg)
  46. def color_text(color, message):
  47. '''
  48. Give colored text.
  49. '''
  50. if not color:
  51. return message
  52. return '{}{}{}'.format(color, message, colorama.Style.RESET_ALL)
  53. def configure_logging(
  54. console_log_level, syslog_log_level=None, log_file_log_level=None, log_file=None
  55. ):
  56. '''
  57. Configure logging to go to both the console and (syslog or log file). Use the given log levels,
  58. respectively.
  59. Raise FileNotFoundError or PermissionError if the log file could not be opened for writing.
  60. '''
  61. if syslog_log_level is None:
  62. syslog_log_level = console_log_level
  63. if log_file_log_level is None:
  64. log_file_log_level = console_log_level
  65. console_handler = logging.StreamHandler()
  66. console_handler.setFormatter(Console_color_formatter())
  67. console_handler.setLevel(console_log_level)
  68. syslog_path = None
  69. if log_file is None:
  70. if os.path.exists('/dev/log'):
  71. syslog_path = '/dev/log'
  72. elif os.path.exists('/var/run/syslog'):
  73. syslog_path = '/var/run/syslog'
  74. if syslog_path and not interactive_console():
  75. syslog_handler = logging.handlers.SysLogHandler(address=syslog_path)
  76. syslog_handler.setFormatter(logging.Formatter('borgmatic: %(levelname)s %(message)s'))
  77. syslog_handler.setLevel(syslog_log_level)
  78. handlers = (console_handler, syslog_handler)
  79. elif log_file:
  80. file_handler = logging.FileHandler(log_file)
  81. file_handler.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s: %(message)s'))
  82. file_handler.setLevel(log_file_log_level)
  83. handlers = (console_handler, file_handler)
  84. else:
  85. handlers = (console_handler,)
  86. logging.basicConfig(
  87. level=min(console_log_level, syslog_log_level, log_file_log_level), handlers=handlers
  88. )