以前書いた「PHPでのログのとり方を考える」では、error_log()関数を使ったログ取りを書きました。
もう2年も前になりますが・・

なんか昔の自分のソースコードを見ると、初々しいと言うか頑張ってると言うか汚いというか・・
まぁ、いろんな感慨にふけったりと・・

組み込み関数もシンプルに書けるのでいいんですが、もっとバリエーションをガツンと出していきたいな ナンテね。。
そこでstaticなクラスとして作成してみようと思って書いてみました。
できるだけシンプルに使えるように気をつけながら・・


ログを取るクラス - Log.php
<?php
/**
* ログを取るクラス
*/
class Log
{
  protected static $_on= true; // 出力切替
  protected static $_prefix = '';
  protected static $_ext = '.log';
  protected static $_path = '';
  const MAXSIZE = 1; // ローテートサイズ[MB]

  /**
   * ログ出力をオフに
   */
  public static function off() {
    self::$_on= false;
  }
  /**
   * ログ出力をオンに(デフォルト)
   */
  public static function on() {
    self::$_on= true;
  }
  /**
   * メッセージログ出力
   */
  public static function info($s, $type="info") {
    if (self::$_on) {
      $file = self::_getfile($type);
      self::_rotate($type);

      // 日付付加
      $s = rtrim($s);
      if (preg_match('/\n|\r/', $s)) {
        $s = self::_dateline() . PHP_EOL
          . preg_replace('/(?:\r\n|\r|\n){1,}/', PHP_EOL, $s);
      } else {
        $s = "[" . self::_getdate() . "] " . self::_caller() . "\t" . $s;
      }
      if ($type === "dbg") {
        $s .= PHP_EOL
          . print_r(error_get_last(), true);
      }
      return file_put_contents($file, rtrim($s) . PHP_EOL, FILE_APPEND | LOCK_EX);
    }
  }
  public static function setpath($path) {
    self::$_path = realpath($path);
  }
  /**
   * エラーログ出力
   */
  public static function err($s) {
    return self::info($s, "err");
  }
  public static function setprefix($prefix) {
    self::$_prefix = $prefix;
  }
  protected static function _getdate($short=false) {
    date_default_timezone_set('Asia/Tokyo');
    return date($short ? 'Y-m-d' : 'Y/m/d H:i:s');
  }
  protected static function _dateline() {
    return str_pad(self::_getdate(), 56, "-", STR_PAD_BOTH);
  }
  protected static function _getfile($type, $rotate=false) {
    $file = rtrim(self::$_path, "/\\")
      . DIRECTORY_SEPARATOR
      . self::$_prefix
      . $type
      . ($rotate ? self::_getdate(true) : "")
      . self::$_ext;
    return $file;
  }
  /**
   * デバッグログ出力
   */
  public static function d($s='') {
    return self::info(print_r(debug_backtrace(), true), "dbg");
  }
  protected static function _rotate($type) {
    $file = self::_getfile($type);
    if (is_writable($file) && filesize($file) > 1024 * 1024 * self::MAXSIZE) {
      rename($file, self::_getfile($type, true));
    }
  }
  protected static function _caller() {
    global $argv;

    if (!empty($argv[0])) $caller = $argv[0];
    else if (isset($_SERVER['SCRIPT_NAME'])) $caller = basename($_SERVER['SCRIPT_NAME']);
    else $caller = 'Unknown';

    return $caller;
  }
}


実行スクリプト - test.php
<?php

// クラス定義読み出し
require_once '/path/to/Log.php';

// 保存先指定
Log::setpath("/path/to");

// メッセージログ出力
Log::info("保存したいメッセージを書く");


メッセージログ出力結果 - /path/to/info.log
[2012/01/14 23:42:02] test.php 保存したいメッセージを書く


ま、こんな感じでしょう。。
staticなクラスメソッドなのでインスタンス化しないで使える手軽さがあります。

保存するメッセージのところに、アクセスしてきた「ホスト名」「ユーザエージェント」「リファラー」等を入れると、アクセスログとして使えますね。

