Как зарегистрировать два logging.Logger?

Есть вот такая функция регистрации logger

import logging

def register_logger(
    base_path: PathLike[str],
    file_name: str,
    register_name: str | None,
    log_level: Any,
) -> logging.Logger:
    path = Path(base_path, f"{file_name}.log")
    logger = logging.getLogger(register_name)
    logger.setLevel(level=log_level)
    handler = logging.FileHandler(path, encoding="utf8")
    formatter = logging.Formatter("\n%(asctime)s - %(levelname)s\n%(message)s")
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    return logger

Нужно зарегистрировать два обработчика: один безымянный, второй с именем. Условно это выглядит так:

logger = register_logger(path, "server", None, 1)
logger_web = register_logger(path, "web", "web", 1)

# И допустим в другом файле
logger_web = logging.getLogger("web")

Но, при попытке использования logger_web.error(...), последний дублирует записи в оба файла. При этом безымянный logger пишет только в свой файл .../path/server.log.

Что здесь может быть не так, и как избежать дублирования записей?


Ответы (1 шт):

Автор решения: Isaac Azimov

Потому что когда создается логгер без имени, то он называется root логгер:

logger = register_logger(path, "server", None, 1)

Он есть по умолчанию и это корневой логгер, к которому все другие логгеры привязаны.

Поэтому, если хочешь абсолютно независимые логгеры, то дай им обоим названия:

logger = register_logger(path, "server", "server", 1)
logger_web = register_logger(path, "web", "web", 1)

UPD:

Если это решение не подходит, то можно использовать фильтр для хэндлера.

Для этого нужно будет сначала удалить все хэндлеры по умолчанию от root логгера. Затем добавить свой хэндлер, в котором есть фильтр.

А фильтре мы будем принимать только логи, которые пришли от root логгера, а все остальные будут игнорироваться.

class OnlyRootFilter(logging.Filter):
    def filter(self, record):
        return record.name == "root"

logger = logging.getLogger()

for handler in logger.handlers:
    logger.removeHandler(handler)

handler = logging.StreamHandler()
handler.addFilter(OnlyRootFilter())
logger.addHandler(handler)

Надеюсь идея понятна, это можно внедрить в твою функцию, например с проверкой if register_name == None: или подобной.

UPDUPD Вот такой простой и короткий вариант:

# Добавим в конце функции register_logger
if register_name is None or register_name == "root":
    handler.addFilter(logging.Filter(name="root"))
→ Ссылка