仕事で使える魔法のLAMP(41):エラーメッセージをどう扱うか?今回は、スクリプト実行時にエラーが発生したことを知らせるメッセージの扱い方を説明します。第37回から、PHPの実行時設定の中でもサーバ全体にかかわる設定、つまりphp.ini)で設定するものについて解説しています。初期設定のままにしておかない方が良いものを選んで、変更すべき理由と、変更する方法を解説してきました。前回までの3回では、リクエストデータの取り扱いに関する設定を解説しました。この設定を誤ると、思わぬところに脆弱な点ができてしまい、セキュリティの面で問題になってしまいます。今回のテーマは、エラーログ関係の設定項目です。PHPスクリプトを実行したとき、エラーが発生すると、エラーの内容を説明するメッセージがWebブラウザ上に現れてしまいます(図1)。スクリプトの文法エラーだけでなく、存在しない変数や関数へのアクセスといったバグを原因とするエラーでも、発生するとその内容を説明するメッセージが出てきます。メモリ不足のような例外が発生したときも同じことです。メモリが足りなくなると、エラーが発生し、このメッセージがそのままWebブラウザに表示される。クリックすると拡大エラー発生を知らせる警告は、アプリケーション開発中やテスト中には、アプリケーションの問題点を知り、バグを潰すために役立つ情報となりますが、実際の本番環境では表示すべきものではありません。Webアプリケーションの利用者が戸惑います。ファイルやディレクトリの構造、PHPアプリケーションの中身や、バグの存在など、攻撃者を喜ばせるようなヒントを与えることになります。セキュリティ上のリスクになり得るのです。エラーメッセージの出力を止める方法はあるのでしょうか。PHPでは、「display_errors」ディレクティブの設定を変更することで、エラー表示を出すか、あるいは止めるかを選択できます。開発中は設定値をOn(エラーを表示する)にしておいたとしても、本番環境では必ずOff(エラーを表示しない)に変えましょう。以降の説明では、本番環境を想定してdisplay_errorsディレクティブの設定をOffにしたものとして説明を続けます。余計なヒントは与えないこれまでも、第38回などで、サーバ上で動作しているのがPHPスクリプトであることを隠すための設定方法をいくつか紹介しています。display_errorsの設定をOffにすることで、Webブラウザなど、表に現れる情報を調べても、動作しているのがPHPスクリプトであるとは分からなくなります。Server(以下Apache)の設定を変更して、拡張子を「.php」ではなく「.html」などにしたPHPファイルを実行可能にすること。Apacheの設定ディレクティブ「ServerTokens」の設定を「Prod」にして、ApacheやPHPのバージョンを隠すこと。PHPの設定ではexpose_phpの設定をOffにして、ヘッダ情報に、PHPを使っているという事実を入れないこと。先ほど説明した、display_errorsの設定をOffにして、余計なヒントになりかねないエラーメッセージの表示を止めることです。display_errorsににている部分を制御するディレクティブに、「display_startup_errors」があります。display_errorsはPHPスクリプト実行中のエラー表示の可否を決めますが、display_startup_errorsはスクリプト実行前の、初期化時に発生するエラーに関係するディレクティブです。この設定も、余計なヒントを与えないために、本番環境ではOffにしておくべきでしょう。ちなみに、初期化時に発生するエラーとは、前回に例として挙げたリクエストデータのサイズ超過などによって発生するエラーです。前回、POSTメソッドで受け取るリクエストデータの最大サイズを指定する「post_max_size」ディレクティブについて解説しました。設定値を超えたデータをサーバに送りつける例もお見せしました。以下のように、エラーの発生を知らせるメッセージは何も出てきませんでした。http://www3026ub.sakura.ne.jp/print_post.phpArray()これは、display_startup_errorsディレクティブの設定値がOffになっているからです。このディレクティブの設定値は初期状態がOffになっています。そこで、試しに設定をOnに変更して同じテストを実行してみましょう。リクエストデータの長さが制限を超えていることを知らせるエラーメッセージが返ってきます。先に説明したように、この設定は標準ではOffになっていますが、アプリケーション開発時やテスト時はOnに変えておくべきです。初期化時に問題が起きたとしても、手掛かりがないと、問題解決もままなりません。この設定は本番環境ではOffにしましょう。直前にお見せした例では、コンソールからcurlコマンドを使って大きなリクエストデータを送りました。帰ってきたエラーメッセージをよく見ると、HTML形式になっています。ブラウザで表示することを想定しているのです。これは、ApacheにPHPを組み込んでいるときに見られるエラーメッセージです。コマンドライン版のPHPではHTMLではなくテキストのエラーメッセージになります。ちなみに、ApacheにPHPを組み込んだときでも「html_errors」ディレクティブの設定をOffにすると、エラーメッセージがテキスト形式になります。エラー情報はログに蓄積エラーの情報は、Webブラウザに表示するだけでなく、ログに記録することもできます。Webブラウザへの表示はその場限りで、Webブラウザを閉じてしまえば情報は消えてしまいます。ログであればすべての情報を蓄積しておいて、あとから確認することができます。開発環境だけでなく、本番環境でも、ログ記録の機能は利用すべきです。本番環境ではdisplay_errorsをOffにしますので、エラーが発生したとしても、その事実をつかむにはログに情報を記録させるように設定するしかありません。ログ記録機能は「log_errors」ディレクティブをOnにすると動き出し、Apacheのエラーログファイルを記録するようになります。Apacheの「ErrorLog」ディレクティブで指定しているファイルです。図1で示したようなエラーなら、その情報は次のような形式でエラーログファイルに残ります。2スクリプト実行前の初期化時に発生するエラーの情報は、前述の通り標準の状態ではWebブラウザには表示されませんが、エラーログには残ります。post_max_sizeの指定を超えたサイズのデータをサーバが受け取ると、次のようなログが残ります。0Apacheのエラーログへの記録は、PHPではなく、PHPからログ内容を受け取ったApacheが処理しています。そのためログの記録先は、Apacheの設定によって決まっているファイルになりますが、他のファイルに記録することもできます。「error_log」ディレクティブにファイル名を指定すると、PHPによって指定ファイルにログが出力されます。ここで注意しなければならないのは、Apacheのエラーログファイルはroot権限で書き込むのに対し、error_logで指定したログファイルにはApacheの実行権限でアクセスするということです。ログファイルを書き込むディレクトリのオーナーやパーミッションを正しく設定しておかないと、ファイルにログを書き込めませんので、十分注意してください。筆者の個人的な意見を言うと、素直にApacheのエラーログを使う方が良いと思います。バーチャルホストごとに設定できることやファイルへのパーミッション設定の手間を考えると、標準設定で使う方が楽だと思います。Apacheのエラーログにあまりに多くのログが残るような場合は、PHPのログは分離したいと考える方もいるでしょう。そのときはphp.iniで設定するのではなく、各バーチャルホストごとにApacheのディレクティブ「PHP_Value」(第36回参照)を利用して、バーチャルホストごとにログファイルを設定するとよいでしょう。ディレクトリごとにログファイルを分けることもできます。Warning」になっているものの2種類があることが分かります。UNIX/Linuxのsyslogにはerror、warning、noticeなど、エラーの深刻さを示すレベルがあることをご存じの読者も多いと思いますが、PHPのログにも、同じようなレベル分けがあるのです。いえ、PHPの場合はレベルというよりも、種類といった方が適切です。syslogのレベルは上下関係が成立していて、例えば「warning以上のエラー」というような表現で設定したり、技術者同士コミュニケーションしたりできますが、PHPではそうは行きません。それぞれのログの種類に上下関係はないのです。PHPがどのような種類のログを残すのかということは、PHPのマニュアルで確認できます。「エラー処理」の中の「定義済み定数」の部分をご覧ください(図2)。それぞれのエラーの種類は、定義済みの定数として表すようになっています。「E_ERROR」は1、「E_WARNING」は2、「E_PARSE」は4、という具合です。この値を指定して、どの種類のログを出力させるかを設定できます。PHPがログに残すメッセージには4種類ある。定数の値が、1、2ときて次が3ではなく4になっていますが、これはビットフィールドで保持する値であるからです。1、2、4のそれぞれを2進数にすると、1、10、100となります。1になっている部分(けた)が、ログの種類を表しているのです。「E_PARSE」と「E_ERROR」を選択したと考えてください。それぞれを指し示す2進数のけたを1にすると、101になります。これは、十進数にすると5です。5を設定すると、この2つを指定したことになります。いった値の使い方を、ビットフィールドと呼びます。この2進数の計算は、ビット演算子で表現できます。E_ERROR」のようにします。すべての種類のログを出力したいときは、定数をすべて「|」で連結すれば良いのですが、PHPはかなり多くの種類のログを出力するので、ビット演算ですべてを表現するのは面倒です。そこで別途、「E_ALL」という、すべての種類のログを表す定数が用意されています。5.3系では、正確には「E_ALL」はすべてのログではなく「E_STRICT」が除いたものを指します。E_STRICT」としなければなりません。5.4系になると「E_ALL」だけですべてのログになります。開発中はすべてのログを表示するようにしておき、ささいな警告も見逃さない環境を作るのが理想です。最終的には警告が出なくなるように開発することで、バグをつぶせます。既存のアプリケーションを利用するときなどは、警告が大量に出ることがあります。このような場合は、ログ出力を抑止したいと考えることもあるでしょう。いったときは「E_ALL」からいくつかの種類のエラーを除いて、エラーを出力させるようにするのです。この指定もビット演算子でできます。~E_NOTICE」とすると「E_ALL」から「E_NOTICE」を取り除くという意味になります。~E_NOTICE」とします。「|」と「&」では、「&」の方が優先順位が高いので、カッコで優先順位を変えなくてはならないためです。E_NOTICEと指定すると、エラーとは言えないが、「バグにつながる可能性がある」という程度の警告も出します。出力が大量になるようなら、上記の手法で絞り込むと良いでしょう。まとめここまで説明してきた内容を反映させると、php.iniの記述は次のようになります。メールマガジンをご購読ください。申し込みページへITmediaはアイティメディア株式会社の登録商標です。