No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

logger.py 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #
  2. # This file is part of Lodel 2 (https://github.com/OpenEdition)
  3. #
  4. # Copyright (C) 2015-2017 Cléo UMS-3287
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Affero General Public License as published
  8. # by the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU Affero General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Affero General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import copy
  20. import logging, logging.handlers
  21. import os.path
  22. # Variables & constants definitions
  23. default_format = '%(asctime)-15s %(levelname)s %(_pathname)s:%(_lineno)s:%(_funcName)s() : %(message)s'
  24. simple_format = '%(asctime)-15s %(levelname)s : %(message)s'
  25. SECURITY_LOGLEVEL = 35
  26. logging.addLevelName(SECURITY_LOGLEVEL, 'SECURITY')
  27. handlers = dict() # Handlers list (generated from settings)
  28. ##@brief Stores sent messages until module is able to be initialized
  29. msg_buffer = []
  30. # Fetching logger for current context
  31. from lodel.context import LodelContext
  32. logger = logging.getLogger(LodelContext.get_name())
  33. ##@brief Module initialisation from settings
  34. #@return True if inited else False
  35. def __init_from_settings():
  36. from lodel.context import LodelContext
  37. try:
  38. LodelContext.expose_modules(globals(), {
  39. 'lodel.settings': ['Settings']})
  40. except Exception:
  41. return False
  42. LodelContext.expose_modules(globals(), {
  43. 'lodel.settings.settings': [('Settings', 'Lodel2Settings')]})
  44. if not Lodel2Settings.started():
  45. return False
  46. # capture warning disabled, because the custom format raises error (unable
  47. # to give the _ preffixed arguments to logger resulting into a KeyError
  48. # exception )
  49. #logging.captureWarnings(True) # Log warnings
  50. logger.setLevel(logging.DEBUG)
  51. for name in Settings.logging._fields:
  52. add_handler(name, getattr(Settings.logging, name))
  53. return True
  54. ##@brief Add an handler, identified by a name, to a given logger
  55. #
  56. # logging_opt is a dict with logger option. Allowed keys are :
  57. # - filename : take a filepath as value and cause the use of a logging.handlers.RotatingFileHandler
  58. # - level : the minimum logging level for a logger, takes values [ 'DEBUG', 'INFO', 'WARNING', 'SECURITY', 'ERROR', 'CRITICAL' ]
  59. # - format : DONT USE THIS OPTION (or if you use it be sure to includes %(_pathname)s %(_lineno)s %(_funcName)s format variables in format string
  60. # - context : boolean, if True include the context (module:lineno:function_name) in the log format
  61. # @todo Move the logging_opt documentation somewhere related with settings
  62. #
  63. # @param name str : The handler name
  64. # @param logging_opt dict : dict containing options ( see above )
  65. def add_handler(name, logging_opt):
  66. logger = logging.getLogger(LodelContext.get_name())
  67. if name in handlers:
  68. raise KeyError("A handler named '%s' allready exists")
  69. logging_opt = logging_opt._asdict()
  70. if 'filename' in logging_opt and logging_opt['filename'] is not None:
  71. maxBytes = (1024 * 10) if 'maxbytes' not in logging_opt else logging_opt['maxbytes']
  72. backupCount = 10 if 'backupcount' not in logging_opt else logging_opt['backupcount']
  73. handler = logging.handlers.RotatingFileHandler(
  74. logging_opt['filename'],
  75. maxBytes = maxBytes,
  76. backupCount = backupCount,
  77. encoding = 'utf-8')
  78. else:
  79. handler = logging.StreamHandler()
  80. if 'level' in logging_opt:
  81. handler.setLevel(getattr(logging, logging_opt['level'].upper()))
  82. if 'format' in logging_opt:
  83. formatter = logging.Formatter(logging_opt['format'])
  84. else:
  85. if 'context' in logging_opt and not logging_opt['context']:
  86. formatter = logging.Formatter(simple_format)
  87. else:
  88. formatter = logging.Formatter(default_format)
  89. handler.setFormatter(formatter)
  90. handlers[name] = handler
  91. logger.addHandler(handler)
  92. ##@brief Remove an handler generated from configuration (runtime logger configuration)
  93. # @param name str : handler name
  94. def remove_handler(name):
  95. if name in handlers:
  96. logger.removeHandler(handlers[name])
  97. # else: can we do anything ?
  98. ##@brief Utility function that disable unconditionnaly handlers that implies console output
  99. # @note In fact, this function disables handlers generated from settings wich are instances of logging.StreamHandler
  100. def remove_console_handlers():
  101. for name, handler in handlers.items():
  102. if isinstance(handler, logging.StreamHandler):
  103. remove_handler(name)
  104. #####################
  105. # Utility functions #
  106. #####################
  107. ##@brief Generic logging function
  108. # @param lvl int : Log severity
  109. # @param msg str : log message
  110. # @param *args : additional positionnal arguments
  111. # @param **kwargs : additional named arguments
  112. def log(lvl, msg, *args, **kwargs):
  113. if len(handlers) == 0: #late initialisation
  114. if not __init_from_settings():
  115. s_kwargs = copy.copy(kwargs)
  116. s_kwargs.update({'lvl': lvl, 'msg':msg})
  117. msg_buffer.append((s_kwargs, args))
  118. return
  119. else:
  120. for s_kwargs, args in msg_buffer:
  121. log(*args, **s_kwargs)
  122. from lodel.context import LodelContext
  123. if LodelContext.multisite():
  124. msg = "CTX(%s) %s" % (LodelContext.get_name(), msg)
  125. caller = logger.findCaller() # Opti warning : small overhead
  126. extra = {
  127. '_pathname': os.path.abspath(caller[0]),
  128. '_lineno': caller[1],
  129. '_funcName': caller[2],
  130. }
  131. logger.log(lvl, msg, extra = extra, *args, **kwargs)
  132. def debug(msg, *args, **kwargs): log(logging.DEBUG, msg, *args, **kwargs)
  133. def info(msg, *args, **kwargs): log(logging.INFO, msg, *args, **kwargs)
  134. def warning(msg, *args, **kwargs): log(logging.WARNING, msg, *args, **kwargs)
  135. def security(msg, *args, **kwargs): log(SECURITY_LOGLEVEL, msg, *args, **kwargs)
  136. def error(msg, *args, **kwargs): log(logging.ERROR, msg, *args, **kwargs)
  137. def critical(msg, *args, **kwargs): log(logging.CRITICAL, msg, *args, **kwargs)