これまた以前に「PHPでIPアドレスを取得」で書いたものですが、realhost()関数を再利用できます。エコです。

Log::info(implode("\t", array(realhost(realaddr()), $_SERVER['HTTP_USER_AGENT'], $_SERVER['HTTP_REFERER'])));


こうやればできそうです。

統計や解析の材料に使えるのが、メッセージログといったところでしょうか。


では次、エラーログ出力の場合です。
処理の途中でエラーが出た場合に、どんなエラーが出たかを書いておいて後で確認する為に使います。
エラーの種類によっては処理を終了させる必要があるので、そんな時は終わらせる直前にどういったエラーが出たか判るように書いておきます。

実行スクリプト - test.php
<?php

// クラス定義読み出し
require_once '/path/to/Log.php';

// 保存先指定
Log::setpath("/path/to");

// エラーログ出力
Log::err("エラーメッセージを書く");


エラーログ出力結果 - /path/to/err.log
[2012/01/14 23:48:36] test.php エラーメッセージを書く


本来の使い方はifブロックに書くことになります。

if (条件) { // 成功したら
   // 処理継続
} else { // 失敗なら
  Log::err("○○条件エラー");
  exit; // 終了
}


エラーログはどんな使い方をするかと考えてみると、例えば「ログインで何度入力してもエラーが出る」というクレームが来た場合に備えて、どんな間違った入力がされたかを保存しておきます。
そうすれば「こういった間違った入力がされてる場合はエラーになります」と説明ができます。
同様のトラブルが多い場合は入力エラーメッセージで指摘したり、FAQを強化したりしてブラッシュアップできるわけです。


最後にデバッグですが、これは開発時に使います。
主に処理中の変数内容確認です。
割と怪しげな部分にあちこち書きまくるような使い方をすると思います。(原始的ではありますが)


実行スクリプト - test.php
<?php

// クラス定義読み出し
require_once '/path/to/Log.php';

// 保存先指定
Log::setpath("/path/to");

// デバッグログ出力
$foo = 1;
Log::d($foo);


デバッグログ出力結果 - /path/to/dbg.log
------------------2012/01/15 00:16:33-------------------
Array
(
  [0] => Array
    (
      [file] => C:\xampp\htdocs\test.php
      [line] => 11
      [function] => d
      [class] => Log
      [type] => ::
      [args] => Array
        (
          [0] => 1
        )
    )
)


[args]要素が渡した変数内容になります。
debug_backtrace()関数を使っているので、ネストの深い関数内から呼び出してもトレースしてくれると思います。

また、Log::d($val)をあちこちに書きまくったらその都度消すかコメントにしないといけませんが、開発中はいちいち消すのが面倒です。
なのでオフ機能を付けました。
以下のようにすればログ出力を抑えられます。


$foo = 1;
Log::d($foo); // ログ出力される

Log::off(); // オフに

Log::d($foo); // ログ出力されない
Log::d($foo); // ログ出力されない
Log::d($foo); // ログ出力されない

Log::on(); // オンに

Log::d($foo); // ログ出力される


その他、複数のシステムが混在する場合に対応できるようにログファイル名にプレフィックスを付加することができるようにしました。

Log::setprefix('MySystemA-');

Log::info("foo"); // MySystemA-info.log
Log::err("foo"); // MySystemA-err.log
Log::d("foo"); // MySystemA-dbg.log

Log::setprefix('MySystemB-');

Log::info("foo"); // MySystemB-info.log
Log::err("foo"); // MySystemB-err.log
Log::d("foo"); // MySystemB-dbg.log


あとはログ容量が膨れ上がった場合に備えて、ファイルサイズが定数値MAXSIZE(MB)を超えたら切り分けるようにしています。

今回作ったLogクラスは今後使っていく中で、よりよくなるようブラッシュアップしていけたらいいかなと思っています。
差し当たりLog::d()からは画面表示もさせた方がいいかも知れませんね。