问题

在最近的工作当中,需要使用到python的一个常见库叫做logging,这个库可以用于项目的日志记录,但是在我使用的过程当中却需要多个log对象分别输出到不同的日志文件当中的时候,我使用以下代码失败了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import logging


logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename='test.log',
filemode='w')

log = logging
log.debug('debug message')

logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename='test2.log',
filemode='w')

log = logging
log.debug('debug message')

按照我浅薄的理解,这应该是两个对象才对,但是为啥最后只有一个日志文件生成,第二个对象并没有生成日志文件呢?

原因

根据官方的解释:

根据basicConfig源码可以发现只有当“handlers”的长度为0时才会进行日志信息配置,否则跳过该配置步骤。那么在步骤1中root已经自动进行了日志信息配置,“handlers”的长度不为0,则步骤2中的“logging.basicConfig”并没有达到日志信息配置的作用。

那么有没有什么办法可以解决呢?


解决没办法配置的问题的答案当然是有的,那就是在python3.8及之后在basicConfig中新增了force参数,当“force=True”时可以强制进行日志信息重新设置。也就是如下代码:

1
2
3
4
5
6
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename='test.log',
filemode='w',
force=True) # 这个参数是重点

但是这还是有问题,因为这个仅仅支持重新配置,并不支持将对象保存起来因此如果代码写成了下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import logging


logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename='test.log',
filemode='w',
force=True)

log1 = logging


logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
filename='test2.log',
force=True,
filemode='w')

log2 = logging
log1.debug('debug message')
log2.debug('debug message')

运行之后就会发现,虽然是两个对象,也生成了两个日志文件,但是却只能往一个文件里面输入日志信息,也就是最后指定的那个文件。

后续疑问

假设就是需要同时存在两个不同的log对象,用于写入不同的日志文件该怎么办?如果是上述方法,也不可能需要写的时候立马修改吧。在学习中我发现另一种方法能够解决这个问题:

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
NG_DIC = {
'version': 1,
'disable_existing_loggers': False,
# 日志输出格式
'formatters': {
'standard': {
'format': "[%(levelname)s][%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]"
"[%(message)s]"
},
'simple': {
'format': "[%(levelname)s] [%(asctime)s] [%(name)s] [%(filename)s:%(lineno)d] [%(message)s]"
},
},
'filters': {},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
# 日志文件名
'filename': "log.log",
# 日志文件的最大值,这里我们设置15M
'maxBytes': 15 * 1024 * 1024,
# 日志文件的数量,设置最大日志数量为10
'backupCount': 10,
# 日志格式
'formatter': 'simple'
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
},
'loggers': {
# 当匹配不到logger就会到这条规则
'tasks': {
'handlers': ["file"],
'level': 'INFO',
'propagate': False, # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递
},
'': {
'handlers': ["file"],
'level': 'INFO',
'propagate': False, # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递
}
}
}

logging.config.dictConfig(LOGGING_DIC)
logger1 = logging.getLogger('tasks')
logging.info("123123123")

使用字典配置的方法可以解决需要创建多个log对象的问题。