python内置模块之Log日志模块

python

前言

 logging 是python中的一个包,封装所有日志功能。

例如获取日志器 logging.getLogger 是logging包的 __init__ 文件中定义的函数(包的 __init__文件中的函数直接可以使用包名.函数名调用),如下 getLogger 代码:

def getLogger(name=None):

"""

Return a logger with the specified name, creating it if necessary.

If no name is specified, return the root logger.

"""

if not name or isinstance(name, str) and name == root.name:

return root

return Logger.manager.getLogger(name)

root = RootLogger(WARNING)

class RootLogger(Logger):

"""

A root logger is not that different to any other logger, except that

it must have a logging level and there is only one instance of it in

the hierarchy.

"""

def __init__(self, level):

"""

Initialize the logger with the name "root".

"""

Logger.__init__(self, "root", level)

def __reduce__(self):

return getLogger, ()

即 getLogger 函数返回值为Logger类的实例对象。

例如为日志器设置默认的日志级别 logger.setLevel() 为logging包的 __init__ 文件 Logger 类中的 setLevel 函数,代码如下:

    def setLevel(self, level):

"""

Set the logging level of this logger. level must be an int or a str.

"""

self.level = _checkLevel(level)

self.manager._clear_cache()

def _checkLevel(level):

if isinstance(level, int):

rv = level

elif str(level) == level:

if level not in _nameToLevel:

raise ValueError("Unknown level: %r" % level)

rv = _nameToLevel[level]

else:

raise TypeError("Level not an integer or a valid string: %r" % level)

return rv

_nameToLevel = {

'CRITICAL': CRITICAL,

'FATAL': FATAL,

'ERROR': ERROR,

'WARN': WARNING,

'WARNING': WARNING,

'INFO': INFO,

'DEBUG': DEBUG,

'NOTSET': NOTSET,

}

CRITICAL = 50

FATAL = CRITICAL

ERROR = 40

WARNING = 30

WARN = WARNING

INFO = 20

DEBUG = 10

NOTSET = 0

一、logging函数根据它们用来跟踪的事件的级别或严重程度来命名。标准级别及其适用性描述如下(以严重程度递增排序):

默认等级是WARNING,只有高于默认等级以上的级别才会打印到控制台。

①将日志直接输出到屏幕

"""

由于默认设置的等级是warning,所有只有warning的信息会输出到控制台。

"""

import logging

logging.debug('debug 信息')

logging.warning('只有这个会输出。。。')

logging.info('info 信息')

输出结果:

②通过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

logging.basicConfig参数:

#logging.basicConfig函数各参数:

filename: 指定日志文件名

filemode: 和file函数意义相同,指定日志文件的打开模式,'w'或'a'

format: 指定输出的格式和内容,format可以输出很多有用信息,如上例所示:

%(levelno)s: 打印日志级别的数值

%(levelname)s: 打印日志级别名称

%(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]

%(filename)s: 打印当前执行程序名

%(funcName)s: 打印日志的当前函数

%(lineno)d: 打印日志的当前行号

%(asctime)s: 打印日志的时间

%(thread)d: 打印线程ID

%(threadName)s: 打印线程名称

%(process)d: 打印进程ID

%(message)s: 打印日志信息

datefmt: 指定时间格式,同time.strftime()

level: 设置日志级别,默认为logging.WARNING【高于该日志级别才会打印到控制台上】

stream: 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略

 

③将日志同时输出到多个Handler

定义handler,并使用addHander()添加到日志器,实现日志输出到多个handler。

a、同时输出到文件和屏幕

import logging

#设置一个basicConfig只能输出到一个Handler

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)

#输出到文件的log级别为debug,输出到stream的log级别为info

logging.debug('This is debug message')

logging.info('This is info message')

logging.warning('This is warning message')

b、添加一个handler:输出到文件,并根据文件大小滚动存储

在a的基础上添加一个handler

from logging.handlers import RotatingFileHandler

#定义一个RotatingFileHandler,最多备份5个日志文件,每个日志文件最大10M

Rthandler = RotatingFileHandler('myapp.log', maxBytes=10*1024*1024,backupCount=5)

Rthandler.setLevel(logging.INFO)

formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')

Rthandler.setFormatter(formatter)

logging.getLogger('').addHandler(Rthandler)

logging几种Handler类型:

logging.StreamHandler(默认):     日志输出到流,可以是sys.stderr、sys.stdout或者文件

logging.FileHandler: 日志输出到文件

