191104-Python 封装一个通用日志插件

文章目录
  1. I. logging 模块
    1. 1. 基本使用姿势
    2. 2. formatter
    3. 3. Handler
      1. a. StreamHandler
      2. b. FileHandler
      3. c. NullHandler
      4. d. WatchedFileHandler
      5. e. RotatingFileHandler
      6. f. TimedRotatingFileHandler
  2. I. 日志模块封装
  3. III. 其他
    1. 1. 一灰灰Blog: https://liuyueyi.github.io/hexblog
    2. 2. 声明
    3. 3. 扫描关注

日志对于一个应用程序而言,不可获取;特别是在问题定位,链路分析往往是最直观,最有效的富足工具;所以在python中可以怎样便捷的使用日志模块呢

I. logging 模块

我们这里使用logging来记录我们的日志,一般来说,我们常用的日志无非以下几个点

  • 日志落盘,存文件,支持定期生成一个备份文件;支持自动删除过期日志
  • 异常时,可以输出完整的堆栈,帮助定位出问题的代码行
  • 不同的日志level

1. 基本使用姿势

首先我们来看一下日志模块的基本使用姿势,默认的场景下,日志输出在控制台;可以通过指定文件名的方式来讲日志输出到对应的文件中

1
2
3
4
5
6
7
8
9
10
11
import logging

# 通过下面的方式进行简单配置输出方式与日志级别
logging.basicConfig(filename='logger.log', level=logging.INFO)

# 下面是简单的使用姿势
logging.debug('debug message')
logging.info('info message')
logging.warning('warn message')
logging.error('error message')
logging.critical('critical message')

因为指定了日志的级别为INFO,所以debug日志将不会显示在日志文件中,具体输出如下

1
2
3
4
INFO:root:info message
WARNING:root:warn message
ERROR:root:warn message
CRITICAL:root:critical message

2. formatter

上面的使用虽说实现了日志记录的功能,但是相对原始;通常我们希望日志的输出包括一些基本信息,如输出日志的时间

这个时候就可以考虑通过format来设置输出日志的格式

格式 描述
%(levelno)s 打印日志级别的数值
%(levelname)s 打印日志级别名称
%(pathname)s 打印当前执行程序的路径
%(filename)s 打印当前执行程序名称
%(funcName)s 打印日志的当前函数
%(lineno)d 打印日志的当前行号
%(asctime)s 打印日志的时间
%(thread)d 打印线程id
%(threadName)s 打印线程名称
%(process)d 打印进程ID
%(message)s 打印日志信息

下面我们在日志的输出中,添加上时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import logging

# 创建一个名为test的日志实例
logger = logging.getLogger('test')

# 创建一个handler,指定输出日志文件为logger.log, 并设定输出样式
fh = logging.FileHandler(filename='logger.log')
fh.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))

# 配置
logger.addHandler(fh)
# 设置日志输出级别为INFO
logger.setLevel(logging.INFO)
logger.info('new info message')

实际输出结果如

1
2019-11-04 18:25:57,519 - INFO - new info message

3. Handler

一下几个说明,主要来自博文: Python模块之Logging(四)——常用handlers的使用

a. StreamHandler

流handler——包含在logging模块中的三个handler之一。

能够将日志信息输出到sys.stdout, sys.stderr 或者类文件对象(更确切点,就是能够支持write()和flush()方法的对象)

1
logging.StreamHandler(stream=None)

b. FileHandler

继承自StreamHandler。将日志信息输出到磁盘文件上

1
logging.FileHandler(filename, mode='a', encoding=None, delay=False)

c. NullHandler

什么都不干,相当于吃掉日志

d. WatchedFileHandler

位于logging.handlers模块中。用于监视文件的状态,如果文件被改变了,那么就关闭当前流,重新打开文件,创建一个新的流。由于newsyslog或者logrotate的使用会导致文件改变。这个handler是专门为linux/unix系统设计的,因为在windows系统下,正在被打开的文件是不会被改变的

e. RotatingFileHandler

位于logging.handlers支持循环日志文件。

1
logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=0)

参数maxBytes和backupCount允许日志文件在达到maxBytes时rollover.当文件大小达到或者超过maxBytes时,就会新创建一个日志文件。上述的这两个参数任一一个为0时,rollover都不会发生。也就是就文件没有maxBytes限制。backupcount是备份数目,也就是最多能有多少个备份。命名会在日志的base_name后面加上.0-.n的后缀,如example.log.1,example.log.1,…,example.log.10。当前使用的日志文件为base_name.log。

f. TimedRotatingFileHandler

定时循环日志handler,位于logging.handlers,支持定时生成新日志文件。

1
logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False)

