模块 日志模块 re模块
1 日志模块
1.1 日志
日志(Log)是一种追踪记录在程序运行过程中所发生的事件的方法。
通过记录和分析日志,可以了解一个系统是否处于正常运行状态,也可以在出现故障时快速定位问题根源。
1.1.1 作用
- 程序调试;
- 了解程序运行情况;
- 程序运行故障分析与问题定位;
- 做用户行为分析,实现改进业务。
1.1.2 日志内容
一条日志信息对应的是一个事件的发生,而一个事件通常需要包括以下几个内容:
- 事件发生时间
- 事件发生位置
- 事件的严重程度–日志级别
- 事件内容
1.2 日志级别
import logging
# 程序在调试过程中运行状态确认
# logging.debug(msg, *args, **kwargs)
logging.debug('调试') # 级别:10
# 程序正常运行时的状态信息
logging.info('消息') # 级别:20
# 警告消息,预测程序之后可能会发生问题,此时程序还处于正常工作状态
logging.warning('警告') # 级别:30
# 错误消息,程序运行过程中发生了错误,部分功能已无法运行。
logging.error('错误') # 级别:40
# 严重错误消息,程序大部分功能已无法正常运行。
logging.critical('严重') # 级别:50
默认日志输出级别为30,即warning。
1.3 日志基本配置
函数 logging.basicConfig(**kwargs) 用于调整日志级别、输出格式等
函数 logging.basicConfig()为日志系统建立一个默认的流处理器(StreamHandler),设置基础配置(如日志级别等)并加到root logger(根Logger)中。
import logging
LOG_FORMAT = "%(asctime)s %(name)s %(levelname)s %(pathname)s %(message)s
TIME_FORMAT = '%Y-%m-%d %H:%M:%S %p'
logging.basicConfig(
# 1. 日志输出位置:终端 文件; 不指定,默认输出到终端
filename=r'./access.log',
# 2. 日志格式
format=LOG_FORMAT,
# 3. 时间格式
datefmt=TIME_FORMAT,
# 4、日志级别
# critical => 50
# error => 40
# warning => 30 默认
# info => 20
# debug => 10
level=10,
# level=logging.DEBUG
)
logging.debug("msg1")
- logging.basicConfig()函数只有在第一次调用时会起作用,后续再次调用该函数时不会产生任何操作。
- 日志器(Logger)是有层级关系的,上面调用的logging模块级别的函数所使用的日志器是RootLogger类的实例,其名称为 root,它是处于日志器层级关系最顶层的日志器,且该实例是以单例模式存在的。
- 如果要记录的日志中包含变量数据,可使用一个格式字符串作为这个事件的描述消息(logging.debug、logging.info等函数的第一个参数),然后将变量数据作为第二个参数*args的值进行传递。
logging.warning('%s is %d years old.', 'Tom', 10) # WARNING:root:Tom is 10 years old.
1.4 日志详细配置
1.4.2 logging日志模块四大组件
- 日志器 Logger
提供了应用程序可一直使用的接口 - 处理器 Handler
将logger创建的日志记录发送到合适的目的输出 - 过滤器 Filter
提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录 - 格式器 Formatter
决定日志记录的最终输出格式
日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置;
日志器可以设置多个处理器,实现同一条日志以不同的格式输出到不同的地方;
每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
每个处理器(handler)都可以设置自己的格式器(formatter)。
settings.py
# 1. 定义三种日志输出格式,日志中可能用到的格式化串如下
# %(name)s Logger的名字 强调:%(name)s为getlogger时指定的名字
# %(levelno)s 数字形式的日志级别
# %(levelname)s 文本形式的日志级别
# %(pathname)s 调用日志输出函数的模块的完整路径名,相当于sys.argv[0]
# %(filename)s 调用日志输出函数的模块的文件名
# %(module)s 调用日志输出函数的模块名
# %(funcName)s 调用日志输出函数的函数名
# %(lineno)d 调用日志输出函数的语句所在的代码行号
# %(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
# %(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
# %(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
# %(thread)d 线程ID
# %(threadName)s 线程名
# %(process)d 进程ID
# %(message)s 用户输出的日志信息
# 2. 设置日志格式
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
'[%(levelname)s][%(message)s]'
simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
test_format = '[%(asctime)s] %(message)s'
# 3. 日志配置字典
LOGGING_DIC = {
'version': 1,
'disable_existing_loggers': False, # 默认值为True,表示禁用已经存在的logger
# 配置文件中“disable_existing_loggers” 参数设置为 False;
# 如果不设置为False,创建了 logger,然后你又在加载日志配置文件之前就导入了模块。logging.fileConfig 与 logging.dictConfig 默认情况下会使得已经存在的 logger 失效。那么,这些配置信息就不会应用到你的 Logger 上。
# 日志输出格式
'formatters': {
'standard': {
'format': standard_format
},
'simple': {
'format': simple_format
},
'test': {
'format': test_format
},
},
# 日志过滤器
'filters': {},
# 日志接收者
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler', # 日志输出到流,将日志输出到屏幕上
'formatter': 'simple'
},
'default': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler', # 日志输出到文件, 可以实现日志回滚
'formatter': 'standard',
'filename': 'a1.log', # 日志文件
'maxBytes': 1024 * 1024 * 5, # 日志大小 5M
'backupCount': 5,
'encoding': 'utf-8', # 日志文件的编码
},
'other': {
'level': 'DEBUG',
'class': 'logging.FileHandler', # 日志输出到文件
'formatter': 'test',
'filename': 'a2.log',
'encoding': 'utf-8',
},
},
# 日志产生者
'loggers': {
# logging.getLogger(__name__)拿到的logger配置
'logger1': {
'handlers': ['default', 'console'],
# 指定日志的接收者,即log数据既写入文件又打印到屏幕
'level': 'DEBUG',
# 日志级别关限制: loggers(第一层) ---> handlers(第二层)
'propagate': False,
# 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递
},
'logger2': {
'handlers': ['other', ],
'level': 'ERROR ',
'propagate': False,
},
'': {
'handlers': ['other', ],
'level': 'ERROR ',
'propagate': False,
},
},
}
common.py
导入日志包
from logging import config # 子模块
from logging import getLogger # 函数
import settings
config包是logging包下的一个子包,logging包下的__init__.py中没有导入config包,因此不能仅通过导入config包来找到其子包config。
另一种导入方式
import logging.config
# 在导入模块过程中会先导入logging模块,再导入其子模块config
# 因此logging.getLogger也会一起导入,之后使用前缀logging.config.
print(logging) # module 'logging'
print(logging.config) # module 'logging.config'
print(logging.getLogger) # function getLogger
获取日志对象
logging.getLogger()方法有一个可选参数name,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值默认为root。
若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。
def get_logger(log_type):
logging.config.dictConfig(settings.LOGGING_DIC)
return logging.getLogger(log_type)
interface.py
from lib import common
bank_logger = common.get_logger('bank')
bank_logger.info('消息1')
bank_logger.info('消息2')
关于logger的层级结构与有效等级的说明:
- logger的名称是一个以’.‘分割的层级结构,每个’.‘后面的logger都是’.'前面的logger的children,例如,有一个名称为 foo 的logger,其它名称分别为 foo.bar, foo.bar.baz 和 foo.bam都是 foo 的后代。
- logger有一个"有效等级(effective level)"的概念。如果一个logger上没有被明确设置一个level,那么该logger就是使用它parent的level;如果它的parent也没有明确设置level则继续向上查找parent的parent的有效level,依次类推,直到找到个一个明确设置了level的祖先为止。需要说明的是,root logger总是会有一个明确的level设置(默认为 WARNING)。当决定是否去处理一个已发生的事件时,logger的有效等级将会被用来决定是否将该事件传递给该logger的handlers进行处理。
- child loggers在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先loggers相关的handlers。因此,我们不必为一个应用程序中所使用的所有loggers定义和配置handlers,只需要为一个顶层的logger配置handlers,然后按照需要创建child loggers就可足够了。我们也可以通过将一个logger的propagate属性设置为False来关闭这种传递机制。
如何使用日志总结:
- 在配置文件settings.py中创建log字典,设置级别,
- 在common.py中导入logging.config包,设置logger方法,
- 在不同接口文件中设置logger,在需要的位置调用并打印不同级别日志信息。
1.5 日志名的命名
上面的例子中日志名为logger1,logger2,会被日志输出格式中的**%(name)s接收。
日志名是用于区分日志种类(日志业务归属)的。
如果大部分的日志的handlers,level,propagate等参数都相同,仅仅日志名不同,可以不指定日志名**,之后如果getLogger接收的日志名不存在,则会去找这个空日志名。
1.6 日志轮转
不能任由日志文件不断增大,因此需要在特定时机对日志文件进行分隔,将日志文件中的内容移到备份文件中存储。
'default': {
'level': 'DEBUG',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件, 日志轮转
'formatter': 'standard',
'filename': 'a1.log', # 日志文件
'maxBytes': 1024 * 1024 * 5, # 日志大小到达5M后进行轮转
'backupCount': 5, # 保存的备份文件最大数量
'encoding': 'utf-8', # 日志文件的编码
},
当日志文件a1.log接收一条日志后体积超过5M后,会将所有内容移到备份文件a1.log.1中,之后继续在日志文件a1.log中接收新日志。
2 软件开发目录规范补充
- 如果程序的入口文件start.py在项目的bin文件夹下,需要配置环境变量,将项目的根目录存入配置环境中。
import os
import sys
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
sys.path.append(BASE_DIR)
- 如果入口文件start.py在项目的根目录下,则无需配置环境变量。
sys.path的第一个文件夹默认是当前执行文件所在的文件夹,即此时环境变量中已经存在项目的根目录了。
3 re模块
3.1 正则
正则是将一些具有特殊含义的符号组合到一起(正则表达式)来描述一类字符串的方法。
正则表达式则描述了一种字符串匹配的模式,可以用来检查检查一个字符串是否与某种模式匹配。
3.2 常用匹配模式
3.3 函数findall
re.findall(pattern, string, flags=0)
搜索string,以列表形式返回全部匹配的子串。
import re
# 匹配一个字符
res = re.findall('\w', '_abc123_/*- *+') # \w 匹配字母,数字或下划线
print(res) # ['_', 'a', 'b', 'c', '1', '2', '3', '_']
res = re.findall('\W', '_abc123/*- *+') # \w 匹配非 字母,数字或下划线
print(res) # ['/', '*', '-', ' ', '*', '+'] 包括空格
res = re.findall('\s', 'a\tb\fc\nd\re ') # 匹配空白字符
print(res) # ['\t', '\x0c', '\n', '\r', ' ']
res = re.findall('\S', 'a\tb\fc\nd\re ') # 匹配非空字符
print(res) # ['a', 'b', 'c', 'd', 'e']
# 匹配字符字串
res = re.findall('ab', ' abcdeab ab') # 匹配相同的字符
print(res) # ['ab', 'ab', 'ab']
res = re.findall('\Aab', ' abcdeab ab') # 匹配字符串开头部分
print(res) # []
res = re.findall('^abc$', 'abc') # \w 匹配字母,数字或下划线
print(res) # ['abc']
# 重复匹配
res = re.findall('a.b', 'a\nb a\tb')
print(res) # ['a\tb'] .能匹配除了\n以外的一个字符
res = re.findall('a.b', 'a\nb a\tb', re.DOTALL)
print(res) # ['a\nb', 'a\tb'] 在此模式下.可以匹配所有字符,包括\n