logging.handlers.RotatingFileHandler 日志输出到文件,基于文件大小滚动存储日志

logging.handlers.TimedRotatingFileHandler 日志输出到文件,基于时间周期滚动存储日志

logging.handlers.SocketHandler: 远程输出日志到TCP/IP sockets

logging.handlers.DatagramHandler: 远程输出日志到UDP sockets

logging.handlers.SMTPHandler: 远程输出日志到邮件地址

logging.handlers.SysLogHandler: 日志输出到syslog

logging.handlers.NTEventLogHandler: 远程输出日志到Windows NT/2000/XP的事件日志

logging.handlers.MemoryHandler: 日志输出到内存中的制定buffer

logging.handlers.HTTPHandler: 通过"GET"或"POST"远程输出到HTTP服务器

 

④通过配置文件配置logger

a、定义配置文件 logger.conf 

#logger.conf

###############################################

[loggers]

keys=root,example01,example02

[logger_root]

level=DEBUG

handlers=hand01,hand02

[logger_example01]

handlers=hand01,hand02

qualname=example01

propagate=0

[logger_example02]

handlers=hand01,hand03

qualname=example02

propagate=0

###############################################

[handlers]

keys=hand01,hand02,hand03

[handler_hand01]

class=StreamHandler

level=INFO

formatter=form02

args=(sys.stderr,)

[handler_hand02]

class=FileHandler

level=DEBUG

formatter=form01

args=('myapp.log', 'a')

[handler_hand03]

class=handlers.RotatingFileHandler

level=INFO

formatter=form02

args=('myapp.log', 'a', 10*1024*1024, 5)

###############################################

[formatters]

keys=form01,form02

[formatter_form01]

format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

datefmt=%a, %d %b %Y %H:%M:%S

[formatter_form02]

format=%(name)-12s: %(levelname)-8s %(message)s

datefmt=

b、 logging.config 获取配置

import logging

import logging.config

logging.config.fileConfig("logger.conf")

logger = logging.getLogger("example01")

logger.debug('This is debug message')

logger.info('This is info message')

logger.warning('This is warning message')

import logging

import logging.config

logging.config.fileConfig("logger.conf")

logger = logging.getLogger("example02")

logger.debug('This is debug message')

logger.info('This is info message')

logger.warning('This is warning message')

二、logging库【python中已封装好的功能模块】采取了模块化的设计,

提供了许多组件:记录器(日志器logger)、处理器(handles)、过滤器和格式化器(formatters)。

1、Logger 暴露了应用程序代码能直接使用的接口。

2、Handler将(记录器产生的)日志记录发送至合适的目的地。

3、Filter提供了更好的粒度控制,它可以决定输出哪些日志记录。

4、Formatter 指明了最终输出中日志记录的布局。

三、Loggers

Logger 对象要做三件事情。

首先,它们向应用代码暴露了许多方法,这样应用可以在运行时记录消息。

其次,记录器对象通过严重程度(默认的过滤设施)或者过滤器对象来决定哪些日志消息需要记录下来。

第三,记录器对象将相关的日志消息传递给所有感兴趣的日志处理器。

常用的记录器对象的方法分为两类:配置和发送消息。

这些是最常用的配置方法:

①Logger.setLevel()指定logger将会处理的最低的安全等级日志信息:

debug是最低的内置安全等级;

critical是最高的内置安全等级。

例如,如果严重程度为INFO,记录器将只处理INFO,WARNING,ERROR和CRITICAL消息,DEBUG消息被忽略。

②Logger.addHandler()和Logger.removeHandler()从记录器对象中添加和删除处理程序对象。处理器详见Handlers。

③Logger.addFilter()和Logger.removeFilter()从记录器对象添加和删除过滤器对象。

四、Handlers

处理程序对象负责将适当的日志消息(基于日志消息的严重性)分派到处理程序的指定目标。【根据分配到的Handlers不同,又将不同的日志信息以不同的方式输出】

Logger 对象可以通过addHandler()方法增加零个或多个handler对象。

举个例子:①一个应用可以将所有的日志消息发送至日志文件;②所有的错误级别(error)及以上级别的日志消息发送至标准输出;③所有的严重级别(critical)日志消息发送至某个电子邮箱。

在这个例子中需要三个独立的处理器,每一个负责将特定级别的日志消息发送至特定的位置。

常用的有4种:

1、 logging.StreamHandler : 控制台输出

向类似与 sys.stdout 或者 sys.stderr 的任何文件对象(file object)输出信息。

