LoginSignup
23
26

More than 3 years have passed since last update.

[コピペでOK] PHPでログを出力する

Posted at

はじめに

PHPでログを出力するLoggerを作成しました。
PHPでは、標準のエラーログや便利なライブラリがありますが、
ここでは、ライブラリを使用しません。
コピペですぐに使えるものを目指しています。

機能としては下記の通りです。

  • ログメッセージを出力する
  • ログレベルの指定
  • ログローテート
  • 出力先の指定

ErrorLog

PHPには、「ErrorLog」という実行時エラーをメッセージに出力する機能があります。
これは、try-catchで補足できないエラーもあります。
今回の作成したLoggerクラスでは対象外です。

詳細は下記の通りです。

PHPでのログ出力 まとめ

Monolog

PHPには、「Monolog」というログ出力のライブラリがあります。
Laravelなどの有名なPHPフレームワークで採用されています。
今回の作成したLoggerクラスでは対象外です。

詳細は下記の通りです。

コード改善に役立ちそうなPHPライブラリ・ツール

実行環境

今回実行した環境は下記の通りです。

実行環境
PHP: 7.3.11-0ubuntu0.19.10.3

使い方

使い方は下記の通りです。

$log = Logger::getInstance();
$log->error('error log.');
$log->warn('warn log.');
$log->info('info log.');
$log->debug('debug log.');

getInstanceメソッドで、Loggerインスタンスを生成して
各ログレベルのメソッドを呼び出します。

出力されるログは下記の通りです。

[2020-04-06 23:35:33.506][835][ERROR] error log.
[2020-04-06 23:35:33.521][835][WARN] warn log.
[2020-04-06 23:35:33.525][835][INFO] info log.
[2020-04-06 23:35:33.545][835][DEBUG] debug log.

「835」この数字は、プロセスIDを表示しています。

Loggerの実装

ConfigクラスでLoggerに関する設定を行います。

config.php
<?php
/**
 * 設定クラス
 */
class Config {
    const IS_LOGFILE = true; // ログファイル出力フラグ true=出力あり/false=なし
    const LOG_LEVEL = 3; // ログレベル 0=ERROR/1=WARN/2=INFO/3=DEBUG
    const LOGDIR_PATH = './logs/'; // ログファイル出力ディレクトリ
    const LOGFILE_NAME = 'console'; // ログファイル名
    const LOGFILE_MAXSIZE = 10485760; // ログファイル最大サイズ(Byte)
    const LOGFILE_PERIOD = 30; // ログ保存期間(日)
}

?>

Loggerクラスの実装は下記の通りです。

logger.php
<?php
/**
 * ログ
 */
class Logger {

    // ログレベル
    const LOG_LEVEL_ERROR = 0;
    const LOG_LEVEL_WARN = 1;
    const LOG_LEVEL_INFO = 2;
    const LOG_LEVEL_DEBUG = 3;

    private static $singleton;

    /**
     * インスタンスを生成する
     */
    public static function getInstance()
    {
        if (!isset(self::$singleton)) {
            self::$singleton = new Logger();
        }
        return self::$singleton;
    }

    /**
     * コンストラクタ
     */
    private function __construct() {
    }

    /**
     * ERRORレベルのログ出力する
     * @param string $msg メッセージ
     */
    public function error($msg) {
        if(self::LOG_LEVEL_ERROR <= Config::LOG_LEVEL) {
            $this->out('ERROR', $msg);
        }
    }

    /**
     * WARNレベルのログ出力する
     * @param string $msg メッセージ
     */
    public function warn($msg) {
        if(self::LOG_LEVEL_WARN <= Config::LOG_LEVEL) {
            $this->out('WARN', $msg);
        }
    }

    /**
     * INFOレベルのログ出力する
     * @param string $msg メッセージ
     */
    public function info($msg) {
        if(self::LOG_LEVEL_INFO <= Config::LOG_LEVEL) {
            $this->out('INFO', $msg);
        }
    }

    /**
     * DEBUGレベルのログ出力する
     * @param string $msg メッセージ
     */
    public function debug($msg) {
        if(self::LOG_LEVEL_DEBUG <= Config::LOG_LEVEL) {
            $this->out('DEBUG', $msg);
        }
    }

    /**
     * ログ出力する
     * @param string $level ログレベル
     * @param string $msg メッセージ
     */
    private function out($level, $msg) {
        if(Config::IS_LOGFILE) {

            $pid = getmypid();
            $time = $this->getTime();
            $logMessage = "[{$time}][{$pid}][{$level}] " . rtrim($msg) . "\n";
            $logFilePath = Config::LOGDIR_PATH . Config::LOGFILE_NAME . '.log';

            $result = file_put_contents($logFilePath, $logMessage, FILE_APPEND | LOCK_EX);
            if(!$result) {
                error_log('LogUtil::out error_log ERROR', 0);
            }

            if(Config::LOGFILE_MAXSIZE < filesize($logFilePath)) {
                // ファイルサイズを超えた場合、リネームしてgz圧縮する
                $oldPath = Config::LOGDIR_PATH . Config::LOGFILE_NAME . '_' . date('YmdHis');
                $oldLogFilePath = $oldPath . '.log';
                rename($logFilePath, $oldLogFilePath);
                $gz = gzopen($oldPath . '.gz', 'w9');
                if($gz) {
                    gzwrite($gz, file_get_contents($oldLogFilePath));
                    $isClose = gzclose($gz);
                    if($isClose) {
                        unlink($oldLogFilePath);
                    } else {
                        error_log("gzclose ERROR.", 0);
                    }
                } else {
                    error_log("gzopen ERROR.", 0);
                }

                // 古いログファイルを削除する
                $retentionDate = new DateTime();
                $retentionDate->modify('-' . Config::LOGFILE_PERIOD . ' day');
                if ($dh = opendir(Config::LOGDIR_PATH)) {
                    while (($fileName = readdir($dh)) !== false) {
                        $pm = preg_match("/" . preg_quote(Config::LOGFILE_NAME) . "_(\d{14}).*\.gz/", $fileName, $matches);
                        if($pm === 1) {
                            $logCreatedDate = DateTime::createFromFormat('YmdHis', $matches[1]);
                            if($logCreatedDate < $retentionDate) {
                                unlink(Config::LOGDIR_PATH . '/' . $fileName);
                            }
                        }
                    }
                    closedir($dh);
                }
            }
        }
    }

    /**
     * 現在時刻を取得する
     * @return string 現在時刻
     */
    private function getTime() {
        $miTime = explode('.',microtime(true));
        $msec = str_pad(substr($miTime[1], 0, 3) , 3, "0");
        $time = date('Y-m-d H:i:s', $miTime[0]) . '.' .$msec;
        return $time;
    }
}

さいごに

ソースコードをGitHubに公開しています。

ソースファイルはこちら

以上です。

23
26
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
26