使用SQLAlchemy的Pyramid异常日志记录 - 命令未提交

3 投票
1 回答
1091 浏览
提问于 2025-04-17 15:34

我正在使用Pyramid这个网页框架,配合SQLAlchemy,连接到MySQL数据库。虽然我做的应用程序能正常工作,但我想通过增强日志记录和异常处理来让它更完美一些。

我一切都是根据Pyramid网站上的基本SQLAlchemy教程来做的,使用会话(session)的方法如下:

DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))

用DBSession来查询效果很好,如果我需要往数据库里添加和提交一些东西,我会这样做:

DBSession.add(myobject)
DBSession.flush()

这样我就能得到新的ID。

接着,我想在数据库中添加日志,所以我参考了这个教程。这个方法看起来很不错。不过,最开始我遇到了一些奇怪的问题,提交时出现了不确定的情况,我不太明白SQLAlchemy是怎么工作的,所以我把“transaction.commit()”改成了“DBSession.flush()”,这样可以强制日志提交(下面会详细解释这个问题!)。

然后我想添加自定义的异常处理,这样我就可以为那些没有被明确捕获的错误提供一个友好的错误页面,同时还能记录日志。根据这份文档,我创建了这样的错误处理器:

from pyramid.view import (
    view_config,
    forbidden_view_config,
    notfound_view_config
    )

from pyramid.httpexceptions import (
    HTTPFound,
    HTTPNotFound,
    HTTPForbidden,
    HTTPBadRequest,
    HTTPInternalServerError
    )

from models import DBSession

import transaction
import logging

log = logging.getLogger(__name__)

#region Custom HTTP Errors and Exceptions
@view_config(context=HTTPNotFound, renderer='HTTPNotFound.mako')
def notfound(request):
    log.exception('404 not found: {0}'.format(str(request.url)))
    request.response.status_int = 404
    return {}

@view_config(context=HTTPInternalServerError, renderer='HTTPInternalServerError.mako')
def internalerror(request):
    log.exception('HTTPInternalServerError: {0}'.format(str(request.url)))
    request.response.status_int = 500
    return {}

@view_config(context=Exception, renderer="HTTPExceptionCaught.mako")
def error_view(exc, request):
    log.exception('HTTPException: {0}'.format(str(request.url)))
    log.exception(exc.message)

    return {}
#endregion

现在我的问题是,异常被捕获了,我的自定义异常视图也如预期出现了。但是,异常并没有被记录到数据库中。看起来这是因为在任何异常发生时,DBSession的事务会被回滚。所以我把日志处理器改回了“transaction.commit”。这样做的结果是,异常日志确实被提交到了数据库,但现在在任何日志语句之后的DBSession操作都会抛出“实例未绑定到会话”的错误……这也能理解,因为我了解到在transaction.commit()之后,会话会被清空。控制台日志总是显示我想要记录的内容,包括将日志信息写入数据库的SQL语句。但在异常发生时,除非我使用transaction.commit(),否则就不会提交日志;而如果我这么做,就会导致transaction.commit()之后的任何DBSession语句都出问题!

那么……我该如何设置才能既能记录到数据库,又能成功捕获并记录异常呢?我觉得我想让日志处理器使用某种独立的数据库会话/连接/实例/其他东西,这样它就能独立工作,但我不太清楚这该怎么实现。

或者我应该完全重新设计我想做的事情吗?

编辑: 我最终选择了一个独立的、专门用于日志的会话,只用来向数据库添加和提交日志信息。这个方法似乎很好,直到我开始将Pyramid控制台脚本整合进来,这时我遇到了会话和数据库提交的问题,发现它们在脚本中并不一定像在实际的Pyramid网页应用中那样工作。

回想起来(现在我正在做的事情),与其记录到数据库,我现在使用标准的日志记录和文件处理器(特别是TimedRotatingFileHandlers),直接记录到文件系统中。

1 个回答

2

使用 transaction.commit() 这个方法会有一个意想不到的副作用,就是其他模型的变化也会被一起提交,这样就不太好。正常情况下,Pyramid 和 ZopeTransactionExtension 的会话设置是这样的:在请求开始时建立一个会话,如果一切顺利,就提交这个会话;如果出现异常,就会把所有的操作都撤回。保持这个逻辑会更好,避免在请求处理中间手动提交数据。

顺便提一下,DBSession.flush() 并不会提交事务,它只是发送 SQL 语句,事务可以在之后被撤回。

对于像异常日志这样的事情,我建议设置一个独立的会话,这个会话不与 Pyramid 的请求/响应周期绑定(也就是不使用 ZopeTransactionExtension),然后用它来创建日志记录。在添加日志记录后,你需要手动提交这个事务:

record = Log("blah")
log_session.add(record)
log_session.commit()

撰写回答