六、日志模块
目录
一、日志级别
二、默认级别
三、logging模块指定全局配置
四、logging模块的Formatter,Handler,Logger,Filter对象
五、Logger与Handler的级别
六、Logger的继承
七、日志应用
1.日志级别
CRITICAL = 50 #严重 ERROR = 40 #错误 WARNING = 30 #警告 INFO = 20 #信息 DEBUG = 10 #调试 NOTSET = 0 #不设置
2.默认级别为warning ,级别30,默认打印到终端
#默认级别是显示30及以上 logging.debug('调试debug') #level 10 logging.info('消息info') #level 20 logging.warning('警告warn')#level 30 logging.error('错误error') #level 40 logging.critical('严重critical') #level 50 ''' 输出结果: WARNING:root:警告warn ERROR:root:错误error CRITICAL:root:严重critical '''
3.为logging模块指定全局配置,针对所有Logger有效,控制打印到文件中
#介绍
可在logging.baseConfig()函数中通过具体参数来更改logging模块默认行为,可用参数有: filename:用指定的文件名创建FileHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。 filemode:文件打开方式,在指定了filename时使用这个参数,默认值为'a',还可指定为‘w’ format:指定handler使用的日志显示格式 datefmt:指定日期时间格式 level:设置rootlogger(后边会讲解具体概念)的日志级别 stream:用指定的stream创建StreamHandler.可以指定输出到sys.stderr,sys.stdout或者文件,默认为sys.stderr。若同时列出了filename和stream俩个参数,则stream参数会被忽略 #格式 %(name)s:Logger的名字,并非用户名 %(levelno)s:数字形式的日志级别 %(levelname)s:文本形式的日志级别 %(pathname)s:调用日志输出函数的模块的完整路径名,可能没有 %(module)s:调用日志输出函数的模块名 %(funcName)s:调用日志输出函数的函数名 %(lineno)d:调用日志输出函数的语句所在的代码行 %(created)f:当前时间,用UNIX标准的表示时间的浮点数表示 %(relativeCreated)d:输出日志信息时的,自Logger创建以来的毫秒数 %(asctime)s:字符串形式的当前时间。默认格式'2018-04-10 19:12:12,898'。逗号后面的是毫秒 %(thread)d:线程ID。可能没有 %(threadName)s:线程名,可能没有 %(process)d:进程ID,可能没有 %(message)s:用户输出消息
import logging #默认就是a模式,不用指定也行 logging.basicConfig( filename='access.log', filemode='a', format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s', datefmt='%Y-%m-%d %H-%M-%S %p', level=10 ) #默认级别是显示30及以上 logging.debug('debug') #level 10 logging.info('info') #level 20 logging.warning('warn')#level 30 logging.error('error') #level 40 logging.critical('critial') #level 50 ''' 结果:在access.log文件里: 2018-04-10 17-07-12 PM - root - DEBUG -日志模块logging: debug 2018-04-10 17-07-12 PM - root - INFO -日志模块logging: info 2018-04-10 17-07-12 PM - root - WARNING -日志模块logging: warn 2018-04-10 17-07-12 PM - root - ERROR -日志模块logging: error 2018-04-10 17-07-12 PM - root - CRITICAL -日志模块logging: critial '''
4.logging模块的Formatter,Handler,Logger,Filter对象
原理图:
import logging #1.Logger对象:产生日志 logger1 = logging.getLogger('访问日志') #名字无所谓,fmt格式里%(name)s #2.Filter对象:过滤日志功能,几乎不用 #3.Handler对象:负责接收Logger传过来的日志,进行日志格式化,可以打印到终端,也可以打印到文件 sh=logging.StreamHandler() #往终端打印 fh1=logging.FileHandler('s1.log',encoding='utf-8') fh2=logging.FileHandler('s2.log',mode='w',encoding='utf-8') #4.Formatter对象:日志格式 formatter1=logging.Formatter( fmt='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s', datefmt='%Y-%m-%d %H-%M-%S %p', ) formatter2=logging.Formatter( fmt='%(asctime)s : %(message)s', datefmt='%Y-%m-%d %H-%M-%S %p', ) #5. 为Handler绑定日志格式 sh.setFormatter(formatter1) fh1.setFormatter(formatter2) fh2.setFormatter(formatter1) #6.为Logger绑定handler logger1.addHandler(sh) logger1.addHandler(fh1) logger1.addHandler(fh2) #7.设置日志级别,Logger与Handler都可以设置,Logger对象设置的级别<=Handler对象设置级别 logger1.setLevel(10) sh.setLevel(40) #8.测试 logger1.debug('测试一下') logger1.info('正常信息') logger1.warning('警告信息') logger1.error('错误信息') logger1.critical('严重信息')
5 .Logger与Handler的级别
对于上面的第7点,已经有说明一个结论,logger是第一级过滤,然后才能到handler,我们可以给logger和handler同时设置level,但是需要知道的是Logger的级别如果比handler高,那实际上handler的级别过滤是没有效果的
6.Logger的继承(了解)
import logging formatter=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S %p',) ch=logging.StreamHandler() ch.setFormatter(formatter) logger1=logging.getLogger('root') logger2=logging.getLogger('root.child1') logger3=logging.getLogger('root.child1.child2') logger1.addHandler(ch) logger2.addHandler(ch) logger3.addHandler(ch) logger1.setLevel(10) logger2.setLevel(10) logger3.setLevel(10) logger1.debug('log1 爷爷') logger2.debug('log2 爸爸') logger3.debug('log3 孙子') ''' 2018-04-10 20:17:32 PM - root - DEBUG -日志模块logging: log1 爷爷 2018-04-10 20:17:32 PM - root.child1 - DEBUG -日志模块logging: log2 爸爸 2018-04-10 20:17:32 PM - root.child1 - DEBUG -日志模块logging: log2 爸爸 2018-04-10 20:17:32 PM - root.child1.child2 - DEBUG -日志模块logging: log3 孙子 2018-04-10 20:17:32 PM - root.child1.child2 - DEBUG -日志模块logging: log3 孙子 2018-04-10 20:17:32 PM - root.child1.child2 - DEBUG -日志模块logging: log3 孙子 '''
7.日志应用
可将Loggers,Handlers,Formats封装成字典,方便调用
import os import logging.config #1.定义三种日志输出格式 standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s]\ [%(filename)s:%(lineno)d][%(levelname)s][%(message)s]' #其中name为getlogger指定的名字 simple_format = '[%(levelname)s][%(asctime)s][%(filename)s]:%(lineno)d%(message)s' id_simple_format = '[%(levelname)s[%(asctime)s] %(message)s]' #log文件的目录 logfile_dir = os.path.dirname(os.path.abspath(__file__)) logfile_name='access.log' #log文件名 #r如果不存在定义的日志目录就创建一个 if not os.path.isdir(logfile_dir): os.mkdir(logfile_dir) #log文件的全路径 logfile_path = os.path.join(logfile_dir,logfile_name) #log配置字典 LOGGING_DIC={ 'version':1, 'disable_existing_loggers':False, 'formatters':{ 'standard':{ 'format':standard_format }, 'simple':{ 'format':simple_format }, }, 'filters':{}, 'handlers':{ #打印到终端的日志 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler',#打印到屏幕 'formatter':'simple' }, #打印到文件的日志里,手机info及以上的日志 'default':{ 'level':'DEBUG', 'class':'logging.handlers.RotatingFileHandler', #保存到文件 'formatter':'standard', 'filename':logfile_path, #日志文件 'maxBytes':1024*1024*5, #日志大小5M 'encoding':'utf-8', #日志文件的编码,再也不用担心中文log乱码了 }, }, 'loggers':{ #logging.getLogger(__name__)拿到的logger配置 '':{ 'handlers':{'default','console'}, #这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level':'DEBUG', 'propagate':True, #向上(更高level的logger)传递 }, }, } def load_my_logging_cfg(): logging.config.dictConfig(LOGGING_DIC) #导入上面定义的logging配置 logger = logging.getLogger(__name__) #生成一个log实例 logger.info('It works!') #计入该文件的运行状态 if __name__ == '__main__': load_my_logging_cfg()
#关于如何拿到logger对象的详细解释 #1.有了上述方式,好处:所有与logging模块有关的配置都写到字典中就可以了,更加清晰,方便管理 #2.我们需要解决的问题是: 2.1 从字典加载配置:logging.config.dictConfig(LOGGING_DIC) 2.2 拿到logger对象来产生日志 logger对象都是配置到字典的loggers键对应的子字典中的 按照我们队logging模块的理解,要想获取某个东西都是通过名字,也就是key来获取的 于是我么要获取不同的logger对象就是logger=logging.getLogger('loggers子字典的key名') 但问题是:如果我们想要不同logger名的logger对象都共用一段配置,那么肯定不能再loggers子字典中定义n个key 'loggers':{ #logging.getLogger(__name__)拿到的logger配置 'l1':{ 'handlers':{'default','console'}, #这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level':'DEBUG', 'propagate':True, #向上(更高level的logger)传递 }, 'l2': { 'handlers': {'default', 'console'}, # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, 'l3': { 'handlers': {'default', 'console'}, # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, } #我们的解决方式是,定义一个空的key 'loggers':{ #logging.getLogger(__name__)拿到的logger配置 '':{ 'handlers':{'default','console'}, #这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level':'DEBUG', 'propagate':True, #向上(更高level的logger)传递 }, 这样我们再取logger对象时 logging.getLogger(__name__),不同的文件__name__不同,这保证了打印日志时标识信息不同,但是拿着该名字去loggers里找key名时却发现找不到,于是默认使用key=''的配置
符合软件定义规范的日志记录功能实例
#在ATM/bin/start.py文件 #入口:启动文件 import sys,os #BASE_DIR是项目根目录,os.path.dirname取目录,os.path.abspath取文件绝对路径,__file__程序名 BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #将项目根目录加入环境变量 sys.path.append(BASE_DIR) from core import src if __name__ =='__main__': src.run() ======================================================== #在ATM/conf/文件里的setting.py #配置文件 import os #BASE_DIR是项目根目录 BASE_PATH=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) #路径拼接,结果E:\python3_file\Pycharm3\ATM\log\access.log LOG_PATH=os.path.join(BASE_PATH,'log','access.log') DB_PATH=os.path.join(BASE_PATH,'db','user') #1.定义三种日志输出格式 standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s]\ [%(filename)s:%(lineno)d][%(levelname)s][%(message)s]' #其中name为getlogger指定的名字 simple_format = '[%(levelname)s][%(asctime)s][%(filename)s]:%(lineno)d%(message)s' id_simple_format = '[%(levelname)s[%(asctime)s] %(message)s]' LOGGING_DIC={ 'version':1, 'disable_existing_loggers':False, 'formatters':{ 'standard':{ 'format':standard_format }, 'simple':{ 'format':simple_format }, }, 'filters':{}, 'handlers':{ #打印到终端的日志 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler',#打印到屏幕 'formatter':'simple' }, #打印到文件的日志里,手机info及以上的日志 'default':{ 'level':'DEBUG', 'class':'logging.handlers.RotatingFileHandler', #保存到文件 'formatter':'standard', 'filename':LOG_PATH, #日志文件 'maxBytes':1024*1024*5, #日志大小5M 'encoding':'utf-8', #日志文件的编码,再也不用担心中文log乱码了 }, }, 'loggers':{ #logging.getLogger(__name__)拿到的logger配置 '':{ 'handlers':{'default','console'}, #这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level':'DEBUG', 'propagate':True, #向上(更高level的logger)传递 }, }, } ======================================================== #在ATM/core/src.py文件 #程序核心代码 #调用ATM/lib/common文件里的日志记录函数 from lib import common #调用ATM/lib/common文件里的sql数据库处理函数 from lib import sql def shop(): print ('购物ing....') def check_balance(): print ('查看余额....') #去数据库函数执行查询余额sql命令 res=sql.execute('select balance from user where id=3') print (res) def transfer(): print ('转账....') log_msg='bank给lisl转了1亿元' #调用日志功能记录日志 logger1=common.load_my_logging_cfg('hello') logger1.info(log_msg) #主函数 def run(): msg=''' 1. 购物 2.查看余额 3.转账 ''' while True: print (msg) choice= input('>>>:').strip() if choice == '1': shop() elif choice =='2': check_balance() elif choice =='3': transfer() else: print ('输入错误') ======================================================== #在ATM/db/user文件里 lisl,18,male,100 zhangsan,28,female,10000 ======================================================== #在ATM/lib/common.py文件里: #自定义库 from conf import setting import logging.config def load_my_logging_cfg(name): logging.config.dictConfig(setting.LOGGING_DIC) #导入上面定义的logging配置 logger = logging.getLogger(name) #生成一个log实例 return logger ======================================================== 在ATM/lib/sql.py文件里: #去ATM/conf/setting.py文件里找到DB_Path的路径 from conf import setting def execute(sql): print ('我是数据库处理函数') ======================================================== #在ATM/log/access.log文件里 [2018-04-11 16:57:21,879][MainThread:67008][task_id:hello][src.py:21][INFO][bank给lisl转了1亿元]