一个生成伪log的程序……

有个朋友让我帮忙写个伪log的生成器。他提供了一个源log文件和一个配置文件,要每隔一段时间就向一个指定的路径上的log文件添加一些新生成的log。

要求是:
1、从配置文件读入参数,根据配置来决定时间间隔与输出log的路径;
2、从源log文件得到生成log的材料;
3、随机从源log里挑选几行出来,把它开头的时间信息替换成当前时间;
4、以固定的时间间隔向目标路径添加新生成的log,并要求不在log文件的末尾生成空行。

配置文件类似这样:
RT_Config.inc:
[system]
//log format
Format=NCSA
//log of the dat
FileName=D:\temp\today.log
//port
Listen_Port=8000
//server IP
IP=127.0.0.1
//server port
Server_port=9000
//time interval (in SECs)
Time_Interval=20


然后源log文件类似这样(截取几行):
2008-03-31 00:00:19 10.11.14.56 - 10.11.1.9 80 GET /acip/shownewsh.asp - 200 Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.1;+SV1)
2008-03-31 00:00:20 10.11.14.56 - 10.11.1.9 80 GET /acip/images/Delighting+you+always+(Red)+789x382.gif - 304 Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.1;+SV1)
2008-03-31 00:00:47 10.11.10.81 - 10.11.1.9 80 GET /acip/shownewsh.asp - 200 Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.0)
2008-03-31 00:01:31 10.11.50.67 - 10.11.1.9 80 GET /acip/shownewsh.asp - 200 Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.1;+SV1)
2008-03-31 00:01:36 10.11.46.90 - 10.11.1.9 80 GET /acip/shownewsh.asp - 200 Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.1;+SV1)
2008-03-31 00:01:38 10.11.44.64 - 10.11.1.9 80 GET /eis/Report/Executive+Information+System - 404 Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.0)

其实就是IIS生成的一些log而已。

他还提出了额外的要求:最好在运行程序的机器上不需要额外安装什么runtime,于是排除掉了Java和C#,以及Perl、Ruby等语言;虽然Java可以GCJ到native,Ruby也可以通过rubyscript2exe来生成带有Ruby解释器的独立exe文件,但它们生成出来的exe都太大了,也排除;C和C++都不够方便,我不太想在这种小程序上用。所以最后我选择了用D来写这个小程序。

===========================================================================

于是问题可以分解为几个小问题,主要是:
1、从配置文件读入配置;
2、拼接出新的log:获取当前时间并格式化到合适的格式上,然后与源log拼接起来;
3、按固定的时间间隔重复执行写出log的动作。

---------------------------------------------------------------------------

第一个问题很好办。我的解决办法是把整个配置文件读进来,分解成行,然后对每行做匹配,把配置文件里的key-value对保存到D语言提供的关联数组里。
char[char[]] config; // 配置参数

这样声明了一个char[] => char[]的关联数组。注意到D里char[]就是字符串了。

在对行做匹配时,我用了三个正则表达式:
RegExp commentPattern = RegExp(r"^//.*");
RegExp sectionPattern = RegExp(r"^\[[^\]]+\]");
RegExp keyValuePattern = RegExp(r"(.+)=(.+)");

第一个正则表达式用于匹配行首为"//"的行。如果匹配则忽略掉该行。这其实是偷懒了——我没有去匹配在行尾的行注释,因为如果配置文件里出现引号包围的字符串,而字符串的正常内容含有"//"的话,直接用r"//.*$"会把不应该认为是注释的东西也包含进去……这里需要更多的处理,但是我懒得做了。直接规定用户必须把注释符号写在行首就是。

第二个正则表达式用于匹配配置文件里的段标记。如果匹配则同注释一样,忽略掉该行。

第三个正则表达式用于验证配置文件的行是否符合key=value的形式。如果符合,则将key-value对保存到关联数组里。

源log文件也同样,整个文件读进来,然后分解为行,保存起来。
char[][] source; // 源log的数据


---------------------------------------------------------------------------

