跳转到主要内容

使用 Lua 调试器调试 Lua 脚本

KeyDB 包含一个完整的 Lua 调试器,可以用来简化编写复杂 KeyDB 脚本的任务。

KeyDB Lua 调试器,代号 LDB,具有以下重要特性:

  • 它使用服务器-客户端模型,因此它是一个远程调试器。KeyDB 服务器充当调试服务器,而默认客户端是 keydb-cli。不过,其他客户端也可以通过遵循服务器实现的简单协议来开发。
  • 默认情况下,每个新的调试会话都是一个派生(forked)会话。这意味着在调试 KeyDB Lua 脚本时,服务器不会阻塞,并且可用于开发或并行执行多个调试会话。这也意味着在脚本调试会话结束后,更改会被**回滚**,这样就可以使用与前一个调试会话完全相同的 KeyDB 数据集再次重新启动新的调试会话。
  • 根据需要,还提供另一种同步(非派生)调试模式,以便可以保留对数据集的更改。在这种模式下,服务器在调试会话活动期间会阻塞。
  • 支持单步执行。
  • 支持静态和动态断点。
  • 支持将被调试的脚本日志记录到调试器控制台。
  • 检查 Lua 变量。
  • 跟踪脚本执行的 KeyDB 命令。
  • 美化打印 KeyDB 和 Lua 的值。
  • 无限循环和长时间执行检测,这会模拟一个断点。

快速入门#

开始使用 Lua 调试器的一个简单方法是观看 redis 的这个视频介绍(命令相同,但使用 keydb 代替 redis)。

重要提示:请确保避免使用您的 KeyDB 生产服务器调试 Lua 脚本。请使用开发服务器。另请注意,使用同步调试模式(非默认模式)将导致 KeyDB 服务器在整个调试会话期间阻塞。

要使用 keydb-cli 开始一个新的调试会话,请执行以下步骤:

  1. 用您偏好的编辑器在某个文件中创建您的脚本。假设您正在编辑位于 /tmp/script.lua 的 KeyDB Lua 脚本。

  2. 通过以下命令开始一个调试会话:

    ./keydb-cli --ldb --eval /tmp/script.lua

请注意,通过 keydb-cli--eval 选项,您可以向脚本传递键名和参数,用逗号分隔,如下例所示:

./keydb-cli --ldb --eval /tmp/script.lua mykey somekey , arg1 arg2

您将进入一个特殊模式,其中 keydb-cli 不再接受其常规命令,而是打印一个帮助屏幕并将未经修改的调试命令直接传递给 keydb。

唯一不传递给 KeyDB 调试器的命令是:

  • quit -- 这将终止调试会话。这就像移除所有断点并使用 continue 调试命令。此外,该命令将退出 keydb-cli
  • restart -- 调试会话将从头开始重新启动,**从文件重新加载新版本的脚本**。因此,一个正常的调试周期包括在进行一些调试后修改脚本,然后调用 restart 以便用新的脚本更改再次开始调试。
  • help -- 此命令被传递给 KeyDB Lua 调试器,它将打印一个命令列表,如下所示:
lua debugger> help
keydb Lua 调试器帮助
[h]elp 显示此帮助。
[s]tep 运行当前行并再次停止。
[n]ext step 的别名。
[c]continue 运行直到下一个断点。
[l]list 列出当前行周围的源代码。
[l]list [line] 列出 [line] 行周围的源代码。
line = 0 表示:当前位置。
[l]list [line] [ctx] 在此形式中 [ctx] 指定显示多少行
在 [line] 行之前/之后。
[w]hole 列出所有源代码。'list 1 1000000' 的别名。
[p]rint 显示所有局部变量。
[p]rint <var> 显示指定变量的值。
也可以显示全局变量 KEYS 和 ARGV。
[b]reak 显示所有断点。
[b]reak <line> 在指定行添加一个断点。
[b]reak -<line> 从指定行移除断点。
[b]reak 0 移除所有断点。
[t]race 显示回溯信息。
[e]eval <code> 执行一些 Lua 代码(在不同的调用帧中)。
[r]edis <cmd> 执行一个 KeyDB 命令。
[m]axlen [len] 将记录的 KeyDB 回复和 Lua 变量转储裁剪到 len 长度。
指定零作为 <len> 意味着无限制。
[a]abort 停止脚本的执行。在同步
模式下,数据集的更改将被保留。
您可以从 Lua 脚本中调用的调试器函数
keydb.debug() 在调试器控制台中产生日志。
keydb.breakpoint() 停止执行,就好像在
下一行代码中有一个断点一样。

请注意,当您启动调试器时,它将以**单步模式**启动。它将在执行脚本中实际执行操作的第一行之前停止。

从这一点开始,您通常调用 step 来执行该行并转到下一行。当您单步执行时,KeyDB 将显示服务器执行的所有命令,如下例所示:

* 在第 1 行停止,停止原因 = 单步跳过
-> 1 keydb.call('ping')
lua debugger> step
<KeyDB> ping
<reply> "+PONG"
* 在第 2 行停止,停止原因 = 单步跳过

<KeyDB><reply> 行显示了刚刚执行的行所执行的命令以及来自服务器的回复。请注意,这只在单步模式下发生。如果您使用 continue 来执行脚本直到下一个断点,命令将不会被转储到屏幕上以防止过多的输出。

终止调试会话#

当脚本自然终止时,调试会话结束,keydb-cli 返回其正常的非调试模式。您可以像往常一样使用 restart 命令重新启动会话。

停止调试会话的另一种方法是手动中断 keydb-cli,通过按 Ctrl+C。请注意,任何破坏 keydb-clikeydb-server 之间连接的事件也将中断调试会话。

当服务器关闭时,所有派生的调试会话都将被终止。

缩写调试命令#

调试可能是一项非常重复的任务。因此,每个 KeyDB 调试器命令都以不同的字符开头,您可以使用单个首字母来指代该命令。

例如,您可以只输入 s 而不是输入 step

断点#

添加和删除断点非常简单,如在线帮助中所述。只需使用 b 1 2 3 4 在第 1、2、3、4 行添加断点。命令 b 0 会移除所有断点。可以使用我们要移除断点所在的行号作为参数来移除选定的断点,但要在行号前加上减号。例如,b -3 会移除第 3 行的断点。

请注意,在 Lua 从不执行的行上添加断点(如局部变量声明或注释)将不起作用。断点会被添加,但由于脚本的这部分永远不会被执行,程序将永远不会停止。

动态断点#

使用 breakpoint 命令可以在特定行添加断点。但有时我们希望仅在发生特殊情况时才停止程序的执行。为此,您可以在您的 Lua 脚本中使用 keydb.breakpoint() 函数。当它被调用时,它会模拟在将要执行的下一行上设置一个断点。

if counter > 10 then keydb.breakpoint() end

此功能在调试时非常有用,这样我们就可以避免手动多次继续脚本执行,直到遇到给定条件。

同步模式#

如前所述,LDB 默认使用派生会话,并回滚脚本在调试期间对数据所做的所有更改。确定性在调试期间通常是一件好事,这样可以开始后续的调试会话,而无需将数据库内容重置为其原始状态。

然而,为了跟踪某些错误,您可能希望保留每个调试会话对键空间所做的更改。当这是一个好主意时,您应该使用 keydb-cli 中的一个特殊选项 ldb-sync-mode 来启动调试器。

./keydb-cli --ldb-sync-mode --eval /tmp/script.lua

请注意,在此模式下,KeyDB 服务器在调试会话期间将无法访问,因此请谨慎使用。

在这种特殊模式下,abort 命令可以中途停止脚本,并保留对数据集所做的更改。请注意,这与正常结束调试会话不同。如果您只是中断 keydb-cli,脚本将被完全执行,然后会话终止。相反,使用 abort,您可以在中途中断脚本执行,并在需要时启动新的调试会话。

从脚本记录日志#

keydb.debug() 命令是一个强大的调试工具,可以在 KeyDB Lua 脚本内部调用,以便将信息记录到调试控制台。

lua debugger> list
-> 1 local a = {1,2,3}
2 local b = false
3 keydb.debug(a,b)
lua debugger> continue
<debug> line 3: {1; 2; 3}, false

如果脚本在调试会话之外执行,keydb.debug() 完全没有效果。请注意,该函数接受多个参数,这些参数在输出中用逗号和空格分隔。

表和嵌套表会以正确的方式显示,以便于调试脚本的程序员观察值。

使用 printeval 检查程序状态#

虽然 keydb.debug() 函数可用于直接从 Lua 脚本内部打印值,但通常在单步执行或在断点处停止时观察程序的局部变量也很有用。

print 命令正是为此而生,它会从当前调用帧开始,向后追溯到之前的调用帧,直至顶层,进行查找。这意味着即使我们在 Lua 脚本的嵌套函数中,我们仍然可以使用 print foo 来查看调用函数上下文中 foo 的值。当不带变量名调用时,print 将打印所有变量及其各自的值。

eval 命令执行一小段 Lua 脚本,但**在当前调用帧的上下文之外**(在当前调用帧的上下文中求值在当前的 Lua 内部机制中是不可能的)。然而,您可以使用此命令来测试 Lua 函数。

lua debugger> e keydb.sha1hex('foo')
<retval> "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"

调试客户端#

LDB 使用客户端-服务器模型,其中 KeyDB 服务器充当调试服务器,通过 RESP 进行通信。虽然 keydb-cli 是默认的调试客户端,但任何 客户端 只要满足以下条件之一,都可以用于调试:

  1. 客户端提供用于设置调试模式和控制调试会话的本机接口。
  2. 客户端提供通过 RESP 发送任意命令的接口。
  3. 客户端允许向 KeyDB 服务器发送原始消息。