大家好,欢迎来到IT知识分享网。
什么是Logger?:
以下是Logger的主要组成部分:
Logger(记录器):是主要的接口,负责发送日志消息。应用程序通常会创建一个或多个Logger对象,每个Logger对象负责一组相关的功能或模块。
Handler(处理器):决定将日志消息发送到何处。Handler可以将日志消息输出到控制台、文件、网络等不同的目标。
Formatter(格式化器):定义日志消息的输出格式。Formatter可以格式化消息的时间、级别、模块名等信息。
Filter(过滤器):允许更细粒度地控制哪些日志消息应该被处理。通过使用过滤器,可以根据需要选择性地记录或忽略某些消息。
下面是一个简单的例子,演示如何使用Logger记录日志:
import logging # 创建Logger对象 名字为 my_logger logger = logging.getLogger('my_logger') logger.setLevel(logging.DEBUG) # 创建文件处理器,并设置级别为DEBUG 输出文件名字为:my_log.log file_handler = logging.FileHandler('my_log.log') file_handler.setLevel(logging.DEBUG) # 创建控制台处理器,并设置级别为INFO console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) # 创建日志消息的格式化器 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') # 将格式化器添加到处理器 file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) # 将处理器添加到记录器 logger.addHandler(file_handler) logger.addHandler(console_handler) # 记录不同级别的日志消息 logger.debug('This is a debug message') logger.info('This is an info message') logger.warning('This is a warning message') logger.error('This is an error message') logger.critical('This is a critical message')
运行效果
同时还可以发现
控制台终端输出的消息 比文件中 少了一条DEBUG消息 ,
这是因为:
终端中设置的是INFO级别(日志消息只有>=INFO才记录),文件时DEBUG级别(日志消息只有>=DEBUG才记录), 而日志的级别从低到高分别是:DEBUG->INFO->WARNING->ERROR->CRITICAL。
对应的解释如下:
- DEBUG: 用于详细的调试信息。在开发和调试阶段使用,通常不应在生产环境中启用。
- INFO: 提供程序正常运行时的信息。适用于生产环境,用于确认程序是否按照预期工作。
- WARNING: 表示可能的问题,但不会影响程序的正常运行。需要注意并进行检查,以确保程序的稳定性。
- ERROR: 指示更严重的问题,可能导致某些功能的失败。需要及时关注和修复。
- CRITICAL: 表示严重的错误,可能导致程序无法继续运行。通常需要立即解决。
- 在给定的日志配置中,如果设置了级别为INFO,那么 INFO、WARNING、ERROR、CRITICAL级别的日志消息都会被记录,而DEBUG级别的消息将被忽略。
同时还有一些没用的小知识:
logging.basicConfig函数各参数:
属性名称 | 格式 | 说明 |
---|---|---|
filename | 指定日志文件名 | |
filemode | 指定文件的打开模式,’w’或者’a’; | |
format | 格式器 | 指定输出的格式和内容 |
datefmt | 指定时间格式,同time.strftime() | |
level | 设置日志级别,默认为logging.WARNNING | |
stream | 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略 |
格式器:
format = logging.Formatter(‘%(asctime)s – %(levelname)s – %(message)s’)
可能会用到的一些其他参数名字: 用法如上
日志处理器 :
console_handler = logging.StreamHandler() #创建日志处理器 logger.addHandler(console_handler) #添加日志处理器
还有一些其他的没用的小知识 :其他日志处理器如下
- StreamHandler:logging.StreamHandler;日志输出到流,可以是sys.stderr,sys.stdout或者文件
- FileHandler:logging.FileHandler;日志输出到文件
- BaseRotatingHandler:logging.handlers.BaseRotatingHandler;基本的日志回滚方式
- RotatingHandler:logging.handlers.RotatingHandler;日志回滚方式,支持日志文件最大数量和日志文件回滚
- TimeRotatingHandler:logging.handlers.TimeRotatingHandler;日志回滚方式,在一定时间区域内回滚日志文件
日志回滚
举个栗子:
import logging from logging.handlers import RotatingFileHandler # 配置日志 logger = logging.getLogger("example_logger") logger.setLevel(logging.DEBUG) # 创建 RotatingFileHandler 处理器,设置日志文件名 和 最大文件大小 backupCout指定要保留的备份日志文件的数量 handler = RotatingFileHandler("example.log", maxBytes=1, backupCount=3) # 配置日志格式 formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s') handler.setFormatter(formatter) # 将处理器添加到 logger logger.addHandler(handler) def simulate_logging(): # 模拟写入日志 for i in range(10): logger.debug(f"This is log entry {i}") if __name__ == "__main__": simulate_logging()
结果很明显了:
一共四个日志文件,注意看文件的内容
在上面的例子中,我们使用了 RotatingFileHandler
处理器,并设置了最大文件大小为 1 字节,备份文件的数量为 3。这意味着当日志文件大小达到 1 字节时,会将当前日志文件切分为一个备份文件,并保留最多 3 个备份文件。
你可以根据实际需求调整 maxBytes
和 backupCount
参数。当日志文件大小达到 maxBytes
时,会触发日志回滚。
还有篇文章不错一定要看:https://www.cnblogs.com/yxh168/articles/11855581.html#:~:text=backupCount,%E6%98%AF%E4%BF%9D%E7%95%99%E6%97%A5%E5%BF%97%E4%B8%AA%E6%95%B0.%E9%BB%98%E8%AE%A4%E7%9A%840%E6%98%AF%E4%B8%8D%E4%BC%9A%E8%87%AA%E5%8A%A8%E5%88%A0%E9%99%A4%E6%8E%89%E6%97%A5%E5%BF%97%2C%E8%8B%A5%E8%AE%BE10%2C%E5%88%99%E5%9C%A8%E6%96%87%E4%BB%B6%E7%9A%84%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B%E4%B8%AD%E5%BA%93%E4%BC%9A%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E6%9C%89%E8%B6%85%E8%BF%87%E8%BF%99%E4%B8%AA10%2C%E8%8B%A5%E8%B6%85%E8%BF%87%2C%E5%88%99%E4%BC%9A%E4%BB%8E%E6%9C%80%E5%85%88%E5%88%9B%E5%BB%BA%E7%9A%84%E5%BC%80%E5%A7%8B%E5%88%A0%E9%99%A4https://www.cnblogs.com/yxh168/articles/11855581.html#:~:text=backupCount,%E6%98%AF%E4%BF%9D%E7%95%99%E6%97%A5%E5%BF%97%E4%B8%AA%E6%95%B0.%E9%BB%98%E8%AE%A4%E7%9A%840%E6%98%AF%E4%B8%8D%E4%BC%9A%E8%87%AA%E5%8A%A8%E5%88%A0%E9%99%A4%E6%8E%89%E6%97%A5%E5%BF%97%2C%E8%8B%A5%E8%AE%BE10%2C%E5%88%99%E5%9C%A8%E6%96%87%E4%BB%B6%E7%9A%84%E5%88%9B%E5%BB%BA%E8%BF%87%E7%A8%8B%E4%B8%AD%E5%BA%93%E4%BC%9A%E5%88%A4%E6%96%AD%E6%98%AF%E5%90%A6%E6%9C%89%E8%B6%85%E8%BF%87%E8%BF%99%E4%B8%AA10%2C%E8%8B%A5%E8%B6%85%E8%BF%87%2C%E5%88%99%E4%BC%9A%E4%BB%8E%E6%9C%80%E5%85%88%E5%88%9B%E5%BB%BA%E7%9A%84%E5%BC%80%E5%A7%8B%E5%88%A0%E9%99%A4
如何捕获traceback,输出系统错误信息
import logging import traceback # 配置日志 logging.basicConfig(level=logging.DEBUG, format='%(asctime)s-%(levelname)s-%(message)s') def divide_numbers(a, b): try: result = a / b return result except Exception as e: # 捕获异常并记录 traceback 到日志 logging.error("An error occurred: %s", e, exc_info=True) # 错误信息捕获 return None def main(): logging.info("logger开始") # 模拟除法操作,可能引发异常 result = divide_numbers(10, 0) # 如果异常被捕获,程序可以继续执行 if result is None: logging.warning("The result is not available due to a previous error.") # 模拟正常情况 result = divide_numbers(10, 2) logging.info("The result is: %s", result) if __name__ == "__main__": main()
结果一目了然了:
多模块的logging
这是更加常见的 因为一个完成的项目应该是包含不同的模块的,我又想针对不同的模块来设置不同的logging,就需要这个了
module.py
import logging # 为模块创建一个日志记录器 logger = logging.getLogger(__name__) """ 在这里,__name__ 是一个特殊变量,它代表当前模块的名字。这样做的好处是,每个模块都可以有自己的日志记录器,通过使用模块名字,你可以更容易地追踪日志消息的来源。 如果你在一个脚本中运行,__name__ 将会是 "__main__"; 如果你在一个模块中导入这个模块,__name__ 将会是模块的名字。 """ # 设置模块的日志级别为DEBUG logger.setLevel(logging.DEBUG) def print_message(): logger.debug("这是来自模块的调试消息。") logger.info("这是来自模块的信息消息。")
main.py
import logging import module # 配置根日志记录器 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s' ) # 为主模块创建一个日志记录器 logger = logging.getLogger(__name__) # logger = logging.getLogger("mainxxxx") #你也可以自己指定 def main(): logger.info("这是来自主模块的信息消息。") module.print_message() if __name__ == "__main__": main()
在这个例子中,主模块main.py
的根日志级别设置为INFO
,而module.py
的日志级别被设置为DEBUG
。这意味着,module.py
中的DEBUG
级别的日志消息将被输出,而在main.py
中只有INFO
级别及以上的消息才会被输出。
这样,当你运行
main.py
时,你将看到类似以下的输出:
2023-12-06 00:00:00,000 - INFO - 这是来自主模块的信息消息。
2023-12-06 00:00:00,000 - DEBUG - 这是来自模块的调试消息。
2023-12-06 00:00:00,000 - INFO - 这是来自模块的信息消息。
注意:
在Python的logging
模块中,basicConfig
函数是用于配置默认的根日志记录器(root logger)的函数,而不是为特定的logger配置参数。当你在一个模块中使用basicConfig
时,它会配置根日志记录器,而不是模块专属的logger。并且只能调用一次,一般放在程序的最开始运行的地方。
如果你想为特定模块配置独立的logger,通常会使用getLogger
来创建一个logger实例,然后对该实例进行配置。这样做的好处是,你可以独立地控制每个logger的配置,而不影响其他模块或根记录器。而且 即使你当前的模块日志记录器 继承了根日志记录器(root logger)的一些设置,只要你在当前的模块中 显示的 设置一些参数 该参数还是会生效 。
比如上面 模块记录器 继承了 root logger 的info 但是显示的 设置了debug 所以该模块的debug 还是会输出
有一个知识点:
当你在不同的模块中使用getLogger(__name__)
时,每个模块都会得到一个独立的logger实例,其名称基于模块的名称。这样,你可以轻松地在日志中识别消息来自哪个模块。这样的好处是:如果你有两个模块main
和module
,它们都使用getLogger(__name__)
创建logger,那么它们的logger名称分别是main
和module
。当你在日志中看到来自main
模块的消息时,你知道这些消息来自main
模块。
例如:
Logger从来不直接实例化,经常通过logging模块级方法(Module-Level Function)logging.getLogger(name=”xxxx”)来获得,其中如果name不指定就用root logger。
名字是以点号分割的命名方式命名的(a.b.c)。这种命名方式里面,后面的loggers是前面logger的子logger,自动继承父loggers的log信息,正因为此,没有必要把一个应用的所有logger都配置一遍,只要把顶层的logger配置好了,然后子logger根据需要继承就行了。对同一个名字的多个调logging.getLogger()方法会返回同一个logger对象
这句话的理解:
通过YAML文件来配置logger
第一个案例:
config.yml
Log: log_level: 1 path_to_log: ./log
log.py
import logging import os import sys from datetime import datetime from logging import handlers import yaml class Logger(object): # 日志级别关系映射 level_relations = { 'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING, 'error': logging.ERROR, 'critical': logging.CRITICAL } def __init__(self, log_file_path, level='info', when='midnight', backCount=30, fmt='%(asctime)s - %(module)s.cpp[line:%(lineno)d] - %(levelname)s: %(message)s'): """ :param log_file_path: 日志文件路径 :param level: 日志级别 :param when: 切割时间间隔:S: 秒, M: 分, H: 小时, D: 天: W0~W6: 星期几, midnight: 每天零点 :param backCount: 保留的日志文件数量 :param fmt: 日志格式 """ self.logger = logging.getLogger(log_file_path) # self.logger.handlers.clear() # 是否已创建日志记录程序 if not self.logger.handlers: self.logger.setLevel(self.level_relations.get(level)) log_formatter = logging.Formatter(fmt) # 日志流处理器,将日志输出到屏幕上 stream_handler = logging.StreamHandler() stream_handler.setFormatter(log_formatter) self.logger.addHandler(stream_handler) # 刷新标准输出缓冲区,确保之前的日志消息被立即输出到屏幕 sys.stdout.flush() # 日志定时循环文件处理器,将日志写入到文件 file_handler = handlers.TimedRotatingFileHandler(filename=log_file_path, when=when, backupCount=backCount, encoding='utf-8') file_handler.setFormatter(log_formatter) self.logger.addHandler(file_handler) def __getattr__(self, name): # log.xxx 时,如果在日志级别中,返回对应的日志级别方法 if name in self.level_relations: return getattr(self.logger, name) # 返回日志对象的指定属性名的值 return self.logger.__getattribute__(name) # 加载配置文件 def get_config(file_name): # 读取配置文件 if len(file_name) == 1: file_name = 'config.yml' else: file_name = file_name[1] # 自定义配置文件名称 try: with open(file_name, 'r', encoding='utf-8') as config_file: cfg = yaml.load(config_file, Loader=yaml.FullLoader) except: with open(file_name, 'r', encoding='ansi') as config_file: cfg = yaml.load(config_file, Loader=yaml.FullLoader) # log保存路径,以年月日命名log文件 log_file_dir = cfg['Log']['path_to_log'] log_file = log_file_dir + "/" + datetime.now().strftime('%m_%d_%Y.log') # 判断log文件夹是否存在,若不存在,创建该目录文件夹 if not os.path.exists(log_file_dir): os.makedirs(log_file_dir) # 日志级别 dict = {"0": "debug", "1": "info", "2": "warning", "3": "error", "4": "critical"} # 读取log路径及设置log等级 log = Logger(log_file, level=dict[str(cfg['Log']['log_level'])]) return log, cfg log, cfg = get_config(sys.argv)
第二个案例:(更推荐的)
resources/logging.yml
version: 1 # 配置文件版本,当前为1 formatters: simple: # 定义一个名为 "simple" 的日志格式 format: '%(asctime)s - %(module)s.cpp[line:%(lineno)d] - %(levelname)s: %(message)s' # 详细定义日志记录的格式,包括时间、模块、行号、日志级别和消息 handlers: timed_rotating: class: logging.handlers.TimedRotatingFileHandler # 使用 TimedRotatingFileHandler 类来处理日志 level: DEBUG # 设置日志处理器的级别为 DEBUG formatter: simple # 使用上面定义的 "simple" 格式 when: midnight # 每天的午夜切分日志文件 interval: 1 # 间隔为1天 backupCount: 30 # 保留最多30个备份文件 filename: ./log/snap-video-service.log # 指定日志文件路径 encoding: utf-8 # 设置日志文件的编码为 utf-8 console_handler: class: logging.StreamHandler # 使用 StreamHandler 类来将日志输出到控制台 level: DEBUG # 设置日志处理器的级别为 DEBUG formatter: simple # 使用上面定义的 "simple" 格式 stream: ext://sys.stdout # 输出到标准输出流 loggers: app: level: DEBUG # 设置 "app" logger 的级别为 DEBUG handlers: [ timed_rotating ] # 将 "timed_rotating" 处理器添加到 "app" logger propagate: no # 防止日志传播给父 logger root: level: INFO # 设置根 logger 的级别为 INFO handlers: [ console_handler, timed_rotating ] # 将两个处理器添加到根 logger
config.yml
Log: log_level: 0 path_to_log: ./log #保存本地配置 #其他的配置xxxx videoSave: #推送视频画面的宽,与输入视频流保持一致 # videoW: 1920 videoW: 1280 #推送视频画面的高,与输入视频流保持一致 # videoH: 1080 videoH: 720
config.py
# 导入必要的模块 import logging import logging.config as log_config import os import yaml # 配置全局日志 def global_config(logging_cnf='resources/logging.yml'): # 创建 'log' 目录,如果目录已存在则忽略 os.makedirs('log', exist_ok=True) # 读取日志配置文件 with open(logging_cnf, encoding='utf-8') as f: # 解析配置文件内容 log_cnf = yaml.safe_load(f.read()) # 通过字典配置 logging 模块 log_config.dictConfig(log_cnf) # 获取根 logger logger = logging.getLogger() # 尝试读取应用程序配置文件 try: with open('./config.yml', 'r', encoding='utf-8') as config_file: # 解析配置文件内容 cfg = yaml.load(config_file, Loader=yaml.FullLoader) except (Exception,) as e: # 记录错误日志,指示配置文件读取失败 logger.error("config.yml配置文件读取失败,请核查")
main.py
import sys import config from utils import VideoSave if __name__ == '__main__': if len(sys.argv) < 2: config.global_config() else: config.global_config(sys.argv[1])
有一个小问题:
如果程序是一个死循环,由多个模块组成,运行在服务器上, 我使用
RotatingFileHandler 处理器的话,我指定每个日志文件的的大小为maxsize,保留backcount=2个副本的话, 那加入我当前处理的日志为C, .1副本内容为 B .2副本为 A (比如说我的日志是一个自增的字母)
那么下一个日志 .1副本内容为C .2副本为B, 内容A日志就被覆盖没了,当前日志处理的是字母D
那就会出现 在服务器的 你当前时刻拷贝的一些日志, 比如10-20号文件,
过了一会 你再去拷贝 就变成了 可能是20-30号文件, 相当于 明明相同的日志内容,时间越靠后 存到了越靠后的日志文件副本中,给我们分析日志带来了问题
(我上午拷贝的 xxx.1 xxxx.2副本) 到了下午 成了 xxxx.10 xxxx.11副本了 我还得重新改名字。。。。
值得阅读的文章:
https://www.jb51.net/article/192489.htmhttps://www.jb51.net/article/192489.htm
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/147981.html