它的构造函数是: StreamHandler([strm]) 

参数介绍:strm参数是一个文件对象。默认是sys.stderr(不传strm参数时,默认将日志信息输出至控制台)。

2、 logging.FileHandler  : 文件输出

用于向一个文件输出日志信息。

构造函数是: FileHandler(filename[,mode]) 

参数介绍:①filename文件名:必须指定一个文件名;②mode是文件的打开方式。默认是’a',即添加到文件末尾。

3、logging.handlers.RotatingFileHandler : 按照大小自动分割日志文件,一旦达到指定的大小重新生成文件

这个Handler类似于上面的FileHandler,但是它可以管理文件大小。当文件达到一定大小之后,它会自动将当前日志文件改名,然后创建一个新的同名日志文件继续输出。

例子:比如日志文件是chat.log。
当chat.log达到指定的大小之后,RotatingFileHandler自动把文件改名为chat.log.1。
不过,如果chat.log.1已经存在,会先把chat.log.1重命名为chat.log.2。。。最后重新创建 chat.log,继续输出日志信息。

构造函数: RotatingFileHandler( filename[, mode[, maxBytes[, backupCount]]]) 

参数介绍:①filename文件名:必须指定一个文件名。②mode是文件的打开方式。默认是’a',即添加到文件末尾。③maxBytes用于指定日志文件的最大文件大小。【如果maxBytes为0,意味着日志文件可以无限大,这时上面描述的重命名过程就不会发生。】。④backupCount用于指定保留的备份文件的个数。【如果指定为2,当上面描述的重命名过程发生时,原有的chat.log.2并不会被更名,而是被删除。】

4、 logging.handlers.TimedRotatingFileHandler :按照时间自动分割日志文件

这个Handler和RotatingFileHandler类似;

不过,它没有通过判断文件大小来决定何时重新创建日志文件,而是间隔一定时间就 自动创建新的日志文件。

重命名的过程与RotatingFileHandler类似:不过新的文件不是附加数字,而是当前时间。

构造函数: TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]]) 

参数介绍:

①filename文件名:必须指定一个文件名。

②mode是文件的打开方式。默认是’a',即添加到文件末尾。③backupCount用于指定保留的备份文件的个数。

④interval是时间间隔。

⑤when参数是一个字符串。表示时间间隔的单位,不区分大小写。它有以下取值:

S 秒

M 分

H 小时

D 天

W 每星期(interval==0时代表星期一)

midnight 每天凌晨

配置方法:

① handlers.setLevel() :记录器的级别决定了消息是否要传递给处理器。每个处理器的级别决定了消息是否要分发。【 logger.setLevel() 为记录器设置日志级别(根据设置的日志级别决定日志信息是否传递给处理器), handlers.setLevel 为处理器设置日志级别(根据每个处理器设置的日志级别决定日志信息是否输出至指定地方)。】

② handlers.setFormatter() :为该处理器选择一个格式化器。

③ handlers.addFilter() 和 handlers.removeFilter():分别配置和取消配置处理器上的过滤器对象。

五、Formatters

Formatter对象设置日志信息最后的规则、结构和内容;【日志生成时的样式】

默认的时间格式为%Y-%m-%d %H:%M:%S。

如下图为Formatter常用的一些信息:

 六、代码示例

代码示例1:get_logger(获取日志器): 

# 导包

import logging.handlers

import os

import time

# 新建 类

class Logs:

root_path = os.path.abspath(os.path.dirname(__file__)).split('shippingSchedule')[0]

# 新建一个日志器变量

__logger = None

# 定义生成日志文件的时间

__log_time = time.strftime("%Y-%m-%d", time.localtime())

# 新建获取日志器的方法

@classmethod

def get_logger(cls):

# 判断日志器为空:

if cls.__logger is None:

# 获取日志器【Logger 暴露了应用程序代码能直接使用的接口】

cls.__logger = logging.getLogger() # 包名.方法名直接调用logging包下的初始化模块中的getLogger()返回RootLogger

# 修改默认级别

cls.__logger.setLevel(logging.DEBUG)

log_path = cls.root_path + os.sep + "python--log" + os.sep + "info.python--log" + "_" + cls.__log_time

# 获取处理器【Handler将(记录器产生的)日志记录发送至合适的目的地】

th = logging.handlers.TimedRotatingFileHandler(filename=log_path,

when="midnight",

interval=1,

backupCount=3,

encoding="utf-8")

