当前位置:   article > 正文

logging的理解和使用一

logging

#参考官方文档----

一、logging基本使用

1、logging概述

logging是软件运行过程中跟踪一些时间发生的一种手段,软件开发会在软件的一些特定时间发生后在代码中添加log,此时会调用logging。日志里面对这个事件信息进行描述,可以包含一些变量数据,具体信息由自己定义。这些事件也存在一定的严重程度,这些也是由开发者赋给事件的严重性,即会有一个时间的Level表示不同严重级别的事件。

2、什么时候会用到logging

logging提供了一系列的functions供使用,例如debug()、info()、warnning()、error()、critical().下述描述了什么时候用logging.

想要执行的任务该任务最适合的工具
一个命令行脚本或者程序的一般用法显示在console输出print()
正常程序执行过程中的一些事件的触发记录loggiing.info()、logging.debug()
触发了一个报警事件warnings.warn()-如果需要client对数据进行处理或者改变;loging.waring()-client可以不做任何处理,这是一个告警提醒用户注意
在运行过程中触发errorRaise 触发一个异常
报告一个异常error但是不触发logging.error()、logging.exception()、logging.critcal

logging的方法使用之前需要定义log的严重级别来确定需要跟踪的事件,下述表格描述标准的级别定义。(可以重写方法添加其他级别)

Level什么时候使用
DEBUG详细信息,只有诊断问题时才需要,就是一般的调试信息
INFO当程序运行时期望的一些信息
WARNING软件运行正常,但是可能会有一些预期之外的事件发生
ERROR由于一些严重问题导致软件一些功能出现问题
CRITICAL很严重的错误直接导致软件不能继续运行

默认的级别时WARNING,意味默认的级别下,显示的日志信息必须时>=WARNING的级别才会进行处理。
日志的记录可以由不同的方式,最简单的是把日志直接输出到console,另外一种就是可以把他们写到文件。

举一个简单的例子:

import logging
logging.warning('Watch out!')
logging.info("I told you so")
  • 1
  • 2
  • 3

运行结果:

WARNING:root:Watch out !
  • 1

打印到console,INFO信息没有打印是因为默认的日志级别是WARNING。当前打印的信息包含级别:loggger 名字:日志信息。(可以设置格式和修改logger name.后面会介绍)

保存日志到文件

比较好的方法是把日志打印在一个文件,后续可以查看。

import logging
logging.basicConfig(filename='example.log',level=logging.DEBUG)
logging.debug("This message should go to the log file")
logging.info("So should this")
logging.warning("And this,too")
  • 1
  • 2
  • 3
  • 4
  • 5

运行上述代码可以在当前目录找到example.log文件,文件内容如下:

DEBUG:root:This message should go to the log file
INFO:root:So should this
WARNING:root:And this,too
  • 1
  • 2
  • 3

上述例子告诉我们如何设置日志级别,设置级别为INFO,所以DEBUG和WARNING比它级别高的日志都可以收集到。
如果运行多次上述的脚本,则每一次运行的日志都会追加到example.log,如果想要不记录之前的运行日志,需要设置filemode参数:
logging.basicConfig(filename=‘examle.log’,level=logging.DEBUG,filemode=‘w’)
这样设置后面再运行直接覆盖前面运行的内容。

多个模块的日志处理

如果程序跑了多个模块,需要处理logging,下述一个例子告诉我们怎么处理这种情况:

#myapp.py
import logging
import mylib
def main():
	logging.basicConfig(filename='myaoo.log',level=logging.INFO)
	logging.info("start")
	mylib.do_something()
	logging.info("Finished!")
if __name__=='__main__':
	main()
#mylib.py
def do_something()
	logging.info("Doing something!")
	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

运行myapp.py,mypp.log内容如下:

INFO:root:start
INFO:root:Doing something!
INFO:root:Finished!
  • 1
  • 2
  • 3

添加变量logging

日志记录事件,有时会需要记录一些数据,
import logging
logging.warning(’%s before you %s’,‘Look’,‘leap!’)
日志展现如下:
WARNING:root:Look before you leap!
如上可以看出加入变量的值就用%s即可

修改信息在日志中的格式

修改日志展示的格式:

