更改或重复使用用户的电子邮件地址

用户记录上的电子邮件字段在用户保存后即被禁用。要更改它,请使用重命名,而不是该字段本身。这会立即更改用户的登录地址:用户会从所有设备上退出登录,旧地址将不再接受登录或密码重置,但密码保持不变。

为什么电子邮件字段被禁用

用户的电子邮件不仅仅是一个字段。它也是用户的唯一 ID(其文档名称)——创建用户时,Frappe 会将 name 设置为该电子邮件地址。所有针对用户写入的记录,包括分配、权限、文档所有权、Created By 和 Modified By 列,都通过该 ID 关联。直接更改该字段会破坏这些关联,因此 Frappe 只允许通过重命名进行更改,该操作会一次性更新 ID 以及所有指向它的记录。

更改用户的电子邮件地址

开始之前

  • 您需要系统管理员角色。
  • 您无法重命名 Administrator 或 Guest 账户——请参阅“如果您误重命名了唯一的管理员账户”。

步骤

  1. 转到用户列表,打开您要更改电子邮件的用户。
  2. 点击表单右上角的菜单 (⋮) 按钮。
  3. 选择重命名。
  4. 在新名称字段中,输入新的电子邮件地址。
  5. 点击重命名。

Frappe 会将用户的 ID 以及所有关联记录(分配、权限、文档历史)更新为新地址。对于历史记录较长的用户,这可能需要几秒钟——运行期间请勿关闭标签页。

重命名后会发生什么

  • 用户会在所有地方退出登录。重命名完成后,Frappe 会立即结束该用户在每个浏览器和设备上的所有活动会话。
  • 密码不会更改。用户使用新电子邮件和现有密码重新登录。不会自动发送密码重置邮件。
  • 旧电子邮件立即失效,包括用于忘记密码。如果有人尝试使用,会看到 User does not exist——这是预期行为,不是错误,因为该记录不再使用该名称。
  • 文档历史得以保留。现有文档上的评论、分配以及 Created By / Modified By 记录仍会在新电子邮件下正确显示该用户。

两个用户可以共用一个电子邮件地址,或重用已禁用用户的电子邮件吗?

  1. 电子邮件是用户的文档 ID,因此它在所有用户(包括已禁用的用户)中必须唯一。尝试创建用户或将用户重命名为已被使用的电子邮件(即使是已禁用用户的电子邮件),都会失败,并提示该 ID 已存在。

如果多个人需要阅读发送到一个地址的邮件,例如共享的 sales@ 收件箱,请为每个人分配各自的用户和各自唯一的电子邮件,并在您的邮件提供商处将所有这些地址指向该共享收件箱:为每个人设置不同的别名(sales-priya@、sales-arjun@),或者如果您的提供商支持,使用同一地址的加号变体(sales+priya@)。每个用户仍然拥有一个真实且唯一的登录地址,而所有邮件都会进入同一个收件箱。

作为最后的手段,如果您无法创建更多地址,系统管理员可以在系统设置 → 登录中开启允许使用用户名登录。这样用户就可以使用用户名而不是电子邮件登录,因此电子邮件本身可以填写占位值。这只会改变他们的登录方式——他们仍然不会收到发送到该占位地址的系统邮件通知,而且每个用户的电子邮件仍然必须唯一。

如果您误重命名了唯一的管理员账户

字面意义上的 Administrator 账户永远无法被重命名——Frappe 会直接阻止。因此,“管理员账户的电子邮件被更改了”始终意味着某个系统管理员级别的用户被重命名或重新创建,而不是 Administrator 本身。

如果您仍有可用的会话或另一个系统管理员账户:

  1. 使用新电子邮件地址和该账户之前的相同密码登录——重命名并未重置密码。
  2. 如果您不知道新电子邮件被改成了什么,请让另一位系统管理员打开用户列表,按名称、电话号码或角色查找该记录,然后将当前电子邮件告诉您。

如果没有人能访问任何系统管理员账户:

  • 在 Frappe Cloud 上,打开您的仪表板,转到通知 → 请求,并批准来自支持团队的站点访问请求,以便他们可以从他们那边查找或重置该账户。
  • 拥有服务器访问权限时,系统管理员可以直接重置管理员本人的密码:bench --site [site-name] set-admin-password [new-password]。这会重置管理员的密码,而不是被重命名用户的密码——请用它重新以管理员身份登录,然后在那里修复被重命名的账户。
  • 更改现有用户的电子邮件 ID 或重命名用户
  • 我收不到密码重置、登录链接或双重身份验证电子邮件

为什么保存、提交或重命名时会出现锁等待超时或死锁?

当你在保存、提交或重命名文档时,如果另一个事务对同一行持有锁,你可能会看到 Lock wait timeout exceeded 或 Deadlock found when trying to get lock。这通常发生在并发提交、长时间运行的后台任务,或重命名一个被许多其他文档链接的文档时。

错误信息

你会看到以下之一,通常出现在“服务器错误”对话框中或错误日志里:

pymysql.err.OperationalError: (1205, 'Lock wait timeout exceeded; try restarting transaction')
pymysql.err.OperationalError: (1213, 'Deadlock found when trying to get lock; try restarting transaction')

在 MariaDB 11.6.2 及更高版本中,你在重命名或繁忙保存时也可能看到这个:

pymysql.err.OperationalError: (1020, "Record has changed since last read in table '<table>'")

<p>Frappe 将这三者都视为死锁,因为从用户的角度来看,MariaDB 较新的快照隔离检查(错误 1020)与错误 1213 是同一个问题。</p>
<h2 id="why-this-happens"><strong>为什么会发生</strong></h2>
<p>InnoDB 是 Frappe 使用的数据库引擎,它会在事务更新或插入某一行时立即锁定该行,并一直持有该锁,直到事务提交或回滚。如果第二个事务需要同一行,它就会等待。默认情况下,MariaDB 最多等待 50 秒(<code>innodb_lock_wait_timeout)后放弃并抛出错误 1205。如果两个事务同时互相等待对方的锁,MariaDB 根本不会等待:它会立即检测到循环等待,回滚其中一个事务,并抛出错误 1213(或者自 MariaDB 11.6.2 起,抛出错误 1020)。

重命名是一个特殊情况。重命名文档会更新其自身的行,然后更新所有指向它的 DocType 中的每个 Link 或 Dynamic Link 字段,全部在同一个事务内完成。重命名一个被许多其他记录引用的文档(例如仓库、物料组或工作流操作)会在多个表上持有锁更长时间。在重命名运行期间,任何其他触及这些相同行的事务(例如针对该仓库保存销售发票)都必须等待,并可能超时。

常见触发原因:

  • 多个用户同时提交更新同一链接记录的文档,例如针对同一客户或仓库的销售发票。
  • 一个后台任务(库存重算、计划任务、批量更新)在用户保存相关文档时持有长事务。
  • 一个浏览器标签页或 API 会话保持打开状态,带有未提交的更改,仍然持有锁。
  • 重命名一个被许多其他文档链接的文档。

对于后台任务,Frappe 已经会自动重试。如果 execute_job 遇到错误 1205 或 1213(或 1020,因为 Frappe 将其映射到相同的死锁检查),它会回滚并以递增的延迟重试该任务最多 5 次。从桌面端进行的保存或提交不会自动重试——用户会立即看到错误。

