使用数据库记录SqlAlchemy日志

3
我正在编写一个小应用程序,使用sqlalchemy将所有内容记录到数据库中。 受到以下内容的启发: python logging to database 以及 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个回答

2
我怀疑问题的原因是 TblLog(...) 调用会产生一条日志记录,因此处理程序会向自身发出记录?!
这不是直接的问题。失败的原因是 SQLAlchemy 在配置映射器时会发出日志消息,其中第一个消息在 TblLog 的映射器完全配置之前发送,因此导致错误。
如果您在 DatabaseHandler 之前向 logger_sqlalchemy 实例添加 StreamHandler,您将能够看到 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 实例来强制映射器在添加处理程序之前进行配置,例如:


```python dummy_instance = 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,请直接使用dpapi客户端将日志写入db,并明确不要将客户端的日志消息写入数据库(否则将遇到相同的递归问题)。
  • 使用HTTPHandler将日志消息发送到Web服务,该服务将日志消息写入数据库。
  • 让SQLAlchemy记录到文件,而其他所有内容都记录到数据库。您甚至可以设置一个cron作业,定期将来自文件的SQLAlchemy日志写入数据库中的单独进程。
  • 作为服务的记录

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接