Import logging
logging.basicConfig(format='%(levelname)s:%(meaasge)s',level=logging.DEBUG)
logging.debug("This message should appear on the console")
logging.info('So should this')
logging.warning('And this,too')
  • 1
  • 2
  • 3
  • 4
  • 5

运行结果:

DEBUG:This message should appear on the console
INFO:So should this
WARNING:And this,too
  • 1
  • 2
  • 3

上述的参数可以自己添加,常用参数由:
%(acstime)s 时间
%(filename)s 日志文件名
%(funcName)s 调用日志的函数名
%(levelname)s 日志的级别
%(module)s 调用日志的模块名
%(message)s 日志信息
%(name)s logger的name,不写的话默认是root
-还有一些其他的,用到的话可以参考官方文档

线程安全

logging模块不需要调用它的用户再做其他的操作使线程安全,它自己已经实现了线程锁,每一个模块调用的时候是锁定了共享数据区域,每一个handler也对IO进行加锁操作。这里了解的不多,暂时先这样。

模块级别功能

logging框架中主要由四个部分组成:

Loggers: 可供程序直接调用的接口
Handlers: 决定将日志记录分配至正确的目的地
Filters: 提供更细粒度的日志是否输出的判断
Formatters: 制定最终记录打印的格式布局

logging.getLogger(name=None)返回一个loggger的对象,这个对象是提供所有的log使用的接口,name如果未None则使用的是root logger,否则可自己定义一个自己的名字。
所有调用日志的接口,如果logger name一样则都是同一个logger实例,不需要在不同的应用中传输。
logging.getLoggerClass()
返回一个标准的logger类,或者是传输给setLoggerClass()的类。可以新建logger类,然后继承该类。
logging.basicConfig(**kwargs)
对logging做一些基本的配置,创建StreamHandler,默认的Formatter,增加这些到rootlogger.

支持参数如下:
filename—日志存储文件路径
filemode–写日志的方式,默认是a即追加模式,w为覆盖填写
format–日志格式
datefmt–时间格式
style–format如果是特别的,要用特定的fotmat格式,默认是%
level–日志过滤的级别
stream–用特定的stream初始化StreamHandler,如果有filename该参数无效
handlers–filename or stream和该参数冲突,否则会引起错误

----其他的功能请参考官方文档

使用方法1-logger使用,配置handler

创建一个logger,定义handlers(stream或者file),对stream和file进行格式添加以及logging.level的添加,接着把handler添加到logger。
在需要添加log的地方调用logging.info()、logging.debug()的等加入自己需要的日志。举例如下:

import logging
#创建logger
log=logging.getLogger("example")
log.setLevel(logging.DEBUG)
hander1=logging.StreamHandler()
hander2=logging.FileHandler('haha.log',mode='w')
hander1.setLevel(logging.INFO)
hander2.setLevel(logging.DEBUG)
formatter1=logging.Formatter("%(asctime)s-%(name)s-%(levelname)s-%(message)s")
formatter2=logging.Formatter("%(levelname)s : %(message)s")
hander1.setFormatter(formatter1)
hander2.setFormatter(formatter2)
log.addHandler(hander1)
log.addHandler(hander2)
log.info("这是一个info信息")
log.debug("这是一个debug信息")
log.warning("这是一个warnning信息")
log.info("这个怎么算呢")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

运行结果:

C:\Users\18566\AppData\Local\Programs\Python\Python38\python.exe C:/Users/18566/Desktop/APPAutoTest/logginglearn/loggingUse.py
2020-08-07 18:13:46,190-example-INFO-这是一个info信息
2020-08-07 18:13:46,191-example-WARNING-这是一个warnning信息
2020-08-07 18:13:46,191-example-INFO-这个怎么算呢

Process finished with exit code 0


haha.log内容:
INFO : 这是一个info信息
DEBUG : 这是一个debug信息
WARNING : 这是一个warnning信息
INFO : 这个怎么算呢

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

查看整体的loggig定义的level级别一定要比handler自己的定义levle低,否则日志会以logging的level进行日志的过滤。

使用方法2—logging.basicConfig()

先看一个程序:

