日志模块 logging的使用

参考链接1

参考链接2

日志模块基础

一、logging.basicConfig()日志的自定义

如果导入后,直接就使用日志,不加一点自定义设置,使用的是日志对象的祖宗root对象的默认设置,将日志打印到屏幕,日志级别为WARNING

通过logging.basicConfig函数对日志的输出格式及方式做相关配置:

import logging

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='myapp.log',
                  filemode='w')
logging.debug(
'This is debug message') logging.info('This is info message') logging.warning('This is warning message') ./myapp.log文件中内容为: Sun, 24 May 2009 21:48:54 demo2.py[line:11] DEBUG This is debug message Sun, 24 May 2009 21:48:54 demo2.py[line:12] INFO This is info message Sun, 24 May 2009 21:48:54 demo2.py[line:13] WARNING This is warning message
filename : 文件名,有这个参数就代表要输出到文件,不用再用FileHandler了
filemode :操作文件的模式,指定了filename,这个才有用,不写默认'a'模式
level :将根记录器级别设置为指定的级别
format :日志格式
datefmt :日期/时间格式
handlers :是一个已经创建的处理程序的迭代器添加到根记录器,就是treamHandler和FileHandler(写这个上面的文件名和模式就不用写了)
basicConfig中可填参数
# 常用
%(asctime)s 字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(name)s Logger的名字,名字可以区分日志的辈分,祖先都是root
%(levelname)s 文本形式的日志级别
%(filename)s 调用日志输出函数的模块的文件名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(message)s日志输出的消息

# 其他
%(levelno)s 数字形式的日志级别
%(pathname)s 调用日志输出函数的模块的完整路径名,可能没有
%(module)s 调用日志输出函数的模块名
%(funcName)s 调用日志输出函数的函数名
%(created)f 当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d 输出日志信息时的,自Logger创建以 来的毫秒数
%(thread)d 线程ID。可能没有
%(threadName)s 线程名。可能没有
%(process)d 进程ID。可能没有
参数format中可设置的格式和内容

二、自定义日志示例

1.将日志同时输出到文件和屏幕

import logging

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='myapp.log',
                filemode='w')

#################################################################################################
#定义一个StreamHandler,将INFO级别或更高的日志信息打印到标准错误,并将其添加到当前的日志处理对象#
console = logging.StreamHandler()
console.setLevel(logging.INFO)
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
console.setFormatter(formatter)
logging.getLogger('').addHandler(console) 
#################################################################################################

logging.debug('This is debug message')
logging.info('This is info message')
logging.warning('This is warning message')

 

屏幕上打印:
root        : INFO     This is info message
root        : WARNING  This is warning message

./myapp.log文件中内容为:
Sun, 24 May 2009 21:48:54 demo2.py[line:11] DEBUG This is debug message
Sun, 24 May 2009 21:48:54 demo2.py[line:12] INFO This is info message
Sun, 24 May 2009 21:48:54 demo2.py[line:13] WARNING This is warning message

2.多文件的项目中使用日志(双输出精简版)

basicConfig的设置是对root这个根log对象进行设置的,root是所有log对象的祖先(详见下一节)

我们的项目中有多个py文件,我们只需要在启动入口文件main.py中用basicConfig设置格式就好了,其他文件如task.py中也会使用根设置

main.py:

import logging
from pathlib import Path

logger = logging.getLogger(__name__)