如何修复

  1. 打开 系统控制台(在顶部搜索栏中搜索)。你需要 系统管理员 角色。
  2. 在 数据库进程 下,打开 显示进程列表。表格会自动填充,无需点击执行,并显示每个打开的数据库会话、其状态以及已运行的时间。
  3. 查找 状态 不是空闲的会话,或者 时间 较高的会话。其 信息 列显示正在运行的查询,如果是一个正在等待其他资源的打开事务,则为空。
  4. 要查看死锁的完整详情,包括是哪个表和行导致的,请从数据库控制台(bench --site <site-name> mariadb</site-name>)运行 SHOW ENGINE INNODB STATUS\G,并阅读 最近检测到的死锁 部分。
  5. 如果某个会话卡住并持有锁,请从同一数据库控制台结束它:
KILL <id>;
</id>

使用进程列表中的 Id。在 Frappe Cloud 上,你无法自行运行 KILL;请提交支持工单并附上进程列表输出。
6. 如果阻塞会话属于后台工作进程,请重启工作进程,而不是直接终止数据库会话:bench restart。如果只有调度器卡住,bench scheduler resume --site <site-name></site-name> 可以在不进行完整重启的情况下恢复它。
7. 重试保存、提交或重命名。

如何预防

  • 后台任务在遇到死锁或锁等待超时时会自动重试最多 5 次,因此繁忙时段的一次性错误通常会自动清除。只有在之后仍然失败,或者用户在桌面端立即被阻塞时才需要介入。
  • 在非繁忙时段重命名一个被许多记录链接的文档(仓库、物料组或成本中心)。
  • 在自定义脚本或报表中,分批提交,而不是在一个事务中循环处理数千条记录。
  • 关闭你保持空闲且带有未保存或未提交更改的浏览器标签页或 API 会话。

如果你使用的是不受支持的版本

如果你的 应用版本 显示 Frappe 或 ERPNext v14 或更早版本,请升级。在这些工单中的几个案例里,真正的原因是较新版本已经修复的慢查询或锁定行为,而生命周期结束的版本不在性能调查的范围内。

如果这不能解决问题

如果错误在非繁忙、非并发使用的情况下仍然持续发生,请提交支持工单,并提供确切的错误文本、您当时正在保存、提交或重命名的文档、您的应用版本,以及错误发生时的进程列表输出(或系统控制台截图)。

  • 后台任务
  • 系统控制台
  • 性能分析与监控

保存电子邮件域或电子邮件账户时出现SMTP超时或连接错误

当你在电子邮件域或电子邮件账户中输入邮件服务器的主机、端口和加密设置,然后点击保存时,设置并没有被保存,反而立刻出现超时或连接错误。这是因为 ERPNext 会在你保存的那一刻测试连接——它不会存储未经测试的设置——所以错误总是指向具体的问题:端口与加密方式组合错误,或者连接在 15 秒内根本无法到达服务器。

错误信息

你会看到以下错误之一,可能是在保存时弹出的提示,也可能是账户已保存但之后邮件发送失败时,出现在电子邮件队列条目的错误字段中:

Invalid Outgoing Mail Server or Port: STARTTLS extension not supported by server.
Invalid Outgoing Mail Server or Port: Connection unexpectedly closed: timed out
Incoming email account not correct. Error connecting via IMAP/POP3: timed out
TimeoutError: timed out

如果你看到的是 421 ... too many failed logins 或 535 ... User has been restricted,请跳到“服务商拒绝连接或账户”一节——那是另一种原因,只是错误标题相同。

为什么会发生

保存电子邮件域或电子邮件账户并不只是存储你的设置。ERPNext 会当场与邮件服务器建立真实连接——接收邮件使用 IMAP/POP3 连接,发送邮件使用 SMTP 连接——并给它 15 秒时间。如果服务器有响应但拒绝了你发送的特定端口/加密组合,或者连接在这段时间内根本没有到达任何地方,保存就会立即失败,并显示上述错误之一。

这些问题分为两种看起来相似但实际不同的问题:

  • 端口/加密组合错误。邮件服务商期望端口和加密方式有特定的配对,把它们弄混就会产生上面提到的 STARTTLS 和“连接意外关闭”错误:

    端口 需要检查的设置 作用
    465 勾选使用 SSL,不勾选使用 TLS 隐式 SSL/TLS——连接从第一个字节起就是加密的。
    587 勾选使用 TLS,不勾选使用 SSL 先以明文连接开始,然后通过 STARTTLS 升级为加密连接。这就是使用 TLS复选框对发送邮件的实际作用——并非隐式 TLS。
    25 很少用于需要认证的发送 大多数服务商阻止在此端口上进行认证提交;请改用 465 或 587。

    在指向 587 端口时勾选使用 SSL,或在指向 465 端口时勾选使用 TLS,是最常见的情况:服务器在该端口上期望一种加密方式,却收到了另一种。

  • 连接根本没有到达服务器。主机名拼写错误、服务商的防火墙不允许来自其自身网络外部的连接,或者——特别是在 Frappe Cloud 上——邮件服务商没有允许 Frappe Cloud 当前的出站 IP 地址进行连接。Frappe Cloud 不会阻止任何出站 SMTP、IMAP 或 POP3 端口——如果连接超时或操作系统报告“网络不可达”,限制几乎总是出在接收邮件服务器一侧,而不是 Frappe Cloud 一侧。

如何修复

  1. 仔细阅读确切的错误文本——上述两种问题需要不同的修复方法,错误信息会告诉你属于哪一种。STARTTLS 或“意外关闭”错误是端口/加密不匹配(第 2 步)。单纯的超时或“网络不可达”是连接从未成功建立(第 3 步)。
  2. 端口/加密不匹配:打开电子邮件域(如果未关联到域,则打开电子邮件账户),根据上表将端口与正确的加密复选框匹配。大多数服务商都会公布两种选项;使用与表中主机和端口组合相匹配的那一种。

此示例(一个测试电子邮件域的发送设置)展示了 587 + 使用 TLS 的配对——这是服务商期望在 587 上使用 STARTTLS 时的正确组合。如果你的服务商公布的是 465 端口,请改为在此处勾选使用 SSL,并保持使用 TLS 不勾选。
3. 连接从未到达服务器:首先确认主机和端口输入正确,然后检查限制是否在服务商一侧。由于 Frappe Cloud 不会阻止出站连接:

  • 找到你站点当前的出站 IP 地址(在 Frappe Cloud 仪表板上进入站点 → 概览),并确认你的邮件服务商的防火墙或 IP 允许列表包含该地址。待核实:此字段的确切标签和位置在本次会话中未在 Frappe Cloud 仪表板上实时重新验证。
  • 如果你的服务商只允许一组固定的 IP,而你的站点出站 IP 会变化(例如服务器调整规格后),要么将新 IP 重新添加到服务商的允许列表,要么将站点迁移到具有静态 IP 的专用服务器上,以免再次发生。静态 IP 是专用服务器上的付费附加项,共享主机上不可用。
  • 如果相同的设置在另一台服务器上有效,但从本站点却无效,这一差异就证明问题出在服务商一侧,而不是 Frappe Cloud 的限制——请向你的邮件服务商反映,而不是联系支持团队。
  1. 服务商拒绝连接或账户:出现 421 ... too many failed logins 或类似的“尝试次数过多”错误,通常意味着错误凭据被重试了足够多次,导致服务商临时封禁了发起连接的 IP——请先修正密码,然后等待或请服务商解除封禁。出现 535 ... User has been restricted(或类似措辞)错误,是邮件服务商设置的账户级限制,而不是连接问题——请联系服务商的支持团队解除限制。
  2. 特定服务商的情况:
  • Microsoft 365 / Outlook.com / Exchange Online:Microsoft 要求这些邮箱使用 OAuth;无论端口和加密设置如何,使用普通 SMTP 密码通常都会失败。请按照 Microsoft Office365 Email OAuth 操作,而不是去排查端口。
  • Gmail / Google Workspace:使用 imap.gmail.com 端口 993 并勾选 Use SSL,以及 smtp.gmail.com 端口 587 并勾选 Use TLS(ERPNext 在 Email Account 上内置的 GMail 服务预设正是使用这些设置)。需要核实:启用了两步验证的 Google Workspace 账户通常需要使用 App Password 而不是常规账户密码——这是 Google 账户的通用政策,并非 Frappe 特有,且本主题中未通过工单确认。
  • PEC(意大利认证电子邮件):使用你的 PEC 服务商公布的确切主机、端口和加密方式——PEC 服务商通常对任何其他设置都会严格拒绝。需要核实:本主题的样本中没有涉及 PEC 的工单;这是一般性指导,并非经验证的模式。