import logging
print(logging._handlerList)
logging.basicConfig(level=logging.INFO,format='%(asctime)s:%(name)s:%(levelname)s:%(message)s')
print(logging._handlerList)
logger1=logging.FileHandler(filename='now.log',mode='w')
print(logger1.setFormatter())
logger2=logging.StreamHandler()
logger1.setLevel(logging.WARNING)
logger2.setLevel(logging.DEBUG)
logging.getLogger().addHandler(logger1)
logging.getLogger().addHandler(logger2)
print(logging._handlerList)
print("+++++++++++++++++++")
logging.info("这是一个logging")
logging.warning("这个号码")
logging.error("error级别日志应该都可以获取")
logger1_log=logging.getLogger("logger1_log")
logger2_log=logging.getLogger("logger2_log")
print("+++++++++++++++++++++++")
print(logging._handlerList)
logger1_log.info("这个时logger1 info")
logger2_log.debug("这个时logger2 debug")
logger2_log.error("这个时logger2 error")
logging.error("放在最后的logging error")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

运行结果如下:

C:\Users\18566\AppData\Local\Programs\Python\Python38\python.exe C:/Users/18566/Desktop/APPAutoTest/logginglearn/loggingUse.py
2020-08-08 10:25:20,403:root:INFO:这是一个logging
[<weakref at 0x00000255A9D888B0; to '_StderrHandler' at 0x00000255A9D7A970>]
这是一个logging
2020-08-08 10:25:20,403:root:WARNING:这个号码
[<weakref at 0x00000255A9D888B0; to '_StderrHandler' at 0x00000255A9D7A970>, <weakref at 0x00000255A9CC0CC0; to 'StreamHandler' at 0x00000255A9A3B6D0>]
这个号码
None
2020-08-08 10:25:20,403:root:ERROR:error级别日志应该都可以获取
error级别日志应该都可以获取
[<weakref at 0x00000255A9D888B0; to '_StderrHandler' at 0x00000255A9D7A970>, <weakref at 0x00000255A9CC0CC0; to 'StreamHandler' at 0x00000255A9A3B6D0>, <weakref at 0x00000255A9CC8DB0; to 'FileHandler' at 0x00000255A9CC1BB0>, <weakref at 0x00000255A9CDE860; to 'StreamHandler' at 0x00000255A9CC1EE0>]
2020-08-08 10:25:20,403:logger1_log:INFO:这个时logger1 info
+++++++++++++++++++
+++++++++++++++++++++++
这个时logger1 info
[<weakref at 0x00000255A9D888B0; to '_StderrHandler' at 0x00000255A9D7A970>, <weakref at 0x00000255A9CC0CC0; to 'StreamHandler' at 0x00000255A9A3B6D0>, <weakref at 0x00000255A9CC8DB0; to 'FileHandler' at 0x00000255A9CC1BB0>, <weakref at 0x00000255A9CDE860; to 'StreamHandler' at 0x00000255A9CC1EE0>]
2020-08-08 10:25:20,403:logger2_log:ERROR:这个时logger2 error
这个时logger2 error
2020-08-08 10:25:20,403:root:ERROR:放在最后的logging error
放在最后的logging error

Process finished with exit code 0

now.log内容:
这个号码
error级别日志应该都可以获取
这个时logger2 error
放在最后的logging error

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

程序分析:
1、关于输出格式,如果对handler没有进行格式的设置,则有一个默认格式,查看logging里面的源码信息

