其實就是 Log Rotation 這件事情。當我們佈署好一個簡單的 CakePHP 環境時,在 /tmp/logs
中可以發現一些放 log 檔案用的地方。然而,因為這些 log 檔案資訊是由 CakePHP 的 Logging 部份來控制,預設是使用檔案存取的方式來存放 log 紀錄。
Log 的使用方法很簡單:
<?php
// 直接使用 CakeLog 類別做靜態呼叫。
CakeLog::write("error", "something wrong.");
// 或者是使用 log 函式呼叫。
$this->log("something wrong", "error");
// 上述兩者呼叫檔案會存放在 /tmp/logs/error.log 這個檔案中。
而,Log 檔案會一天一天的長大,雖然,我們在正式站上,可能不會在那麼多地方下 Log 紀錄。但是為了避免萬一,許多關鍵點還是會紀錄起來,以免發生死無對證之類的事情。這種時候,Log Rotation 就有其必要性。所以說,我們可以做一個 Components 塞在 CakePHP 裡面,並且在 app_controller.php
加入,讓他可以預先載入。
重點是,我們打開 CakePHP 核心中的 CakeLog(libs/cake_log.php
)中,可以看到幾個跟 LOG 有關的東西。主要是這個地方,他把這些東西都換成數字了,所以得留心這個部份。
<?php
if (!defined('LOG_WARNING')) {
define('LOG_WARNING', 3);
}
if (!defined('LOG_NOTICE')) {
define('LOG_NOTICE', 4);
}
if (!defined('LOG_DEBUG')) {
define('LOG_DEBUG', 5);
}
if (!defined('LOG_INFO')) {
define('LOG_INFO', 6);
}
if (!defined('LOG_ERROR')) {
define('LOG_ERROR', 2);
}
if (!defined('LOG_ERR')) {
define('LOG_ERR', LOG_ERROR);
}
首先,我們有個設定的部份,寫在 core.php
裡面。三種紀錄時間區隔,每日、每星期、每月。
<?php
/*
* Log Rotation
*/
Configure::write('Log.rotate', array(
'daily' => array(
LOG_DEBUG => 5,
LOG_ERROR => 10,
),
'weekly' => array(
'*' => 3
),
'monthly' => array()
));
最後是 Components 加入 logrotation.php
檔案。
<?php
/**
* Copy from CakeLog::write()
*/
if (!defined('LOG_WARNING')) {
define('LOG_WARNING', 3);
}
if (!defined('LOG_NOTICE')) {
define('LOG_NOTICE', 4);
}
if (!defined('LOG_DEBUG')) {
define('LOG_DEBUG', 5);
}
if (!defined('LOG_INFO')) {
define('LOG_INFO', 6);
}
開頭的基礎設定爾等。
<?php
class LogrotationComponent extends Object {
/**
* Name.
*/
var $name = 'Logrotation';
/**
* Default rotating files.
*
* @public
*/
var $defaultRotate = 5;
/**
* Max rotating times, crash?
*
* @public
*/
var $maxRotate = 100;
/**
* Default confugure.
*
* @private.
*/
var $__conf = array();
/**
* Server time.
*
* @private;
*/
var $__time = null;
/**
* Get timestamp data from Cache.
*
* @private
*/
var $__cache = array();
/**
* For LOG.
*
* @private
*/
var $__logfloder = null;
從 startup 開始,也從 startup 結束,我並沒有另外呼叫其他函式。
<?php
function initialize(&$controller, $settings = array()) {
if (!class_exists('Folder')) {
uses('folder');
}
}
function startup(&$controller) {
if (!($this->__conf = Configure::read('Log.rotate'))) {
return;
}
$this->__time = $_SERVER['REQUEST_TIME'];
$this->__cache = array(
'daily' => Cache::read('Log.daily'),
'weekly' => Cache::read('Log.weekly'),
'monthly' => Cache::read('Log.monthly')
);
if ($this->__cache['daily'] === false
|| $this->__cache['weekly'] === false
|| $this->__cache['monthly'] === false
) {
Cache::write('Log.daily', $this->__time);
Cache::write('Log.weekly', $this->__time);
Cache::write('Log.monthly', $this->__time);
CakeLog::write('debug', 'Cache log timestamp maybe lost. Reset it now.');
return;
}
$this->__logfolder = new Folder(LOGS);
$this->__loadingConfigure();
$timeToStr = date("Y/m/d H:i:s", $this->__time);
// 24 hrs rotation.
if ( ceil($this->__time - $this->__cache['daily']) >= 60*60*24 ) {
$this->__rotation($this->__conf['daily']);
Cache::write('Log.daily', $this->__time);
CakeLog::write('debug', 'Daily Log Rotation.');
}
// 7 days rotation.
if ( ceil($this->__time - $this->__cache['weekly']) >= 60*60*24*7 ) {
$this->__rotation($this->__conf['weekly']);
Cache::write('Log.weekly', $this->__time);
CakeLog::write('debug', 'Weekly Log Rotation.');
}
// 30 days rotation.
if ( ceil($this->__time - $this->__cache['monthly']) >= 60*60*24*30 ) {
$this->__rotation($this->__conf['monthly']);
Cache::write('Log.monthly', $this->__time);
CakeLog::write('debug', 'Monthly Log Rotation.');
}
}
底下兩個公開函式,可以設定最大的執行次數,跟預設 log 檔案要累加的次數。
<?php
function setMaxRotate($times = 100) {
$this->maxRotate = $times;
}
function setDefaultRotate($logs = 5) {
$this->defaultRotate = $logs;
}
底下全部都是私有函式,只是因為要相容到 PHP4,所以就這樣啦。
<?php
function __rotation($logFiles) {
foreach ($logFiles as $logType => $numberOfLogs) {
$filename = $this->__getLogFilename($logType);
$files = $this->__logfolder->find(basename($filename).'.*',true);
$files = array_reverse($files);
$this->__dorotate($files, $numberOfLogs);
}
}
function __dorotate($files, $numberOfLogs, $rotateCount = 1) {
if (count($files) <= 0 || $rotateCounter >= $this->maxRotate) {
return;
}
$file = $files[0];
$fileInfo = pathinfo(LOGS.$file);
if ( is_numeric($fileInfo['extension']) ) {
if ( $numberOfLogs > 0
&& ($fileInfo['extension']+1) > $numberOfLogs
&& is_file(LOGS.$file)
) {
unlink(LOGS.$file);
}
$newFile = basename($file, $fileInfo['extension']) . ($fileInfo['extension'] + 1);
$move = array('from' => LOGS.$file, 'to' => LOGS.$newFile);
} elseif ( $fileInfo['extension'] === 'log' ) {
$move = array('from' => LOGS.$file, 'to' => LOGS.$file.'.1');
} else {
$move = null;
array_shift($files);
}
if (!IS_NULL($move)) {
exec('mv '.$move['from'].' '.$move['to'], $output, $return_val);
if ($return_val === 0) {
array_shift($files);
}
}
unset($move);
$this->__dorotate($files, $numberOfLogs, $rotateCount+1);
}
function __loadingConfigure() {
$configured = array();
foreach ($this->__conf as $interval => $logFiles) {
foreach ($logFiles as $logType => $numberOfLogs) {
if (!is_numeric($numberOfLogs)) {
$numberOfLogs = $this->defaultRotate;
}
if (!isset($default) && $logType === '*') {
$default = array($interval, $numberOfLogs);
} else {
$configured[] = basename($this->__getLogFilename($logType));
}
}
}
if (isset($default)) {
list($interval, $numberOfLogs) = $default;
unset($this->__conf[$interval]['*']);
$logs = $this->__logfolder->find('.*\.log', true);
foreach ($logs as $filename) {
if (!in_array($filename, $configured)) {
$this->__conf[$interval][basename($filename, '.log')] = $numberOfLogs;
}
}
}
}
function __getLogFilename($loggerType) {
/**
* Clone directly from CakeLog::write()
*
* cakephp/libs/cake_log.php, line 211.
*/
if (!defined('LOG_ERROR')) {
define('LOG_ERROR', 2);
}
if (!defined('LOG_ERR')) {
define('LOG_ERR', LOG_ERROR);
}
$levels = array(
LOG_WARNING => 'warning',
LOG_NOTICE => 'notice',
LOG_INFO => 'info',
LOG_DEBUG => 'debug',
LOG_ERR => 'error',
LOG_ERROR => 'error'
);
if (isset($loggerType) && is_int($loggerType) && isset($levels[$loggerType])) {
$loggerType = $levels[$loggerType];
}
switch($loggerType) {
case 'error':
case 'warning':
$filename = LOGS . 'error.log';
break;
case 'notice':
case 'info':
case 'debug':
$filename = LOGS . 'debug.log';
break;
default:
$filename = LOGS . $loggerType . '.log';
}
return $filename;
}
}
?>
最後在 app_controller.php
中加入。
public $components = array('RequestHandler', 'Logrotation');
然後就這樣了。