如果这样仍无法解决

  • 如果你的邮件服务商确认端口已开放,但连接仍然仅从 Frappe Cloud 失败,请提交支持工单,附上确切的错误文本、站点名称以及 Email Domain/Account 的设置(不含密码)。
  • 如果错误是关于 SSL 证书验证,而不是超时或 STARTTLS 不匹配,那么这是邮件服务器一侧的证书问题,不在本文的讨论范围内。

如何预防

  • 当业务域名自有的 SMTP 在 Frappe Cloud 上持续失败时,作为备用方案,请使用内置的 Email Delivery Service,而不是为外发邮件配置自己的 Email Account——参见 Upgrade the Email Delivery Service plan。需要核实:它是否会在没有任何 Email Account 被标记为 Default Outgoing 时自动使用,还是需要显式步骤,本次会话中未确认。
  • 如果你的邮件服务商严格采用 IP 白名单,请一次性申请一个静态 IP,而不是每次 Frappe Cloud 的出站 IP 变化时都重新添加。
  • Email Account
  • Email Domain
  • Microsoft Office365 Email OAuth
  • Obtain a Static IP
  • Upgrade the Email Delivery Service plan

我没有收到密码重置、登录链接或双重验证邮件

你点击了忘记密码、通过电子邮件链接登录,或者你开启了双重身份验证(2FA),但包含重置链接、登录链接或验证码的邮件始终没有送达。在所有这些情况下,Frappe 都会将邮件加入队列,并依赖站点自身的发件邮件设置来实际发送它——因此,解决办法几乎总是出在电子邮件账户或电子邮件队列中,而不是密码重置或 2FA 功能本身。

错误信息

通常屏幕上根本不会显示任何错误——你只是一直收不到邮件。如果确实有错误,通常是下面这个,在你尝试通过电子邮件链接登录时会以弹窗形式显示:

Please setup default outgoing Email Account from Tools > Email Account

或者,如果已配置账户但配置有误,会显示在电子邮件队列条目自身的错误字段中:

Invalid Outgoing Mail Server or Port: Connection unexpectedly closed

如果你遇到的是通用的服务器错误,或者提到 reset_password 的堆栈跟踪,而不是上述任一情况,请跳到“如果这样仍无法解决”。

为什么会发生这种情况

Frappe 不会在你请求的那一刻就发送密码重置、登录链接或 2FA 邮件。它会将邮件写成一个新的电子邮件队列条目,并从那里发送,使用被标记为默认发件的那个电子邮件账户。双重身份验证邮件也走同样的路径:如果双重身份验证方式是电子邮件或短信,Frappe 会在每次登录时以同样的方式通过邮件或短信发送验证码;即使使用 OTP 应用方式,在用户首次开启 2FA 时,Frappe 也会在身份验证器应用被视为注册完成之前,先发送一封设置确认邮件。

因此,所有这三种症状——收不到重置邮件、收不到登录链接、收不到 2FA 验证码——通常都归结为同样的两件事:是否存在一个可用的默认发件电子邮件账户,以及电子邮件队列是否实际发送了该条目。如果这两者都不是原因,邮件仍可能被送达,但被当作垃圾邮件过滤掉,或者因队列自身的发送间隔而被延迟。

如何解决

  1. 打开电子邮件账户列表(在 Awesome Bar 中搜索“Email Account”)。检查至少有一个账户勾选了启用发件,并且在发件(SMTP)设置下恰好有一个账户勾选了默认发件。如果没有任何账户勾选,请打开一个可用的账户,勾选这两个复选框,然后保存。
  2. 打开电子邮件队列列表,找到你期望的那封邮件的条目(按收件人筛选,或按最后更新时间排序以查看最新的)。检查其状态:
  • 错误:打开该条目并阅读错误字段。此处出现服务器/端口/身份验证相关的消息,意味着默认发件账户的 SMTP 设置不正确——请在电子邮件账户中修正它们并重新发送。
  • 未发送或发送中:队列尚未将其发出。默认情况下,队列每 4 分钟发送一次(站点的 scheduler_interval)。等待几分钟,或者请拥有 bench 访问权限的系统管理员立即运行它:bench --site [site-name] execute frappe.email.queue.flush。
  • 已发送:在 Frappe 这一侧投递已成功。请转到第 3 步。
  1. 如果队列显示已发送但邮件仍未送达,请先检查收件人的垃圾邮件或废件文件夹。如果邮件一直被过滤,很可能是发送域名缺少该电子邮件账户域名的 SPF 或 DKIM DNS 记录——请让管理该域名 DNS 的人员按照邮件服务商公布的值添加它们。
  2. 在 Frappe Cloud 上,如果站点使用的是内置的电子邮件投递服务应用而不是你自己的 SMTP 账户,请确认它没有达到套餐的发送上限。请参阅“升级电子邮件投递服务套餐”。电子邮件配置本身的产品支持取决于你站点的支持计划层级。
  3. 如果缺失的邮件具体是 2FA 验证码或设置邮件,请先重新检查第 1 步——这是同一个默认发件原因,而不是单独的 2FA 问题。如果第 1 步已经正确,请打开系统设置 → 登录,确认双重身份验证方式与你期望的一致(OTP 应用、短信或电子邮件)。

若要在不等待邮件的情况下让被锁定的用户重新登录,系统管理员可以在系统设置 → 登录中取消勾选启用双重身份验证,临时为所有人关闭 2FA;或者通过从全部角色中移除双重身份验证权限,将其限定到特定角色。在底层邮件问题解决后,再重新开启它。

如果这样仍无法解决

  • 如果错误是提到 reset_password 或 AttributeError 的服务器堆栈跟踪,而不是缺少默认发件账户,那么这是代码层面的问题,通常与最近的版本升级有关,而不是电子邮件配置问题。请提交支持工单,附上来自错误日志的完整堆栈跟踪以及站点的 Frappe 和应用版本(帮助 → 关于)。
  • 如果电子邮件队列条目显示已发送,SPF/DKIM 已经正确,但电子邮件仍然从未到达,请提交支持工单。请包含站点名称、电子邮件队列条目的名称和状态,以及这是影响一个用户还是所有人。

如何预防

  • 只保留一个标记为默认发件的电子邮件账户,并在任何更改后通过给自己发送密码重置或登录链接电子邮件来进行测试。
  • 如果您添加或更改电子邮件账户,请重新检查默认发件——新账户很容易忘记设置,或者两个账户都标记为发件但都没有设为默认。
  • 电子邮件账户
  • 如果收件人没有收到电子邮件怎么办
  • 设置双因素认证
  • 升级电子邮件投递服务计划

更新到v16后我的工作区或桌面图标消失了

将站点从 v15 更新到 v16,或升级到更高的 v16 版本后,你依赖的某个工作区或桌面图标可能会变得不一样:自定义布局恢复为默认布局、桌面上缺少某个图标,或者整个工作区都不见了。这是三种不同的情况,对应三种不同的解决办法,所以先要把它们区分清楚。