class Formatter(object):
    """
    Formatter instances are used to convert a LogRecord to text.

    Formatters need to know how a LogRecord is constructed. They are
    responsible for converting a LogRecord to (usually) a string which can
    be interpreted by either a human or an external system. The base Formatter
    allows a formatting string to be specified. If none is supplied, the
    the style-dependent default value, "%(message)s", "{message}", or
    "${message}", is used.

    The Formatter can be initialized with a format string which makes use of
    knowledge of the LogRecord attributes - e.g. the default value mentioned
    above makes use of the fact that the user's message and arguments are pre-
    formatted into a LogRecord's message attribute. Currently, the useful
    attributes in a LogRecord are described by:

    %(name)s            Name of the logger (logging channel)
    %(levelno)s         Numeric logging level for the message (DEBUG, INFO,
                        WARNING, ERROR, CRITICAL)
    %(levelname)s       Text logging level for the message ("DEBUG", "INFO",
                        "WARNING", "ERROR", "CRITICAL")
    %(pathname)s        Full pathname of the source file where the logging
                        call was issued (if available)
    %(filename)s        Filename portion of pathname
    %(module)s          Module (name portion of filename)
    %(lineno)d          Source line number where the logging call was issued
                        (if available)
    %(funcName)s        Function name
    %(created)f         Time when the LogRecord was created (time.time()
                        return value)
    %(asctime)s         Textual time when the LogRecord was created
    %(msecs)d           Millisecond portion of the creation time
    %(relativeCreated)d Time in milliseconds when the LogRecord was created,
                        relative to the time the logging module was loaded
                        (typically at application startup time)
    %(thread)d          Thread ID (if available)
    %(threadName)s      Thread name (if available)
    %(process)d         Process ID (if available)
    %(message)s         The result of record.getMessage(), computed just as
                        the record is emitted
    """
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

解释语句有一句话:the style-dependent default value, “%(message)s”, “{message}”, or
“${message}”, is used.
所以在上述输出有一个单独的message信息,这是因为不管是logger1还是logger2都没有对格式做设置,默认的格式就是只输出message.
2、为什么还输出了一个带格式的日志呢?
看到程序中输出的handlersList,最开始没有添加任何handler时,默认时有一个标准的错误输出的:[<weakref at 0x00000255A9D888B0; to ‘_StderrHandler’ at 0x00000255A9D7A970>]

配置了logging.basciConfig(),没有定义任何的handler,logging.basciConfig如果没有说明handler时console还是file则默认是streamHandler,所以此时的handlerList变为:
[<weakref at 0x00000255A9D888B0; to ‘_StderrHandler’ at 0x00000255A9D7A970>, <weakref at 0x00000255A9CC0CC0; to ‘StreamHandler’ at 0x00000255A9A3B6D0>]
3、增加了2个handler之后,HandlerList变为:
[<weakref at 0x00000255A9D888B0; to ‘_StderrHandler’ at 0x00000255A9D7A970>, <weakref at 0x00000255A9CC0CC0; to ‘StreamHandler’ at 0x00000255A9A3B6D0>, <weakref at 0x00000255A9CC8DB0; to ‘FileHandler’ at 0x00000255A9CC1BB0>, <weakref at 0x00000255A9CDE860; to ‘StreamHandler’ at 0x00000255A9CC1EE0>]
分别添加了一个streamHandler和一个FileHandler,所以fie里面会写一次不带格式的日志。因为handlerList只添加了一个fileHandler且没有定义格式。有2个streamHandler,一个是在logging.BasicConfig中定义了格式,logger2 streamHandler采用默认格式,所以日志输入格式:

2020-08-08 10:46:21,428:root:ERROR:放在最后的logging error
放在最后的logging error
  • 1
  • 2

4、增加了2个logger的名字,可以看出所有的handler会接受所有的logger的日志进行track,所以root,logger1_log,logger2-log都会写到log和cosole

日志参数采用文件配置

导入库文件logging.config

def fileConfig(fname, defaults=None, disable_existing_loggers=True):
    """
    Read the logging configuration from a ConfigParser-format file.

    This can be called several times from an application, allowing an end user
    the ability to select from various pre-canned configurations (if the
    developer provides a mechanism to present the choices and load the chosen
    configuration).
    """
    import configparser

    if isinstance(fname, configparser.RawConfigParser):
        cp = fname
    else:
        cp = configparser.ConfigParser(defaults)
        if hasattr(fname, 'readline'):
            cp.read_file(fname)
        else:
            cp.read(fname)

    formatters = _create_formatters(cp)

    # critical section
    logging._acquireLock()
    try:
        _clearExistingHandlers()

        # Handlers add themselves to logging._handlers
        handlers = _install_handlers(cp, formatters)
        _install_loggers(cp, handlers, disable_existing_loggers)
    finally:
        logging._releaseLock()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

用configparser解析ini文件,ini文件的写法如下:

