最近一直想自己的批量框架,参考了POC-T框架和sqlmap的框架结构,发现logging模块被大量用来处理控制台输出以及日志记录,鉴于我自己也要写框架,那么本文就记录下我的logging模块学习记录。

为什么需要logging

在开发过程中,如果程序出现了问题,我们可以使用编辑器的Debug模式来检查bug,但是在发布之后,我们的程序相当于在一个黑盒状态去运行,我们只能看到运行效果,可是程序难免出错,这种情况的话我们就需要日志模块来记录程序当前状态、时间状态、错误状态、标准输出等,这样不论是正常运行还是出现报错,都有记录,我们可以针对性的快速排查问题。

因此,日志记录对于程序的运行状态以及debug都起到了很高效的作用。如果一个程序没有标准的日志记录,就不能算作一个合格的开发者。

logging和print的对比

  • logging对输出进行了分级,print没有
  • logging具有更灵活的格式化功能,比如运行时间、模块信息
  • print输出都在控制台上,logging可以输出到任何位置,比如文件甚至是远程服务器

logging的结构拆分

模块用途
Logger记录日志时创建的对象,调用其方法来传入日志模板和信息生成日志记录
Log RecordLogger对象生成的一条条记录
Handler处理日志记录,输出或者存储日志记录
Formatter格式化日志记录
Filter日志过滤器
Parent HandlerHandler之间存在分层关系

简单的实例

import logging

logger = logging.getLogger("Your Logger")
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
formatter = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='%Y/%m/%d %H:%M:%S')
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.info("this is info msg")
logger.debug("this is debug msg")
logger.warning("this is warn msg")
logger.error("this is error msg")

输出

2019/01/13 13:21:17 - Your Logger - INFO - this is info msg
2019/01/13 13:21:17 - Your Logger - DEBUG - this is debug msg
2019/01/13 13:21:17 - Your Logger - WARNING - this is warn msg
2019/01/13 13:21:17 - Your Logger - ERROR - this is error msg

我们来理解下这个实例。

首先创建了一个logger对象作为生成日志记录的对象,然后设置输出级别为DEBUG,然后创建了一个StreamHandler对象handler,来处理日志,随后创建了一个formatter对象来格式化输出日志记录,然后把formatter赋给handler,最后把handler处理器添加到我们的logger对象中,完成了整个处理流程。

知道整个流程之后我们来看一些细的东西。

Level

logging模块中自带了几个日志级别

等级数值对应方法
CRITICAL50logger.critical(“msg”)
FATAL50logger.fatal(“msg”)
ERROR40logger.error(“msg”)
WARNING30logger.warning(“msg”)
WARN30logger.warn(“msg”)废弃
INFO20logger.info(“msg”)
DEBUG10logger.debug(“msg”)
NOTSET0

在我们的实例中我们设置了输出级别为DEBUG

logger.setLevel(logging.DEBUG)

那么在DEBUG级别之下的也就是NOTSET级别的不会被输出。

如果我们把级别设置为INFO,那么我们实例的输出应该是

2019/01/13 13:21:17 - Your Logger - INFO - this is info msg
2019/01/13 13:21:17 - Your Logger - WARNING - this is warn msg
2019/01/13 13:21:17 - Your Logger - ERROR - this is error msg

只会输出比INFO级别高的日志。

Handler

logging提供的Handler有很多,我简单列举几种

种类位置用途
StreamHandlerlogging.StreamHandler日志输出到流,可以是 sys.stderr,sys.stdout 或者文件
FileHandlerlogging.FileHandler日志输出到文件
SMTPHandlerlogging.handlers.SMTPHandler远程输出日志到邮件地址
SysLogHandlerlogging.handlers.SysLogHandler日志输出到syslog
HTTPHandlerlogging.handlers.HTTPHandler通过”GET”或者”POST”远程输出到HTTP服务器

Formatter

fmt参数和datefmt两个参数分别对应日志记录的格式化和时间的格式化。

fmt可用的占位符简单列举几种,更多请参考这里

占位符作用
%(levelname)s打印日志级别的名称
%(pathname)s打印当前执行程序的路径,其实就是sys.argv[0]。
%(filename)s打印当前执行程序名。
%(funcName)s打印日志的当前函数。
%(lineno)d打印日志的当前行号。
%(asctime)s打印日志的时间。
%(thread)d打印线程ID。
%(threadName)s打印线程名称。
%(process)d打印进程ID。
%(processName)s打印线程名称。
%(module)s打印模块名称。
%(message)s打印日志信息。