# 获取格式化器【Filter提供了更好的粒度控制,它可以决定输出哪些日志记录】

fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"

fm = logging.Formatter(fmt) # Formatter 指明了最终输出中日志记录的布局。

# 将格式化器添加到处理器中

th.setFormatter(fm)

# 将处理器添加到日志器中

cls.__logger.addHandler(th)

# 返回日志器

return cls.__logger

if __name__ == '__main__':

log = Logs.get_logger()

log.info("测试信息级别日志")

log.error("测试错误级别")

代码示例2:输出log到控制台以及同时将log写入log文件

# encoding:utf-8

import logging

from logging import handlers

class Logger(object):

level_relations = {

'debug': logging.DEBUG,

'info': logging.INFO,

'warning': logging.WARNING,

'error': logging.ERROR,

'crit': logging.CRITICAL

} # 日志级别关系映射

def __init__(self, filename, level='info', when='D', backCount=3,

fmt='%(asctime)s -%(name)s- %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'):

self.logger = logging.getLogger(filename) # 获取日志器(filename是日志器的名称,如果不填,默认日志器名称为root)

format_str = logging.Formatter(fmt) # 设置日志格式

self.logger.setLevel(self.level_relations.get(level)) # 设置日志级别

sh = logging.StreamHandler() # 输出至控制台

sh.setFormatter(format_str) # 设置输出至控制台上日志格式

th = handlers.TimedRotatingFileHandler(filename=filename, when=when, backupCount=backCount,

encoding='utf-8') # 往文件里写入#指定间隔时间自动生成文件的处理器

# 实例化TimedRotatingFileHandler

# interval是时间间隔,backupCount是备份文件的个数,如果超过这个个数,就会自动删除,when是间隔的时间单位,单位有以下几种:

# S 秒

# M 分

# H 小时、

# D 天、

# W 每星期(interval==0时代表星期一)

# midnight 每天凌晨

th.setFormatter(format_str) # 设置文件里写入的格式

self.logger.addHandler(sh) # 把对象加到logger里

self.logger.addHandler(th)

if __name__ == '__main__':

log = Logger('all.log', level='debug')

log.logger.debug('debug')

log.logger.info('info')

log.logger.warning('警告')

log.logger.error('报错')

log.logger.critical('严重')

Logger('error.log', level='error').logger.error('error')

代码示例3:输出log日志到控制台以及同时将log日志写入log文件+装饰器函数代替测试用例脚本中函数调用。

# -*- encoding:utf-8 -*-

import logging

import os

import time

import traceback

from functools import wraps

"""handlers是什么?"""

# logging模块中包含的类

# 用来自定义日志对象的规则(比如:设置日志输出格式、等级等)

# 常用子类:StreamHandler、FileHandler

# StreamHandler 控制台输出日志

# FileHandler 日志输出到文件

# 日志文件路径

LOG_PATH = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "log")

if not os.path.exists(LOG_PATH):

os.mkdir(LOG_PATH)

class Logger:

def __init__(self):

# 创建日志路径和时间

self.log_path = os.path.join(LOG_PATH, "{}.log".format(time.strftime("%Y%m%d")))

# 创建一个logger日志对象,并传参为log日志器命名为log

self.logger = logging.getLogger("log")

# 为日志器设置默认的日志级别

self.logger.setLevel(logging.DEBUG)

# 创建日志格式对象

self.formater = logging.Formatter('[%(asctime)s][%(filename)s %(lineno)d][%(levelname)s]: %(message)s')

# 创建FileHandler对象(输出log至文件)

self.file_handlers = logging.FileHandler(self.log_path, mode='a', encoding="UTF-8")

# 创建StreamHandler对象(输出log至控制台)

self.console_handlers = logging.StreamHandler()

# FileHandler对象定义日志级别

self.file_handlers.setLevel(logging.DEBUG)

# StreamHandler对象定义日志级别

self.console_handlers.setLevel(logging.DEBUG)

# 设置FileHandler处理器的格式

self.file_handlers.setFormatter(self.formater)

# 设置StreamHandler处理器的格式

self.console_handlers.setFormatter(self.formater)

# logger日志对象加载FileHandler对象

self.logger.addHandler(self.file_handlers)

# logger日志对象加载StreamHandler对象

self.logger.addHandler(self.console_handlers)

Logger = Logger().logger

# 定义一个装饰器,为修饰测试方法提供附加操作(测试方法调用前,测试方法调用后)

def decorate_log(func):

@wraps(func)

