Sibainu Relax Room

愛犬の柴犬とともに過ごす部屋

PHPエラーログ

おれは気分がいいぞと飛行機みみになっている柴犬です。

概要

PHP におけるエラー・警告などを記録するにはどうすればよいのかと疑問に思いしらべてみました。

エラーなどを捉えるには set_error_handler 関数を使うことが分かりました。

その記録には、set_error_handler 関数にコールバック関数を渡しそこに記録の処理をすればよいことが分かりました。

以上のことをまとめましたので投稿します。

ユーザー定義のエラーハンドラ関数を設定する場合、set_error_handler 関数を使います。説明の公式HPは次のとおりです。

https://www.php.net/manual/ja/function.set-error-handler.php

今回、お世話になった本です。

fopen 関数を使った方法

メインファイルを errorlog.php として、別ファイル errlog.php の中にエラーハンドラ「myErrorHandler」を記述しています。

ファイル名 errorlog.php

copy

<?php
require_once 'errlog.php';

// 定義したエラーハンドラを設定する
$old_error_handler = set_error_handler("myErrorHandler");

// エラー処理のテスト用関数
function scale_by_log($vect, $scale)
{
  if (!is_numeric($scale) || $scale <= 0) {
    trigger_error("log(x) for x <= 0 is undefined, you used: scale = $scale", E_USER_ERROR);
  }

  if (!is_array($vect)) {
    trigger_error("Incorrect input vector, array of values expected", E_USER_WARNING);
    return null;
  }

  $temp = array();
  foreach ($vect as $pos => $value) {
    if (!is_numeric($value)) {
      trigger_error("Value at position $pos is not a number, using 0 (zero)", E_USER_NOTICE);
      $value = 0;
    }
    $temp[$pos] = log($scale) * $value;
  }

  return $temp;
}

// エラーを発生します。まず、数値でない項目が混ざった配列を定義します。
echo "vector a\n";
$a = array(2, 3, "foo", 5.5, 43.3, 21.11);
print_r($a);

// 二番目の配列を生成します。
echo "----\nvector b - a notice (b = log(PI) * a)\n";
/* Value at position $pos is not a number, using 0 (zero) */
$b = scale_by_log($a, M_PI);
print_r($b);

// 配列の代わりに文字列を渡しており、問題を発生します。
echo "----\nvector c - a warning\n";
/* Incorrect input vector, array of values expected */
$c = scale_by_log("not array", 2.3);
var_dump($c); // NULL

// ゼロまたは負数の対数が定義されないという致命的なエラーを発生します。
echo "----\nvector d - fatal error\n";
/* log(x) for x <= 0 is undefined, you used: scale = $scale" */
$d = scale_by_log($a, -2.5);
var_dump($d); // ここには到達しません
?>

ファイル名 errlog.php

copy

<?php
// エラーハンドラ関数
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
  if (!(error_reporting() & $errno)) {
    // error_reporting 設定に含まれていないエラーコードのため、
    // 標準の PHP エラーハンドラに渡されます。
    return;
  }

  // $errstr はエスケープする必要があるかもしれません。
  $errstr = htmlspecialchars($errstr);

  switch ($errno) {
    case E_USER_ERROR:
      $errnostr = 'エラー';
      break;

    case E_USER_WARNING:
      $errnostr = '警告';
      break;

    case E_USER_NOTICE:
      $errnostr = '注意';
      break;

    default:
      $errnostr = '不明なエラー';
      break;
  }
  writelog($errnostr, $errstr, $errfile, $errline);
  /* PHP の内部エラーハンドラを実行しません */
  return true;
}