第二个问题在D的标准库Phobos的支持下也很好办。特别是当D的字符串是基于char[],也就是说是一个动态数组,用起来很方便。
先要得到当前的系统时间。std.date包里有足够的函数来解决这问题。用std.date.getUTCtime()获取当前时间后,用XXXFromTime()的几个函数把时间转换为字符串,然后用std.string.format()以指定的格式将它们拼接起来。
得到当前时间的字符串之后,随机从源log里选一行出来,把当前时间与去除掉时间的源log拼起来就行。

---------------------------------------------------------------------------

第三个问题比较讨厌。要按照固定的时间间隔来做些事情的话,Java、C#高级语言和JavaScript等脚本语言都提供了直接能用的timer机制,但C/C++的层次上则没有直接能用的timer,D的Phobos也没有。换言之我们要自己实现定时器。有两种思路:
1、busy wait
2、sleep and wait

busy wait就是例如while(true)然后在里面检查时间间隔是不是大于或等于规定值,满足条件的时候执行动作
sleep and wait是在每次执行完动作之后让自己(一个线程)休眠一定时间,等“醒来”的时候再执行一次动作,再休眠,如此循环。

很明显busy wait是很糟糕的选择——它不停的循环,什么事都不做却占着CPU。但是这种做法在C/C++里却很常见。或许大家都很无奈吧 = =
刚才经过隔壁宿舍的的时候看到了一本叫做《C游戏编程从入门到精通》的书,顺手翻了翻,读到了它(还是说是Andre LaMothe?)提供的控制时间延迟的函数:
void Delay(int clicks)
{
unsigned int far *clock = (unsigned int far *)0x0000046CL;
unsigned int now;
now = *clock;
while (abs(*clock - now) < clicks) { }
}

噢天哪。居然用上了内存到寄存器的地址映射——也就是说直接从寄存器读了当前的clock tick。这比用系统API更“糟糕”了,不采用。这本书居然是在2000年之后才出的,好神奇啊 =v=
(“糟糕”不是说这代码不好。事实上总是得有这样的代码存在于某处,一般是在库里,像是说操作系统的API会有这样的函数的实现。不过某个寄存器到底映射到的地址对平台的依赖性太大了,我们最好不要自己的*应用程序*里直接用……)

sleep and wait则需要库的支持。在C/C++/D里,没有办法不依赖于平台来做这件事。不过依赖就依赖吧诶,总比busy wait好。在D的官网论坛上有人写了一个还不错的实现:[url]http://www.digitalmars.com/d/archives/digitalmars/D/learn/Implementing_a_timer_using_threads_6170.html[/url]
下面的代码里就使用了那帖里的Timer。

===========================================================================

说起来,我没写如何让这程序退出的逻辑……要退出就要强制结束进程了 =v=
另外我没有检查目标log的路径是否存在,如果路径上的目录不存在的话程序也会出错。这个得递归的调用mkdir才好解决,懒得做……
用来编译的环境是DMD D 1.028。
好吧,其它也没什么了,完整的程序如下:

loggen.d
import std.conv;
import std.date;
import std.file;
import std.path;
// import std.random; // for rand()
import std.regexp;
import std.stdio;
import std.string;

import std.c.stdlib;
import std.c.time;

static const char[] CONFIG_FILENAME = "./RT_Config.inc";
static const char[] LOG_SOURCE_FILENAME = "./record.log";
static const int FOREVER = -1;

char[][char[]] config; // configuration data
int timeInterval; // time interval
char[] logFileName; // destination log file's name
char[][] source; // source log contents

// Load the configuration data,
// and set time interval/destination filename
bool loadConfig() {
char[] file = cast(char[])read(CONFIG_FILENAME);
if(file == null) return false;
char[][] lines = splitlines(file);

RegExp commentPattern = RegExp(r"^//.*");
RegExp sectionPattern = RegExp(r"^\[[^\]]+\]");
RegExp keyValuePattern = RegExp(r"(.+)=(.+)");
foreach (char[] line; lines) {
if (commentPattern.test(line)) continue; // skip comments
if (sectionPattern.test(line)) continue; // skip section tags
if (keyValuePattern.test(line)) {
config[toupper(keyValuePattern.match(1))] = keyValuePattern.match(2);
}
}

timeInterval = toInt(config["TIME_INTERVAL"]);
logFileName = config["FILENAME"];

return true;
}

