Регистрация 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 из файла в базу данных в отдельном процессе.
- Вход как услуга