更优雅的日志方案

一、CMRESHandler

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
import logging
import sys
from os import makedirs
from os.path import dirname, exists

from cmreslogging.handlers import CMRESHandler

loggers = {}

LOG_ENABLED = True # 是否开启日志
LOG_TO_CONSOLE = True # 是否输出到控制台
LOG_TO_FILE = True # 是否输出到文件
LOG_TO_ES = False # 是否输出到 Elasticsearch

LOG_PATH = './runtime.log' # 日志文件路径
LOG_LEVEL = 'DEBUG' # 日志级别
LOG_FORMAT = '%(levelname)s - %(asctime)s - process: %(process)d - %(filename)s - %(name)s - %(lineno)d - %(module)s - %(message)s' # 每条日志输出格式
ELASTIC_SEARCH_HOST = 'eshost' # Elasticsearch Host
ELASTIC_SEARCH_PORT = 9200 # Elasticsearch Port
ELASTIC_SEARCH_INDEX = 'runtime' # Elasticsearch Index Name
APP_ENVIRONMENT = 'dev' # 运行环境,如测试环境还是生产环境

def get_logger(name=None):
global loggers

if not name: name = __name__

if loggers.get(name):
return loggers.get(name)

logger = logging.getLogger(name)
logger.setLevel(LOG_LEVEL)

# 输出到控制台
if LOG_ENABLED and LOG_TO_CONSOLE:
stream_handler = logging.StreamHandler(sys.stdout)
stream_handler.setLevel(level=LOG_LEVEL)
formatter = logging.Formatter(LOG_FORMAT)
stream_handler.setFormatter(formatter)
logger.addHandler(stream_handler)

# 输出到文件
if LOG_ENABLED and LOG_TO_FILE:
# 如果路径不存在,创建日志文件文件夹
log_dir = dirname(LOG_PATH)
if not exists(log_dir): makedirs(log_dir)
# 添加 FileHandler
file_handler = logging.FileHandler(LOG_PATH, encoding='utf-8')
file_handler.setLevel(level=LOG_LEVEL)
formatter = logging.Formatter(LOG_FORMAT)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

# 输出到 Elasticsearch
if LOG_ENABLED and LOG_TO_ES:
# 添加 CMRESHandler
es_handler = CMRESHandler(hosts=[{'host': ELASTIC_SEARCH_HOST, 'port': ELASTIC_SEARCH_PORT}],
# 可以配置对应的认证权限
auth_type=CMRESHandler.AuthType.NO_AUTH,
es_index_name=ELASTIC_SEARCH_INDEX,
# 一个月分一个 Index
index_name_frequency=CMRESHandler.IndexNameFrequency.MONTHLY,
# 额外增加环境标识
es_additional_fields={'environment': APP_ENVIRONMENT}
)
es_handler.setLevel(level=LOG_LEVEL)
formatter = logging.Formatter(LOG_FORMAT)
es_handler.setFormatter(formatter)
logger.addHandler(es_handler)

# 保存到全局 loggers
loggers[name] = logger
return logger

logger = get_logger('123')
logger.debug('this is a message')

二、loguru

1、基础用法

1
2
3
4
from loguru import logger

trace = logger.add('runtime.log')
logger.debug('this is a debug message')

2、滚动记录日志文件

我们可以配置rotation参数来指定日志文件的生成方式,跟通常的日志记录一样,我们可以设置按照文件大小、时间、日期等来指定生成策略。

1
2
3
4
5
6
7
8
9
from loguru import logger

# 超过200M就新生成一个新文件
trace = logger.add('runtime_{time}.log', rotation='200 MB')
# 定时创建log文件
# trace = logger.add('runtime_{time}.log', rotation='00:00')
# 每隔一周创建一个log文件
# trace = logger.add('runtime_{time}.log', rotation='1 week')
logger.debug('this is a debug message')

3、指定日志文件的有效期

我们可以通过retention参数来指定日志文件的保留时长。

1
2
3
4
5
from loguru import logger

# 每天中午12点创建一个日志文件,日志文件保留7天
trace = logger.add('runtime_{time}.log', rotation='00:00', retention='7 days')
logger.debug('this is a debug message')

4、配置压缩文件

为了节省空间,我们可能存在压缩日志文件的需求,这个loguru也可以实现。

1
2
3
4
from loguru import logger

trace = logger.add('runtime.log', compression='zip')
logger.debug('this is a debug message')

5、异常捕获

loguru不仅可以记录日志,还可以捕获异常信息,这个可以帮助我们更好地追溯错误原因。
有两种异常捕获方式:通过catch装饰器捕获和通过exception方法捕获。
(1)catch装饰器捕获

1
2
3
4
5
6
7
8
9
from loguru import logger

logger.add('runtime.log')

@logger.catch
def a_function(x):
return 1 / x

a_function(0)

(2)exception方法捕获

1
2
3
4
5
6
7
8
9
10
11
from loguru import logger

logger.add('runtime.log')

def b_function1(x):
try:
return 1 / x
except ZeroDivisionError:
logger.exception("exception!!!")

b_function1(0)