你会看到的现象

  • 你自定义过的工作区(例如在 Home、Accounting 或 Stock 等标准工作区中添加了卡片、数字卡片或 HTML 块)显示的是新的默认布局,你添加的内容不见了。
  • 你自己创建的工作区或桌面图标(不是随应用一起提供的)完全不见了:它不在 Workspace 列表中,不在桌面图标网格中,也不在侧边栏中。
  • 工作区仍然存在,但看起来被清空了:你记得的卡片和链接都不见了,只剩下一个快捷方式面板。
  • 你创建的桌面图标不见了,但它链接到的 Workspace 仍然存在。

为什么会这样

v16 同时改变了两件事:它重新设计了每个应用自带的标准工作区(因此 Home、Accounting 等内置工作区与 v15 相比会有意呈现不同的外观),并且把许多原本位于工作区主体内的 doctype 和报表链接移到了左侧边栏。这两项改动都不会删除你的数据,但如果你之前自定义过某个工作区,它可能会看起来被清空了。

另外,更新站点时会运行 bench migrate,它会从应用的 JSON 文件中重新导入每个标准工作区,并删除更新清理步骤无法与已安装应用中的文件匹配的任何工作区、桌面图标或侧边栏行——将其视为不再属于任何应用的 fixture。你通过 desk 自行创建的工作区本应被排除在此清理之外,但在某些 v16 版本中,migrate 打上了应用或模块标签的站点创建的公共工作区被误清理并删除了。Frappe 已在多个 v16 补丁版本中修复了若干此类情况;保持在最新的补丁版本是避免遇到未修复问题的最佳保护措施。

这两件事组合起来形成三种不同的情况:

  1. 标准工作区的自定义内容被覆盖了,而不是被删除了。 旧布局仍保留在该工作区的编辑历史中。
  2. 你创建的工作区或图标被彻底删除了。 它已从实时列表中消失,但可能仍作为 Deleted Document 存在,或存在于备份中。
  3. 没有任何内容被删除或覆盖——它移动了位置,或者对你隐藏了。 内容现在在侧边栏中,或者某个权限或角色把该图标过滤掉了。

如何判断你属于哪种情况

  1. 打开 Workspace 列表(在 Awesome Bar 中搜索“Workspace”),按名称查找该工作区。
  • 如果它存在但看起来不一样:请转到“如果标准工作区被覆盖了”。
  • 如果它完全不存在:继续执行第 2 步。
  1. 在 Awesome Bar 中搜索“Deleted Document”,并将 Deleted Doctype 筛选为 Workspace、Desktop Icon 或 Workspace Sidebar。如果你找到一行包含缺失名称的记录,说明它被删除了——请转到“如果它被删除了”。
  2. 如果某个桌面图标不见了,但它的 Workspace 仍在列表中,则该图标的链接可能是被过滤掉了,而不是被删除了。请检查:
  • 该图标在 Desktop Icon 列表中的 Hidden 字段。
  • 该图标或其工作区是否带有你的用户不具备的角色(Has Role)。
  • 该工作区是否为 Private——私有工作区以及链接到它的任何图标,仅对拥有它的用户可见。
  1. 如果以上都不能解释,请清除缓存的桌面状态:注销后重新登录,或请系统管理员在站点上运行 bench clear-cache。

如果标准工作区被覆盖了

在 v16.50 及更高版本中,Frappe 可以为你重放编辑:

  1. 在 Awesome Bar 中搜索“Restore Workspace”。此页面需要 Workspace Manager 角色——如果你没有该角色,请让系统管理员授予。
  2. 该页面会列出此站点在更新覆盖之前编辑过的所有标准工作区,以及最后编辑时间和编辑者。
  3. 点击工作区旁边的 Restore。这会将你的旧布局作为站点自定义写回,因此该工作区会在其通常地址上再次显示该布局。
  4. 如果页面显示的是警告而不是恢复所有内容,请阅读它——某些编辑(例如,修改随应用提供的卡片的现有行,而不是新增一行)无法带入自定义中,需要手动重做。

如果您的站点运行的是较早的 v16 版本,则此页面尚不存在。请将站点更新到 v16.50 或更高版本后再使用,或者手动重建布局——通过 编辑工作区 重新添加卡片、链接或 HTML 块,如果有更新前后的截图可以参考。

如果它被删除了

  1. 打开您找到的 已删除文档 记录,然后点击 恢复。这会用原始名称和内容重新创建该文档。要使其生效,您需要对被恢复的 doctype(Workspace、Desktop Icon 或 Workspace Sidebar)拥有创建权限。
  2. 如果相关行是一起被删除的(例如,一个工作区及其侧边栏和桌面图标同时被删除),请逐一恢复——仅恢复工作区不会带回其图标或侧边栏。
  3. 如果 已删除文档 中也没有显示任何内容,或者您需要站点完全恢复到更新前的状态,请改用更新前所做的备份来恢复站点。在 Frappe Cloud 上,使用仪表板中站点的 备份 选项卡;在自托管的 bench 上,使用 bench --site [site-name] restore [backup-file]。恢复数据库备份会替换该备份之后站点上的所有数据,而不仅仅是工作区,因此在执行此操作前请与站点管理员确认。
  4. 工作区或图标恢复后,在再次允许站点迁移之前,请将站点更新到 v16 系列的最新补丁。如果删除是由上述迁移时清理导致的,保持最新是确保避免再次发生的唯一方法——清除工作区的 应用 或 模块 字段并非在每个版本上都经过确认的修复方法,因为至少在一个站点上,该字段在保存时会自行重置。

如何预防

  • 避免就地自定义标准(应用自带)工作区。请改为构建您自己的工作区,否则就要预期在下次更新后需要使用 恢复工作区(v16.50+)或手动重做。
  • 将您自己构建的任何内容保持为 私有 工作区,这样它只需要对您可见。私有工作区永远不会成为上述迁移时 fixture 清理的候选对象。
  • 将站点保持在 v16 系列的最新补丁版本上。上述大多数删除情况已在后续补丁中修复。
  • 在任何更新之前进行备份,或者至少对您的工作区布局截图。

如果这不能解决问题

提交支持工单,并提供以下信息:站点名称、更新前后从 帮助 → 关于 中获取的 Frappe 和应用版本、缺失或已更改的工作区/图标的准确名称、它是标准工作区还是您创建的工作区、它影响所有用户还是单个用户,以及您是否在 已删除文档 中找到了匹配的行(如果找到,请包含其名称和删除时间戳)。如果更新是自动运行的(共享的 Frappe Cloud bench),还请注明日期,以便支持人员将其与站点的更新日志进行匹配。

  • 工作区
  • 工作区自定义
  • 工作区访问

电子邮件账户显示“凭据无效”或SMTP/IMAP认证错误

当您保存电子邮件账户,或者当 Frappe 通过某个电子邮件账户发送和接收邮件时,邮件服务器可能会拒绝登录。您会看到保存错误、电子邮件账户被禁用,或者邮件卡在队列中。

错误信息

根据登录被拒绝的位置,您会看到以下其中一种情况:

保存时,来自发送服务器

Incorrect Configuration
Invalid credentials for Email Account
Incorrect Configuration
Invalid Outgoing Mail Server or Port: Connection unexpectedly closed

在计划同步时,来自接收服务器

Authentication failed while receiving emails from Email Account: <name>.
Message from server: b'[AUTHENTICATIONFAILED] Invalid credentials (Failure)'
</name>

来自第三方 SMTP 中继,出现在错误日志中

smtplib.SMTPSenderRefused: (502, b'5.7.0 Please authenticate first', ...)

