`
RednaxelaFX
  • 浏览: 3022629 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

一个生成伪log的程序……

    博客分类:
  • D
阅读更多
有个朋友让我帮忙写个伪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的官网论坛上有人写了一个还不错的实现:http://www.digitalmars.com/d/archives/digitalmars/D/learn/Implementing_a_timer_using_threads_6170.html
下面的代码里就使用了那帖里的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);
        }
    }
}
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics