设置一个 class 级别的记录器
set up a class-level logger
我可以轻松配置 global 记录器的属性:
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] [%(levelname)s]: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )",
stream=sys.stdout
)
如何在 class-level 实现同等的东西? (下面的代码 没有 工作)
import logging
class SomeClass:
def __init__(self) -> None:
self.logger = logging.getLogger(__name__)
self.logger.dictConfig({
"level": logging.INFO,
"format": "[%(asctime)s] [%(levelname)s]: %(message)s",
"datefmt": "%d/%m/%Y ( %H:%M:%S )",
"stream": sys.stdout
})
def foo(self) -> None:
self.logger.info("foooo ...")
c = SomeClass()
c.foo()
这是我得到的错误:
$ python logger.py 2>&1 | grep "Error"
AttributeError: 'Logger' object has no attribute 'dictConfig'
编辑:
我寻找一个 single 初始化命令,而不是像:
self.logger.setLevel(...)
self.logger.setFormatter(...)
我正在寻找比这更好的解决方案:
import sys
import logging
class SomeClass:
def __init__(self) -> None:
self.logger = logging.getLogger(__name__)
H = logging.StreamHandler(sys.stdout)
H.setLevel(logging.INFO)
H.setFormatter(
logging.Formatter(
fmt="[%(asctime)s] %(levelname)s: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )"
))
self.logger.addHandler(H)
def foo(self) -> None:
self.logger.warning("foooo ...")
c = SomeClass()
c.foo()
首先,如果您希望它成为 class-level 记录器,我会将记录器定义为 class 属性。其次,当您调用 logging.getLogger
而不是 __name__
时,我会使用记录器名称,而是使用 class 独有的名称。由于您可以在不同的模块中重复使用相同的 class 名称,因此我将使用 __name__
和 class 名称的组合。为了演示这一点,下面的第二个演示有两个 class SomeClass
实例,一个在脚本文件中,一个在名为 workers
的模块中。这些 classes 将实例化记录器,其唯一区别是记录消息的格式。但首先:
在同一脚本文件中包含多个日志记录的示例 Class
import sys
import logging
class SomeClass1:
logger = logging.getLogger(__name__ + '.SomeClass1')
H = logging.StreamHandler(sys.stdout)
H.setLevel(logging.INFO)
H.setFormatter(
logging.Formatter(
fmt="SomeClass1: [%(asctime)s] %(levelname)s: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )"
))
logger.addHandler(H)
def foo(self) -> None:
self.logger.warning("foooo ...")
class SomeClass2:
logger = logging.getLogger(__name__ + '.SomeClass2')
H = logging.StreamHandler(sys.stdout)
H.setLevel(logging.INFO)
H.setFormatter(
logging.Formatter(
fmt="SomeClass2: [%(asctime)s] %(levelname)s: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )"
))
logger.addHandler(H)
def bar(self) -> None:
self.logger.warning("bar ...")
c1 = SomeClass1()
c1.foo()
c2 = SomeClass2()
c2.bar()
打印:
SomeClass1: [30/05/2022 ( 09:14:06 )] WARNING: foooo ...
SomeClass2: [30/05/2022 ( 09:14:06 )] WARNING: bar ...
在不同模块中具有相同 Class 名称的示例
workers.py
import sys
import logging
class SomeClass:
logger = logging.getLogger(__name__ + '.SomeClass')
H = logging.StreamHandler(sys.stdout)
H.setLevel(logging.INFO)
H.setFormatter(
logging.Formatter(
fmt="workers module: [%(asctime)s] %(levelname)s: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )"
))
logger.addHandler(H)
def foo(self) -> None:
self.logger.warning("foooo ...")
script.py
import sys
import logging
import workers
class SomeClass:
logger = logging.getLogger(__name__ + '.SomeClass')
H = logging.StreamHandler(sys.stdout)
H.setLevel(logging.INFO)
H.setFormatter(
logging.Formatter(
fmt="Script File: [%(asctime)s] %(levelname)s: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )"
))
logger.addHandler(H)
def foo(self) -> None:
self.logger.warning("foooo ...")
c1a = SomeClass()
c1b = SomeClass()
c1a.foo()
c1b.foo()
c2 = workers.SomeClass()
c2.foo()
打印:
Script File: [30/05/2022 ( 09:23:02 )] WARNING: foooo ...
Script File: [30/05/2022 ( 09:23:02 )] WARNING: foooo ...
workers module: [30/05/2022 ( 09:23:02 )] WARNING: foooo ...
您可能不喜欢这个答案,但在 Python 中,class-level 记录器并没有真正意义 - 不像 Java 和 C#,其中 class 是软件分解的单位,在Python中模块就是那个单位。因此,__name__
给出了 模块的名称 而不是其中定义的任何特定 class。
此外,日志记录配置(关于处理程序、格式化程序、过滤器等)是在 应用程序 级别而不是库级别完成的,因此它应该只真正完成一次在 __name__ == '__main__'
条件下,而不是随机 classes.
如果您确实需要比模块级别更精细的日志记录,请为您的记录器使用 __name__ + 'SomeClass'
等记录器名称。
日志记录文档列出了许多 anti-patterns 与最佳实践相反的内容。
通常,除了单个 getLogger 调用之外,您不应该费心处理 setFormatter、setLevel 和诸如此类的方法系列,也不应该管理记录器实例的生命周期。如果您需要超越 logging.basciConfig
中的可能性,请使用 logging.config
模块!
鉴于您的 SomeClass
存在于一个模块中,因此其导入路径(因此 __name__
变量的值)为 some.project.some.module
,在您的应用程序启动期间的某处您应该配置所有
等日志记录工具
import logging.config
logging.config.dictConfig({
"version": 1,
"formatters": {
"default": {
"class": "logging.Formatter",
"format": "[%(asctime)s] [%(levelname)s]: %(message)s",
"datefmt": "%d/%m/%Y ( %H:%M:%S )",
},
},
"handlers": {
"stdout": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": sys.stdout,
},
"null": {
"class": "logging.NullHandler",
}
},
"loggers": {
"some.project.some.module": {
"level": "INFO",
"propagate": True,
"handlers": ["null"],
},
},
"root": {
"handlers": ["stdout"],
"level": "INFO",
},
})
如果只将最顶层的根记录器附加到实际写入 file/stdout/whatever 的处理程序,我就更容易思考日志记录模块。通过这种方式,其他记录器仅作为调整每个模块的日志记录级别的一种方式,并可能注入特殊的错误处理程序。
查看 更详细的答案,了解为什么记录器对您的 class 不是特殊的,而是它的模块。
我可以轻松配置 global 记录器的属性:
logging.basicConfig(
level=logging.INFO,
format="[%(asctime)s] [%(levelname)s]: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )",
stream=sys.stdout
)
如何在 class-level 实现同等的东西? (下面的代码 没有 工作)
import logging
class SomeClass:
def __init__(self) -> None:
self.logger = logging.getLogger(__name__)
self.logger.dictConfig({
"level": logging.INFO,
"format": "[%(asctime)s] [%(levelname)s]: %(message)s",
"datefmt": "%d/%m/%Y ( %H:%M:%S )",
"stream": sys.stdout
})
def foo(self) -> None:
self.logger.info("foooo ...")
c = SomeClass()
c.foo()
这是我得到的错误:
$ python logger.py 2>&1 | grep "Error"
AttributeError: 'Logger' object has no attribute 'dictConfig'
编辑: 我寻找一个 single 初始化命令,而不是像:
self.logger.setLevel(...)
self.logger.setFormatter(...)
我正在寻找比这更好的解决方案:
import sys
import logging
class SomeClass:
def __init__(self) -> None:
self.logger = logging.getLogger(__name__)
H = logging.StreamHandler(sys.stdout)
H.setLevel(logging.INFO)
H.setFormatter(
logging.Formatter(
fmt="[%(asctime)s] %(levelname)s: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )"
))
self.logger.addHandler(H)
def foo(self) -> None:
self.logger.warning("foooo ...")
c = SomeClass()
c.foo()
首先,如果您希望它成为 class-level 记录器,我会将记录器定义为 class 属性。其次,当您调用 logging.getLogger
而不是 __name__
时,我会使用记录器名称,而是使用 class 独有的名称。由于您可以在不同的模块中重复使用相同的 class 名称,因此我将使用 __name__
和 class 名称的组合。为了演示这一点,下面的第二个演示有两个 class SomeClass
实例,一个在脚本文件中,一个在名为 workers
的模块中。这些 classes 将实例化记录器,其唯一区别是记录消息的格式。但首先:
在同一脚本文件中包含多个日志记录的示例 Class
import sys
import logging
class SomeClass1:
logger = logging.getLogger(__name__ + '.SomeClass1')
H = logging.StreamHandler(sys.stdout)
H.setLevel(logging.INFO)
H.setFormatter(
logging.Formatter(
fmt="SomeClass1: [%(asctime)s] %(levelname)s: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )"
))
logger.addHandler(H)
def foo(self) -> None:
self.logger.warning("foooo ...")
class SomeClass2:
logger = logging.getLogger(__name__ + '.SomeClass2')
H = logging.StreamHandler(sys.stdout)
H.setLevel(logging.INFO)
H.setFormatter(
logging.Formatter(
fmt="SomeClass2: [%(asctime)s] %(levelname)s: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )"
))
logger.addHandler(H)
def bar(self) -> None:
self.logger.warning("bar ...")
c1 = SomeClass1()
c1.foo()
c2 = SomeClass2()
c2.bar()
打印:
SomeClass1: [30/05/2022 ( 09:14:06 )] WARNING: foooo ...
SomeClass2: [30/05/2022 ( 09:14:06 )] WARNING: bar ...
在不同模块中具有相同 Class 名称的示例
workers.py
import sys
import logging
class SomeClass:
logger = logging.getLogger(__name__ + '.SomeClass')
H = logging.StreamHandler(sys.stdout)
H.setLevel(logging.INFO)
H.setFormatter(
logging.Formatter(
fmt="workers module: [%(asctime)s] %(levelname)s: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )"
))
logger.addHandler(H)
def foo(self) -> None:
self.logger.warning("foooo ...")
script.py
import sys
import logging
import workers
class SomeClass:
logger = logging.getLogger(__name__ + '.SomeClass')
H = logging.StreamHandler(sys.stdout)
H.setLevel(logging.INFO)
H.setFormatter(
logging.Formatter(
fmt="Script File: [%(asctime)s] %(levelname)s: %(message)s",
datefmt="%d/%m/%Y ( %H:%M:%S )"
))
logger.addHandler(H)
def foo(self) -> None:
self.logger.warning("foooo ...")
c1a = SomeClass()
c1b = SomeClass()
c1a.foo()
c1b.foo()
c2 = workers.SomeClass()
c2.foo()
打印:
Script File: [30/05/2022 ( 09:23:02 )] WARNING: foooo ...
Script File: [30/05/2022 ( 09:23:02 )] WARNING: foooo ...
workers module: [30/05/2022 ( 09:23:02 )] WARNING: foooo ...
您可能不喜欢这个答案,但在 Python 中,class-level 记录器并没有真正意义 - 不像 Java 和 C#,其中 class 是软件分解的单位,在Python中模块就是那个单位。因此,__name__
给出了 模块的名称 而不是其中定义的任何特定 class。
此外,日志记录配置(关于处理程序、格式化程序、过滤器等)是在 应用程序 级别而不是库级别完成的,因此它应该只真正完成一次在 __name__ == '__main__'
条件下,而不是随机 classes.
如果您确实需要比模块级别更精细的日志记录,请为您的记录器使用 __name__ + 'SomeClass'
等记录器名称。
日志记录文档列出了许多 anti-patterns 与最佳实践相反的内容。
通常,除了单个 getLogger 调用之外,您不应该费心处理 setFormatter、setLevel 和诸如此类的方法系列,也不应该管理记录器实例的生命周期。如果您需要超越 logging.basciConfig
中的可能性,请使用 logging.config
模块!
鉴于您的 SomeClass
存在于一个模块中,因此其导入路径(因此 __name__
变量的值)为 some.project.some.module
,在您的应用程序启动期间的某处您应该配置所有
import logging.config
logging.config.dictConfig({
"version": 1,
"formatters": {
"default": {
"class": "logging.Formatter",
"format": "[%(asctime)s] [%(levelname)s]: %(message)s",
"datefmt": "%d/%m/%Y ( %H:%M:%S )",
},
},
"handlers": {
"stdout": {
"formatter": "default",
"class": "logging.StreamHandler",
"stream": sys.stdout,
},
"null": {
"class": "logging.NullHandler",
}
},
"loggers": {
"some.project.some.module": {
"level": "INFO",
"propagate": True,
"handlers": ["null"],
},
},
"root": {
"handlers": ["stdout"],
"level": "INFO",
},
})
如果只将最顶层的根记录器附加到实际写入 file/stdout/whatever 的处理程序,我就更容易思考日志记录模块。通过这种方式,其他记录器仅作为调整每个模块的日志记录级别的一种方式,并可能注入特殊的错误处理程序。
查看