日志记录

记录事件可以大大改善调试体验。Frappe 的开发和生产环境都自带日志记录功能。

根据访问方式的不同,日志主要分为两大类。桌面日志(Desk Logs)存储在站点数据库中,可以作为文档进行访问和查询;服务器日志(Server Logs)则存储在由日志轮转系统管理的文件中。

桌面日志

可以通过桌面界面(Desk UI)访问的日志(通常是从 Awesomebar 中搜索查找)。这些日志一般用于追踪操作事件;但你也可以利用它们的 API 来追踪 Frappe 应用中的任何内容。

Core 模块中的部分日志包括:

  • 访问日志
  • 活动日志
  • 错误日志
  • 计划任务日志

你可以通过嵌入的链接了解更多信息。了解每种日志的最佳方式是直接在你的站点上查看它们。

服务器日志

与桌面日志相比,服务器日志通常包含更底层的、事务性的数据。从版本 13 开始,日志也可以在站点级别获取。这些站点日志由 Frappe 应用程序创建,而许多 bench 级别的日志文件则由支持 Frappe 环境的进程生成。在你的 bench 文件夹中,你可以在以下位置找到日志:

  • ./logs
  • ./sites/{site}/logs

在撰写本文时,只有 frappe.web.log 和 scheduler.log 会在站点和 bench 级别记录日志。在 bench 级别,一些最有用的文件可能是:

  • bench.log
  • scheduler.log
  • worker.log

在这些日志中,你可以追踪已执行的 bench 命令、调度器或后台任务运行的状态。

> 要在你的站点上启用 Frappe Web 日志记录,请使用 > enable_frappe_logger 更新站点配置:true

错误快照

除此之外,Frappe 还会为你的站点维护 HTTP 响应码 500 及以上的错误快照。这样做是为了在发生服务器错误时提供更多的故障上下文。例如,某个桌面操作因 HTTP 状态码 500 而失败,这会在 ./sites/{site}/frappe.log 文件中添加一条相应的日志,如下所示:

2021-05-06 14:08:12,177 ERROR frappe New Exception collected with snapshot id: 2021-05-06 14:08:11.940809-127.0.0.1-bde
Site: frappeframework.com
Form Dict: {'docs': '{"name":"assets.frappeframework.com","owner":"Administrator","creation":"2020-04-04 14:58:18.771247","modified":"2021-02-08 09:40:56.820300","modified_by":"gavin@frappe.io","idx":0,"docstatus":0,"status":"Active","hostname":"assets","domain":"frappeframework.com","cluster":"Default","provider":"Generic","is_server_setup":1,"ip":"192.168.29.100","private_ip":"10.120.120.120","agent_password":"*****","is_primary":1,"doctype":"Server","__last_sync_on":"2021-05-06T08:38:08.931Z"}', 'method': 'ping_agent', 'cmd': 'run_doc_method'}

