fork download
  1. # -*- coding: utf-8 -*-
  2. import logging
  3. import sys
  4. import inspect
  5. from functools import wraps
  6. from datetime import datetime
  7.  
  8. # Python 2兼容性处理
  9. try:
  10. from collections import OrderedDict
  11. except ImportError:
  12. from ordereddict import OrderedDict
  13.  
  14.  
  15. class OptimizedFormatter(logging.Formatter):
  16. def __init__(self, *args, **kwargs):
  17. logging.Formatter.__init__(self, *args, **kwargs)
  18. self._frame_blacklist = set([id(inspect.currentframe())])
  19.  
  20. def format(self, record):
  21. try:
  22. # 自动捕获调用信息
  23. self._inject_frame_info(record)
  24.  
  25. # 参数格式化
  26. args_str = self._format_args(record)
  27. if args_str:
  28. record.msg = u"%s ⎸ %s" % (record.msg, args_str)
  29.  
  30. # 时间格式化优化
  31. record.asctime = self._format_time(record)
  32.  
  33. return logging.Formatter.format(self, record)
  34. except Exception as e:
  35. return u"⚠️ FORMAT_ERROR | %s | Raw: %s" % (e, record.msg)
  36.  
  37. def _inject_frame_info(self, record):
  38. """注入调用帧信息"""
  39. if not all([hasattr(record, a) for a in ('module', 'lineno', 'funcName')]):
  40. frame = self._find_valid_frame()
  41. if frame:
  42. record.module = self._get_module_name(frame)
  43. record.lineno = frame.f_lineno
  44. record.funcName = frame.f_code.co_name
  45.  
  46. def _find_valid_frame(self):
  47. """查找有效调用帧(兼容Python 2)"""
  48. stack = inspect.stack()
  49. for frame_info in reversed(stack):
  50. frame = frame_info[0]
  51. if id(frame) in self._frame_blacklist:
  52. continue
  53. if self._is_logging_frame(frame):
  54. continue
  55. return frame
  56. return inspect.currentframe()
  57.  
  58. def _is_logging_frame(self, frame):
  59. """判断是否日志类自身帧"""
  60. module = inspect.getmodule(frame)
  61. return module and module.__name__ == __name__
  62.  
  63. def _get_module_name(self, frame):
  64. """安全获取模块名"""
  65. try:
  66. module = inspect.getmodule(frame)
  67. return module.__name__ if module else 'unknown'
  68. except:
  69. return 'unknown'
  70.  
  71. def _format_args(self, record):
  72. """格式化参数"""
  73. parts = []
  74. # 位置参数
  75. if getattr(record, 'positional_args', None):
  76. parts.extend(self._safe_repr(a) for a in record.positional_args)
  77. # 关键字参数
  78. if getattr(record, 'extra_kwargs', None):
  79. parts.extend(u"%s=%s" % (k, self._safe_repr(v))
  80. for k, v in record.extra_kwargs.items())
  81. return u" ⎸ ".join(parts) if parts else u""
  82.  
  83. def _safe_repr(self, value):
  84. """安全类型转换(兼容Python 2)"""
  85. try:
  86. if isinstance(value, dict):
  87. return u"{%s}" % u",".join(u"%s:%s" % (self._safe_repr(k), self._safe_repr(v))
  88. for k, v in value.iteritems())
  89. if isinstance(value, (list, tuple)):
  90. brackets = u"[]" if isinstance(value, list) else u"()"
  91. return u"%s%s%s" % (brackets[0], u",".join(self._safe_repr(x) for x in value), brackets[1])
  92. return self._safe_str(value)
  93. except:
  94. return u"<UNREPRESENTABLE>"
  95.  
  96. def _safe_str(self, obj):
  97. """安全字符串转换(处理Python 2的bytes/unicode)"""
  98. try:
  99. if isinstance(obj, str):
  100. try:
  101. return obj.decode('utf-8')
  102. except UnicodeDecodeError:
  103. return obj.decode('latin-1', 'replace')
  104. if isinstance(obj, unicode):
  105. return obj
  106. return unicode(str(obj), 'utf-8', errors='replace')
  107. except:
  108. return u"<UNCONVERTIBLE>"
  109.  
  110. def _format_time(self, record):
  111. """高性能时间格式化"""
  112. return datetime.fromtimestamp(record.created).strftime('%H:%M:%S')
  113.  
  114.  
  115. class SimpleLogger(object):
  116. _initialized = False
  117.  
  118. @classmethod
  119. def _ensure_init(cls):
  120. if not cls._initialized:
  121. root = logging.getLogger()
  122. root.setLevel(logging.DEBUG)
  123. if not root.handlers:
  124. handler = logging.StreamHandler()
  125. handler.setFormatter(OptimizedFormatter(
  126. fmt=u'[%(asctime)s] [%(levelname)s] %(module)s:%(lineno)d ➤ %(funcName)s | %(message)s',
  127. datefmt=None # 使用自定义时间格式化
  128. ))
  129. root.addHandler(handler)
  130. cls._initialized = True
  131.  
  132. @classmethod
  133. def _log(cls, level, msg, *args, **kwargs):
  134. cls._ensure_init()
  135. try:
  136. # 获取调用上下文
  137. frame = inspect.currentframe().f_back.f_back
  138. module = inspect.getmodule(frame).__name__ if inspect.getmodule(frame) else 'unknown'
  139.  
  140. logger = logging.getLogger(module)
  141. if not logger.handlers:
  142. logger.propagate = True
  143.  
  144. # 创建LogRecord(兼容Python 2)
  145. record = logger.makeRecord(
  146. name=logger.name,
  147. level=level,
  148. fn=None,
  149. lno=None,
  150. msg=msg,
  151. args=(),
  152. exc_info=None,
  153. extra={
  154. 'positional_args': args,
  155. 'extra_kwargs': kwargs
  156. },
  157. func=inspect.getframeinfo(frame)[2]
  158. )
  159.  
  160. # 手动注入行号信息
  161. record.lineno = frame.f_lineno
  162.  
  163. logger.handle(record)
  164. except Exception as e:
  165. sys.stderr.write(u"LOG_ERROR: %s\n" % e)
  166.  
  167. @classmethod
  168. def debug(cls, msg, *args, **kwargs):
  169. cls._log(logging.DEBUG, msg, *args, **kwargs)
  170.  
  171. @classmethod
  172. def info(cls, msg, *args, **kwargs):
  173. cls._log(logging.INFO, msg, *args, **kwargs)
  174.  
  175. @classmethod
  176. def warning(cls, msg, *args, **kwargs):
  177. cls._log(logging.WARNING, msg, *args, **kwargs)
  178.  
  179. @classmethod
  180. def error(cls, msg, *args, **kwargs):
  181. cls._log(logging.ERROR, msg, *args, **kwargs)
  182.  
  183.  
  184. if __name__ == "__main__":
  185. # 测试中文日志
  186. SimpleLogger.info(u"用户登录", u"张三", ip="192.168.1.100")
  187.  
  188. # 测试混合参数
  189. SimpleLogger.error("配置错误", "database", code=500, detail=OrderedDict([("line", 42), ("file", "app.conf")]))
  190.  
  191. # 测试二进制数据
  192. bad_data = '\xe6\x97\xa0' # GBK编码的汉字"无"
  193. SimpleLogger.warning(b"invalid data", bad_data, sector=[0x12, 0xff, 0x7f])
  194.  
  195. # 测试嵌套参数
  196. SimpleLogger.debug("调试信息", {"key": [1, 2, 3]}, timeout=30.5)