保存时对话框的样子——冒号后面的措辞取决于拒绝连接的对象。

这四种情况含义相同:服务器不接受您提供的登录 ID 和密码。具体措辞取决于失败发生在保存时(发送)还是计划同步期间(接收),以及您的服务商如何报告该问题。

为什么会发生这种情况

当您保存电子邮件账户时,Frappe 会使用您输入的登录 ID、密码、服务器、端口和加密方式(TLS 或 SSL)连接到邮件服务器,并尝试进行身份验证。只有当所有这些都与服务器期望的一致时,服务器才会接受登录。密码错误、端口与加密方式不匹配,或者服务商不再允许使用普通密码的登录流程(Gmail 和 Microsoft 现在都要求这样做),都会返回同一种拒绝。

如果电子邮件账户之前一直正常工作,后来开始失败,通常的原因是服务商那边更改了密码,或者启用了双重身份验证(2FA),这会使 Frappe 等应用使用的账户密码失效。

如何修复

1. 检查基本设置

  1. 打开电子邮件账户,滚动到身份验证部分(服务器字段则在发送(SMTP)设置中)。
  2. 如果账户登录所用的地址与发送时使用的地址不同,请勾选使用不同的电子邮件 ID,并在备用电子邮件 ID中输入真实的登录信息。
  3. 重新输入密码。复制粘贴的密码可能带有尾部空格或换行符,从而导致与密码错误相同的错误。
  4. 对照服务商公布的信息,检查发送服务器(或电子邮件服务器)和端口。
  5. 检查使用 TLS和使用 SSL——只应勾选其中一项,并且必须与端口匹配:
  • 端口 587 → 勾选使用 TLS,取消勾选使用 SSL。
  • 端口 465 → 勾选发送使用 SSL,取消勾选使用 TLS。

在同一端口下两项都勾选或两项都不勾选,本身就是导致此错误的常见原因。

发送(SMTP)设置——使用 TLS 和端口已用方框标出,这是最常出现不匹配的一对设置。

如果使用 TLS和使用 SSL呈灰色且无法更改,说明该电子邮件账户已关联到一个电子邮件域。发送设置来自该电子邮件域,而不是来自账户。请打开关联的电子邮件域进行更改,或者清除电子邮件账户上的域字段,以便改为在那里解锁这些字段。

2. 适用于 Gmail 或 Google Workspace

Google 不再接受您的普通账户密码用于 Frappe 或任何非 Google 产品的应用。将服务设置为GMail——这会自动填入 IMAP/SMTP 服务器、端口和加密方式——然后使用以下其中一种方式:

将服务设置为 GMail 会预设服务器、端口和加密方式。

  • 应用专用密码。在 myaccount.google.com/security 为 Google 账户开启两步验证,然后在同一页面生成应用专用密码,并将其用作电子邮件账户的密码,而不是您的登录密码。应用专用密码只有在开启两步验证后才有效。如果 2FA 未开启,Google 不会允许您生成应用专用密码,账户会继续拒绝普通密码。
  • OAuth,如果您完全不想存储密码。这需要在 Google Cloud 中设置一个已连接应用,并将其选为电子邮件账户的身份验证方法。设置流程与 Microsoft Office365 电子邮件 OAuth 相同——一个已连接应用、一个重定向 URI,以及用于 IMAP 和 SMTP 的邮件作用域。

3. 适用于第三方 SMTP 服务商(SMTP2Go、Brevo、Sendgrid 等)

这些服务商完全不使用您的电子邮件收件箱密码。它们会签发自己的 SMTP 用户名和 API 密钥,与任何账户登录信息分开:

  1. 在服务商的控制面板中,找到(或生成)用于发送邮件的SMTP 用户名和SMTP 密码 / API 密钥。例如,对于 Sendgrid,SMTP 用户名就是 apikey,密码是生成的 API 密钥,而不是账户密码。
  2. 在电子邮件账户上,勾选使用不同的电子邮件 ID,并在其中输入服务商的 SMTP 用户名。
  3. 将 API 密钥输入为密码。
  4. 请完全按照提供商文档中的说明输入传出服务器和端口,并与第 1 步中该端口对应的 使用 TLS / 使用 SSL 设置保持一致。

如果你专门使用 Brevo,并且在匹配上述所有设置后仍然遇到 (502, b'5.7.0 Please authenticate first'),你的站点可能需要 smtp_no_ehlo_after_auth 站点配置键——某些中继会拒绝 Python 的 SMTP 客户端在登录后发送的额外 EHLO 命令。从 Frappe v16.17.0 起,此问题已默认修复。在更早的版本上,请提交支持工单并引用此错误。

4. 在 Frappe 之外测试凭据

在再次保存电子邮件账户之前,请先确认登录本身可以正常工作:

  • 在你的电子邮件客户端(Outlook、Thunderbird、Gmail 或 Outlook 网页应用)中,使用相同的服务器、端口、加密方式和密码添加同一账户。如果在那里也失败,则问题出在凭据或提供商,而不是 Frappe。

  • 如果你有 shell 访问权限,请直接测试 SMTP 登录:

    openssl s_client -connect smtp.gmail.com:587 -starttls smtp
    

    然后输入 EHLO、AUTH LOGIN,以及 base64 编码的登录 ID 和密码。返回 235 响应表示登录成功。返回 535 或 534 响应表示服务器拒绝了登录。

一旦登录在 Frappe 之外可以正常工作,请返回电子邮件账户并使用相同的值再次保存。

检查失败后发生了什么

登录失败并不总是立即以对话框形式显示。请检查以下两个位置:

  • 电子邮件队列:打开电子邮件队列列表并按 状态 = 错误 筛选。打开失败的行,查看服务器针对该特定电子邮件返回的拒绝消息。
  • 错误日志:如果电子邮件账户被自动禁用(在计划同步中反复出现身份验证失败后会发生这种情况),请在错误日志列表中搜索其被禁用时间附近的完整回溯信息,然后在修复凭据后重新启用该电子邮件账户。

如何预防

  • 为 Gmail 和 Microsoft 账户开启双重验证并改用应用密码(或 OAuth),以免在提供商强制更改之前措手不及。
  • 当提供商的密码或 API 密钥轮换时,请在同一天更新电子邮件账户——过期的密码会使账户保持禁用状态,并悄无声息地停止通知、欢迎电子邮件和警报。

如果这不能解决问题

如果登录在 Frappe 之外成功,但在电子邮件账户上仍然失败,请提交支持工单并附上:确切的错误文本、电子邮件账户名称,以及失败发生在 保存、传出发送还是 传入同步时。

  • 电子邮件账户
  • 电子邮件域
  • 在 ERPNext 中设置 Sendgrid SMTP 电子邮件
  • Microsoft Office365 电子邮件 OAuth

更新后打印失败,提示“’NoneType’ object is not callable”或Jinja错误

突然间,就在一次 Frappe 更新之后,你一打开打印视图或尝试下载 PDF,所有打印格式(或某个特定格式)就立刻报错。错误信息只给出了行号,却没有说明是哪个格式,所以很难看出该修什么。

错误信息

你会看到下面其中一种,可能出现在打印视图的对话框中,也可能出现在 PDF/下载的响应里:

Print Format Error

Error in print format on line 19: 'NoneType' object is not callable

或者是更完整的版本,出现在你从 PDF 下载链接得到的服务器错误页面中:

File "apps/frappe/frappe/templates/print_formats/standard.html", line 19, in top-level template code

{{ render_field(df, doc, no_of_cols) }}

File "env/lib/python3.x/site-packages/jinja2/sandbox.py", line 401, in call

return __context.call(__obj, *args, **kwargs)

TypeError: 'NoneType' object is not callable

...

