Screen で CGI のデバッグ。

先日のエントリで、

GNU screen を使い始めて数ヶ月が経ち、ようやく慣れてきました。...

という id:naoya さんの質問に回答してみた、と書きました。
そのときに emacs-w3m で直接回答することができませんでしたと、ちょっと拗ねながら書いたのですが、naoya さんから謝られてしまいました。いえいえこちらこそすみません、我儘言いまして。気にしないで下さい。
;; と言いながら少し期待してたり……

私が回答した後も、色々な Tips が書き込まれている様です。
私も参考にさせて頂きます。

この質問で GNU Screen の Tips を思い返してみたとき、以前に何処かで知ったのだけど詳細が思い出せなかった件を思い出しました。(変な日本語だ……)

それは、Screen のセッション切り離しの機能を利用して CGIデバッグを行なうという Tips です。もう何処で見たのか、どなたが書かれていたのか、そもそもいつ頃の話だったのかすら思い出せないのですが。
何か別件の調べものをしていて目に留まったんだと思います。何処かの ML のアーカイブか、掲示板の様なところだったと思いますが。そのときは「ふーん、便利そうだなあ」位で一応、頭には入れたのですが、どこかに落として来てしまったんでしょう。
とにかく、Screen を使って CGI の実行時にセッションを切り離し、その後、Screen のセッション接続機能を利用して terminal から接続し、デバッグを行なうというものだったと記憶しています。

最近では CatalystRoR など、Web アプリケーションのフレームワークでは、Internal Server Error が起きるとスタックトレースなどを綺麗に出力してくれます。
しかし、Old Type な Perl 使いとしては、慣れ親しんだ perldb のインタフェースでインタラクティブデバッグできるのに越したことはないのでした。それは Ruby でも同様です。
また、CGIperl script にコマンドラインからの起動でも CGI と同様に動作する様に引数処理などを加えておいてデバッグすることは可能ですが、環境変数など、面倒なことが多いですよね。

実は、この Screen を利用したデバッグについて、今年の夏頃に思い出して調べてみたんですが、bookmark もしていないし、自分の tips notes にも無く、grep しても見付からず。
Google さんに訊いてみても見付かりませんでした。

で、今回、情報を探し出すのは諦めて、自分で少し考えてみました。
うろ覚えですが記憶にあるのは、

  • CGI として screen 上で perldb (または ruby -r debug) を動かす。
  • screen は起動したらすぐさま detach する。
  • terminal から attach してデバッグする。

ということです。これらのピースを正しく組み合わせられれば良い筈なのです。

先ず、CGI として screen 上で perldb を動かすなら、

$ cat test.pl
#!/usr/bin/perl
#
print "This script is Test script of perldb.<br>\n";
print "<br>\n";

とかいう perl script を書いておいて、それをキックするための、

$ cat test.cgi
#!/bin/sh
echo "Content-type:text/html"
echo ""
echo "Test!! [${SCRIPT_NAME}]<br>"
/usr/bin/screen /usr/bin/perl -d ./test.pl
echo "*** Executed perldb.<br>"

等としてやれば良いですね。

しかし、これでは、

  Must be connected to a terminal.

などと叱られてしまい、実行できません。

そこで、第二のポイントである、

  • screen は起動したらすぐさま detach する。

です。
Screen の man page を良く読むと、

-d -m
    screen を "detached" モードで起動する。新たなセッションが生成されるが、その
    セッションへアタッチしない。これはシステムのスタートアップスクリプトで便利
    である。

と書かれていて、起動時に detach する様に指示できることが判ります。
これを利用すれば良いのでは、ということで、CGI の sh script を、

$ cat test.cgi
#!/bin/sh
echo "Content-type:text/html"
echo ""
echo "Test!! [${SCRIPT_NAME}]<br>"
/usr/bin/screen -d -m /usr/bin/perl -d ./test.pl
echo "*** Executed perldb.<br>"

等としてやると、何事も無かった様にレスポンスがブラウザに返ってきます。
しかし、perl script が表示する筈の `This script is Test script of perldb.' はレスポンスに含まれません。
ps コマンドでプロセスを見てみると、

$ ps -ef | grep -i screen | grep -v grep
root     22518     1  0 06:28 ?        00:00:00 /usr/bin/SCREEN -d -m /usr/bin/perl -d ./test.pl

と、screen 上で perldb が動作しているのが判ります。
そう、perl が debug mode で動作しているために perl script の実行結果がレスポンスに含まれなかったのですね。
つまり、意図した動作をしているということになります。
ここで、

/usr/bin/screen -D -m /usr/bin/perl -d ./test.pl

としておくと、この時点ではブラウザにサーバからのレスポンスは返って来ず、待たされたままになります。この違いは Screen のマニュアルページを見て下さい。

後は、

です。
これは、screen を起動することが可能な端末から、

screen -r apache/22518

(22518 は、ps で得られた SCREEN の pid) とすれば attach できます。
ここで、pid は、`pid.tty.host' な形式で指定しないとならない場合もある様です。この辺りはマニュアルページを見て下さい。

ここ、少しはしょって書きましたが、screen では、他ユーザのセッションに接続することができる様な設定が可能です。
CGI で実行されたプロセスは、通常、httpd のアカウントで動作しますので、一般ユーザが接続するためには、予め、それが可能な様に設定しておく必要があるのです。

そのためには、

  • screen のバイナリが suid されていること。
  • (.screenrc で) multiuser on が指定されていること。
  • (.screenrc で) acladd USERS が指定されていること。(USERS は接続を許可するユーザ名をカンマで区切ったリスト)

が必要です。

これらの設定を行なっておけば、上記のコマンドで CGI として起動されたスクリプトデバッグすることが可能になります。
ここでは perl で書きましたが、screen の配下で起動するインタプリタruby -r debug とすれば ruby script のデバッグも可能です。

.screenrc は httpd (ここでは apache でしたが) が CGI プロセスを実行するユーザの ~/.screenrc か、/etc/screenrc ということになりますが、セキュリティ面には十分にご注意下さい。私は apache の ~/.screenrc を上記の設定にしましたが、通常は置いておかずに debug を行なうときだけにしておこうと思っています。

この方法の難点は、CGI のレスポンスがブラウザに返らないことです。標準出力は perldb が喰ってしまいますので。そのため、画面構成が複雑で、その表示内容の確認が難しいとか、画面に出力される内容がデバッグに大きく影響する場合などは止めた方が良いかもしれませんね。

さて、はてなでは perl でシステムを組まれておられる様ですので、結構使えるのではないかと思って書いてみたのですが、ひょっとしたらもう同じこと、もしくはもっと賢い方法を実現されているのかもしれません。それでしたら賢い方法を教えて下さい。;-)

また、今回の方法は私が記憶を頼りに何とか動かしたもので、自分自身、この方法による実績はこれまでにありません。色々と不備があることは考えられますので、このエントリを見て同じことをされる方がいらっしゃったら、自己責任でお願いしますね。

あと、このエントリを見て、この話を書いたのは私だ、とか、この記事知ってるぞ、とか、これじゃダメだぞ、こうしとけ、とかありましたらコメント頂ければありがたいです。

因みに、私は大きく Emacs に依存しているので、普段は perldb も Emacs から実行しています。なので、Screen で perldb を使うだけだとちょっと使い辛いのは否めません。
起動済みの perldb のプロセスに Emacs から attach できれば良いのですが、サブプロセス以外のプロセスと直接しゃべる方法を知りません。
それができれば Emacs 上から debug できるのではないかと考えてはいるのですが。

と書いておくと誰かが出来る様にしてくれるかなあ…… なんてね。