在互联网后台服务器中常需要记录互联网软件的流水日志,日志服务器和入库工具则是处理此类功能。
日志服务器会接受逻辑服务器发送的日志消息,将其写入本地日志文件。每隔一段时间,再由日志入库服务器将日志文件导入数据库。
日志服务器接收其他服务器服务器发送的json格式日志消息,并写入到日志文件。
日志服务器不需要解析消息内容,定时批量写入日志文件。
日志文件包含日志描述文件和日志数据文件。
日志服务器接收其他服务器发来的消息,消息头跟一般消息一致,命令类型为服务器指定命令。
消息体为json格式,日志命令为独立命令,根据日志解析配置来填写。内容如下:
{
"log_cmd": 1,
"fields": {
"uid": 10000,
"guid": "10001",
"nickname": "张三",
"mac": "64-00-6A-05-3B-DD",
"ip": "192.168.10.124",
"login_type": "1",
"login_time": 1447816170,
"logout_time": 1447816270,
"logout_type": 1,
"online_time": 100,
"log_time": 1447816270
}
}
日志命令log_cmd为1,需要记录的字段在fields内。
日志描述文件会记录数据记录的个数、文件的标识和版本,以及保留字段。日志文件头为以后的日志拓展和简要分析提供依据。日志描述文件的命名方式为*.fb。
日志文件头格式如下:
struct LogHeader
{
LogHeader()
{
memset(this, 0, sizeof(LogHeader));
}
//日志文件固定标识为: 'L', 'O', 'G', 0分别占uint32的一个字节
uint32 nIdent;
//日志文件格式版本号
uint32 nVersion;
//日志文件中存储的记录数量
uint32 nRecordCount;
//保留字节
char sReserves[20];
};
日志数据文件存储日志数据记录,包含数据记录头和数据记录体。日志数据文件命名方式为*.fd。
日志数据记录存储格式如下:
数据记录头1 数据记录体1 数据记录头2 数据记录体2...
数据记录头包含本条日志的简要信息。消息内容变动时修改数据记录头的数据版本号。
可以一次写入多个日志消息到日志文件,以提高写入效率。在内存中以消息队列的方式来存储将要写文件的日志消息。
数据记录头格式:
struct LogDataHeader
{
uint16 nCmdType;//命令类型
uint16 nDataVersion;//数据版本号
uint16 nBodySize;//数据记录体大小
};
数据记录头初始化,例如:
struct LogDataHeader logDataHeader;
logDataHeader.nCmdType = 1;//日志表类型
logDataHeader.nDataVersion = 1;//数据版本号
logDataHeader.nBodySize = fieldsSize;//数据记录体大小,为日志消息中消息体的fields的长度(即实际日志内容的长度)
数据体内容为具体的消息内容,一个数据记录体包含一个日志消息的消息体,不需要包含消息头,格式为json格式的消息体,具体格式参考日志解析。
数据记录体跟logic服务器发来的消息的fields字段的内容一致,例如:
{
"uid": 10000,
"guid": "10001",
"nickname": "张三",
"mac": "64-00-6A-05-3B-DD",
"ip": "192.168.10.124",
"login_type": "1",
"login_time": 1447816170,
"logout_time": 1447816270,
"logout_type": 1,
"online_time": 100,
"log_time": 1447816270
}
接收日志后,存储在内存的日志队列。定时器定时检查日志队列。每隔一段时间写一次日志到最新的日志文件,每隔一段时间创建新的日志文件。
配置日志目录和日志备份目录,日志消息队列最大长度,写日志时间间隔,创建新日志文件时间间隔。配置如下:
{
"datalog_path":"datalog/logfile/",
"datalog_path_bak":"datalog/logfilebak/",
"log_queue_num":10,
"logtime_write_interval":10.0,
"logtime_create_log_interval":120.0
}
日志入库服务器定时检查日志文件,并读取日志文件,解析之后根据不同的表分别写入不同的db后缀文件,然后采用load data 方式入库。入库成功完成后将移动已读日志文件到日志备份目录,和把已入库的db后缀文件移动到db后缀文件备份目录。
读取非最新的日志文件,并根据配置文件信息,将读取的日志内容进行解析,然后分别写入临时文件。
日志入库服务器在启动时加载日志解析配置,并保存各个命令的对应的转换后的入库格式。在解析日志文件时,将读取数据记录头的命令类型,和数据记录体的日志消息体的数据内容,根据日志表配置的入库格式来解析日志消息体数据,然后写入到db后缀文件。
日志表是预先创建在mysql的一系列与日志有关的表。存储在一个单独的日志库中。日志表都以日志时间为索引,不分表,在容量扩大后可以分区。
每张日志表都需要配置在日志解析配置文件中,包括表指定的类型数字,表名和表字段。
日志解析配置文件(LogTables.json),只使用英文和数字,格式如下:
{
" logcmds":[log_cmd1],
" log_cmd1": {
"table": "tablename1",
"fields": [
"field1",
"field2"
]
}
}
Logcmds为需要加载的表的列表
log_cmd 为日志类型,为数字,每个表有一个表类型,定义在程序中。
table为数据库表名,为字符串和数字组合。
fields为需要插入的字段名的列表。注意字段列表的字段顺序需要跟数据表的字段顺序是完全一致的。
field为字段名。配置字段名跟数据表的字段的名字完全一样。
例如,对于用户登录日志,配置如下
{
" logcmds":[1],
"1": {
"tablename": "tb_userlogin_log",
"fields": [
"uid",
"guid",
"nickname",
"mac",
"ip",
"login_type",
"login_time",
"logout_time",
"logout_type",
"online_time",
"log_time"
]
}
}
其中,日志命令类型为1,表名为tb_userlogin_log,字段名为"uid","guid","nickname","mac","ip","login_type","login_time","logout_time","logout_type","online_time","log_time"
把解析后的日志文件写入到db文件,并备份日志文件到日志文件备份目录。导入db文件到mysql数据库,然后备份db文件到db文件备份目录。
配置数据库连接信息,日志文件目录和日志备份文件目录、db文件目录和备份目录。配置如下
{
"dbip":"192.168.18.22",
"dbport":3306,
"dbuser":"im",
"dbpwd":"im",
"dbname":"db_im3_log",
"dbcharacterset":"utf8",
"datalog_path":"datalog/logfile/",
"datalog_path_bak":"datalog/logfilebak/",
"dblog_path":"datalog/dbfile/",
"dblog_path_bak":"datalog/dbfilebak/"
}
db后缀文件以表名来命名,如tablename.db(db为文件拓展名,保存在db文件目录,简称db文件),分别存储将要入库到不同表的日志数据,需要写入的表跟db文件同名(也是tablename),采用各个文件分别加载的方式。手动提前创建需要的表,分区规则之后按需求根据时间段手动创建。
db文件为导入数据库文件,文件格式为制表符'\t'分开各个字段,每行表示一条记录,行末尾为换行符LF (即’\n’)表示。
db文件中的各个字段跟配置文件中的字段需要一一对应,由于消息版本的不一致导致字段对不上的(包括表字段修改,字段减少或增加),以空字段补上,加载入数据表时会被替换成默认值。
db文件字段分隔符采用制表符'\t',日志内容中的字符串若使用制表符,比如含有tab键,则必须在前面加上转义符号'\',否则在同时出现单引号'和制表符(如' ,后面的为tab键)时会发生错位;字符串中可以出现字符'\t'。
采用mysql load方式来入库,加载需指定插入的表的字段。语句如下:
load data local infile "D:/ tb_userlogin_log.log" into table db. tb_userlogin_log fields terminated by '\t' enclosed by '\'' lines terminated by '\n' (uid, guid, nickname,mac,ip,login_type, login_time,logout_time,logout_type,online_time,log_time)
加载语句的字段生成来自于配置文件。表添加新字段时,对旧的日志消息可以兼容,如果表字段减少了,则不保存被减少;如果字段修改了,则不保存被修改字段,或者根据版本号进行修改。
编写脚本start_logtodbserver.sh来运行程序入库程序logtodbserver。
命令 start 开启
命令 stop 关闭
不带命令则默认执行start命令
#!/bin/bash
ASYNC_SERVER_PATH=`pwd`
ASYNC_BIN=${ASYNC_SERVER_PATH}/bin
ASYNC_CONF=${ASYNC_SERVER_PATH}/conf
logtodbwork()
{
if test $( pgrep "LogToDBServer_" | wc -l ) -eq 0
then #don't has this service
${ASYNC_BIN}/LogToDBServer ${ASYNC_CONF}/LogToDBServer.json
else
#it has been started
sleep 1
fi
}
logtodbstopwork()
{
#echo "stopservice:LogToDBServer"
#ps -ef |grep LogToDBServer
pkill -9 LogToDBServer -u `whoami`
}
case $1 in
start)
logtodbwork
;;
stop)
logtodbstopwork
sleep 2
;;
*)
logtodbwork
;;
esac
采用crontab 定时运行shell脚本来执行程序。
crontab -e 编辑用户的cron配置文件保存退出,系统会自动就存放于/var/spool/cron/目录中,文件以用户名命名
linux的cron服务是每隔一分钟去读取一次/var/spool/cron,/etc/crontab,/etc/cron.d下面所有的内容
Example of job definition:
.---------------- minute (0 - 59)
| .------------- hour (0 - 23)
| | .---------- day of month (1 - 31)
| | | .------- month (1 - 12) OR jan,feb,mar,apr ...
| | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
| | | | |
* * * * * user-name command to be executed
* * * * * command
分 时 日 月 周 命令
第1列表示分钟1~59, 每分钟用*或者 */1表示
第2列表示小时1~23(0表示0点)
第3列表示日期1~31
第4列表示月份1~12
第5列标识号星期0~6(0表示星期天)
第6列要运行的命令
crontab [ -u user ] 文件
crontab [ -u user ] { -l | -r | -e }
-u:指定某一用户
-e:执行文字编辑器来设定用户(当前用户或指定用户)时程表,内定的文字编辑器是vi.
-r:删除用户时程表.
-l:列出用户时程表.
如 * * * * * /bin/usershell 每天每分钟执行一次/bin/usershell
0-12 * * * * /bin/usershell 每天每小时从0到12分钟每分钟执行一次/bin/usershell
* */2 * * * /bin/usershell 每天每2小时执行一次/bin/usershell
* 1,3,5,7 * * * /bin/usershell 每天每逢1,3,4,7点执行一次/bin/usershell
暂定每30分钟执行一次入库脚本。
执行日志入库工具(LogToDBServer)准备:
编辑crontab
crontab -e
*/30 * * * * /work/chenjiayi/imdev/im3/start_logtodbserver.sh
/sbin/service crond start