logcontext.py 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. from functools import wraps
  2. import threading
  3. import logging
  4. class LoggingContext(object):
  5. __slots__ = ["parent_context", "name", "__dict__"]
  6. thread_local = threading.local()
  7. class Sentinel(object):
  8. __slots__ = []
  9. def copy_to(self, record):
  10. pass
  11. sentinel = Sentinel()
  12. def __init__(self, name=None):
  13. self.parent_context = None
  14. self.name = name
  15. def __str__(self):
  16. return "%s@%x" % (self.name, id(self))
  17. @classmethod
  18. def current_context(cls):
  19. return getattr(cls.thread_local, "current_context", cls.sentinel)
  20. def __enter__(self):
  21. if self.parent_context is not None:
  22. raise Exception("Attempt to enter logging context multiple times")
  23. self.parent_context = self.current_context()
  24. self.thread_local.current_context = self
  25. return self
  26. def __exit__(self, type, value, traceback):
  27. if self.thread_local.current_context is not self:
  28. logging.error(
  29. "Current logging context %s is not the expected context %s",
  30. self.thread_local.current_context,
  31. self
  32. )
  33. self.thread_local.current_context = self.parent_context
  34. self.parent_context = None
  35. def __getattr__(self, name):
  36. return getattr(self.parent_context, name)
  37. def copy_to(self, record):
  38. if self.parent_context is not None:
  39. self.parent_context.copy_to(record)
  40. for key, value in self.__dict__.items():
  41. setattr(record, key, value)
  42. @classmethod
  43. def wrap_callback(cls, callback):
  44. context = cls.current_context()
  45. @wraps(callback)
  46. def wrapped(*args, **kargs):
  47. cls.thread_local.current_context = context
  48. return callback(*args, **kargs)
  49. return wrapped
  50. class LoggingContextFilter(logging.Filter):
  51. def __init__(self, **defaults):
  52. self.defaults = defaults
  53. def filter(self, record):
  54. context = LoggingContext.current_context()
  55. for key, value in self.defaults.items():
  56. setattr(record, key, value)
  57. context.copy_to(record)
  58. return True
  59. class PreserveLoggingContext(object):
  60. __slots__ = ["current_context"]
  61. def __enter__(self):
  62. self.current_context = LoggingContext.current_context()
  63. def __exit__(self, type, value, traceback):
  64. LoggingContext.thread_local.current_context = self.current_context