frappe.exceptions.PrintFormatError: Error in print format on line 19: 'NoneType' object is not callable

还有一种相关的情况,显示的是 KeyError 或其他 Jinja 异常,而不是 NoneType,但同样呈现“所有打印格式都挂了”的模式。原因和下面的修复方法是一样的。

为什么会这样

打印格式是通过 Jinja 渲染的:html 模板通过 Jinja 的沙箱调用 Python 函数并读取文档字段。'NoneType' object is not callable 的意思是 Jinja 试图把某个东西当作函数来调用,但那个东西的值是 None——通常是一个空字段(当 备注 为空时就是 {{ doc.remarks() }}),或者是沙箱无法解析的模板宏。

大多数“更新后所有打印格式都坏了”的报告,都能追溯到 Frappe Framework 本身的一次短暂回归,出在 standard.html 模板上——所有内置打印格式和基于 Builder 的打印格式都共用这个模板。一次核心改动导致其中一个模板宏在两个版本之间的短暂窗口内返回了 None。Frappe 修复这类问题很快(几小时到几天内),并以补丁版本发布修复,但在你的站点升级到那个版本之前,所有打印和 PDF 都会以同样的方式失败。同一个模板上已经不止一次发生过这种情况,所以“先更新看看是否修好了”是合理的第一步,而不是一次性的做法。

如果只有 一个 打印格式失败,其余都正常,那原因就在这个格式本身:某个自定义打印格式(或合作伙伴定制过的格式)的 Jinja 或自定义 HTML 调用了某个字段、doctype 方法或打印模板钩子,而应用更新把它删除了、重命名了,或者对某些文档留空了。

如何修复

  1. 打开文档,点击打印图标,把 打印格式 切换到另一个(内置格式,或 标准)。如果它能正常渲染,问题就出在你原来用的那个格式上——跳到第 4 步。如果所有格式都失败,问题就在 Frappe 本身——继续第 2 步。


  2. 阅读 traceback,确认它与本问题相符,并找出失败的位置。在打印视图中,重新选择出错的格式以查看对话框;从 PDF 下载链接(或附带 PDF 的邮件/报表)打开完整的服务器错误页面——那里有完整的 traceback。


最后一行会给出异常名称,如果是打印格式特有的错误,还会给出你模板中的行号:


如果失败发生在后台任务中(定时报表、附带 PDF 的邮件)——也就是没有人能看到对话框——请从 Awesome Bar 打开 错误日志,查找同一文档在相近时间的记录;它带有相同的 traceback。
3. 检查你的应用是否都在匹配的、最新的版本上。打开 帮助 → 关于(或在 bench 目录下运行 bench version),对比 已安装应用 列表:Frappe Framework、你的主应用(ERPNext 等)以及任何自定义应用都应该在同一条版本线上(全是 v15,或全是 v16——不能混用),而且都不应落后于该版本线的最新补丁版本。


如果 Frappe Framework 落后了,就更新它:

bench update

bench --site [site-name] migrate

bench build

bench clear-cache

bench clear-website-cache

即使 bench update 无错误地完成,bench build 和两个清除缓存的命令也很重要——过期的 JS/CSS 构建或缓存的页面会继续显示旧的、损坏的行为。在 Frappe Cloud 上,你不需要自己运行这些:公共(共享)bench 会在每周发布日自动更新;在私有 bench 组上,打开站点仪表盘中的 更新 并应用待处理的更新。
4. 如果只有你自己的自定义打印格式失败,打开它(打印格式 列表,或从打印视图点击 自定义),检查 traceback 指出的那一行。查找某个字段引用——它可能对某些文档为空,却被当作函数调用(用 {{ doc.some_field() }} 而不是 {{ doc.some_field }}),或者从某个模板路径导入的宏被应用更新改掉了。修复模板,或者如果这个格式是在更新之前构建的,就按当前的标准布局重新创建它。

如何预防

  • 让 Frappe Framework 和其他应用保持在同一版本线上,并使用该版本线的最新补丁版本——这些故障大多会在某个小版本发布中修复,保持更新即可避免。
  • 任何更新之后,在团队重新开始使用站点之前,先用你依赖的每个自定义打印格式各打印一份文档。

如果这样仍不能解决

提交支持工单,并附上:确切的错误文本、打印格式名称、帮助 → 关于中的 Frappe Framework 和应用版本,以及你当时正在打印的文档。如果你有完整的回溯信息(来自服务器错误页面或错误日志),请一并附上——这是支持人员确认这是已知的 Framework 回归问题还是你自己的打印格式问题的最快方式。

  • 打印
  • 在哪里可以找到标准打印格式

更新后打印失败,提示“’NoneType’ object is not callable”或Jinja错误

突然间,就在 Frappe 更新之后,你一打开打印视图或尝试下载 PDF,所有打印格式(或某个特定格式)就立刻失败了。错误信息会指出行号,但不会说明是哪个格式,所以很难看出该修什么。

错误信息

你会看到以下之一,可能是在打印视图中的对话框里,也可能是在 PDF/下载响应中:

Print Format Error

Error in print format on line 19: 'NoneType' object is not callable

或者是更完整的版本,出现在你从 PDF 下载链接得到的服务器错误页面中:

File "apps/frappe/frappe/templates/print_formats/standard.html", line 19, in top-level template code

{{ render_field(df, doc, no_of_cols) }}

File "env/lib/python3.x/site-packages/jinja2/sandbox.py", line 401, in call

return __context.call(__obj, *args, **kwargs)

TypeError: 'NoneType' object is not callable

...

frappe.exceptions.PrintFormatError: Error in print format on line 19: 'NoneType' object is not callable

一个相关的版本会显示 KeyError 或其他 Jinja 异常,而不是 NoneType,但同样呈现”所有打印格式都挂了”的模式。原因和下面的修复方法是一样的。

为什么会发生这种情况

打印格式是通过 Jinja 渲染的:html 模板通过 Jinja 的沙箱调用 Python 函数并读取文档字段。'NoneType' object is not callable 的意思是 Jinja 试图把某个东西当作函数来调用,但那个东西求值结果是 None——通常是一个空白字段(当 备注 为空时就是 {{ doc.remarks() }}),或者是沙箱无法解析的模板宏。

大多数”更新后所有打印格式都坏了”的报告,都可以追溯到 Frappe Framework 本身的一个短暂回归问题,出在每个内置打印格式和基于 Builder 的打印格式共用的 standard.html 模板中——一次核心改动导致其中一个模板宏在两次发布之间的短暂窗口内返回了 None。Frappe 会很快修复这些问题(几小时到几天内),并以补丁版本的形式发布修复,但在你的站点升级到那个版本之前,每次打印和 PDF 都会以同样的方式失败。同一个模板上已经不止一次发生过这种情况,所以”先更新,看看是否修好了”是一个合理的首选操作,而不是一次性的做法。

如果只有一个打印格式失败,其余的都正常,那原因就在那个格式本身:某个自定义打印格式(或合作伙伴定制过的格式)的 Jinja 或自定义 HTML 调用了某个字段、doctype 方法或打印模板钩子,而某个应用更新将其删除、重命名,或对某些文档留空了。

如何修复

  1. 打开文档,点击打印图标,把 打印格式 切换到另一个(某个内置格式,或 标准)。如果它能正常渲染,问题就出在你之前用的那个格式上——跳到第 4 步。如果所有格式都失败,问题就在 Frappe 本身——继续第 2 步。
  2. 阅读 traceback,确认它与本问题相符,并找出失败的位置。在打印视图中,重新选择失败的格式以查看对话框;从 PDF 下载链接(或附带 PDF 的邮件/报表)中,改为打开完整的服务器错误页面——那里有完整的 traceback。

最后一行会指出异常,对于打印格式相关的错误,还会指出你模板中的行号:

如果失败发生在后台任务中(定时报表、附带 PDF 的邮件)——也就是说没有人能看到对话框——请从 Awesome Bar 打开 错误日志,查找同一时间附近针对同一文档的条目;它带有相同的 traceback。
3. 检查你的应用是否处于匹配的、当前的版本。打开 帮助 → 关于(或在 bench 目录下运行 bench version),对比 已安装应用 列表:Frappe Framework、你的主应用(ERPNext 等)以及任何自定义应用都应处于同一版本线(全部 v15,或全部 v16——不要混用),并且都不应落后于该版本线的最新补丁版本。

如果 Frappe Framework 落后了,请更新它:

bench update

bench --site [site-name] migrate

bench build

bench clear-cache

bench clear-website-cache

即使 bench update 无错误地完成,bench build 和两个清除缓存的命令也很重要——过期的 JS/CSS 构建或缓存的页面可能会继续显示旧的、损坏的行为。在 Frappe Cloud 上,你不需要自己运行这些:公共(共享)bench 会在其每周发布日自动更新;在私有 bench 组上,打开站点仪表板中的 更新 并应用待处理的更新。
4. 如果只有你自己的自定义打印格式失败,请打开它(打印格式 列表,或从打印视图中的 自定义),检查 traceback 指出的那一行。查找某个字段引用——它可能对某些文档是空白的,却被当作函数调用(用 {{ doc.some_field() }} 而不是 {{ doc.some_field }}),或者查找从某个应用更新改变了的模板路径导入的宏。修复模板,或者如果该格式是在更新之前构建的,就基于当前的标准布局重新创建它。

如何预防

  • 让 Frappe Framework 和其他应用保持在同一版本线,并处于该版本线的最新补丁版本——大多数这类故障都会在补丁版本中修复,保持最新就能避免它们。
  • 在任何更新之后,在团队重新开始使用站点之前,用你依赖的每个自定义打印格式各打印一份文档。

如果这样仍无法解决

请提交支持工单,并附上:确切的错误文本、打印格式名称、从 帮助 → 关于 中获取的 Frappe Framework 和应用版本,以及你当时正在打印的文档。如果你有完整的回溯信息(来自服务器错误页面或错误日志),请一并附上——这是支持团队确认该问题属于已知的 Framework 回归问题,还是你自己的打印格式中的问题的最快方式。

  • 打印
  • 在哪里可以找到标准打印格式

Sentry集成

Sentry 是一个错误跟踪和性能监控服务。每当发生错误时,应用程序会向它发送一份报告。Sentry 会将相同的错误归并为一个 issue,显示堆栈跟踪和上下文,并在出现新 issue 时提醒团队。你可以使用 sentry.io 上的托管服务,也可以自行部署。借助 Sentry,你可以在一个地方找到所有站点的生产环境错误,而不必逐个搜索每个站点的错误日志。

概述

当你设置一个环境变量并且站点选择启用遥测后,Frappe 会将意外的服务器错误和 desk JavaScript 错误发送到 Sentry 项目。可选变量可以添加性能跟踪、性能分析和 SQL span。

  • 每个 bench 一个 DSN。 DSN 来自进程环境,而不是来自 site_config.json。由同一组 web 和 worker 进程服务的所有站点都会报告到同一个 Sentry 项目。
  • 两个开关。 DSN 为进程开启 Sentry SDK。系统设置中的复选框允许发送使用数据以改进应用程序(enable_telemetry)决定每个站点是否发送任何内容。
  • 错误跟随错误日志。 在服务器上,当 Frappe 写入一条错误日志时,事件会发送到 Sentry。如果你看到一条错误日志,Sentry 应该也有。
  • 站点通过名称区分。 每个事件都会将 Sentry 用户 ID 设置为站点名称,因此你可以在 Sentry 中按站点筛选。

设置

为 web 和 worker 进程设置 FRAPPE_SENTRY_DSN,重启它们,然后在每个应报告的站点上启用遥测。

变量 值 效果
FRAPPE_SENTRY_DSN 你项目的 DSN 必需。在 web 服务器和后台 worker 中启动 Sentry SDK,并在 desk 中加载浏览器 SDK。
SENTRY_TRACING_SAMPLE_RATE 0.0 到 1.0 之间的浮点数,例如 0.1 可选。为该比例的 web 请求和作业发送性能跟踪。
SENTRY_PROFILING_SAMPLE_RATE 0.0 到 1.0 之间的浮点数 可选。为该比例的被跟踪事务发送 CPU 性能分析。需要启用跟踪。
ENABLE_SENTRY_DB_MONITORING 任意非空值,例如 1 可选。将每个 SQL 查询记录为一个 span,并将每次数据库连接记录为一个面包屑。
  1. 在 Sentry 中创建一个项目(平台:Python)并复制其 DSN。
  2. 将这些变量添加到 Frappe web 服务器(gunicorn)以及每个 bench worker 进程的环境中。使用 supervisor 时,使用每个程序的 environment= 行。使用 Docker 时,在后端和 worker 容器上设置它们。
  3. 重启进程。Frappe 仅在进程启动时读取这些变量。
  4. 在每个站点上,打开系统设置并勾选允许发送使用数据以改进应用程序。从 CLI 执行:bench --site <site> execute frappe.db.set_single_value --args "['System Settings', 'enable_telemetry', 1]"</site>。
  5. 在浏览器中重新加载 desk,以便加载浏览器 SDK。

如果设置了 DSN 但站点的 enable_telemetry 处于关闭状态,该站点不会发送任何内容:没有服务器事件,没有浏览器事件,并且 desk 不会加载 SDK。

捕获的内容

Sentry 会获取开发者可以在代码中修复的错误。Frappe 有意向用户显示的错误则被排除在外。

来源 发送到 Sentry 不发送
Web 请求 以 HTTP 5xx 结束的未捕获异常。在开发者模式下,Frappe 记录的每个错误。 frappe.ValidationError、frappe.PermissionError 及其子类(例如 frappe.throw() 消息、必填字段错误)。正常的 4xx 响应。
后台作业 失败并写入错误日志的作业。Worker 在每个作业后刷新事件。 与 web 请求相同的异常过滤器。
frappe.log_error() 来自应用代码的每次调用,如果有活动异常则作为异常事件,否则作为带有标题和回溯的消息。 错误日志插入本身失败的调用。
Desk(浏览器) /desk 上的未捕获 JavaScript 错误和未处理的 promise 拒绝。 JS 中由 frappe.throw() 引发的错误。门户和网站页面:SDK 仅在 desk 中加载。会话跟踪已关闭。

服务器 SDK 运行时关闭了 Sentry 的默认集成。它不会挂钩 Python logging,因此 frappe.logger().error(...) 不会创建 Sentry 事件。来自 CSSUTILS 日志记录器的事件会被丢弃。

发布版本是 Frappe 版本(服务器上为 frappe.__version__,浏览器中为 frappe.boot.versions.frappe),因此你可以看到是哪个版本引入了错误。

性能监控