捕获Traceback

try:
    result = 10 / 0
except Exception:
    logger.error('Faild to get result', exc_info=True)
    # 或者用下面这个
    # logging.exception('Error')
logger.info('Finished')

输出

2019/01/13 14:11:18 - Your Logger - ERROR - Faild to get result
Traceback (most recent call last):
  File "E:/Python/test.py", line 22, in <module>
    result = 10 / 0
ZeroDivisionError: division by zero
2019/01/13 14:11:18 - Your Logger - INFO - Finished

这样会更合理的捕获异常信息。

自定义日志级别

import logging

INFO, WARN, ERROR, SUCCESS = range(1, 5)
# print(SYSINFO, WARN, ERROR, SUCCESS)
logging.addLevelName(INFO, '*')
logging.addLevelName(WARN, '!')
logging.addLevelName(ERROR, 'x')
logging.addLevelName(SUCCESS, '+')

logger = logging.getLogger('LOGGER')
handler = logging.StreamHandler()
formatter = logging.Formatter(fmt='%(asctime)s [%(levelname)s] %(message)s', datefmt='%Y/%m/%d %H:%M:%S')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(INFO)

logger.log(INFO, "INFO")
logger.log(WARN, "WARN")
logger.log(ERROR, "ERROR")
logger.log(SUCCESS, "SUCCESS")

输出

2019/01/13 14:17:59 [*] INFO
2019/01/13 14:17:59 [!] WARN
2019/01/13 14:17:59 [x] ERROR
2019/01/13 14:17:59 [+] SUCCESS

先定义级别和数值,然后调用addLevelName(级别名,'输出名')。记得数值不能小于等于0,注意输出日志的级别。

给输出加上颜色

用到了一个第三方的脚本ansistrm.py,下载地址https://gist.github.com/Y4er/6300ccff3a6628ea7bda24e514013476 原作者脚本不支持win10,我修复了一下。

将这个脚本ansistrm.py和你的log.py放到同一目录,然后log.py如下内容

#!/usr/bin/env python3 
# -*- coding: utf-8 -*- 
# @Time : 2019/1/12 21:01 
# @Author : Y4er 
# @Site : http://Y4er.com
# @File : log.py 


import logging

INFO, WARN, ERROR, SUCCESS = range(1, 5)
# print(SYSINFO, WARN, ERROR, SUCCESS)
logging.addLevelName(INFO, '*')
logging.addLevelName(WARN, '!')
logging.addLevelName(ERROR, 'x')
logging.addLevelName(SUCCESS, '+')

logger = logging.getLogger('YOUR LOGGER')
try:
    from ansistrm import ColorizingStreamHandler

    handle = ColorizingStreamHandler()
    handle.level_map[logging.getLevelName('*')] = (None, 'cyan', False)
    handle.level_map[logging.getLevelName('+')] = (None, 'green', False)
    handle.level_map[logging.getLevelName('x')] = (None, 'red', False)
    handle.level_map[logging.getLevelName('!')] = (None, 'yellow', False)
except Exception as e:
    print(e)
    handle = logging.StreamHandler()

formatter = logging.Formatter('%(asctime)s - [%(levelname)s]  %(message)s', '%Y/%m/%d %H:%M:%S')
handle.setFormatter(formatter)
logger.addHandler(handle)
logger.setLevel(INFO)


class LOGGER:
    @staticmethod
    def info(msg):
        return logger.log(INFO, msg)

    @staticmethod
    def warning(msg):
        return logger.log(WARN, msg)

    @staticmethod
    def error(msg):
        return logger.log(ERROR, msg)

    @staticmethod
    def success(msg):
        return logger.log(SUCCESS, msg)


LOGGER.info("INFO msg")
LOGGER.warning("warning msg")
LOGGER.error("error msg")
LOGGER.success("success msg")

在这个脚本中,我写了一个类以及其下的四个静态方法,那么可以这么调用

LOGGER.info("INFO msg")
LOGGER.warning("warning msg")
LOGGER.error("error msg")
LOGGER.success("success msg")

运行效果:

写在文后

是时候抛弃print了!

参考链接

Python中logging模块的基本用法 - 崔庆才老师

原版ansistrm.py - vsajip

修复ansistrm.py以支持win10