logging.ini
[loggers]
keys=root,example
[handlers]
keys=consoleHandler,FileHandler
[formatters]
keys=consoleFmt,fileFmt
[formatter_consoleFmt]
format=%(asctime)s::%(levelname)s::%(name)s::%(message)s
[formatter_fileFmt]
format=%(name)s::%(levelname)s::%(message)s
[logger_root]
level=DEBUG
handlers=consoleHandler,FileHandler
[logger_example]
handlers=consoleHandler
qualname=example
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=consoleFmt
args=(sys.stdout,)
[handler_FileHandler]
class=FileHandler
level=DEBUG
formatter=fileFmt
args=('./wwxxyy.log','w')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

脚本如下:

import logging.config
logging.config.fileConfig('./logging.ini')
logging.info("this is a info log")
logging.debug("this is s debug log")
logging.warning("this is a warning log")
logging.error("this is a error log")
#
logger=logging.getLogger("example")
logger.error("hahhahhah")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

运行结果是:

C:\Users\18566\AppData\Local\Programs\Python\Python38\python.exe C:/Users/18566/Desktop/APPAutoTest/logginglearn/loggingUse.py
2020-08-08 18:48:55,391::INFO::root::this is a info log
2020-08-08 18:48:55,391::WARNING::root::this is a warning log
2020-08-08 18:48:55,391::ERROR::root::this is a error log
2020-08-08 18:48:55,391::ERROR::example::hahhahhah

Process finished with exit code 0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

wwwxxxyyy.log内容如下:

root::INFO::this is a info log
root::DEBUG::this is s debug log
root::WARNING::this is a warning log
root::ERROR::this is a error log
  • 1
  • 2
  • 3
  • 4

具体实现还要继续看源码,目前还没看明白源码,先记录下怎么实现把。
ini配置文件是用configParser读取出section option然后做一堆处理的。
ini文件包含的内容分如下几部分:
1、先要写你要的logger,section='loggers ’ option=‘keys’,keys的内容写你需要传入的logger name.

[loggers]
keys=root,example
  • 1
  • 2

2、要写传入的数据的目的地,目前用的多的streamHandler ,fileHandler,其他的请参考官方文档。handlers这里的名字可以随便写,为了方便读写当然最好是见词知意。

[handlers]
keys=consoleHandler,fileHandler
  • 1
  • 2

3、像之前采用basicConfig也是需要加fmt的,当然不加的话就是默认格式

[formatters]
keys=fmt1,fmt2
  • 1
  • 2

4、上述指定的数据,下述对数据进行配置,先配置fmt,其实不分先后,只要在文件能找到即可

[formatter_fmt1]
format=(%asctime)s::::%(levelname)s::%(name)s:%(message)s
[formatter_fmt2]
format=(%levelname)s:%(name)s:%(message)s
  • 1
  • 2
  • 3
  • 4

5、指定handler配置,指定每个handler的参数

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=fmt1
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=INFO
formatter=fmt2
args=("log.log","w")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

6、添加到logger,添加handler到logger

[logger_root]
handlers=consoleHandler,fileHandler
level=DEBUG
[logger_example]
handlers=condoleHandler
level=INFO
qualname=example
propagate=1
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

qualname=example
propagate=0
加2个参数的原因:
对于跟记录器以外的记录器要添加qualname和propagate零个参数。
级别和处理程序条目解释为跟记录器,除非如果非根记录器的级别被指定为NOTSET,则系统会在层次结构更高的层次上咨询记录器以确定记录器的有效级别。传播条目设置为1(propagate)以指示消息必须传播到记录器层次结构中自此记录器的处理程序,或者0表示消息不传播到层次结构中的处理程序。质量名qualname是记录器的分层通道名称,也就是应用程序用来获取记录器的名称。

这一段解释感觉非常拗口,个人理解:
如果不设置logger或者设置为‘’(实际也是root),记录器相当于没有分层,就只记录root 名字的logger的内容。如果设置了其他的logger name,记录器需要分层处理,设置的其他logger name配置区分不同的分层,当然还带了一个分层不分层的标记propagate标记,1为分层,0不分层。–个人理解,仅供参考

先写到这里把,后续有进步再追加!!!!!!!!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/590658
推荐阅读
相关标签
  

闽ICP备14008679号