Python logging:如何避免未打印日志时执行其中的函数?
问题描述
我有一些仅用于调试的耗时较长的函数,当前即使在更高日志级别运行时,这些函数仍会执行,但我希望避免这种情况。我知道可以用if logger.getEffectiveLevel()来规避,但有没有更优雅的实现方式?
示例代码
import logging import time def waitsec(): time.sleep(5) logger = logging.getLogger("Logger") logger.setLevel(logging.ERROR) logger.error(f'Before call waitsec') logger.debug(f'This is not printed, but still waits 5 seconds {waitsec()}') # 希望完全跳过这行的执行 logger.error(f'After call waitsec')
当前输出
Before call waitsec After call waitsec
优雅的解决方案
1. 用isEnabledFor做前置判断
这是贴合日志模块设计的标准写法,语义比直接判断级别更清晰:
import logging import time def waitsec(): time.sleep(5) logger = logging.getLogger("Logger") logger.setLevel(logging.ERROR) logger.error(f'Before call waitsec') if logger.isEnabledFor(logging.DEBUG): logger.debug(f'This will only run when DEBUG is enabled {waitsec()}') logger.error(f'After call waitsec')
只有当DEBUG级别启用时,才会执行waitsec()并输出日志,彻底避免不必要的耗时。
2. 改用日志原生的延迟求值格式
原问题的核心是f-string会在调用logger.debug前就完成所有表达式计算,换成%格式化的日志写法,就能让参数里的函数调用延迟到日志实际输出时才执行:
# 用%s占位符替代f-string,此时waitsec()不会立即执行 logger.debug('This will only run when DEBUG is enabled %s', waitsec())
只有当DEBUG级别生效时,waitsec()才会被调用。
3. 用装饰器批量处理调试函数
如果有大量这类耗时调试函数,写个装饰器统一控制执行逻辑:
import logging import time from functools import wraps def debug_only(func): @wraps(func) def wrapper(*args, **kwargs): logger = logging.getLogger(func.__module__) if logger.isEnabledFor(logging.DEBUG): return func(*args, **kwargs) return None return wrapper @debug_only def waitsec(): time.sleep(5) return "waited 5s" logger = logging.getLogger("Logger") logger.setLevel(logging.ERROR) logger.error(f'Before call waitsec') logger.debug(f'This is not printed, and waitsec is not called {waitsec()}') logger.error(f'After call waitsec')
被装饰的函数在非DEBUG级别下会直接返回None,不会执行耗时逻辑。
4. 自定义延迟日志适配器(进阶)
如果需要全局统一处理延迟求值,可以封装一个日志适配器:
import logging class LazyLoggerAdapter(logging.LoggerAdapter): def debug(self, msg, *args, **kwargs): if self.isEnabledFor(logging.DEBUG): # 自动执行可调用参数 args = [arg() if callable(arg) else arg for arg in args] super().debug(msg, *args, **kwargs) logger = LazyLoggerAdapter(logging.getLogger("Logger"), {}) logger.setLevel(logging.ERROR) def waitsec(): time.sleep(5) return "waited 5s" logger.error(f'Before call waitsec') logger.debug('This will only run when DEBUG is enabled %s', waitsec) # 直接传函数对象 logger.error(f'After call waitsec')
适配器会在DEBUG级别生效时才调用传入的函数对象,无需手动判断。
内容的提问来源于stack exchange,提问作者Sombrero
相关产品推荐
相关产品推荐