设置 SENTRY_TRACING_SAMPLE_RATE 以在 Sentry 中查看缓慢的端点和作业。在繁忙的站点上从低值开始(0.01 到 0.1),因为每个被跟踪的请求都会作为事务计费。

  • Web 请求。 Frappe 将 WSGI 应用包装在 Sentry 的中间件中。事务按端点命名:RPC 调用为 /api/method/<dotted.path></dotted.path>,否则为 URL 路径。
  • 后台任务。事务以任务的方法命名。计划任务则使用调度器任务类型。任务上下文包含任务 ID、其参数、是否为计划任务,以及它在队列中的等待时间(秒)。
  • SQL span。启用 ENABLE_SENTRY_DB_MONITORING 后,每次 frappe.db.sql() 调用都会变成一个带有查询文本的 span,并且每次数据库连接都会添加一条面包屑。这就是你查找 N+1 查询的方法。它也会给每次查询增加开销,所以在繁忙的生产环境中启用前请先测量。
  • 性能分析。SENTRY_PROFILING_SAMPLE_RATE 是被追踪事务的一部分,因此 0.5 在追踪率为 0.1 时会分析 5% 的请求。

当这三个变量中的任意一个被设置时,Frappe 还会在每个请求和任务开始时标记作用域(事务名称、站点、用户、追踪 ID)。这仅在启用了遥测的站点上发生。

数据与隐私

错误事件可能包含业务和个人数据。请像对待生产数据库一样对待你的 Sentry 项目,并限制可以读取它的人员。

Sentry 中的字段 内容
用户 ID 站点名称,例如 “erp.example.com”
标签 frappe_user 已登录用户,例如 “jane@example.com”,或 “Guest”
标签 frappe_trace_id X-Frappe-Request-ID 标头(如果请求带有的话)
请求 URL、方法、查询字符串和标头。SDK 会剥离 Cookie、Authorization 和其他敏感标头。不包含客户端 IP。
上下文 “JSON Body” / “Form Data” 完整的请求负载,未经过滤。这可能包含文档数据和个人数据。
额外 job 任务 ID、方法、所有任务参数、队列等待时间
堆栈跟踪 带有局部变量的代码帧(Sentry SDK 默认)
消息 对于 frappe.log_error() 调用:Error Log 标题和回溯
模块、argv 已安装的 Python 包和进程命令行
SQL span 仅查询文本。查询值不会被发送。

遥测复选框不涵盖性能数据。选择性加入检查针对错误事件和作用域标签运行。追踪、性能分析和 SQL span 会为进程中的每个请求和任务启动。如果一个 bench 托管了未选择加入的站点,请不要在其上设置这三个性能变量。

对于其他你绝不能存储的内容(例如包含国民身份证号或银行详细信息的字段),请使用 Sentry 的服务器端数据擦除功能。

故障排除

症状 检查项
完全没有事件 FRAPPE_SENTRY_DSN 是否在运行进程的环境中,而不仅仅是你的 shell 中?设置后你是否重启了 Web 和工作进程?站点的 enable_telemetry 是否开启?
存在 Error Log,但 Sentry 中没有任何内容 该异常是否是 ValidationError 或 PermissionError 的子类?这些会被有意跳过。
Sentry 中有 500 错误,但没有 Error Log 几秒后再检查。请求错误会以延迟插入的方式写入 Error Log。
缺少任务错误 检查工作进程是否具有 DSN。工作进程在启动时会启动自己的 SDK。
没有浏览器错误 打开 desk 并检查浏览器控制台中 frappe.boot.sentry_dsn 是否有值。如果为空,则站点的遥测已关闭。Content-Security-Policy 规则必须允许向你的 Sentry 主机发起请求。
事务过多或 Sentry 账单过高 降低 SENTRY_TRACING_SAMPLE_RATE 或将其移除,然后重启。
缺少来自脚本或 bench console 的错误 只有 Web 服务器和 bench worker 会启动 SDK。其他 CLI 命令不会向 Sentry 报告。

代码位于 frappe/utils/sentry.py、frappe/app.py(Web)、frappe/utils/background_jobs.py(工作进程)、frappe/utils/error.py(log_error 钩子)和 frappe/public/js/sentry.bundle.js(浏览器)中。

为什么我无法导出数据或看不到Excel选项?

Frappe 让你几乎可以从任何列表中把记录导出为 Excel 或 CSV。如果你找不到导出操作、在数据导出工具中看不到你需要的文档类型,或者导出的文件打开后只有列标题没有数据行,这几乎总是因为缺少权限或存在生效的筛选条件,而不是 bug。无论你是从销售工作区导出客户列表、从财务导出采购发票列表,还是导出任何其他文档类型,情况都一样。

在哪里找到导出

有两种导出数据的方式。两种都可以选择 CSV 或 Excel。

从列表或报表视图导出

  1. 打开列表,例如客户。
  2. 用复选框选中一条或多条记录(或用表头行的复选框全选)。
  3. 点击操作 → 导出。
  4. 在导出数据对话框中,将文件类型设为 CSV 或 Excel,选择要包含的字段,然后点击导出。

从数据导出工具导出

  1. 进入数据导出(使用搜索栏,或从模块侧边栏选择数据 → 数据导出)。
  2. 在选择文档类型中,选择要导出的文档类型。
  3. 选择 CSV 或 Excel 作为文件类型。
  4. 选择要导出的字段,如有需要可添加筛选条件,然后点击导出。

为什么你可能看不到导出,或者看不到某个文档类型

两种导出方式都检查同一个角色权限:导出。角色需要对该文档类型有读取权限才能访问其记录,还需要导出权限才能下载它们。如果分配给你的任何角色都没有某个文档类型的导出权限:

  • 操作中的导出选项仍然会显示,但导出会因权限错误而失败。
  • 该文档类型根本不会出现在数据导出工具的选择文档类型字段中。该字段只列出你有导出权限的文档类型。

如果你在没有权限的情况下尝试导出,会看到:

You are not allowed to export <doctype> doctype
</doctype>

要从列表的报表视图导出,你的角色还需要对该文档类型有报表权限。没有它,报表视图本身就不可用,因此也就没有东西可以导出。

如何修复

系统管理员可以授予权限:

  1. 进入角色权限管理器。
  2. 选择文档类型,例如客户。
  3. 找到该用户所拥有角色对应的行,例如销售用户。
  4. 勾选导出。如果读取和报表尚未勾选,也一并勾选。
  5. 让用户刷新页面后重试。

如果该角色勾选了“仅限创建者”,用户只能导出自己创建的记录。如果他们还需要导出其他人创建的记录,请取消勾选。

为什么导出的文件只有列标题没有数据

如果下载的文件打开后只有一行标题、没有记录,说明导出执行了,但匹配到零行。这种情况发生在:

  • 列表或导出数据对话框中的筛选条件排除了所有记录。对话框的导出类型可以设为筛选后的记录,这样只会导出列表当前筛选条件匹配的内容。如果这些筛选条件太窄,或者指向不存在的值,就什么都匹配不到。
  • 用户权限对记录的限制比你预期的更严格。对某个关联字段设置的用户权限(例如将用户限制到某一个公司)会像作用于列表一样作用于导出。一条记录可能出现在列表中,但如果限制发生了变化,或者“仅限创建者”把你限制在非你创建的记录之外,它仍可能被排除在导出之外。

例如,如果你把客户列表筛选到一个没有客户的地区,并用筛选后的记录导出,下载的文件就只有 ID 列标题,没有数据行。

要修复它:

  1. 清除列表中的筛选条件,或将导出类型切换为所有记录,然后重新导出。
  2. 如果记录在列表中可见,但导出时仍然缺失,请检查角色权限管理器中你的角色是否勾选了“仅限创建者”,并检查用户权限中是否有针对你的用户的任何限制。

如何预防

  • 在导出大型列表之前,清除你不需要的筛选条件,或确认已应用的筛选条件仍然匹配你期望的记录。
  • 设置新角色时,对它需要导出的每个文档类型同时授予读取、报表和导出权限,这样该角色就不会出现部分记录可见但无法导出的情况。
  • 用户与权限
  • 数据导出
  • 编辑报表的导出/打印权限