赞
踩
参考python日志模块的使用
参考Python之日志处理(logging模块)
参考python标准日志模块logging及日志系统设计
Python + logging 日志和日志文件(一天一个日志文件)
handlers 可以设置按时间文件超大小自动新增
python 双日志,子进程事件日志按时间轮转,多进程错误日志按大小轮转
python多进程日志以及分布式日志的实现
日志是一种可以追踪某些软件运行时所发生事件的方法。
软件开发人员可以向他们的代码中调用日志记录相关的方法来表明发生了某些事情。
一个事件可以用一个可包含可选变量数据的消息来描述。
此外事件也有重要性的概念,这个重要性也可以被称为严重性级别(level)。
(1)日志的作用
通过log的分析,可以方便用户了解系统或软件、应用的运行情况;如果你的应用log足够丰富,也可以分析以往用户的操作行为、类型喜好、地域分布或其他更多信息;如果一个应用的log同时也分了多个级别,那么可以很轻易地分析得到该应用的健康状况,及时发现问题并快速定位、解决问题,补救损失。
(2)日志的等级
在软件开发阶段或部署开发环境时,为了尽可能详细的查看应用程序的运行状态来保证上线后的稳定性,我们可能需要把该应用程序所有的运行日志全部记录下来进行分析,这是非常耗费机器性能的。
当应用程序正式发布或在生产环境部署应用程序时,我们通常只需要记录应用程序的异常信息、错误信息等,这样既可以减小服务器的I/O压力,也可以避免我们在排查故障时被淹没在日志的海洋里。
怎样才能在不改动应用程序代码的情况下实现在不同的环境记录不同详细程度的日志呢?这就是日志等级的作用了,我们通过配置文件指定我们需要的日志等级就可以了。
(3)日志字段信息与日志格式
一条日志信息对应的是一个事件的发生,而一个事件通常需要包括以下几个内容:
事件发生时间;
事件发生位置;
事件的严重程度–日志级别;
事件内容;
上面这些都是一条日志记录中可能包含的字段信息,当然还可以包括一些其他信息,如进程ID、进程名称、线程ID、线程名称等。
日志格式就是用来定义一条日志记录中包含那些字段的,且日志格式通常都是可以自定义的。
(4)常用库
几乎所有开发语言都会内置日志相关功能,或者会有比较优秀的第三方库来提供日志操作功能,比如:log4j。它们功能强大、使用简单。Python自身也提供了一个用于记录日志的标准库模块–logging。
logging模块提供了两种记录日志的方式:
第一种方式是使用logging提供的模块级别的函数。
第二种方式是使用logging日志系统的四大组件。
其实logging所提供的模块级别的日志记录函数也是对logging日志系统相关类的封装而已。
其中函数
一、常用函数
logging.basicConfig(**kwargs)
用于指定“要记录的日志级别”、“日志格式”、“日志输出位置”、
“日志文件的打开模式”等信息,
下面几个都是用于记录各个级别日志的函数。
logging.debug(msg, *args, **kwargs) 创建一条严重级别为DEBUG的日志记录
logging.info(msg, *args, **kwargs) 创建一条严重级别为INFO的日志记录
logging.warning(msg, *args, **kwargs) 创建一条严重级别为WARNING的日志记录
logging.error(msg, *args, **kwargs) 创建一条严重级别为ERROR的日志记录
logging.critical(msg, *args, **kwargs) 创建一条严重级别为CRITICAL的日志记录
logging.log(level, *args, **kwargs) 创建一条严重级别为level的日志记录
logging.basicConfig(**kwargs) 对root logger进行一次性配置
二、简单日志输出案例
import logging logging.debug("This is a debug log.") logging.info("This is a info log.") logging.warning("This is a warning log.") logging.error("This is a error log.") logging.critical("This is a critical log.") 等价于 logging.log(logging.DEBUG, "This is a debug log.") logging.log(logging.INFO, "This is a info log.") logging.log(logging.WARNING, "This is a warning log.") logging.log(logging.ERROR, "This is a error log.") logging.log(logging.CRITICAL, "This is a critical log.") 输出 WARNING:root:This is a warning log. ERROR:root:This is a error log. CRITICAL:root:This is a critical log.
(1)logging模块提供的日志记录函数所使用的日志器设置的日志级别是WARNING,因此只有WARNING级别的日志记录以及大于它的ERROR和CRITICAL级别的日志记录被输出了,而小于它的DEBUG和INFO级别的日志记录被丢弃了。
(2)输出结果中每行日志记录的各个字段含义分别是:
日志级别:日志器名称:日志内容
之所以会这样输出,是因为logging模块提供的日志记录函数,
所使用的日志器设置的日志格式默认是BASIC_FORMAT,其值为:
"%(levelname)s:%(name)s:%(message)s"
(3)logging模块提供的日志记录函数所使用的日志器设置的处理器所指定的日志输出位置默认为:sys.stdout,即输出到控制台。
一、该函数可接收的关键字参数如下:
(1)filename 指定日志输出目标文件的文件名, 指定该设置项后日志信息就不会被输出到控制台了。 (2)filemode 指定日志文件的打开模式,默认为'a'。 需要注意的是,该选项要在filename指定时才有效。 (3)format 指定日志格式字符串,即指定日志输出时所包含的字段信息以及它们的顺序。 logging模块定义的格式字段下面会列出。 (4)datefmt 指定日期/时间格式。 需要注意的是,该选项要在format中包含时间字段%(asctime)s时才有效。 (5)level 指定日志器的日志级别。 (6)stream 指定日志输出目标stream, 如sys.stdout、sys.stderr以及网络stream。 需要说明的是,stream和filename不能同时提供, 否则会引发ValueError异常。 (7)style Python 3.2中新添加的配置项。 指定format格式字符串的风格,可取值为'%'、'{'和'$',默认为'%'。 (8)handlers Python 3.3中新添加的配置项。 该选项如果被指定,它应该是一个创建了多个Handler的可迭代对象, 这些handler将会被添加到root logger。 需要说明的是:filename、stream和handlers 这三个配置项只能有一个存在, 不能同时出现2个或3个,否则会引发ValueError异常。
二、logging模块中可以用于format格式字符串中字段如下:
(1)asctime %(asctime)s 日志事件发生的时间--人类可读时间,如:2003-07-08 16:49:45,896 (2)created %(created)f 日志事件发生的时间--时间戳,就是当时调用time.time()函数返回的值 (3)relativeCreated %(relativeCreated)d 日志事件发生的时间相对于logging模块加载时间的相对毫秒数(目前还不知道干嘛用的) (4)msecs %(msecs)d 日志事件发生事件的毫秒部分 (5)levelname %(levelname)s 该日志记录的文字形式的日志级别 ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL') (6)levelno %(levelno)s 该日志记录的数字形式的日志级别(10, 20, 30, 40, 50) (7)name %(name)s 所使用的日志器名称,默认是'root' (8)message %(message)s 日志记录的文本内容,通过 msg % args计算得到的 (9)pathname %(pathname)s 调用日志记录函数的源码文件的全路径 (10)filename %(filename)s pathname的文件名部分,包含文件后缀 (11)module %(module)s filename的名称部分,不包含后缀 (12)lineno %(lineno)d 调用日志记录函数的源代码所在的行号 (13)funcName %(funcName)s 调用日志记录函数的函数名 (14)process %(process)d 进程ID (15)processName %(processName)s 进程名称,Python 3.1新增 (16)thread %(thread)d 线程ID (17)threadName %(thread)s 线程名称
一、配置日志级别
import logging
logging.basicConfig(level=logging.DEBUG)
logging.log(logging.DEBUG, "This is a debug log.")
logging.log(logging.INFO, "This is a info log.")
logging.log(logging.WARNING, "This is a warning log.")
logging.log(logging.ERROR, "This is a error log.")
logging.log(logging.CRITICAL, "This is a critical log.")
输出
DEBUG:root:This is a debug log.
INFO:root:This is a info log.
WARNING:root:This is a warning log.
ERROR:root:This is a error log.
CRITICAL:root:This is a critical log.
二、配置日志输出到文件和日志格式
import logging
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s"
logging.basicConfig(filename='my.log', level=logging.DEBUG, format=LOG_FORMAT)
logging.debug("This is a debug log.")
logging.info("This is a info log.")
logging.warning("This is a warning log.")
logging.error("This is a error log.")
logging.critical("This is a critical log.")
输出到my.log文件中
2021-08-25 10:32:35,272 - DEBUG - This is a debug log.
2021-08-25 10:32:35,273 - INFO - This is a info log.
2021-08-25 10:32:35,274 - WARNING - This is a warning log.
2021-08-25 10:32:35,274 - ERROR - This is a error log.
2021-08-25 10:32:35,275 - CRITICAL - This is a critical log.
三、举例如下
import logging
# (1)%(asctime)s日志事件发生的时间--人类可读时间,如:2003-07-08 16:49:45,896
# (2)%(levelname)s该日志记录的文字形式的日志级别('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
# (3)%(filename)s pathname的文件名部分,包含文件后缀
# (4)%(message)s 日志记录的文本内容
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(filename)s - %(message)s"
logging.basicConfig(filename='my.log', level=logging.WARNING, format=LOG_FORMAT)
logging.debug("This is a debug log.")
logging.info("This is a info log.")
logging.warning("This is a warning log.")
logging.error("This is a error log.")
logging.critical("This is a critical log.")
Python中,让多个py文件的logging输出到同一个日志log文件中。
适用于主程序及其调用的子程序,主模块及其调用的子模块。
对于python子程序文件,完全不需要多余额外设置,只需要:
import logging;然后直接使用logging.info,logging.debug函数。
即可实现把日志内容输出去,具体输出的形式,取决于主文件用logging.basicConfig所配置的形式。
输出的位置,即主文件的log文件。
这样,至少可以很好地实现,将多个py文件的logging内容都输出到同一个主文件的log文件中,并且格式都是设置好的,统一的。
一、main.py程序文件
# -*- coding: utf-8 -*-
import logging
from demo import fun
LOG_FORMAT = "%(asctime)s - %(levelname)s - %(filename)s - %(message)s"
logging.basicConfig(filename='my.log', level=logging.DEBUG, format=LOG_FORMAT)
if __name__ == "__main__":
logging.debug("This is a debug log.")
fun()
二、demo.py程序文件
import logging
def fun():
print("输出到日志文件中")
logging.info("This is subprogram results")
运行主程序以后,发现my.log文件中记录的日志信息为
2023-05-01 08:27:50,162 - INFO - main.py - This is a debug log.
2023-05-01 08:27:50,163 - INFO - demo.py - This is subprogram results
import logging import time from multiprocessing import Process LOG_FORMAT = "%(asctime)s - %(levelname)s - %(thread)d -%(filename)s - %(message)s" # logging.basicConfig(filename='my.log', level=logging.DEBUG, format=LOG_FORMAT) logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT) def fun(): i = 0 while True: i = i+1 time.sleep(1) logging.info("This is subFun results") if i > 3: break if __name__ == '__main__': # p进程执行fun函数 p1 = Process(target=fun) p1.start() # 进程开始 logging.info("This is mainFun results") p2 = Process(target=fun) p2.start() # 进程开始
import logging from concurrent_log_handler import ConcurrentTimedRotatingFileHandler import re def create_logger(log_name): # (1)创建logger对象,传入logger名字 logger = logging.getLogger(log_name) # (2)设置日志记录等级 logger.setLevel(logging.INFO) # (3)创建一个handler,用于写入日志文件 log_file_handler = ConcurrentTimedRotatingFileHandler(filename=log_name, when="S", interval=10, backupCount=3) log_file_handler.suffix = "%Y-%m-%d_%H-%M-%S.log" log_file_handler.extMatch = re.compile(r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}.log$") # (4)定义日志输出格式 log_format = '%(asctime)s-%(filename)s-%(lineno)s-%(levelname)s:%(message)s' formatter = logging.Formatter(log_format) log_file_handler.setFormatter(formatter) # (5)给logger添加handler logger.addHandler(log_file_handler) return logger # 生成一个logger mylogger = create_logger("useLogger")
其余的地方在使用时,直接引入
from ConfigLogger import mylogger
(1)loggers
提供应用程序代码直接使用的接口
(2)handlers
用于将日志记录发送到指定的目的位置
(3)filters
提供更细粒度的日志过滤功能,用于决定哪些日志记录将会被输出
(4)formatters
用于控制日志信息的最终输出格式
logging模块提供的模块级别的那些函数实际上也是通过这几个组件的相关实现类来记录日志的,只是在创建这些类的实例时设置了一些默认值。
python的标准库里的日志系统从python2.3开始支持。
引入import logging这个模块即可使用。
如果你想开发一个日志系统, 既要把日志输出到控制台, 还要写入日志文件,可以按下面的方式进行使用。
import logging # 创建一个logger logger = logging.getLogger('mylogger') logger.setLevel(logging.DEBUG) # 创建一个handler,用于写入日志文件 fh = logging.FileHandler('test.log') fh.setLevel(logging.DEBUG) # 再创建一个handler,用于输出到控制台 ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) # 定义handler的输出格式 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') fh.setFormatter(formatter) ch.setFormatter(formatter) # 给logger添加handler logger.addHandler(fh) logger.addHandler(ch) # 记录一条日志 logger.info('foorbar')
logging库提供了两个可以用于日志滚动的class,一个是RotatingFileHandler,它主要是根据日志文件的大小进行滚动,另一个是TimeRotatingFileHandler,它主要是根据时间进行滚动。
(1)filename:日志文件名的prefix; (2)when:是一个字符串,用于描述滚动周期的基本单位, 字符串的值及意义如下: “S”: Seconds “M”: Minutes “H”: Hours “D”: Days “W”: Week day (0=Monday) “midnight”: Roll over at midnight (3)interval: 滚动周期,单位由when指定, 比如:when='D',interval=1,表示每天产生一个日志文件。 (4)backupCount: 表示日志文件的保留个数; 表示日志保存个数,会自动滚动更新 (5)suffix是指日志文件名的后缀, suffix中通常带有格式化的时间字符串, filename和suffix由“.”连接构成文件名, 例如:filename=“runtime”,suffix=“%Y-%m-%d.log”, 生成的文件名为runtime.2015-07-06.log。 (6)extMatch是一个编译好的正则表达式,用于匹配日志文件名的后缀, 它必须和suffix是匹配的,如果suffix和extMatch匹配不上的话, 过期的日志是不会被删除的。 例如suffix=“%Y-%m-%d.log”, extMatch的只应该是re.compile(r”^\d{4}-\d{2}-\d{2}.log$”)。
默认情况下,在TimedRotatingFileHandler对象初始化时,suffix和extMatch会根据when的值进行初始化:
'S':suffix="%Y-%m-%d_%H-%M-%S", extMatch=r"\^d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}";
'M':suffix="%Y-%m-%d_%H-%M",extMatch=r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}";
'H':suffix="%Y-%m-%d_%H",extMatch=r"^\d{4}-\d{2}-\d{2}_\d{2}";
'D':suffxi="%Y-%m-%d",extMatch=r"^\d{4}-\d{2}-\d{2}";
'MIDNIGHT':"%Y-%m-%d",extMatch=r"^\d{4}-\d{2}-\d{2}";
'W':"%Y-%m-%d",extMatch=r"^\d{4}-\d{2}-\d{2}";
文件名称中不要有空格,否则会出现单引号。
import logging import time import re from logging.handlers import TimedRotatingFileHandler def setup_log(log_name): # (1)创建logger对象,传入logger名字 logger = logging.getLogger(log_name) # (2)设置日志记录等级 logger.setLevel(logging.INFO) # (3)创建一个handler,用于写入日志文件 log_file_handler = TimedRotatingFileHandler(filename=log_name, when="S", interval=10, backupCount=3) log_file_handler.suffix = "%Y-%m-%d_%H-%M-%S.log" log_file_handler.extMatch = re.compile(r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}.log$") # (4)定义日志输出格式 log_format = '%(asctime)s-%(filename)s-%(lineno)s-%(levelname)s:%(message)s' formatter = logging.Formatter(log_format) log_file_handler.setFormatter(formatter) # (5)给logger添加handler logger.addHandler(log_file_handler) return logger if __name__ == "__main__": logger = setup_log("ds_update") count = 0 while count < 30: logger.error("error message") time.sleep(20) count = count + 1
# 当即将超过大小时,文件将被关闭,并且将静默打开一个新文件以进行输出。
# backupCount 表示日志保存个数,会自动滚动更新
# 每隔500字节保存成一个日志文件
import logging import time import re from logging.handlers import TimedRotatingFileHandler from logging.handlers import RotatingFileHandler def setup_log(log_name): # (1)创建logger对象,传入logger名字 logger = logging.getLogger(log_name) # (2)设置日志记录等级 logger.setLevel(logging.INFO) # (3)创建一个handler,用于写入日志文件 log_file_handler = RotatingFileHandler(filename=log_name, mode="a",maxBytes=20, backupCount=3, encoding='utf-8') # (4)定义日志输出格式 log_format = '%(asctime)s-%(filename)s-%(lineno)s-%(levelname)s:%(message)s' formatter = logging.Formatter(log_format) log_file_handler.setFormatter(formatter) # (5)给logger添加handler logger.addHandler(log_file_handler) return logger if __name__ == "__main__": logger = setup_log("update") count = 0 while count < 30: logger.error("error message") time.sleep(20) count = count + 1
因为项目中要读取18台设备的数据,由于python多线程比较慢,因此开了18个子进程,每台设备使用一个事件日志文件,全部设备共用一个错误日志文件。
事件日志,看情况暂定的保留10天。
错误日志,一般不会报错,所以按文件的大小进行轮转。
python自带的logging模块是线程安全的,事件日志单个进程可以使用该模块,但是错误日志是多进程共用,会报如下错误。
--- Logging error ---
Traceback (most recent call last):
PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。
所以需要使用第三方的concurrent_log_handler模块。
pip install concurrent_log_handler
本质上是通过加锁的方式解决。
一、ConfigLogger.py
import logging from concurrent_log_handler import ConcurrentTimedRotatingFileHandler import re def create_logger(log_name): # (1)创建logger对象,传入logger名字 logger = logging.getLogger(log_name) # (2)设置日志记录等级 logger.setLevel(logging.INFO) # (3)创建一个handler,用于写入日志文件 log_file_handler = ConcurrentTimedRotatingFileHandler(filename=log_name, when="S", interval=10, backupCount=3) log_file_handler.suffix = "%Y-%m-%d_%H-%M-%S.log" log_file_handler.extMatch = re.compile(r"^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}.log$") # (4)定义日志输出格式 log_format = '%(asctime)s-%(filename)s-%(thread)d-%(lineno)s-%(levelname)s:%(message)s' formatter = logging.Formatter(log_format) log_file_handler.setFormatter(formatter) # (5)给logger添加handler logger.addHandler(log_file_handler) return logger # 生成一个logger mylogger = create_logger("useLogger")
二、main.py
from multiprocessing import Process
from demo import fun
from ConfigLogger import mylogger
if __name__ == '__main__':
# p进程执行fun函数
p1 = Process(target=fun)
p1.start() # 进程开始
mylogger.info("This is mainFun results")
p2 = Process(target=fun)
p2.start() # 进程开始
三、demo.py
import time
from ConfigLogger import mylogger
def fun():
count = 0
while count < 30:
mylogger.error("error message")
time.sleep(20)
count = count + 1
import logging
logger = logging.getLogger([name])
返回一个logger实例,如果没有指定name,返回root logger。
只要name相同,返回的logger实例都是同一个而且只有一个。
即name和logger实例是一一对应的。
这意味着,无需把logger实例在各个模块中传递。只要知道name,就能得到同一个logger实例。
# -- coding: utf-8 -- import logging import os.path import time # 初始化全局的日志输出格式 LOG_FORMAT = '%(asctime)s - %(levelname)s--->: %(message)s' logging.basicConfig(format=LOG_FORMAT, level=logging.DEBUG, filemode='a') def createLogFile(name, level): ''' :param name: 日志名,附加到日志文件名称里 :param level: 日志等级 ''' # 一、创建一个logger logger = logging.getLogger() # 无参调用,返回root logger logger.setLevel(logging.INFO) # Log等级总开关 # 二、创建一个handler,用于写入日志文件 cur_time = time.strftime('%Y-%m-%d', time.localtime(time.time())) log_path = os.getcwd() + '/Logs/' # 日志文件目录 is_exists = os.path.exists(log_path) # 判断目录是否存在,否即创建 if not is_exists: os.makedirs(log_path) # 创建日志文件名 log_name = log_path + '{}-'.format(cur_time) + name + '.log' fh = logging.FileHandler(log_name, mode='a') fh.setLevel(level) # 输出到file的log等级的开关 # 三、定义handler的输出格式 formatter = logging.Formatter("%(asctime)s - %(levelname)s--->: %(message)s") fh.setFormatter(formatter) # 四、给logger添加handler logger.addHandler(fh) def print_info(info, *args): # 确保每天生成新的文件名称 createLogFile("info", logging.INFO) if len(args) > 0: log = "" for arg in args: log += "{}" logging.info((info + log).format(*args)) else: logging.info(info) def print_error(error, *args): # 确保每天生成新的文件名称 createLogFile("error",logging.ERROR) if len(args) > 0: log = "" for arg in args: log += "{}" logging.error((error + log).format(*args)) else: logging.error(error) def deleteBigFile(path): for file in os.listdir(path): fsize = os.path.getsize(f'{path}{file}') if (fsize > 1 * 1024 * 1024 * 1024): os.remove(f'{path}{file}') if __name__ == '__main__': print_info("正常",2,4,7) print_error("错误",5,8,9)
import time import log_helper from flask import Flask app = Flask(__name__) # 定义一个装饰器 def access_log(fn): def wrapper(): t_begin = time.time() res = fn() t_end = time.time() print("调试输出查看响应时间",t_begin,t_end) return res return wrapper @app.route('/') @access_log def hello_world(): # 记录日志信息 log_helper.print_error("有访客进入错误") return 'Welcome to use me!' if __name__ == '__main__': app.run(host='127.0.0.1', port=9000, threaded=True)
异常信息的获取对于程序的调试非常重要,可以有助于快速定位有错误程序语句的位置。
采用traceback模块
需要导入traceback模块,此时获取的信息最全,与python命令行运行程序出现错误信息一致。使用traceback.print_exc()打印异常信息到标准错误,就像没有获取一样,或者使用traceback.format_exc()将同样的输出获取为字符串。你可以向这些函数传递各种各样的参数来限制输出,或者重新打印到像文件类型的对象。
try:
pass
except Exception as err:
# 只提取指定部分
message = "".join(traceback.format_exception_only(err.__class__, err))
#或者,提取全部信息
message = "".join(traceback.format_exc())
logger.error("计算出错{}".format(message))
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。