def init_log():
    log_dir = Path('log')
    if not log_dir.exists():
        log_dir.mkdir()
    logging.basicConfig(level=logging.DEBUG, 
              format='%(asctime)s %(levelname)s %(name)s: %(message)s',
              handlers=[logging.StreamHandler(), # 输出到控制台,没必要设置什么额外参数                logging.FileHandler(filename=log_dir/'wibot.log', encoding='utf8') # 输出到文件,还有其他的参数,可以进源码查看 ])

其他文件:只需导入,给个名字,就可以直接使用(名字可以决定log的辈分),

import logging

logger = logging.getLogger(__name__) # 生成一个以文件名为名字的log对象,是root的后代

logger.info('大哥已经设置好了,随便输出吧')

当然,除了继承自定义的root的设置,在每个文件中也可以自定义自己的格式,见下一节

三、logging.getLogger(name=None日志的继承

首先要从logging.getLogger([name=None])这个函数来说起:

他是使用工厂方法返回一个Logger实例

指定name,返回一个名称为name的Logger实例,如果再次使用相同的名字,是实例化一个对象。

未指定name,返回Logger实例,名称是root,即根Logger。

Logger对象是有辈分的,每一辈使用 '.' 点号分割,如:对于foo来说,foo是foo.bar的父parent,foo.bar是foo的子child,名字为foo.bar、foo.bar.baz、foo.bam都是foo的后代,全都是根对象root的后代

有点难理解可以看后面的例子,话不多说先上结论:

1. 每一个Logger实例的level如同入口,让水流进来,如果这个门槛太高,信息就进不来。例如log2.info('log3 info'),如果log2定义的级别高于info级别,就不会又信息通过log2

2. 如果level没有设置,就用父logger的,如果父logger的level也没有设置,继续找父的父的,最终找到root上,如果root设置了就用它的,如果root没有设置,root的默认值是WARNING

3.消息传递流程:

在某个logger上产生某种级别的信息,首先和logger的level检查,如果消息level低于logger的EffectiveLevl有效级别,消息丢弃,不会再向父logger传递该消息。如果通过(大于等于)检查后,则把消息交给logger所有的handler处理,每一个handler需要和自己level比较来决定是否处理。

如果没有一个handler,或者消息已经被handler处理过了,则需要通过本logger的propagate属性是否为True,Ture则把这个消息会继续发给父Logger,当前Logger的父Logger称为当前Logger,新的Logger的所有Handler继续处理消息。

4. logger实例初始的propagate属性为True,即允许想父logger传递消息

5. logger.basicConfig

如果root没有handler,就默认创建一个StreamHandler,如果设置了filename,就创建一个FileHandler。如果设置了format参数,就会用它生成一个formatter对象,并把这个formatter加入到刚才创建的handler上,然后把这些handler加入到root.handlers列表上。level 是设置给root.logger的。

如果root.handlers列表不为空,logging.basicConfig的调用什么都不做。

import logging
DATEFMT ="[%Y-%m-%d %H:%M:%S]"
FORMAT = "%(asctime)s %(thread)d %(message)s"
logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt=DATEFMT,filename='class_test.log')
 
root = logging.getLogger()
print(root.name,type(root),root.parent,id(root))
 
logger = logging.getLogger(__name__)
print(logger.name, type(logger), id(logger), id((logger.parent)))
 
logger1 = logging.getLogger(__name__ + ".ok")
print(logger1.name, type(logger1), id(logger1), id((logger1.parent)))
 
print(logger1.parent,id(logger1.parent))
 
运行结果:
root <class 'logging.RootLogger'> None 4367575248
__main__ <class 'logging.Logger'> 4367575864 4367575248
__main__.ok <class 'logging.Logger'> 4367575920 4367575864
<logging.Logger object at 0x10453eb38> 4367575864
log对象名字决定辈分

子代可以自定义自己的等级,不影响父parent的级别::

import logging
 
FORMAT = "%(asctime)s %(thread)d %(message)s"
logging.basicConfig(level=logging.WARNING,format=FORMAT,datefmt="[%Y-%m-%d %H:%M:%S]")
 
root = logging.getLogger()
print(1,root,id(root)) #RootLogger,根Logger
root.info('my root') #低于定义的WARNING级别,所以不会记录
 
loga = logging.getLogger(__name__) #Logger继承自RootLogger
print(2,loga,id(loga),id(loga.parent))
print(3,loga.getEffectiveLevel()) #数值形式的有效级别
 
loga.warning('before')
loga.setLevel(28) #设置级别为28
print(4,loga.getEffectiveLevel())
loga.info('after')#
loga.warning('after1')
 
运行结果:
[2017-12-17 16:31:20] 4320629568 before
1 <logging.RootLogger object at 0x104534f28> 4367535912
2 <logging.Logger object at 0x1044ef630> 4367250992 4367535912
3 30
4 28
[2017-12-17 16:31:20] 4320629568 after1
子代自定义日志等级

logger实例,如果设置了level,就用它和信息的级别比较,否则,继承最近的祖先的level:

import logging
 
FORMAT = "%(asctime)s %(thread)d %(message)s"
logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt="[%Y-%m-%d %H:%M:%S]")
 
root = logging.getLogger() #根Logger级别为INFO 20
print('root:',root.getEffectiveLevel())
 
log1 = logging.getLogger('s')
log1.setLevel(logging.ERROR) #级别为ERROR 40
print('log1:',log1.getEffectiveLevel())
log1.error('log1 error')
 
log2 = logging.getLogger('s.s1') #继承自log1 40,无法使用warning
log2.setLevel(logging.WARNING) #设置为WARNING 30,才可以使用warning
print('log2:',log2.getEffectiveLevel())
log2.warning('log2 warning')
 
运行结果:
[2017-12-17 16:52:22] 4320629568 log1 error
root: 20
log1: 40
[2017-12-17 16:52:22] 4320629568 log2 warning
log2: 30
继承规律

继承会影响日志消息的传递:

import logging
 
FORMAT = "%(asctime)s %(thread)d %(message)s"
logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt="[%Y-%m-%d %H:%M:%S]")
 
