一、常用日志方案
-
自定义封装log类
rq = time.strftime('%Y%m%d', time.localtime(time.time())) class Log(object): '''日志类 ''' def __init__(self, name): self.path = "/User/aaa/log/" # 定义日志存放路径 self.filename = self.path + rq + '.log' # 日志文件名称 self.name = name # 为%(name)s赋值 self.logger = logging.getLogger(self.name) #控制日志文件中记录级别 self.logger.setLevel(logging.INFO) #控制输出到控制台日志格式、级别 self.ch = logging.StreamHandler() gs = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s[line:%(lineno)d] - %(message)s') self.ch.setFormatter(gs) # self.ch.setLevel(logging.NOTSET) 写这个的目的是为了能控制控制台的日志输出级别,但是实际中不生效,不知道为啥,留着待解决 #日志保留10天,一天保存一个文件 self.fh = logging.handlers.TimedRotatingFileHandler(self.filename, 'D', 1, 10) #定义日志文件中格式 self.formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s[line:%(lineno)d] - %(message)s') self.fh.setFormatter(self.formatter) self.logger.addHandler(self.fh) self.logger.addHandler(self.ch) class customError(Exception): u''' 自定义异常类,用在主动输出异常时使用,用 raise关键字配合使用,例: if True: pass else: raise customError(msg) ''' def __init__(self, msg=None): self.msg = msg def __str__(self): if self.msg: return self.msg else: return u"某个不符合条件的语法出问题了"
在其他模块中引用的用法:
from Function.Log_main_class import * try: log = Log("casename") log.info("msg") if True: pass else: raise customError(msg) except BaseException as msg: log.exception(msg)
特别说明:def exception(self, msg)方法的使用,可以将控制台中输出的错误日志全部保存到日志文件中,对于根据日志分析问题的场景需求,特别有用。这个函数一般配合try使用:
try: r = 10/0 print r except Exception as ex: log.exception(ex)
-
Python3彩色日志包
-
安装
pip install colorful-logger
-
使用
-
默认logger
可以直接使用默认的logger实例输出日志,默认的日志等级是warning
from colorful_logger.logger import logger with logger: logger.debug("This is a debug message.") logger.info("This is a info message.") logger.warning("This is a warning message.") logger.error("This is a error message.") logger.critical("This is a critical message.") logger.fatal("This is a fatal message.")
如你所见,logger需要在with语句中执行,因为本包使用的是QueueListener调用日志输出,使用logger输出日志前需要调用start()方法,使用结束后需要调用stop()方法,我将这两个方法封装到了with语句中,非特殊场景下,不需要单独调用start()和stop()方法。
如果显式调用了start()方法,一定要在调用日志后执行stop()方法
-
自定义logger
也可以自定义name,日志等级、是否在终端显示、是否保存到文件:
from colorful_logger.logger import get_logger, DEBUG logger = get_logger(name="sample_logger", level=DEBUG, file_path="./test.log") with get_logger(name="sample_logger", level=DEBUG, file_path="./test.log", file_colorful=True) as logger: logger.debug("This is a debug message.") logger.info("This is a info message.") logger.warning("This is a warning message.") logger.error("This is a error message.") logger.critical("This is a critical message.") logger.fatal("This is a fatal message.")
在with语句外输出日志时可能会有意外情况,达不到预期结果。
输出到文件的日志默认不是彩色日志
如果你需要在文件中保存彩色日志,将file_colorful参数设置为True即可,本例中保存的就是彩色日志。
彩色日志文件的作用特只有一个,就是在终端查看实时日志:
tailf -f -n 1000 xxx.log
这样查看日志才是彩色的
FATAL
或CRITICAL
本就是影响程序运行的严重错误,而 python 默认的日志管理器中此方法与其他方法没有什么区别,这让我觉得莫名其妙,在本包中,我在fatal
方法中加入了sys.exit(1)
用来退出程序。如果在程序出现严重错误时不想退出程序,可以调用critical
方法。get_logger方法:
def get_logger( name: Optional[str] = None, level: int = logging.WARNING, show: bool = True, file_path: Optional[str] = None, file_colorful: bool = False, ) -> Logger: ...
- name logger实例名,可以在不同的实例对象调用日志时为日志命名
- level日志等级
- show是否在终端显示。如果你想用此彩色日志包的话,通常是想在终端显示的吧
- file_path是否保存到文件。默认是None,当其不是None时,会保存到对应的文件中
- file_colorful保存到文件的日志是否为彩色,默认为False,以python默认的日志格式保存
-
-
子logger
子logger除了name与父logger不同,其余均相同,也不会输出第三方库的日志
子logger在父logger的with语句中执行并不意味着一定在with语句中直接调用,在with语句中的某个函数中执行就可以,比如:
# main.py from colorful_logger import get_logger from colorful_logger.logger import DEBUG from other_file import test # parent logger logger = get_logger(name="sample_logger", level=DEBUG, file_path="./test.log") with logger: test() # other_file.py test_logger = child_logger("test_logger", logger) def test(): test_logger.error("test error")
-
-
-
Python中更优雅的记录日志
-
安装使用
pip install loguru
-
快速上手
如图
logging
一样,loguru
也有定义日志等级。不同的日志等级,输出效果也不一样(默认的等级由低到高是DEBUG
、INFO
、WARNING
、ERROR
、CRITICAL
,也可以自己使用level
函数定义)from loguru import logger logger.debug("That's it, beautiful and simple logging!") logger.info("this is a info message!")
类似
logging
中的logger.addHandler
,loguru统一使用add
函数来管理格式、文件输出、过滤等操作,它提供了许多参数来实现logger.addHandler
中的配置更加简单方便。其中
sink
是最重要的参数,可以传入不同的数据类型。传入文件路径、文件句柄、sys.stderr
、甚至logging
模块的Handler
如FileHandler
、StreamHandler
等,这样就可以快速实现自定义的Handler
配置。通过给
remove
方法传递add
方法返回的对象, 可以删除add
方法添加的sink
,这里的remove
并不是删除test2.log
文件,而是停止向该文件输出日志,需要需要继续记录日志则需要重新add
日志文件。from loguru import logger obj = logger.add(sink='info.txt', format="{time} {level} {message}", filter="my_module", level="INFO") logger.info("That's it, beautiful and simple logging!") logger.remove(obj)
用
rotation
、retention
、compression
进行日志窗口、更新、压缩管理。logger.add("file_1.log", rotation="10 MB") # 日志文件的大小是10M,过大就会重新生成一个文件 logger.add("file_2.log", rotation="00:00") # 每天0点创建新日志文件 logger.add("file_3.log", rotation="1 week") # 自动更新旧文件 logger.add("file_X.log", retention="3 days") # 每3天自动清理旧文件 logger.add("file_Y.log", compression="zip") # zip压缩文件
支持控制台输出添加颜色, 除了基础色,
loguru
甚至允许16进制、RGB格式的颜色值和加粗、下划线等样式。logger.add(sys.stdout, colorize=True, format="<green>{time}</green> <level>{message}</level>")
使用装饰器
@logger.catch
可以和logging
一样使用logger.exception
函数来记录异常信息。@logger.catch def my_function(x, y, z): # An error? It's caught anyway! return 1 / (x + y + z) def func(a, b): return a / b def nested(c): try: func(5, c) except ZeroDivisionError: logger.exception("What?!") nested(0)
使用
exception
方法输出的异常信息包含堆栈信息和当前变量的值,方便问题定位。2018-07-17 01:38:43.975 | ERROR | __main__:nested:10 - What?! Traceback (most recent call last): File "test.py", line 12, in <module> nested(0) └ <function nested at 0x7f5c755322f0> > File "test.py", line 8, in nested func(5, c) │ └ 0 └ <function func at 0x7f5c79fc2e18> File "test.py", line 4, in func return a / b │ └ 0 └ 5 ZeroDivisionError: division by zero
使用
serialize
可以将日志转换为JSON
格式,enqueue
可以保证多线程、多进程安全。logger.add("somefile.log", serialize=True) # 序列化为json logger.add("somefile.log", enqueue=True)
修改时间格式:
logger.add("file.log", format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}")
-
-
Python中logging记录日志,使用@log装饰器
-
导入依赖包
import logging #日志 import time # 时间 import os # 路径
-
创建一个logger
logger = logging.getLogger() #获取logger logger.setLevel(logging.DEBUG) # 设置记录等级时debug
-
创建日志文件
rq = time.strftime('%Y-%m-%d-%H', time.localtime(time.time())) #以小时分割记录日志 log_path = os.getcwd() + '/../Logs/all/' #设置日志路径 log_name = log_path + rq + '_all.log' #设置日志名称 logfile = log_name if not os.path.exists(log_path): # 创建路径 os.makedirs(log_path) for root, dirs, files in os.walk(os.path.dirname(log_path)): # 删除空文件 for i in files: fpath = os.path.join(root, i) if os.path.getsize(fpath) == 0: os.remove(fpath) if not os.path.exists(logfile): # 创建文件 f = open(logfile, mode='w', encoding="utf-8") f.close() fh_all = logging.FileHandler(logfile, mode='a', encoding='utf-8') # 输出到文件 fh_all.setLevel(logging.DEBUG) # 以上是所有日志,其他分类日志同上 ch = logging.StreamHandler() ch.setLevel(logging.WARNING) # 控制台输出的日志级别
-
定义输出格式
formatter = logging.Formatter( "%(asctime)s - %(filename)s[line:%(lineno)d](%(funcName)s) - %(levelname)s: %(message)s") fh_debug.setFormatter(formatter) # 添加格式 logger.addHandler(fh_debug) # 保存 ch.setFormatter(formatter) logger.addHandler(ch)
-
完整代码
import logging import os import time logger = logging.getLogger() logger.setLevel(logging.DEBUG) # Log等级总开关 # 第二步,创建一个 file handler,用于写入日志文件 def log(func): @functools.wraps(func) def wrapper(*args, **kw): dicc = {} dinp = {} varnames = func.__code__.co_varnames deft = func.__defaults__ if deft is None: deft = () for i in range(len(args)): dinp[varnames[i]] = str(args[i]) for j in range(len(deft)): dinp[varnames[i + j + 1]] = str(deft[j]) for i, j in kw.items(): dinp[i] = str(j) # print(str(func.__name__)) filter = ContextFilter( os.path.basename(str(func.__code__.co_filename)), int(func.__code__.co_firstlineno), str(func.__name__)) try: aa = func(*args, **kw) except Exception as e: aa = 'err:' + str(e) if aa is None: dretrun = '' elif isinstance(aa, str): dretrun = aa elif isinstance(aa, tuple): dretrun = list(aa) else: dretrun = str(aa) # dicc['run_info'] = dinfo dicc['run_input'] = dinp dicc['run_return'] = dretrun logger.addFilter(filter) logger.debug(dicc) logger.error(func.__name__ + '运行错误:', exc_info=True) logger.removeFilter(filter) raise e if aa is None: dretrun = '' elif isinstance(aa, str): dretrun = aa elif isinstance(aa, tuple): dretrun = list(aa) else: dretrun = str(aa) # dicc['run_info'] = dinfo dicc['run_input'] = dinp dicc['run_return'] = dretrun logger.addFilter(filter) logger.debug(dicc) logger.removeFilter(filter) return aa return wrapper
-
使用logger记录日志
logger.error('err', exc_info=True) #exc_info 记录错误信息
-
使用@log装饰器记录log
-
导入依赖包
import functools
-
完整代码
def log(func): @functools.wraps(func) def wrapper(*args, **kw): dicc = {} dinp = {} varnames = func.__code__.co_varnames deft = func.__defaults__ if deft is None: deft = () for i in range(len(args)): dinp[varnames[i]] = str(args[i]) for j in range(len(deft)): dinp[varnames[i + j + 1]] = str(deft[j]) for i, j in kw.items(): dinp[i] = str(j) # print(str(func.__name__)) filter = ContextFilter( os.path.basename(str(func.__code__.co_filename)), int(func.__code__.co_firstlineno), str(func.__name__)) try: aa = func(*args, **kw) except Exception as e: aa = 'err:' + str(e) if aa is None: dretrun = '' elif isinstance(aa, str): dretrun = aa elif isinstance(aa, tuple): dretrun = list(aa) else: dretrun = str(aa) # dicc['run_info'] = dinfo dicc['run_input'] = dinp dicc['run_return'] = dretrun logger.addFilter(filter) logger.debug(dicc) logger.error(func.__name__ + '运行错误:', exc_info=True) logger.removeFilter(filter) raise e if aa is None: dretrun = '' elif isinstance(aa, str): dretrun = aa elif isinstance(aa, tuple): dretrun = list(aa) else: dretrun = str(aa) # dicc['run_info'] = dinfo dicc['run_input'] = dinp dicc['run_return'] = dretrun logger.addFilter(filter) logger.debug(dicc) logger.removeFilter(filter) return aa return wrapper
-
发现输出文件格式不是喜欢的类型,在最上方添加方法
class ContextFilter(logging.Filter): # filename = 'IP' # lineno = 'USER' def __init__(self, filename, lineno, funcname): self.filename = filename self.lineno = lineno self.funcname = funcname def filter(self, record): record.filename = self.filename record.lineno = self.lineno record.funcName = self.funcname return True
-
-
二、原文链接
https://www.jianshu.com/p/2898de660cb7
https://www.jianshu.com/p/aff42e5e6099
https://www.jianshu.com/p/843d4189bb42
https://www.jianshu.com/p/3f184414d781