1,日志处理
通常我们处理文件,大多数都是处理完即程序退出,但在IT行业里,尤其是互联网公司,日志不是一时性的,而是源源不断的一直生成中,所以要求你的程序也需要像linux 下的tail -f命令一样,可以一直跟着文件读并处理日志,当日志文件按时天(或小时或分钟)切换时,也需要日志处理程序也可以无缝的处理
那么如何实现模拟linux下的tail -f方式处理目录下日志文件呢?
首先,需要将目录中的文件读取到map 中,然后按生成时间一个个的处理,当处理到最后一个的时候需要进行以下判断:
1,是否有新的文件生成
2,是否读到文件尾
当有新的文件生成时需要进行文件切换,否则在当前文件尾端等待新的追加日志
获取目录中文件列map如下:
其中,searchDir为目录, pattern为查找的日志文件(一般为2011-01-01-01.log,这样这个 pattern为20*.log即可)
输出的map中,key为日志文件的时间,value为日志文件名(这里需要注意,如果文件日志时间一样将会导致map中的key只有一个而遗漏日志,这时需要将key换成计数的整数则可以避免)
#include <iostream>
#include <map>
#include <libgen.h>//basename | dirname
#include <fstream>
#include <dirent.h>
#include <fnmatch.h>
#include <sys/stat.h>
#include <signal.h>
#include <unistd.h>
#include <ctime>
#include <stdlib.h>
using namespace std;
std::map<int,string> getfilelist( std::string &searchDir ,std::string &pattern ) {
struct stat buf;
std::map <int, std::string> dirNames;
DIR* dir = opendir( searchDir.c_str() );
if( NULL != dir ) {
struct dirent* entry;
if( '/' != searchDir[searchDir.size() - 1] ) {
searchDir += '/';
}
int ret = 0;
for(; NULL != (entry = readdir(dir)); ) {
if(('.' == entry->d_name[0] && ('\0' == entry->d_name[1] || ('.' == entry->d_name[1] && '\0' == entry->d_name[2])))){
continue;
}
ret = fnmatch( pattern.c_str(), entry->d_name, FNM_PATHNAME|FNM_PERIOD );
if(ret == 0) {
string tmp = string(searchDir) + entry->d_name;
//int retst = lstat(entry->d_name, &buf);
int retst = lstat(tmp.c_str(), &buf);
if( 0 != retst ) {
//cout << "lstat_error"<<endl;
}
dirNames.insert( pair<int, string>(buf.st_ctime, searchDir + entry->d_name) );
} else if( ret == FNM_NOMATCH ) {
continue ;
}
}
closedir( dir );
}
return dirNames;
}
int main(int argc, char** argv){
std::map<int,std::string>file_list;
std::string dir="yourfile_dir/";
std::string pat="*";
time_t current = time(NULL);
cout << current <<endl;
file_list = getfilelist(dir, pat);
for(std::map<int, std::string>::iterator it=file_list.begin(); it != file_list.end(); it++) {
std::string filename = it->second;
size_t ftime = it->first;
cout << filename << "\t" << ftime <<endl;
char*base = basename(filename.c_str());
cout << base <<endl;
}
}
有这这个getfilelist对应的map后,剩下的就是遍历这个map,按次序处理每一个value对应的文件,
只不是在处理时要看下是否达到上面说的1,2两个条件,需要注意的地方如下示:
为方便描述这里给出php程序示例,同时没有对文件操作等进行返回值判断
并且,每一个处理的日志文件都有一个对应的 .fpos的记录处理位置的文件(存放处理的位置),如果文件处理正常结束切换则将文件改名为 .fdone否则 .fbad
这里这个f_no就是为了上面提到的1,2所设置的判断条件
另外,这个逻辑虽然可以处理基本的tail -f方式处理文件,但是仍然存在很多问题(这里我希望需要你自己来修改,代码就不上了,这里只是给出思路与注意事项而已)
function process_filelist($filename_lists) {
$f_no = 0;
foreach($filename_lists as $key => $filename) {
$fp = fopen($filename, 'r');
$skip_pos = GetFilePos($filename);//get position
$f_size = filesize($filename);
if($skip_pos == $f_size) {
if(1 == $len_lists){
return ERR_SAME_FILE;
}
}
$seek_ret = fseek($fp,$skip_pos,SEEK_SET);
if (0 != $seek_ret) {
return ERR_SEEK_FILE;
}
$fon = $filename . ".fpos";
$fpos = fopen("$fon","w");
$buffer = "";
$file_tail_not_valid = 0;
while($buffer = fgets($fp)) {
$len = strlen($buffer);
if ($buffer[$len - 1] != "\n" && feof($fp)) {
$file_tail_not_valid = 1;
break;
}
/*to do somethine*/
// use $buffer
$skip_pos += $len;
fseek($fpos,0);
$str_pos = strval( $skip_pos );
$wret = fwrite($fpos,$str_pos);
}
fclose($fp);
fclose($fpos);
if($f_no < $len_lists && $len_lists > 1) {
if (1 == $file_tail_not_valid) {
$re_ok = rename("$filename","$filename.fbad");
} else {
if (($len_lists-1) != $f_no) {
$tmp_f = $filename . ".fdone";
$re_ok = rename("$filename","$tmp_f");
}
}
}
$f_no++;
}
}