通过PostgreSQL触发器对当前Python解释器进行日志堆栈跟踪



我试图找到一个在我们的生产服务器上不时发生的错误,但无法以其他方式重现:数据库中的某些值以我不希望的方式更改。

我可以编写一个 PostgreSQL 触发器,如果发生此错误就会触发,并从该触发器引发异常。我会看到执行不需要的SQL语句的Python回溯。

但在这种情况下,我不想停止处理请求。

有没有办法从PostgreSQL触发器中记录Python/Django回溯?

我知道这不是三元,因为数据库代码在不同的 linux 进程下运行,具有不同的用户 ID。

我正在使用Python,Django,PostgreSQL,Linux。

我想这并不容易,因为数据库触发器在与 python 解释器不同的上下文中运行。

请询问您是否需要更多信息。

更新

一种解决方案可能是覆盖 psycopg2 的连接通知。

Is there a way to log the Python/Django traceback from within a PostgreSQL trigger?

不,没有

  • (SQL(查询在DBMS服务器上执行,触发器中的代码也是如此。
  • Python代码在客户端上执行,这是一个不同的进程,可能由不同的用户执行,甚至可能在不同的机器上执行。

服务器(检测条件(和客户端(需要执行堆栈转储(之间的唯一连接是连接的套接字。您可以尝试通过一些状态代码来扩展服务器的回复(如果有的话(,客户端使用该状态代码来堆叠转储自身。仅当触发器是当前事务的一部分而不是某些不相关的进程时,这才有效。

另一种方式是:大规模伐木。使 DBMS 将每个提交的 SQL 写入其日志文件。这可能会导致大量日志条目,您必须检查这些条目。

给定此设置

(django/python) -[SQL connection]-> (PostgreSQL server)

你的直觉

我想这并不容易,因为数据库触发器在与 python 解释器不同的上下文中运行。

是正确的。至少,我们无法完全按照您想要的方式执行此操作;并非没有太多杂技。

但是,有一些选项,每个选项都有缺点:

  • 如果你在SQLAlchemy中使用django,你可以注册事件侦听器(ORM事件或核心事件(,以检测你正在搜寻的这个错误的SQL语句,并记录回溯。
  • 围绕 SQL 驱动程序编写一个包装器,检查要查找的错误 SQL 语句,并在每次检测到回溯时记录回测。
  • 给每个SQL事务或每个django请求一个ID(可能只是werkzeug的请求绑定存储管理器中的某个UUID(。从这里,我们获得更多选择:

    • 将记录器配置为在任意位置记录此请求 ID,并在 SQLAlchemy 中记录所有 SQL 语句。这使您可以将 Django 请求和特定函数调用与 SQL 语句相关联。您可以使用 SQLAlchemy 中的echo=执行此操作。
    • 在每个 SQL 语句中包含此请求 ID(额外列?(,然后将此 ID 记录在 PostgreSQL 触发器中,并带有RAISE NOTICE。这使您可以将django中的客户端活动与PostgreSQL中的服务器端活动相关联。
  • 本着慈善专业倡导的
  • "生产测试"精神,将每个请求发送到 Django 应用程序的沙盒副本,该副本读取/写入生产数据库的沙盒副本。在沙盒数据库中,引发异常并记录回溯。

    • 您可以进一步了解此想法并创建较小的"异步"设置。例如,对于每个请求,您可以触发同一请求的异步副本(例如,使用芹菜(,该请求命中配置了 PostgreSQL 触发器的数据库以失败并记录回溯。
  • 在 PostgreSQL 触发器中使用RAISE EXCEPTION来回滚当前事务。在 Python 中,捕获该特定异常,记录它,然后重复事务,稍微更改数据(额外的列?(以指示这是重试,触发器不应失败。

有没有理由你不能选择所有行值到Python中,然后完全在Python中进行检测?

因此,如果您能够在查询执行后检测到条件,则可以记录条件和/或引发异常。

然后你需要的是像哨兵或新遗物这样的工具。

你可以使用 LISTEN+NOTIFY。

首先让一些守护进程线程 LISTEN,在 db 触发器中,您可以执行 NOTIFY。

守护程序线程接收通知事件,并可以转储主线程的堆栈跟踪。

如果你使用psycopg2,你可以使用这个

# Overwriting connetion.notices via Django
class MyAppConfig(AppConfig):
def ready(self):
connection_created.connect(connection_created_check_for_notice_in_connection)
class ConnectionNoticeList(object):
def append(self, message):
if not 'some_magic_of_db_trigger' in message:
return
logger.warn('%s %s' % (message, ''.join(traceback.format_stack())))
def connection_created_check_for_notice_in_connection(sender, connection, **kwargs):
connection.connection.notices=ConnectionNoticeList()

最新更新