Success #stdin #stdout #stderr 0.08s 68728KB
stdin
Standard input is empty
stdout
Standard output is empty
stderr
[2025-03-07 09:57:29,721] [INFO] Unknown module:186 ➤ <module> | 用户登录 ⎸ 张三 ⎸ ip=192.168.1.100
Traceback (most recent call last):
  File "/usr/lib/pypy/lib-python/2.7/logging/__init__.py", line 874, in emit
    msg = self.format(record)
  File "/usr/lib/pypy/lib-python/2.7/logging/__init__.py", line 747, in format
    return fmt.format(record)
  File "prog.py", line 35, in format
    return u"⚠️ FORMAT_ERROR | %s | Raw: %s" % (e, record.msg)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe9 in position 0: ordinal not in range(128)
Logged from file None, line 189
[2025-03-07 09:57:29,731] [WARNING] Unknown module:193 ➤ <module> | invalid data ⎸ 无 ⎸ sector=[18,255,127]
Traceback (most recent call last):
  File "/usr/lib/pypy/lib-python/2.7/logging/__init__.py", line 874, in emit
    msg = self.format(record)
  File "/usr/lib/pypy/lib-python/2.7/logging/__init__.py", line 747, in format
    return fmt.format(record)
  File "prog.py", line 35, in format
    return u"⚠️ FORMAT_ERROR | %s | Raw: %s" % (e, record.msg)
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe8 in position 0: ordinal not in range(128)
Logged from file None, line 196