Регистрация SqlAlchemy в базе данных - PullRequest
0 голосов
/ 07 февраля 2019

Я пишу небольшое приложение, которое должно регистрировать все в базе данных, используя 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 Ответ

0 голосов
/ 08 февраля 2019

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

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

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

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

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

Если вы затем удалите DatabaseHandler из регистратора и просто оставьте StreamHandler, вы увидите, что этот метод делает (Iтакже удалил ваш 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 из файла в базу данных в отдельном процессе.
  • Регистрация в качестве службы
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...