def log(*args, **kwargs):

Logger.info(f'------开始执行{func.__name__}------')

try:

func(*args, **kwargs)

except Exception as e:

Logger.error(f'------{func.__name__}执行失败,失败原因:{e}------')

Logger.error(f"{func.__name__} is error,here are details:{traceback.format_exc()}")

raise e

else:

Logger.info(f'------{func.__name__}执行成功------')

return log

@decorate_log

def hbq():

assert 1 == 1

if __name__ == '__main__':

hbq()

# Logger.info("---测试开始---")

# Logger.error("---测试结束---")

# Logger.debug("---测试结束---")

衍生:结合代码示例3:

python装饰器functools.wraps(func)详解

1、先看一段代码:

def is_login(func):

def foo(*args, **kwargs):

return func(*args, **kwargs)

return foo

def test():

print('我是:', test.__name__)

@is_login

def test1():

print('我是:', test1.__name__)

@is_login

def test2():

print('我是:', test2.__name__)

if __name__ == '__main__':

test()

test1()

test2()

运行结果:

我是: test

我是: foo

我是: foo

可以发现函数的函数名即 func.__name__已被装饰器改变,变成了装饰器内返回的函数的函数名

2、在装饰器内返回的函数的函数名上新增 @wraps 装饰器/ functools.wraps(func) 装饰器

from functools import wraps

def is_login(func):

@wraps(func)

def foo(*args, **kwargs):

return func(*args, **kwargs)

return foo

def test():

print('我是:', test.__name__)

@is_login

def test1():

print('我是:', test1.__name__)

@is_login

def test2():

print('我是:', test2.__name__)

if __name__ == '__main__':

test()

test1()

test2()

运行结果:

我是: test

我是: test1

我是: test2

结论:

 @wraps 可以保证被装饰器修饰的函数的 func.__name__ 的值即函数名保持不变。

装饰器的优化

以时间装饰器为例,进行优化

  • 装饰器的统一模板

from functools import wraps

# 对函数的装饰器, 对类func最好为cls

def decorate(func):

@wraps(func)

# 增添或修改功能的函数

def wrapper(*args,**kwargs):

# 执行被装饰的函数

result = func(*args,**kwargs)

# 返回结果

return result

# 返回内层函数

return wrapper

普通--时间装饰器

from functools import wraps

import time

from random import randint

def use_time(func):

@wraps(func)

def wrapper(*args,**kwargs):

st_time = time.time()

result = func(*args,**kwargs)

end_time = time.time()

print(f'{func.__name__}函数use_time:{end_time-st_time}s')

return wrapper

@use_time

def foo():

time.sleep(randint(1,3))

for _ in range(3):

foo()

运行结果:

foo函数use_time:3.0130558013916016s

foo函数use_time:1.0116651058197021s

foo函数use_time:1.01423978805542s

下面对改装饰器进行优化(解耦)

  • 可以发先上面时间装饰器计算的结果,只能在控制台上打印
  • 那我们怎样才能将它输出为日志呢???
  • 我们需要将他的结果进行自定输出

# 在增加一层函数

from functools import wraps

import time

from random import randint

def record(output):

def use_time(func):

@wraps(func)

def wrapper(*args, **kwargs):

st_time = time.time()

func(*args, **kwargs)

end_time = time.time()

# print(f'{func.__name__}函数use_time:{end_time-st_time}s')

output(func.__name__, 'use', end_time - st_time)

return wrapper

return use_time

# 改装饰器的结果就可以自定义了,下面以print函数为例

@record(print)

def foo():

time.sleep(randint(2, 5))

if __name__ == '__main__':

foo()

结果输出日志

# 在增加一层函数

from functools import wraps

import time

from random import randint

def record(output):

def use_time(func):

@wraps(func)

def wrapper(*args, **kwargs):

st_time = time.time()

func(*args, **kwargs)

end_time = time.time()

# print(f'{func.__name__}函数use_time:{end_time-st_time}s')

output(func.__name__, end_time - st_time)

return wrapper

return use_time

def write_log(name, content):

with open('./time.log', 'a', encoding='utf-8')as f:

f.write(f'{name}耗时:{content}\r\n') # \r\n 换行

# 只需要将装饰器改为@record(write_log)

@record(write_log)

def foo():

time.sleep(randint(2, 5))

if __name__ == '__main__':

foo()

以上是 python内置模块之Log日志模块 的全部内容, 来源链接: utcz.com/z/388175.html

回到顶部