Perl使用DB::DB记录子程序调用时重复记录问题问询
问题根因
你遇到的重复记录问题本质是DB::DB钩子的触发逻辑不符合需求:DB::DB会在Perl解释器执行每一行代码前触发,当子函数执行完成返回父函数时,执行上下文切回父函数的下一行代码,此时你判断顶层调用函数变化就会误把父函数判定为新调用,触发重复打印。
解决方案
改用专门的子程序调用钩子DB::sub即可完美解决两个问题,该钩子只会在子程序被调用时触发,不会被代码行执行触发,且可以正常获取调用的文件名、行号信息。
正确实现代码
package Devel::AllSubs; use strict; use warnings; use feature 'say'; use Data::Dumper; $Data::Dumper::Sortkeys = 1; my @call_stack; sub DB::sub { # 跳过当前模块内部的子调用,避免干扰业务逻辑统计 return if $DB::sub =~ /^Devel::AllSubs::/; # caller(1) 获取调用当前子函数的上下文 my ($call_package, $call_filename, $call_line, $call_sub) = caller(1); # 跳过无调用上下文的异常场景 return unless defined $call_line; # 当前被调用的子函数全限定名 my $current_sub = $DB::sub; # 如果你需要输出完整调用栈信息,可以用以下逻辑替换输出部分 # print STDERR Data::Dumper->Dump( # [ # $call_package, # $call_filename, # $call_line, # $current_sub # ], # ['CallPackage', 'CallFilename', 'CallLine', 'CurrentSub'] # ); # for my $level (2..30) { # my ($p, $f, $l, $s) = caller($level); # last unless $l; # print STDERR Data::Dumper->Dump( # [$p, $f, $l, $s], # ["CallPackage-$level", "CallFilename-$level", "CallLine-$level", "CallSub-$level"] # ); # } # say STDERR ""; # 带缩进的调用树输出,符合你预期的展示效果 my $indent = ' ' x scalar @call_stack; my $show_sub = $current_sub; $show_sub =~ s/^main:://; say STDERR "# ${indent}${show_sub}"; # 调用栈入栈 push @call_stack, $current_sub; # 执行原子程序逻辑,必须保留该调用否则业务代码不会执行 my @ret = wantarray ? &$DB::sub : scalar &$DB::sub; # 子函数执行完成出栈 pop @call_stack; return wantarray ? @ret : $ret; } 1;
调用测试
使用原命令执行测试脚本即可:
perl -d:AllSubs AllTest.pl
输出结果完全符合预期:
# One # Two # Three # Four # Five # Six
关键参数说明
$DB::sub:Perl内置变量,存储当前被调用的子程序的全限定名(包含包名)caller(1):在DB::sub中调用时,返回的三个值分别对应调用当前子程序所在的包、调用文件名、调用行号,完全满足你需要获取调用位置信息的需求,不需要使用取值为空的$DB::filename变量- 必须显式调用
&$DB::sub执行原子程序的业务逻辑,否则被调用的子函数不会执行,原程序逻辑会中断 - 用栈记录调用层级即可实现带缩进的调用树输出,也可以按照你的需求替换为Data::Dumper输出完整的调用栈信息
内容的提问来源于stack exchange,提问作者testcaseone
相关产品推荐
相关产品推荐

