【问题标题】:Log Stacktrace of current Python Interpreter via PostgreSQL trigger通过 PostgreSQL 触发器记录当前 Python 解释器的 Stacktrace
【发布时间】:2018-08-16 09:24:25
【问题描述】:

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

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

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

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

我知道这不是微不足道的,因为 DB 代码在具有不同用户 ID 的不同 linux 进程下运行。

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

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

如果您需要更多信息,请询问。

更新

一种解决方案可能是覆盖 psycopg2 的 connection.notices

【问题讨论】:

  • 你在except块中尝试过logger.exception()吗?
  • 您可以 RAISE NOTICE ... 代替引发异常,这不会中止当前事务。不知道如何将 python 堆栈跟踪包含到消息正文中。数据库事件是另一种可能性,但这需要更多的工具。
  • @joop 困难的部分是如何从 postgres 中获取 python 堆栈跟踪。
  • 不清楚:I would see the Python traceback which executes the unwanted SQL statement.你在PL/Python中有触发函数?
  • @joop 到目前为止,我没有执行 pl/pythonu,但如果需要,我可以这样做。

标签: python django postgresql


【解决方案1】:
Is there a way to log the Python/Django traceback from within a PostgreSQL trigger?

不,没有

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

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

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

【讨论】:

  • 将每个 sql 语句写入日志文件在这种情况下毫无意义。我会看到:“啊,我现在正在寻找错误的 SQL 语句”。但我不知道相关的 python 代码在哪里。在这种情况下,问题就更加复杂了,因为仅凭语句不足以确定 sql 语句是否是我搜索的语句。我需要在 sql 语句中不可见的其他行值。但我可以用 postgresql 中的触发方法来捕捉它。这行得通。
【解决方案2】:

鉴于此设置

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

你的直觉

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

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

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

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

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

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

您是否有理由不能将所有行值都选择到 Python 中,然后完全在 Python 中进行检测?

【讨论】:

    【解决方案3】:

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

    那么您需要像SentryNew Relic 这样的工具。

    【讨论】:

    • 好吧,我把这个问题读作“我有一个特殊情况,有时会发生,但我不想停止执行,但仍然使用回溯记录状态”。也许我理解错了。
    • 只要我能检测到 python 中的情况,我就可以记录堆栈跟踪(没有 Sentry 和 New Relic)。当前事实:我可以稍后在 python 中检测情况(但是我没有回溯代码是如何更改的)或者我可以在 db 触发器中检测情况。但是话又说回来,我没有对进行更改的代码进行追溯。
    【解决方案4】:

    您可以使用 LISTEN+NOTIFY。

    首先让一些守护线程LISTEN,然后在数据库触发器中执行NOTIFY

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

    【讨论】:

    • 不,这并不完美。它不是同步的。通知和转储堆栈跟踪之间存在时间间隔。当堆栈跟踪被转储时,解释器可能已经在做其他事情了。
    【解决方案5】:

    如果你使用 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()
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多