日志的第一行提到了该错误的快照 ID,在本例中是 2021-05-06 14:08:11.940809-127.0.0.1-bde。在 ./sites/frappeframework.com/error-snapshots 下应该有一个以该快照 ID 标识的对应文件,文件名为 2021-05-06 14:08:11.940809-127.0.0.1-bde.json。文件内容可能是:

    {
    "etype": "UnboundLocalError",
    "evalue": "UnboundLocalError("local variable 'headers' referenced before assignment")",
    "exception": {
      "args": [...],
      "with_traceback": [...]
    },
    "frames": [
      {
      "call": "(request=<request 'https:="" frappeframework.com="" api="" method="" run_doc_method'="" [post]="">)",
      "dump": {
        "frappe. api": "<module 'frappe.api'="" from="" '="" home="" frappe="" frappe-bench="" apps="" api.py'="">",
        "frappe.api. handle": "<function handle="">",
        "global frappe": "<module 'frappe'="" from="" '="" home="" frappe="" frappe-bench="" apps="" __init__.py'="">",
        "response": "None"
      },
      "file": "/home/frappe/frappe-bench/apps/frappe/frappe/app.py",
      "func": "application",
      "lines": {
        "66": "",
        "67": "elif request.path.startswith("/api/"):",
        "68": "response = frappe.api.handle()",
        "69": "",
        "70": "elif request.path.startswith('/backups'):"
      },
      "lnum": 68
      },
    ...
    ],
    "locals": {
      "data": "None",
      "files": "None",
      "method": "GET",
      "path": "ping",
      "self": "<orchestrator.agent.agent object="">",
      "url": "https://assets.frappeframework.com:443/agent/ping"
    },
    "pyver": "Python 3.8.2: /home/frappe/frappe-bench/env/bin/python (prefix: /home/frappe/frappe-bench/env)",
    "timestamp": "2021-05-06 14:08:11.940971",
    "traceback": "Traceback (most recent call last):
 File "/home/frappe/frappe-bench/env/lib/python3.8/site-packages/cryptography/fernet.py", line 119, in _verify_signature
 h.verify(data[-32:])
 File "/home/frappe/frappe-bench/env/lib/python3.8/site-packages/cryptography/hazmat/primitives/hmac.py", line 74, in verify
 ctx.verify(signature)
 File "/home/frappe/frappe-bench/env/lib/python3.8/site-packages/cryptography/hazmat/backends/openssl/hmac.py", line 75, in verify
 raise InvalidSignature("Signature did not match digest.")
cryptography.exceptions.InvalidSignature: Signature did not match digest.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
 File "/home/frappe/frappe-bench/apps/frappe/frappe/app.py", line 68, in application
 response = frappe.api.handle()
 File "/home/frappe/frappe-bench/apps/frappe/frappe/api.py", line 54, in handle
 return frappe.handler.handle()
 File "/home/frappe/frappe-bench/apps/orchestrator/orchestrator/agent.py", line 474, in request
 headers=headers,
UnboundLocalError: local variable 'headers' referenced before assignment
"
    }

这些快照会定期与站点数据库同步,并可以通过桌面端访问,记录会添加到 DocType Error Snapshot 下。它们从创建之日起保留一个月,之后每条记录会被删除。

> 错误快照默认启用追踪。要为你站点禁用此功能,请使用 > disable_error_snapshot 更新站点配置:true

进程

服务器日志可能由 bench CLI、你的进程管理器或 Frappe 应用程序直接生成。你的进程管理器负责启动和运行 Frappe 环境的所有组件,并将输出流和错误流定向到日志文件。

除了“始终运行”的默认日志记录外,你还可以使用 Monitor 来记录更多关于站点请求的信息。

维护

如果你不注意的话,进程管理器生成的日志文件可能会随着时间的推移变得非常大。要了解你需要追踪哪些文件,请查看你 bench 上的 supervisor.conf 或 Procfile,具体取决于你运行的是生产实例还是开发实例。

旧日志的自动清理

Frappe 中的大多数日志 DocType 都支持可配置的保留期限,并带有合理的默认值。

“Log Settings” DocType 让你可以为默认的日志 DocType 配置保留期限。

你也可以通过向 DocType 控制器添加一个静态方法来将自定义日志 DocType 注册到日志设置中,如下所示。实现了此方法的自定义日志 DocType 将自动显示在 Log Settings 中。

class CustomLoggingDoctype(Document):
 @staticmethod
    def clear_old_logs(days=180):
        from frappe.query_builder import Interval
        from frappe.query_builder.functions import Now

        table = frappe.qb.DocType("Custom Logging Doctype")
        frappe.db.delete(table, filters=(table.modified < (Now() - Interval(days=days))))

更多

在生产环境中,你可能希望为 Frappe 应用程序或构成 Frappe 环境的进程记录更多信息。以下是一些你可以查阅的资源,以了解更多。

Frappe 应用程序

  • Frappe 日志 API

服务器监控

  • MySQL
  • PostgreSQL
  • NGINX