はじめに
PHPでログを出力するLoggerを作成しました。
PHPでは、標準のエラーログや便利なライブラリがありますが、
ここでは、ライブラリを使用しません。
コピペですぐに使えるものを目指しています。
機能としては下記の通りです。
- ログメッセージを出力する
- ログレベルの指定
- ログローテート
- 出力先の指定
ErrorLog
PHPには、「ErrorLog」という実行時エラーをメッセージに出力する機能があります。
これは、try-catchで補足できないエラーもあります。
今回の作成したLoggerクラスでは対象外です。
詳細は下記の通りです。
Monolog
PHPには、「Monolog」というログ出力のライブラリがあります。
Laravelなどの有名なPHPフレームワークで採用されています。
今回の作成したLoggerクラスでは対象外です。
詳細は下記の通りです。
実行環境
今回実行した環境は下記の通りです。
実行環境
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に公開しています。
以上です。