参数when决定了时间间隔的类型,参数interval决定了多少的时间间隔。如when=‘D’,interval=2,就是指两天的时间间隔,backupCount决定了能留几个日志文件。超过数量就会丢弃掉老的日志文件

symbol 说明

‘S’ | 秒
‘M’ | 分
‘H’ | 时
‘D’ | 天
‘W0’-‘W6’ | 周一至周日
‘midnight’ | 每天的凌晨

I. 日志模块封装

接下来我们的目标是封装一个简单好用的日志工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# -*- coding: utf-8 -*-
import logging
import os
from logging.handlers import TimedRotatingFileHandler

class LoggerWrapper:
def __init__(self):
self._logger = {}

@staticmethod
def _get_path(action, path=""):
"""
根据日志名,创建对应的日志路径
:param path:
:return:
"""
if action != 'logs':
action = "logs/" + action + "/"

path = action + path
if not os.path.exists(path):
# 当目录不存在时,主动创建
os.makedirs(path)

return path

def _gen_logger(self, action='logs', log_name='Crawler'):
base_logger = logging.getLogger(log_name)
base_logger.setLevel(logging.INFO)

log_file = self._get_path(action, log_name) + "/" + log_name + ".log"
ch = TimedRotatingFileHandler(log_file, when='D', encoding="utf-8", backupCount=10)
ch.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
base_logger.addHandler(ch)
base_logger.propagate = 0
return base_logger

def get_logger(self, name=None):
if name is None:
key = env_wrapper.get_current_task_name()
else:
key = name

if key not in self._logger:
action, path = key, env_wrapper.get_current_task_name()
self._logger[key] = self._gen_logger(action, path)

return self._logger[key]

上面是一个简单的按天回滚日志,且只保存最近10天;但是在我们的测试环境下,我们可能希望日志也同时输出一份到控制台

因此我们可以稍微改造一下,拿一个EnvWrapper类,来保存项目根路径,是否输出一份到控制台

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class EnvWrapper:
def __init__(self):
self._module_path = None
self._log_console = False

def init_env(xxx):
# 下面是环境初始化
pass

def get_module_path(self):
return self._module_path

def console_log_enable(self):
return self._log_console

env_wrapper = EnvWrapper()
`

然后我们的日志工具类可以改造成下面的case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# -*- coding: utf-8 -*-
# create by yihui 11:32 18/12/19
import logging
import os
from logging.handlers import TimedRotatingFileHandler

from src.env.EnvWrapper import env_wrapper

class LoggerWrapper:
def __init__(self):
self._logger = {}
self._console_init = False

@staticmethod
def _get_path(action, path=""):
"""
根据日志名,创建对应的日志路径
:param path:
:return:
"""
if action != 'logs':
action = "logs/" + action + "/"

path = env_wrapper.get_module_path() + "/" + action + path
if not os.path.exists(path):
# 当目录不存在时,主动创建
os.makedirs(path)

return path

def _gen_logger(self, action='logs', log_name='Crawler'):
base_logger = logging.getLogger(log_name)
base_logger.setLevel(logging.INFO)

log_file = self._get_path(action, log_name) + "/" + log_name + ".log"
ch = TimedRotatingFileHandler(log_file, when='D', encoding="utf-8")
ch.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
base_logger.addHandler(ch)
base_logger.propagate = 0

if env_wrapper.console_log_enable() and not self._console_init:
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
console.setFormatter(formatter)
base_logger.addHandler(console)
self._console_init = True

return base_logger

def get_logger(self, name=None):
if name is None:
key = env_wrapper.get_current_task_name()
else:
key = name

if key not in self._logger:
action, path = key, env_wrapper.get_current_task_name()
self._logger[key] = self._gen_logger(action, path)

return self._logger[key]

def error(self, msg, name=None):
log = self.get_logger(name)
log.error(msg)

def warn(self, msg, name=None):
log = self.get_logger(name)
log.warning(msg)

def info(self, msg, name=None):
log = self.get_logger(name)
log.info(msg)

def debug(self, msg, name=None):
log = self.get_logger(name)
log.debug(msg)

def exception(self, msg, name=None):
"""
打印堆栈信息
:param msg:
:param name:
:return:
"""
log = self.get_logger(name)
log.exception(msg)


SpiderLogger = LoggerWrapper()
logger = SpiderLogger.get_logger
debug = SpiderLogger.debug
info = SpiderLogger.info
error = SpiderLogger.error
warning = SpiderLogger.warn
exception = SpiderLogger.exception

III. 其他

1. 一灰灰Bloghttps://liuyueyi.github.io/hexblog

一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛

2. 声明

尽信书则不如,已上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

3. 扫描关注

一灰灰blog

QrCode

知识星球

goals

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×