1. 日志的意义
日志是个好东西,但却并不是所有人都愿意记,直到出了问题才追悔莫及,长叹一声,当初要是记日志就好了。
但记日志却是个技术活,不能什么都不记,但也不能什么都记。如果记了很多没用的信息,反而给查日志排错的过程增加很多困难。
所以,日志要记录在程序的关键节点,而且内容要简洁,传递信息要准确。要清楚的反应出程序当时的状态,时间,错误信息等。
只有做到这样,我们才能在第一时间找到问题,并且解决问题
开发阶段,所有的问题都可以通过调试,在程序中输出,但项目上线后,会进行统一的错误处理,不能将错误信息暴漏出来,所以最好的方式,就是将程序运行信息存储在日志中
程序上线后,是万万不能没有日志的
2. django 中如何处理日志
Django 使用 Python 内置的 logging 模块处理系统日志,所以,只要掌握了 Python 中的 logging 模块,基本也就能够在 django 中使用日志了
3. Python 中使用日志
这里简单介绍 Python 中 logging 模块的使用,下一章再聊如何在 django 中使用
logging 模块的使用主要包含如下几个方面
- 将日志信息直接输出
- 将日志信息保存到文件中
- 输出变量到日志中
- 更改消息显示格式
- 覆盖日志
- 日志配置
3.1 直接输出日志信息
首先导入 logging 模块,然后在下面选择方法进行日志输出
级别 | 何时使用 |
---|
DEBUG | 细节信息,仅当诊断问题时适用。 | INFO | 确认程序按预期运行。 | WARNING | 表明有已经或即将发生的意外(例如:磁盘空间不足)。程序仍按预期进行。 | ERROR | 由于严重的问题,程序的某些功能已经不能正常执行 | CRITICAL | 严重的错误,表明程序已不能继续执行 |
简单例子
logging.debug('出现了bug')
logging.info('一般信息')
logging.warning('警告信息以下级别默认不出现')
logging.error('出现了错误')
logging.critical('严重问题')
日志信息,被直接输出出来,并没有记录到日志中
3.2 设置日志级别
上面的 logging.info('一般信息') 和 logging.debug('出现了bug') 并没有任何输出,原因在于 logging 模块默认只输出 WARNING以上级别(包含 WARNING)
通过 basicConfig 方法更改日志级别
# 更改日志级别
logging.basicConfig(level=logging.INFO)
logging.info('一般信息')
logging.debug('出现了bug')
logging.warning('警告信息以下级别默认不出现')
logging.error('出现了错误')
logging.critical('严重问题')
级别 INFO 高于 DEBUG,如果希望 logging.debug 方法生效,需要更改 level 为 logging.DEBUG
3.3 保存日志到文件
实际开发中,日志信息一定要保存到文件的
basicConfig 方法页可以设置日志文件的目录信息
修改案例中第1行代码,添加 filename 参数,设置日志文件目录和名称
logging.basicConfig(filename='0707.log', level=logging.INFO)
打开日志文件,发现乱码了
3.4 设置编码
basicConfig 方法页可以设置日志文件编码
logging.basicConfig(filename='0707.log', encoding='utf-8', level=logging.INFO)
3.5 覆盖日志文件
默认情况下,新的日志内容采用的是追加模式
可以通过 filemode 参数设置覆盖之前的日志内容
logging.basicConfig(filename='0707.log', filemode='w', encoding='utf-8', level=logging.INFO)
basicConfig() 被设计为一次性的配置,只有第一次调用会进行操作,随后的调用不会产生有效操作
此段的意思是,当程序启动后,第一次调用上面的方法,会生效,后面如果程序没有重新启动,无论调用多少次,此代码都不会生效
看下面代码
logging.basicConfig(filename='0707.log', filemode='w', encoding='utf-8', level=logging.INFO)
logging.info('一般信息')
启动程序,或者修改代码保存时也会热重载,此时日志文件内容就会被覆盖
但在没有重启的情况下,无论上面代码执行多少次,都不会覆盖内容,而是追加
此种模式的意义在于:程序重启后,旧的日志对于我们没有意义的情况
3.6 记录变量到日志
可以使用下面两种方式进行变量的格式化
logging.basicConfig(filename='0707.log', filemode='w', encoding='utf-8', level=logging.INFO)
logging.info('采用 %s 的方式输出变量', '%s')
logging.info('采用{}的方式输出变量'.format('format'))
3.7 更改显示消息的组成
这是默认情况下日志消息的组成
如果想更改,可以通过 basicConfig 方法的 format 参数设置
logging.basicConfig(format='%(levelname)s:%(message)s', filename='0707.log', filemode='w', encoding='utf-8',
level=logging.INFO)
logging.info('采用 %s 的方式输出变量', '%s')
logging.info('采用{}的方式输出变量'.format('format'))
下面只显示级别和日志内容,没有 root
代码中levelname 和 message LogRecord 的属性,完整属性列表如下
args | 此属性不需要用户进行格式化。 | 合并到 msg 以产生 message 的包含参数的元组,或是其中的值将被用于合并的字典(当只有一个参数且其类型为字典时)。 |
---|
asctime | %(asctime)s | 表示人类易读的 LogRecord 生成时间。 默认形式为 ‘2003-07-08 16:49:45,896’ (逗号之后的数字为时间的毫秒部分)。 | created | %(created)f | LogRecord 被创建的时间(即 time.time() 的返回值)。 | exc_info | 此属性不需要用户进行格式化。 | 异常元组(例如 sys.exc_info )或者如未发生异常则为 None 。 | filename | %(filename)s | pathname 的文件名部分。 | funcName | %(funcName)s | 函数名包括调用日志记录. | levelname | %(levelname)s | 消息文本记录级别('DEBUG' ,'INFO' ,'WARNING' ,'ERROR' ,'CRITICAL' )。 | levelno | %(levelno)s | 消息数字的记录级别 (DEBUG , INFO , WARNING , ERROR , CRITICAL ). | lineno | %(lineno)d | 发出日志记录调用所在的源行号(如果可用)。 | message | %(message)s | 记入日志的消息,即 msg % args 的结果。 这是在发起调用 Formatter.format() 时设置的。 | module | %(module)s | 模块 (filename 的名称部分)。 | msecs | %(msecs)d | LogRecord 被创建的时间的毫秒部分。 | msg | 此属性不需要用户进行格式化。 | 在原始日志记录调用中传入的格式字符串。 与 args 合并以产生 message ,或是一个任意对象 (参见 使用任意对象作为消息)。 | name | %(name)s | 用于记录调用的日志记录器名称。 | pathname | %(pathname)s | 发出日志记录调用的源文件的完整路径名(如果可用)。 | process | %(process)d | 进程ID(如果可用) | processName | %(processName)s | 进程名(如果可用) | relativeCreated | %(relativeCreated)d | 以毫秒数表示的 LogRecord 被创建的时间,即相对于 logging 模块被加载时间的差值。 | stack_info | 此属性不需要用户进行格式化。 | 当前线程中从堆栈底部起向上直到包括日志记录调用并引发创建当前记录堆栈帧创建的堆栈帧信息(如果可用)。 | thread | %(thread)d | 线程ID(如果可用) | threadName | %(threadName)s | 线程名(如果可用) |
比如,修改上面代码,加上 asctime 属性
logging.basicConfig(format='%(levelname)s:%(message)s:%(asctime)s', filename='0707.log', filemode='w', encoding='utf-8',
level=logging.INFO)
查看日志
3.8 模块化
上面介绍的方法已经可以为程序配置日志功能了
logging 模块页提供了模块化的方法,通过下面几个组件来配置日志
- 记录器:暴露了应用程序代码直接使用的接口
- 处理器:将日志记录(由记录器创建)发送到适当的目标
- 过滤器:提供了更细粒度的功能,用于确定要输出的日志记录
- 格式器:指定最终输出中日志记录的样式
其实完成的还是上面的功能,只不过可以进行模块化拆分,比如可以创建多个处理器,多个格式器,通过配置的方式进行处理器、格式器的切换
3.8.1 通过 Python 方法配置
# 创建记录器
logger = logging.getLogger('simple')
# 设置日志记录级别
logger.setLevel(logging.DEBUG)
# 创建处理器
ch = logging.StreamHandler()
# 设置处理器级别
ch.setLevel(logging.DEBUG)
# 创建格式器
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 为处理器设置格式器
ch.setFormatter(formatter)
# 将处理器添加到记录器
logger.addHandler(ch)
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
总结:
①:创建记录器:记录器中提供了 info、debu 、warning、error、critical 方法用来记录日志
②:设置记录器级别:此级别了哪个级别以上的信息会被记录到日志中
③:创建处理器:处理器决定如何处理消息、比如打印到控制台、写入日志文件、发送邮件等
④:设置处理器级别:决定处理器发送哪些消息。记录器中设置的级别,决定哪个级别以上的消息会被发送给处理器,处理器中的设置级别决定了,哪些消息会被处理,比如哪些消息会被写到文件中。
⑤:创建格式器:决定日志的格式和日志中包含哪些内容
⑥:将格式器添加到处理器:处理器将使用此格式器格式化日志
⑦:将处理器添加到记录器:记录器将使用此处理器处理日志
如果希望日志保存到文件中,只需重新创建一个处理器,将其添加到记录器中即可
# 创建控制器,将日志写入到文件中
ch_file = logging.FileHandler('aa.log')
ch_file.setLevel(logging.DEBUG)
# 为处理器设置格式器
ch_file.setFormatter(formatter)
# 将处理器添加到记录器
logger.addHandler(ch_file)
3.8.2 配置字典
Python 3.2中引入的一种新的配置日志记录的方法–用字典来保存logging配置信息。这相对于上面所讲的基于配置文件来保存logging配置信息的方式来说,功能更加强大,也更加灵活,因为我们可把很多的数据转换成字典。比如,我们可以使用JSON格式的配置文件、YAML格式的配置文件,然后将它们填充到一个配置字典中;或者,我们也可以用Python代码构建这个配置字典,或者通过socket接收pickled序列化后的配置信息。总之,你可以使用你的应用程序可以操作的任何方法来构建这个配置字典
编写配置字典,然后通过 logging.config.dictConfig 方法调用此字典
1、简单配置
# 用于格式化
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
log_dict = {
"version": 1,
'disable_existing_loggers': False,
"formatters": {
"simple": {
'format': simple_format
}
},
"handlers": {
"console": {
"class": 'logging.StreamHandler',
"level": 'INFO',
"formatter": "simple"
}
},
"loggers": {
"simple": {
"level": "DEBUG",
"handlers": ['console'],
'propagate': True,
}
}
}
logging.config.dictConfig(log_dict)
logger = logging.getLogger('simple')
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
①:创建了一个名为 simple 的格式器
②:创建了一个名为 console 的处理器,并设置处理器使用的格式器为 simple
③:创建了额一个名为 simple 的记录器,并设置记录器使用的处理器为 console
2、增加新的格式器、处理器和记录器
创建一个新的格式器
standard = '%(levelname)s:%(asctime)s:%(filename)s:%(lineno)d:%(message)s'
添加
创建新的处理器,用于将日志写入文件
在记录器中添加此处理器
运行程序,日志将同时打印到控制台和日志文件中
这里应该体会到了使用配置字典的好处
创建记录器时,选择 standard ,则只会输出 ERROR 以上的日志信息
3、配置文件-yaml
开发时,最好的方式,当然不是在程序中编写代码做出上面配置
而是,新建配置文件,程序运行时,读取配置文件
我们将配置写到一个 yaml ,然后读取此文件
根目录下新建 log_config.yml (yaml 文件的后缀名可以是yaml 或者 yml)
version: 1
formatters:
simple:
format: '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
standard:
format: '%(levelname)s:%(asctime)s:%(filename)s:%(lineno)d:%(message)s'
handlers:
console:
class: logging.StreamHandler
level: DEBUG
formatter: simple
file:
class: logging.FileHandler
formatter: simple
filename: logtest.log
loggers:
simple:
level: DEBUG
handlers: [ file ]
propagate: True
standard:
level: ERROR
handlers: [ console,file ]
propagate: True
视图函数代码,读取 yaml 文件,并将其转换成 dict,使用 logging.config.dictConfig 方法加载配置,并使用其中名称为 simple 的记录器
BASE_DIR = Path(__file__).resolve().parent.parent
with open(str(BASE_DIR) + '\config.yaml', 'r', encoding='utf-8') as f:
file = f.read()
config = yaml.load(file, Loader=yaml.FullLoader)
logging.config.dictConfig(config)
logger = logging.getLogger('simple')
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
切换记录器,看看效果
练习:自己再配置几个处理器和格式器
4、写到 settings 中
也可以将上面的配置直接写到 settings.py 中
变量名称必须为 LOGGING
# 日志配置
LOGGING = {
"version": 1,
"formatters": {
"simple": {
"format": '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
},
"standard": {
"format": '%(levelname)s:%(asctime)s:%(filename)s:%(lineno)d:%(message)s'
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "simple"
},
"file": {
"class": "logging.FileHandler",
"formatter": "simple",
"filename": "logtest.log"
}
},
"loggers": {
"simple": {
"level": "DEBUG",
"handlers": ["file"],
"propagate": True
},
"standard": {
"level": "ERROR",
"handlers": ["console", "file"],
"propagate": True
}
}
}
视图函数
logger = logging.getLogger('simple')
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
4. django 日志
上面介绍了 Python 中使用日志的方式,有了这些基础之后,学习 django 中日志的使用就事半功倍了
django 中仍然使用了 logging 模块做日志处理
其实,3.8 节已经讲完了,就是这么用。。。。
附:关于 logging 模块的详细说明
在Python的logging模块中,主要包含下面四大方面:
- Loggers: 记录器
- Handlers:处理器
- Filters: 过滤器
- Formatters: 格式化器
下文详细说明一下这四大模块
4.1 Loggers
logger 是日志系统的入口。每个 logger 都是命名了的 bucket, 消息写入 bucket 以便进一步处理。
logger 可以配置日志级别。日志级别描述了由该 logger 处理的消息的严重性。Python 定义了下面几种日志级别:
DEBUG :排查故障时使用的低级别系统信息;INFO :一般的系统信息;WARNING :描述系统发生了一些小问题的信息;ERROR :描述系统发生了大问题的信息;CRITICAL :描述系统发生严重问题的信息;
每一条写入 logger 的消息都是一条日志记录。每一条日志记录也包含日志级别,代表对应消息的严重程度。日志记录还包含有用的元数据,来描述被记录的事件细节,例如堆栈跟踪或者错误码。
当logger处理一条消息时,会将自己的日志级别和这条消息的日志级别做对比。如果消息的日志级别匹配或者高于 logger 的日志级别,它就会被进一步处理。否则这条消息就会被忽略掉。
当 logger 确定了一条消息需要处理之后,会把它传给Handler。
4.2 Handlers
Handler是决定如何处理logger中每一条消息的引擎。它描述特定的日志行为,比如把消息输出到屏幕、文件或网络 socket。
和logger一样,handler也有日志级别的概念。如果一条日志记录的级别不匹配或者低于handler的日志级别,对应的消息会被 handler忽略。
一个logger可以有多个handler,每一个handler可以有不同的日志级别。这样就可以根据消息的重要性不同,来提供不同格式的输出。例如,你可以添加一个 handler 把ERROR和CRITICAL消息发到寻呼机,再添加另一个handler把所有的消息(包括 ERROR和CRITICAL消息)保存到文件里以便日后分析。
4.3 过滤器
在日志从 logger 传到 handler 的过程中,使用 Filter 来做额外的控制。
默认情况下,只要级别匹配,任何日志消息都会被处理。不过,也可以通过添加filter来给日志处理的过程增加额外条件。例如,可以添加一个filter只允许某个特定来源的ERROR消息输出。
Filter还被用来在日志输出之前对日志记录做修改。例如,可以写一个filter,当满足一定条件时,把日志记录从ERROR降到 WARNING级别。
Filter在logger和handler中都可以添加;多个filter可以链接起来使用,来做多重过滤操作。
4.4 Formatters
日志记录最终是需要以文本来呈现的。Formatter 描述了文本的格式。一个 formatter 通常由包含 LogRecord attributes 的 Python 格式化字符串组成,不过你也可以为特定的格式来配置自定义的 formatter。
|