1、简介
日志是一种可以追踪某些软件运行时所发生事件的方法。软件开发人员可以向他们的代码中调用日志记录相关的方法来表明发生了某些事情。一个事件可以用一个可包含可选变量数据的消息来描述。此外,事件也有重要性的概念,这个重要性也可以被称为严重性级别(level)。
2、作用
通过log的分析,可以方便用户了解系统或软件、应用的运行情况;如果你的应用log足够丰富,也可以分析以往用户的操作行为、类型喜好、地域分布或其他更多信息;如果一个应用的log同时也分了多个级别,那么可以很轻易地分析得到该应用的健康状况,及时发现问题并快速定位、解决问题,补救损失。简单来讲就是,我们通过记录和分析日志可以了解一个系统或软件程序运行情况是否正常,也可以在应用程序出现故障时快速定位问题。比如,做运维的同学,在接收到报警或各种问题反馈后,进行问题排查时通常都会先去看各种日志,大部分问题都可以在日志中找到答案。再比如,做开发的同学,可以通过IDE控制台上输出的各种日志进行程序调试。对于运维老司机或者有经验的开发人员,可以快速的通过日志定位到问题的根源。可见,日志的重要性不可小觑。日志的作用可以简单总结为以下3点:
程序调试
了解软件程序运行情况,是否正常
软件程序运行故障分析与问题定位
如果应用的日志信息足够详细和丰富,还可以用来做用户行为分析,如:分析用户的操作行为、类型喜好、地域分布以及其它更多的信息,由此可以实现改进业务、提高商业利益。
3、Logging工作流程
1.logging模块使用过程
1.第一次导入logging模块或使用reload函数重新导入logging模块,logging模块中的代码将被执行,这个过程中将产生logging日志系统的默认配置。
2.自定义配置(可选)。logging标准模块支持三种配置方式: dictConfig,fileConfig,listen。其中,dictConfig是通过一个字典进行配置Logger,Handler,Filter,Formatter;fileConfig则是通过一个文件进行配置;而listen则监听一个网络端口,通过接收网络数据来进行配置。当然,除了以上集体化配置外,也可以直接调用Logger,Handler等对象中的方法在代码中来显式配置。
3.使用logging模块的全局作用域中的getLogger函数来得到一个Logger对象实例(其参数即是一个字符串,表示Logger对象实例的名字,即通过该名字来得到相应的Logger对象实例)。
4.使用Logger对象中的debug,info,error,warn,critical等方法记录日志信息。
2.logging模块处理流程
1.判断日志的等级是否大于Logger对象的等级,如果大于,则往下执行,否则,流程结束。
2.产生日志。第一步,判断是否有异常,如果有,则添加异常信息。第二步,处理日志记录方法(如debug,info等)中的占位符,即一般的字符串格式化处理。
3.使用注册到Logger对象中的Filters进行过滤。如果有多个过滤器,则依次过滤;只要有一个过滤器返回假,则过滤结束,且该日志信息将丢弃,不再处理,而处理流程也至此结束。否则,处理流程往下执行。
4.在当前Logger对象中查找Handlers,如果找不到任何Handler,则往上到该Logger对象的父Logger中查找;如果找到一个或多个Handler,则依次用Handler来处理日志信息。但在每个Handler处理日志信息过程中,会首先判断日志信息的等级是否大于该Handler的等级,如果大于,则往下执行(由Logger对象进入Handler对象中),否则,处理流程结束。
5.执行Handler对象中的filter方法,该方法会依次执行注册到该Handler对象中的Filter。如果有一个Filter判断该日志信息为假,则此后的所有Filter都不再执行,而直接将该日志信息丢弃,处理流程结束。
6.使用Formatter类格式化最终的输出结果。注:Formatter同上述第2步的字符串格式化不同,它会添加额外的信息,比如日志产生的时间,产生日志的源代码所在的源文件的路径等等。
7.真正地输出日志信息(到网络,文件,终端,邮件等)。至于输出到哪个目的地,由Handler的种类来决定。
3、日志等级
级别 日志函数 描述
DEBUG logging.debug() 最低级别,追踪问题时使用
INFO logging.info() 记录程序中一般事件的信息,或确认一切工作正常
WARNING logging.warning() 记录信息,用于警告
ERROR logging.error() 用于记录程序报错信息
CRITICAL logging.critical() 最高级别,记录可能导致程序崩溃的错误
4、日志格式允许的字段
格式 描述
%(levelno)s 打印日志级别的数值
%(levelname)s 打印日志级别名称
%(pathname)s 打印当前执行程序的路径
%(filename)s 打印当前执行程序名称
%(funcName)s 打印日志的当前函数
%(lineno)d 打印日志的当前行号
%(asctime)s 打印日志的时间
%(thread)d 打印线程id
%(threadName)s 打印线程名称
%(process)d 打印进程ID
%(message)s 打印日志信息
5、简单应用
importlogging
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
----------------------------------------------------------------------------------------
WARNING:root:warningmessage
ERROR:root:errormessage
CRITICAL:root:criticalmessage
可见,默认情况下Python的logging模块将日志打印到了标准输出中,且只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING(日志级别等级CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET)
6、基本用法
# ! /usr/bin/python
# -*- coding: utf-8 -*
importlogging
importlogging.handlers
#设置日志格式
fmt= '%(asctime)s %(levename)7.7s %(funcName)s: %(message)s'
formatter= logging.Formatter(fmt, datefmt="%Y-%m-%d %H:%M:%S")
#设置 handler
handler= logging.handlers.TimedRotatingFileHandler('myapp.log',when='D',backupCount=30)
handler.setFormatter(formatter)
#定义 logger对象
logger= logging.getLogger("MyApp")
logger.addHandler(handler)
logger.setLevel(logging.INFO)
7、灵活配置日志格式(多次运行会被覆盖)
importlogging
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename='/root/log/test.log',
filemode='w')
logging.debug('debug message')
logging.info('info message')
logging.warning('warning message')
logging.error('error message')
logging.critical('critical message')
[root@RedHat_testlog]# cat test.log
Tue, 25Feb202014:17:412.py[line:8] DEBUGdebugmessage
Tue, 25Feb202014:17:412.py[line:9] INFOinfomessage
Tue, 25Feb202014:17:412.py[line:10] WARNINGwarningmessage
Tue, 25Feb202014:17:412.py[line:11] ERRORerrormessage
Tue, 25Feb202014:17:412.py[line:12] CRITICALcriticalmessage
8、logging库Formatter组件
#!/bin/python
# -*- coding: UTF-8 -*-
importlogging
logger= logging.getLogger()
# 创建一个handler,用于写入日志文件
fh= logging.FileHandler('test.log')
# 再创建一个handler,用于输出到控制台
ch= logging.StreamHandler()
formatter= logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
logger.addHandler(fh) #logger对象可以添加多个fh和ch对象
logger.addHandler(ch)
logger.debug('logger debug message')
logger.info('logger info message')
logger.warning('logger warning message')
logger.error('logger error message')
logger.critical('logger critical message')
----------------------------------------------------------------------------------------
2020-02-2514:33:10,717-root-WARNING-loggerwarningmessage
2020-02-2514:33:10,717-root-ERROR-loggererrormessage
2020-02-2514:33:10,717-root-CRITICAL-loggercriticalmessage
logging库提供了多个组件:Logger、Handler、Filter、Formatter。Logger对象提供应用程序可直接使用的接口,Handler发送日志到适当的目的地,Filter提供了过滤日志信息的方法,Formatter指定日志显示格式。从这个输出可以看出logger= logging.getLogger()返回的Logger名为root。这里没有用logger.setLevel(logging.Debug)显示的为logger设置日志级别,所以使用默认的日志级别WARNIING,故结果只输出了大于等于WARNIING级别的信息。
9、创建两个logger对象
# ! /usr/bin/python
# -*- coding: utf-8 -*
importlogging
importlogging.handlers
fh= logging.FileHandler('test.log')
#设置日志格式
ch= logging.StreamHandler()
formatter= logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
ch.setFormatter(formatter)
#定义 logger对象
logger1= logging.getLogger('logger')
logger1.setLevel(logging.DEBUG)
logger2= logging.getLogger('mylogger')
logger2.setLevel(logging.INFO)
logger1.addHandler(fh)
logger1.addHandler(ch)
logger2.addHandler(fh)
logger2.addHandler(ch)
logger1.debug('logger1 debug message')
logger1.info('logger1 info message')
logger1.warning('logger1 warning message')
logger1.error('logger1 error message')
logger1.critical('logger1 critical message')
logger2.debug('logger2 debug message')
logger2.info('logger2 info message')
logger2.warning('logger2 warning message')
logger2.error('logger2 error message')
logger2.critical('logger2 critical message')
[root@RedHat_testlog]# cat test.log
2020-02-2515:02:27,366-logger-DEBUG-logger1debugmessage
2020-02-2515:02:27,367-logger-INFO-logger1infomessage
2020-02-2515:02:27,367-logger-WARNING-logger1warningmessage
2020-02-2515:02:27,367-logger-ERROR-logger1errormessage
2020-02-2515:02:27,367-logger-CRITICAL-logger1criticalmessage
2020-02-2515:02:27,367-mylogger-INFO-logger2infomessage
2020-02-2515:02:27,367-mylogger-WARNING-logger2warningmessage
2020-02-2515:02:27,367-mylogger-ERROR-logger2errormessage
2020-02-2515:02:27,367-mylogger-CRITICAL-logger2criticalmessage
----------------------------------------------------------------------------------------
1.我们明明通过logger1.setLevel(logging.DEBUG)将logger1的日志级别设置为了DEBUG,为何显示的时候没有显示出DEBUG级别的日志信息,而是从INFO级别的日志开始显示呢?
原来logger1和logger2对应的是同一个Logger实例,只要logging.getLogger(name)中名称参数name相同则返回的Logger实例就是同一个,且仅有一个,也即name与Logger实例一一对应。在logger2实例中通过logger2.setLevel(logging.INFO)设置mylogger的日志级别为logging.INFO,所以最后logger1的输出遵从了后来设置的日志级别。
2.为什么logger1、logger2对应的每个输出分别显示两次?
这是因为我们通过logger= logging.getLogger()显示的创建了rootLogger,而logger1= logging.getLogger('mylogger')创建了rootLogger的孩子(root.)mylogger,logger2同样。而孩子,孙子,重孙……既会将消息分发给他的handler进行处理也会传递给所有的祖先Logger处理。
10、写入配置文件
1.定义一个 yaml 配置文件
# 文件名:config.yaml
version: 1
formatters:
brief:
format: "%(asctime)s - %(message)s"
simple:
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
handlers:
console:
class: logging.StreamHandler
formatter: brief
level : INFO
stream: ext://sys.stdout
file:
class: logging.FileHandler
formatter: simple
level: DEBUG
filename: debug.log
error:
class: logging.handlers.RotatingFileHandler
level: ERROR
formatter: simple
filename: error.log
maxBytes: 10485760
backupCount: 20
encoding: utf8
loggers:
main.core:
level: DEBUG
handlers: [console, file, error]
root:
level: DEBUG
handlers: [console]
2.定义一个主入口文件
# 文件名:main.py
importlogging
importcore
importyaml
importlogging.config
importos
defsetup_logging(default_path='config.yaml', default_level=logging.INFO):
path= default_path
ifos.path.exists(path):
withopen(path, 'r', encoding='utf-8') asf:
config= yaml.load(f)
logging.config.dictConfig(config)
else:
logging.basicConfig(level=default_level)
deflog():
logging.debug('Start')
logging.info('Exec')
logging.info('Finished')
if__name__== '__main__':
yaml_path= 'config.yaml'
setup_logging(yaml_path)
log()
core.run()
#这里我们定义了一个 setup_logging() 方法,里面读取了 yaml 文件的配置,然后通过 dictConfig() 方法将配置项传给了 logging 模块进行全局初始化
3.这个模块还引入了另外一个模块 core
# 文件名:core.py
importlogging
logger= logging.getLogger('main.core')
defrun():
logger.info('Core Info')
logger.debug('Core Debug')
logger.error('Core Error')
4.控制台运行结果输出
2018-06-0317:07:12,727-Exec
2018-06-0317:07:12,727-Finished
2018-06-0317:07:12,727-CoreInfo
2018-06-0317:07:12,727-CoreInfo
2018-06-0317:07:12,728-CoreError
2018-06-0317:07:12,728-CoreError
5.在 debug.log 文件中则包含了 core.py 的运行结果
2018-06-0317:07:12,727-main.core-INFO-CoreInfo
2018-06-0317:07:12,727-main.core-DEBUG-CoreDebug
2018-06-0317:07:12,728-main.core-ERROR-CoreError
11、对logging模块进行封装
1.创建目录
[root@RedHat_testlog]# ls
bin lib logs
# bin里面有主函数main.py
# lib里面有log.py
2.展现代码
# log.py
[root@RedHat_testlib]# cat log.py
#coding=utf-8
importlogging
fromlogging.handlersimportTimedRotatingFileHandler
definitlog(log_file,loglevel,rotat=True):
global logger
#生成一个日志对象
logger= logging.getLogger()
logger.setLevel(loglevel)
ifloglevelislogging.DEBUG:
formatter= logging.Formatter('%(asctime)s %(levelname)s %(threadName)s %(funcName)s %(lineno)d %(message)s ')
else:
formatter= logging.Formatter('%(asctime)s %(levelname)s %(threadName)s [%(funcName)s] %(message)s ')
# Create a file handler to store error messages
#fhdr = logging.FileHandler(log_file)
ifrotat:
#fhdr = TimedRotatingFileHandler(log_file,when='midnight',backupCount=3000)
#fhdr.suffix= "%Y%m%d"
fhdr= TimedRotatingFileHandler(log_file,when='D',interval=1,backupCount=200)
fhdr.suffix= "%Y-%m-%d.log"
else:
fhdr= logging.FileHandler(log_file)
fhdr.setFormatter(formatter)
# Create a stream handler to print all messages to console
chdr= logging.StreamHandler()
chdr.setFormatter(formatter)
#
logger.addHandler(fhdr)
logger.addHandler(chdr)
# main.py
[root@RedHat_testbin]# cat main.py
#! /usr/bin/python
# -*- coding: utf-8 -*-
importsys,os,string,inspect
sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),'lib'))
importlogging
importlog
importjson
logger= logging.getLogger()
logfile=os.path.join('/root/log/logs','dyscripty.log')
loglevel=logging.INFO
log.initlog(logfile, loglevel)
a= '123456789'
logger.info(a)
3.展示效果
[root@RedHat_testbin]# python main.py
2020-02-2515:14:33,596 INFOMainThread[<module>] 123456789
[root@RedHat_testlogs]# cat dyscripty.log
2020-02-2515:14:33,596 INFOMainThread[<module>] 123456789
12、Django中的日志配置
1.Django的日志在/your_project_name/settings.py文件中配置
# log 首先创建日志存储路径.
importlogging
importdjango.utils.log
importlogging.handlers
log_path= os.path.join(BASE_DIR, "logs")
ifnotos.path.exists(log_path):
os.makedirs("logs")
# DJANGO_LOG_LEVEL=DEBUG
LOGGING= {
'version': 1, # 保留字
'disable_existing_loggers': False, # 禁用已经存在的logger实例
# 日志文件的格式
'formatters': {
# 详细的日志格式
'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'
},
# 定义一个特殊的日志格式
'collect': {
'format': '%(message)s'
}
},
# 过滤器
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
# 处理器
'handlers': {
'console': { # 在终端打印
'level': 'DEBUG',
'filters': ['require_debug_true'], # 只有在Django debug为True时才在屏幕打印日志
'class': 'logging.StreamHandler', #
'formatter': 'simple'
},
'default': { # 默认的
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_DIR+'/logs/', "all.log"), # 日志文件
'maxBytes': 1024*1024*50, # 日志大小 50M
'backupCount': 3, # 最多备份几个
'formatter': 'standard',
'encoding': 'utf-8',
},
'error': { # 专门用来记错误日志
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_DIR+'/logs/', "error.log"), # 日志文件
'maxBytes': 1024*1024*50, # 日志大小 50M
'backupCount': 5,
'formatter': 'standard',
'encoding': 'utf-8',
},
'collect': { # 专门定义一个收集特定信息的日志
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_DIR+'/logs/', "collect.log"),
'maxBytes': 1024*1024*50, # 日志大小 50M
'backupCount': 5,
'formatter': 'collect',
'encoding': "utf-8"
},
'scprits_handler': {
'level':'DEBUG',
'class':'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_DIR+'/logs/', "script.log"),
'maxBytes': 1024*1024*5,
'backupCount': 5,
'formatter':'standard',
}
},
'loggers': {
'django': { # 默认的logger应用如下配置
'handlers': ['default', 'console', 'error'], # 上线之后可以把'console'移除
'level': 'DEBUG',
'propagate': True, # 向不向更高级别的logger传递
},
'collect': { # 名为 'collect'的logger还单独处理
'handlers': ['console', 'collect'],
'level': 'INFO',
},
'scripts': {
'handlers': ['scprits_handler'],
'level': 'INFO',
'propagate': False
},
},
}
2.此配置的三个部分
formatters: 指定输出的格式,被handler使用。
handlers:指定输出到控制台还是文件中,以及输出的方式。被logger引用。
loggers:指定django中的每个模块使用哪个handlers。以及日志输出的级别。
注意:日志的输出级别是由loggers中的每个模块中level选项定义。如果没有配置,那么默认为warning级别。
handlers与loggers都存在level,两者不同:
1、loggers中的level表示可以接受的错误级别,就是说loggers接受level或者比level更高级别的错误,由propagate决定:propagate为True, 则向上传播;
2、handlers的level表示日志级别
3.使用
importlogging
logger= logging.getLogger('django')
logger.info('-------------------------')
logger.error(str(e))
logger.warn('warn')
logger.debug('debug')
4.效果图
如果文章有任何错误欢迎不吝赐教,其次大家有任何关于运维的疑难杂问,也欢迎和大家一起交流讨论。关于运维学习、分享、交流,笔者开通了微信公众号【运维猫】,感兴趣的朋友可以关注下,欢迎加入,建立属于我们自己的小圈子,一起学运维知识。群主还经营一家Orchis饰品店,喜欢的小伙伴欢迎????前来下单。
扫描二维码
获取更多精彩
运维猫公众号
有需要技术交流的小伙伴可以加我微信,期待与大家共同成长,本人微信:
扫描二维码
添加私人微信
运维猫博主
扫码加微信
最近有一些星友咨询我知识星球的事,我也想继续在星球上发布更优质的内容供大家学习和探讨。运维猫公众号平台致力于为大家提供免费的学习资源,知识星球主要致力于即将入坑或者已经入坑的运维行业的小伙伴。
点击阅读原文 查看更多精彩内容!!!