root = logging.getLogger()
print(1,root.getEffectiveLevel()) #RootLogger,根Logger
 
log1 = logging.getLogger('s')
print(2,log1.getEffectiveLevel())
 
h1 = logging.FileHandler('test.log')
h1.setLevel(logging.WARNING)
log1.addHandler(h1)
print(3,log1.getEffectiveLevel())
 
log2 = logging.getLogger('s.s2')
print(4,log2.getEffectiveLevel())
 
h2 = logging.FileHandler('test1.log')
h2.setLevel(logging.WARNING)
log1.addHandler(h2)
print(3,log1.getEffectiveLevel())
 
 
log2.warning('log2 info---')
 
 
运行结果:
1 20
[2017-12-17 19:02:53] 7956 log2 info---
2 20
3 20
4 20
3 20

# test.log和test1.log最终都会记录一份"log2 info---"
子代自定义handler

同样,handler也可以设置使用logging.Formatter()设置格式和Logging.Filter()设置过滤器:

import logging
 
FORMAT = "%(asctime)s %(thread)d %(message)s"
logging.basicConfig(level=logging.INFO,format=FORMAT,datefmt="[%Y-%m-%d %H:%M:%S]")
 
root = logging.getLogger()
print(1,root.getEffectiveLevel()) #RootLogger,根Logger
 
log1 = logging.getLogger('s')#模块化用__module__,函数化用__name__作为Logger名,Logger同名内存中也只有一个
print(2,log1.getEffectiveLevel())
 
h1 = logging.FileHandler('test.log')
h1.setLevel(logging.WARNING)
fmt1 = logging.Formatter('[%(asctime)s] %(thread)s %(threadName)s log1-handler1 %(message)s')
h1.setFormatter(fmt1) #重新个性化定义记录的格式化字符串
log1.addHandler(h1)
filter1 = logging.Filter('s') #过滤器 会记录s, s.s2的信息
log1.addFilter(filter1)
print(3,log1.getEffectiveLevel())
 
log2 = logging.getLogger('s.s2')
print(4,log2.getEffectiveLevel())
 
h2 = logging.FileHandler('test1.log')
h2.setLevel(logging.WARNING)
log1.addHandler(h2)
filter1 = logging.Filter('s.s2') #过滤器不会记录s.s2的消息,只会记录自己的消息
log1.addFilter(filter1)
print(3,log1.getEffectiveLevel())
 
log1.warning('log1 warning===')
log2.warning('log2 warning---')
 
运行结果:
test.log: #handler1记录了到了log1和log2的信息
[2017-12-17 19:43:12,654] 5872 MainThread log1-handler1 log1 warning===
[2017-12-17 19:43:12,654] 5872 MainThread log1-handler1 log2 warning---
 
 
test1.log:    #handler2只记录了它自己的信息
log2 warning---
View Code

loggerLevel --> FilterConditions --> HandlerLevel --> 父LoggerFilter --> 父LoggerHandler --> RootHandler --> 标准输出或记录到日志:

import logging,datetime
 
FORMAT = "%(asctime)s %(thread)d %(message)s"
logging.basicConfig(level=logging.WARNING,format=FORMAT,datefmt="[%Y-%m-%d %H:%M:%S]")
 
"--------------root--------------------"
root = logging.getLogger()
print(1,root.getEffectiveLevel())
 
"--------------log1-------------------"
log1 = logging.getLogger('s')
log1.setLevel(logging.ERROR)
print(2,log1.getEffectiveLevel())
 
h1 = logging.FileHandler('h1.log')
h1.setLevel(logging.INFO)
log1.addHandler(h1)
 
"--------------log2-------------------"
log2 = logging.getLogger('s.s2')
log2.setLevel(logging.WARNING)
print(3,log2.getEffectiveLevel())
 
# h2 = logging.StreamHandler()
h2 = logging.FileHandler('h2.log')
h2.setLevel(logging.WARNING) # 最低40
f2 = logging.Filter('s.s3') # log2 = s.s2 ,  s, s.s2   s.s3
h2.addFilter(f2)
log2.addHandler(h2)
 
 
log2.warning('4,log2 warning -- {}'.format(datetime.datetime.now()))
 
运行结果:
1 30
2 40
3 30
[2017-12-19 14:23:43] 2508 4,log2 warning -- 2017-12-19 14:23:43.364928
 
#h1.log
4,log2 warning -- 2017-12-19 14:23:43.364928
 
 
#h2.log
空,没有写入
View Code

 

posted @ 2020-10-30 09:32  www.pu  Views(584)  Comments(0Edit  收藏  举报