// Load the source log contents
bool loadSource() {
char[] file = cast(char[])read(LOG_SOURCE_FILENAME);
if(file == null) return false;
source = splitlines(file);

return true;
}

// get a random number in the range [min, max)
// min and max should be a positive integer
int random(int min, int max) {
int rand = std.c.stdlib.random(20);
while (rand < 5) rand = std.c.stdlib.random(20);
return rand;
}

// get a string representation of the current system time
// in the format "YYYY-MM-DD hh:mm:ss"
char[] getCurrentTimeString() {
d_time lNow = std.date.getUTCtime();
return std.string.format("%04d-%02d-%02d "
~ std.date.toTimeString(lNow)[0..$-9],
YearFromTime(lNow),
MonthFromTime(lNow),
DateFromTime(lNow));
}

// generate a line of dummy log
char[] getLogLine() {
int rand = std.c.stdlib.random(source.length);
return getCurrentTimeString() ~ source[rand][19..$];
}

// exit the program abnormally
void abort(char[] message) {
writefln("Error: " ~ message);
exit(1);
}

/*
// execute an action repeatly with specified time interval
// busy wait version
void repeat(void delegate() action, int interval, int limit) {
if (action == null) return;
if (interval < 0) return;

time_t old = time(null);
while (limit != 0) {
time_t now = time(null);
if (now >= old + interval) {
old = now;
action();
--limit;
}
}
}
*/

// execute an action repeatly with specified time interval
// sleep-and-wait version
// TODO: fix the limit param...
void repeat(void delegate() action, int interval, int limit) {
Timer timer = new Timer(interval, action, (limit < 0));
timer.start();
timer.wait();
}

// program entry point
void main(char[][] args) {
// load configuration
if (!loadConfig()) abort("invalid configuration file.");

// load source log
if (!loadSource()) abort("invalid source log file.");

// append a few lines of log first
int r = random(5, 20);
// append generated log to destination
for (int i = 0; i < r; ++i) {
if (exists(logFileName) && isfile(logFileName)) {
append(logFileName, newline ~ getLogLine());
} else { // destinatin log file doesn't exist, create one
// TODO: check and create directories on the path
// write log's header comment
write(logFileName,
"#Software: Microsoft Internet Information Services 5.0" ~ newline
~ "#Version: 1.0" ~ newline
~ "#Date: 2008-03-31 00:00:19" ~ newline
~ "#Fields: date time c-ip cs-username s-ip s-port cs-method"
~ " cs-uri-stem cs-uri-query sc-status cs(User-Agent)" ~ newline);
// write first line of log content
append(logFileName, getLogLine());
}
}

// append new log to destination, in specified time interval
repeat(delegate() {
// get a random number in the range [5, 20)
r = random(5, 20);
// append generated log to destination
for (int i = 0; i < r; ++i) {
append(logFileName, newline ~ getLogLine());
}
}, timeInterval, FOREVER);
}

//===============================================================
// Timer, written by Dennis Kempin
//===============================================================

import std.thread;

version(Windows) {
import std.c.windows.windows;
}

version(linux) {
import std.c.linux.linux;
}

class Timer: Thread {
private void delegate() action;
private int waitTime;
private bit autoRestart;

this(int waitTime, void delegate() action, bit autoRestart=false) {
this.action = action;
this.waitTime = waitTime;
this.autoRestart = autoRestart;
}

protected this(int waitTime, bit autoRestart=false) {
this.waitTime = waitTime;
this.autoRestart = autoRestart;
}

override int run() {
sleep(waitTime);
execute();
while(autoRestart)
{
sleep(waitTime);
execute();
}
return 0;
}

void execute() {
action();
}

private void sleep(int time) {
version(Windows) {
Sleep(time*1000);
}

version(linux) {
usleep(time*1000);
}
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值