Регистрация SqlAlchemy в базе данных

Я пишу небольшое приложение, которое должно записывать все в базу данных с помощью sqlalchemy. Вдохновленный этим:

регистрация Python в базе данных

и это:

https://docs.pylonsproject.org/projects/pyramid-cookbook/en/latest/logging/sqlalchemy_logger.html Я нашел решение, которое отлично работает для всех сообщений журнала всех задействованных библиотек, кроме тех, которые генерируются sqlalchemy (!) сам.

Вот минимальный пример, воспроизводящий мою проблему:

import logging
import datetime
from sqlalchemy import Column, DateTime, String, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()


 # define table
class TblLog(Base):
    __tablename__ = 'Tbl_Log'

    LOG_TIME = Column(DateTime, primary_key=True)
    LOG_NAME = Column(String(100))
    LOG_LEVEL = Column(String(100))
    LOG_MSG = Column(String(2000))

    def __init__(self, time, name, lvl, msg):
        self.LOG_TIME = time
        self.LOG_NAME = name
        self.LOG_LEVEL = lvl
        self.LOG_MSG = msg

# custom log handler that emits to the database
class DatabaseHandler(logging.Handler):

    def __init__(self, session):
        super().__init__()
        self.session = session
        self.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
        self.setLevel(logging.DEBUG)

    def emit(self, record):

        self.format(record)

        log_time = datetime.datetime.strptime(record.__dict__['asctime'], "%Y-%m-%d %H:%M:%S,%f")

        log_record = TblLog(log_time, record.__dict__['name'], record.__dict__['levelname'], record.__dict__['message'])

        self.session.add(log_record)
        self.session.commit()

проверка этого с включенными журналами sqlalchemy (!):

if __name__ == '__main__':
    # simple logging config
    logging.basicConfig(
        format='%(asctime)s : %(name)s : %(levelname)s : %(message)s',
        level=logging.DEBUG,
    )
    logger_sqlalchemy = logging.getLogger('sqlalchemy')
    logger_sqlalchemy.setLevel(logging.INFO)


    # test with sqlite in memory database
    DB_STRING = 'sqlite:///:memory:'
    engine = create_engine(DB_STRING, echo=False)
    Base.metadata.create_all(engine)
    Session = sessionmaker()
    session = Session(bind=engine)

    # adding custom handler:
    logger_sqlalchemy.addHandler(DatabaseHandler(session))

    logger_sqlalchemy.info('this is a test message')

Это поднимает

AttributeError: у объекта 'NoneType' нет атрибута 'set'

Я могу вставить весь след в случае необходимости. Я подозреваю, что проблема возникает из-за того, что вызов TblLog(...) создает запись журнала и, следовательно, обработчик отправляет записи самому себе?!

Что было бы лучшим решением для этой проблемы, то есть я могу записать сообщения журнала sqlalchemy в базу данных, используя sqlalchemy в обработчике??

Я застрял здесь, спасибо за любую помощь...

1 ответ

Решение

Я подозреваю, что проблема возникает из-за того, что вызов TblLog(...) создает запись журнала и, следовательно, обработчик отправляет записи самому себе?!

Это не насущная проблема. Причиной сбоя является то, что SQLAlchemy отправляет сообщения журнала во время настройки преобразователя, первое из которых отправляется раньше TblLog Маппер полностью настроен и, следовательно, ваша ошибка.

Если вы добавите StreamHandler на ваш logger_sqlalchemy экземпляр перед DatabaseHandler вы сможете увидеть сообщения журнала logger_sqlalchemy получает прямо до крушения. Сообщение журнала, которое отключает это (TblLog|Tbl_Log) _post_configure_properties() started который исходит от _post_configure_properties() метод. Строка документации для этого метода включает в себя:

Это отложенный этап настройки, который должен быть выполнен после создания всех картографов.

Так что это подсказка, что конфиг маппера для TblLog не закончено

Если вы затем удалите DatabaseHandler от регистратора и просто оставить StreamHandler вы увидите, что этот метод делает дальше (я также удалил ваш basicConfig() для ясности):

(TblLog|Tbl_Log) _post_configure_properties() started
# this is where your code crashed originally
(TblLog|Tbl_Log) initialize prop LOG_TIME
(TblLog|Tbl_Log) initialize prop LOG_NAME
(TblLog|Tbl_Log) initialize prop LOG_LEVEL
(TblLog|Tbl_Log) initialize prop LOG_MSG
(TblLog|Tbl_Log) _post_configure_properties() complete

Итак, как вы можете видеть, некоторая инициализация дескрипторов столбцов, кажется, происходит после того, как это первое сообщение журнала отправлено. Вот почему вы получаете ошибку, ORM не готов к тому времени, когда вы пытаетесь ее использовать.

Вы могли бы создать пустышку TblLog экземпляр, чтобы заставить маппер сконфигурировать перед добавлением обработчика, например:

# ensure TblLog mapper configured
TblLog(time=None, name=None, lvl=None, msg=None)
logger_sqlalchemy.addHandler(DatabaseHandler(session))
logger_sqlalchemy.info('this is a test message')

Но тогда вы столкнетесь с новой проблемой: SQLAlchemy генерирует логи в процессе сброса / фиксации. Итак, когда первое сообщение журнала сбрасывается в базу данных, оно генерирует новое сообщение журнала, которое само генерирует новое сообщение журнала и т. Д. И т. Д.... бесконечная рекурсия.

Итак, мой ответ:

Что было бы лучшим решением для этой проблемы, то есть я могу записать сообщения журнала sqlalchemy в базу данных, используя sqlalchemy в обработчике??

Ответ будет отрицательным, если вы пытаетесь также записать в журнал sqlalchemy.

Некоторые возможные решения:

  • Не используйте SQLAlchemy для записи сообщений журнала в базу данных. Предполагая, что вы используете SQLAlchemy в других частях вашего приложения, используйте клиент dpapi непосредственно для записи журналов в базу данных и явно не записывайте сообщения журнала клиента в базу данных (в противном случае вы столкнетесь с той же проблемой рекурсии),
  • отправлять сообщения журнала в веб-службу, используя HTTPHandler который записывает сообщения журнала в базу данных.
  • Иметь sqlalchemy журнал в файл, а все остальное входит в базу данных. Можно даже настроить задание cron для периодической записи журналов sqlalchemy из файла в базу данных в отдельном процессе.
  • Вход как услуга
Другие вопросы по тегам