当前位置 : 主页 > 编程语言 > python >

python内置模块之Log日志模块

来源:互联网 收集:自由互联 发布时间:2022-06-20
Log日志模块 前言 logging是python中的一个包,封装所有日志功能。 例如获取日志器logging.getLogger是logging包的__init__文件中定义的函数(包的__init__文件中的函数直接可以使用包名.函数名调

Log日志模块

前言

 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函数根据它们用来跟踪的事件的级别或严重程度来命名。标准级别及其适用性描述如下(以严重程度递增排序):

python内置模块之Log日志模块_打印日志

 python内置模块之Log日志模块_打印日志_02

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

①将日志直接输出到屏幕

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

import logging

logging.debug('debug 信息')
logging.warning('只有这个会输出。。。')
logging.info('info 信息')

输出结果:

python内置模块之Log日志模块_日志文件_03

 

②通过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常用的一些信息:

python内置模块之Log日志模块_日志输出_04

 六、代码示例

代码示例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()

 

去期待陌生,去拥抱惊喜。

网友评论