如何在Mojolicious中获取准确的caller()及物理源码堆栈跟踪?
Mojolicious中获取准确caller()信息的解决方法
问题场景
在Mojolicious::Lite应用中使用caller(0)获取调用信息时,返回的文件路径、行号并非实际源码位置,不符合预期:
示例代码(/foo/bar/app.pl)
#!/usr/bin/env perl use Mojolicious::Lite; get '/' => sub { my $c = shift; my ($package, $filename, $line, $subroutine, $hasargs, $wantarray, $evaltext, $is_require, $hints, $bitmask, $hinthash) = caller(0); $c->render(text => 'p=' . $package . ',' . 'f=' . $filename . ',' . 'l=' . $line . ',' . 'm=' . $subroutine ); }; app->start;
实际输出
- 用
/foo/bar/app.pl daemon启动:
p=Mojolicious,f=/usr/local/share/perl5/Mojolicious.pm,l=202,m=main::__ANON__
- 用
morbo /foo/bar/app.pl启动:
p=Mojolicious,f=/usr/local/share/perl5/Mojolicious.pm,l=202,m=Mojo::Server::Sandbox::b89e17a47746799cd9250564e1c58531::__ANON__
期望输出
f=/foo/bar/app.pl l=7 m=main::__ANON__
解决方法
1. 遍历调用栈定位目标帧
Mojolicious会内部包装路由回调,caller(0)指向框架内部调用点,需遍历栈帧找到匹配源码文件的条目:
get '/' => sub { my $c = shift; my ($package, $filename, $line, $subroutine); my $level = 0; while (1) { ($package, $filename, $line, $subroutine) = caller($level++); last unless defined $filename; # 匹配你的应用文件路径 last if $filename eq '/foo/bar/app.pl'; } $c->render(text => 'f=' . $filename . "\n" . 'l=' . $line . "\n" . 'm=' . $subroutine ); };
2. 适配morbo沙箱模式
morbo会将代码加载到沙箱包中,子例程名带沙箱前缀,可通过正则修正:
get '/' => sub { my $c = shift; my ($package, $filename, $line, $subroutine); my $level = 0; while (1) { ($package, $filename, $line, $subroutine) = caller($level++); last unless defined $filename; last if $filename eq '/foo/bar/app.pl'; } # 替换沙箱子例程名为原始格式 $subroutine = 'main::__ANON__' if $subroutine =~ /::__ANON__$/; $c->render(text => 'f=' . $filename . "\n" . 'l=' . $line . "\n" . 'm=' . $subroutine ); };
原理说明
Mojolicious的路由回调由框架调度逻辑触发,caller(0)返回的是框架内部的调用位置。通过遍历调用栈跳过框架内部帧,即可定位到回调函数实际定义的源码帧,获取正确的文件路径、行号和子例程信息。
内容的提问来源于stack exchange,提问作者mvanle
相关产品推荐
相关产品推荐