function writelog(string $errnostr, string $errstr, string $errfile, string $errline)
{
  $dateStr = date('Y-m-d');
  $logfile = 'error' . $dateStr . '.log';
  if (file_exists($logfile)) {
    //何もしません
  } else {
    $headname = array('日時', 'スクリプト名', 'ブラウザOS', '移動元', 'エラー文', 'エラー内容', 'ファイル名', 'エラー行');
    touch($logfile);
    $file = fopen($logfile, 'a');
    flock($file, LOCK_EX);
    fwrite($file, implode("\t", $headname) . "\n");
    flock($file, LOCK_UN);
    fclose($file);
  }

  $data[] = date('Y/m/d H:i:s');
  $data[] = $_SERVER['SCRIPT_NAME'];
  $data[] = $_SERVER['HTTP_USER_AGENT'];
  $data[] = @$_SERVER['HTTP_REFERER'];
  $data[] = $errnostr;
  $data[] = $errstr;
  $data[] = $errfile;
  $data[] = $errline;
  $file = fopen($logfile, 'a+') or die('ログを記録できませんでした');
  flock($file, LOCK_EX);
  fwrite($file, implode("\t", $data) . "\n");
  flock($file, LOCK_UN);
  fclose($file);
}

ファイルの中を見てみる

一応目的を達成しているようです。

ただ、面白いのはスクリプト名とファイル名です。フォルダーの仕切りの記号が違います。

error_log関数を使う

ログファイルを fopen で開いて書き込み中、他のアクセスが原因のエラーのログが書き込めないという問題があります。

ですので、error_log 関数で書き込むことにします。error_log 関数のリファレンスを読んでも fopen のような弊害の記事がないので大丈夫と推測しました。

error_log 関数の公式HPは次のとおりです。

https://www.php.net/manual/ja/function.error-log.php

コードの見直しは、set_error_handler 関数のリファレンスの中の例を参考にしました。

copy

<?php
// エラーハンドラ関数
function myErrorHandler($errno, $errstr, $errfile, $errline)
{

  if (!(error_reporting() & $errno)) {
    // error_reporting 設定に含まれていないエラーコードのため、
    // 標準の PHP エラーハンドラに渡されます。
    // ログの書き込みの対象になりません。
    return;
  }

  // $errstr はエスケープする必要があるかもしれません。
  // とありますのでエスケープします。
  $errstr = htmlspecialchars($errstr, ENT_QUOTES || ENT_HTML5, 'UTF-8');

  switch ($errno) {
    case E_USER_ERROR:
      $errnostr = 'エラー';
      break;

    case E_USER_WARNING:
      $errnostr = '警告';
      break;

    case E_USER_NOTICE:
      $errnostr = '注意';
      break;

    default:
      $errnostr = '不明なエラー';
      break;
  }
  // 書き込み
  writelog($errnostr, $errstr, $errfile, $errline);

  /* PHP の内部エラーハンドラを実行しません */
  // return true;
  // ログの書き込みのみしたいので次のようにします。
  return;
};

function writelog(string $errnostr, string $errstr, string $errfile, string $errline)
{
  $dateStr = date('Y-m-d');
  $logfile = dirname(__FILE__) . '/error' . $dateStr . '.log';
  if (file_exists($logfile)) {
    //何もしません
  } else {
    $headname = array('日時', 'スクリプト名', 'ブラウザOS', '移動元', 'エラー文', 'エラー内容', 'ファイル名', 'エラー行');
    touch($logfile);
    error_log(implode("\t", $headname) . "\n", 3, $logfile);
  }

  $data[] = date('Y/m/d H:i:s');
  $data[] = $_SERVER['SCRIPT_NAME'];
  $data[] = $_SERVER['HTTP_USER_AGENT'];
  $data[] = $_SERVER['HTTP_REFERER'] ?? 'データがありません';
  /*
  if (!isset($_SERVER['HTTP_REFERER'])) {
    $data[] = 'データがありません';
  } else {
    $data[] = $_SERVER['HTTP_REFERER'];
  };*/
  $data[] = $errnostr;
  $data[] = $errstr;
  $data[] = $errfile;
  $data[] = $errline;
  error_log(implode("\t", $data) . "\n", 3, $logfile);
}