Краткий ответ
Это из-за специального свойства, которое называется propagate
. Это флаг, который определяет, должен ли регистратор передавать запись журнала своему родительскому регистратору (propagate=True
) или нет (propagate=False
). Вам нужно либо удалить общую конфигурацию из промежуточных регистраторов, оставив только уникальные данные, либо передать "propagate": false
всем регистраторам.
Длинный ответ
иерархия регистраторов и распространение записей журнала
Все регистраторы в Python организованы в иерархию: всегда есть корневой регистратор, возвращаемый при вызове getLogger
без имени:
root_logger = logging.getLogger()
Все регистраторы, которые вы создаете с именем, являются дочерними по отношению к корневому регистратору, поэтому
my_logger = logging.getLogger('my-special-logger')
имеет root_logger
в качестве родителя. Теперь, когда вы звоните my_logger.info('Hello world')
, происходит следующее: my_logger
будет
- обрабатывать саму запись
- проверьте, установлено ли для
propagate
значение True
, если да, то он передаст запись своему родительскому регистратору (в данном случае корневому регистратору), который также будет обрабатывать запись.
Это также отображается на блок-схеме , вы можете проверить это, если хотите.
Теперь вы, вероятно, уже можете догадаться, что произойдет, если я настрою оба регистратора следующим образом:
"loggers": {
"my-special-logger": {
"handlers": ["debug_file_handler"]
},
},
"root": {
"handlers": ["debug_file_handler"]
}
В каждой входящей записи журнала my-special-logger
записывает запись в debug.log
, затем передает запись дальше до root
, что также записывает запись в debug.log
. Таким образом, запись появится дважды в конце.
иерархия пространства имен модуля
Однако возникает вопрос: почему в журнале отладки есть три копии некоторых записей? Ответ можно найти в Advanced Logging Tutorial :
Каждый экземпляр [logger] имеет имя, и они концептуально расположены в иерархии пространства имен, используя точки (точки) в качестве разделителей. Например, регистратор с именем scan
является родителем регистраторов scan.text
, scan.html
и scan.pdf
.
Теперь вы можете оценить иерархию логгера в вашем коде:
root <b>-> writes to debug.log</b>
└── spam_application
└── spam_application.auxiliary <b>-> writes to debug.log</b>
└── spam_application.auxiliary.Auxiliary <b>-> writes to debug.log</b>
решение
Преимущество распространения записей в иерархии журналов заключается в том, что вам не нужно повторять конфигурацию для каждого регистратора. Итак, когда ваше приложение должно войти в систему debug.log
, добавьте debug_file_handler
к корневому регистратору один раз, и он уже будет обслуживать все другие регистраторы, независимо от того, добавляете ли вы их в конфигурацию или нет.
Таким образом, ваша первоначальная конфигурация может быть уменьшена до:
{
"version": 1,
"disable_existing_loggers": false,
"formatters": { ... },
"handlers": {
"debug_file_handler": { ... }
},
"root": {
"level": "DEBUG",
"handlers": ["debug_file_handler"]
}
}
Альтернативой этому будет передача "propagate": false
каждому явно настроенному регистратору, чтобы сообщения не обрабатывались несколько раз debug_file_handler
:
{
"version": 1,
...
"loggers": {
"spam_application.auxiliary.Auxiliary": {
"level": "DEBUG",
"handlers": ["debug_file_handler"],
"propagate": false
},
"spam_application.auxiliary": {
"level": "DEBUG",
"handlers": ["debug_file_handler"],
"propagate": false
}
},
...