日志离线收集处理方案——1.数据采集

一. 埋点

   测试时,需要自己模拟一个网站系统,在其中需要埋点的页面中的<head></head>中加入如下代码:

         <script src="tongji.js"></script>   注:tongji.js 就是需埋点的 js 文件

下面是 tongji.js 文件的代码:

/**函数可对字符串进行编码,这样就可以在所有的计算机上读取该字符串。*/
function ar_encode(str)
{
    //进行URL编码
    return encodeURI(str);
}


/**屏幕分辨率*/
function ar_get_screen()
{
    var c = "";

    if (self.screen) {
        c = screen.width+"x"+screen.height;
    }

    return c;
}

/**颜色质量*/
function ar_get_color()
{
    var c = "";

    if (self.screen) {
        c = screen.colorDepth+"-bit";
    }

    return c;
}

/**返回当前的浏览器语言*/
function ar_get_language()
{
    var l = "";
    var n = navigator;

    if (n.language) {
        l = n.language.toLowerCase();
    }
    else
    if (n.browserLanguage) {
        l = n.browserLanguage.toLowerCase();
    }

    return l;
}

/**返回浏览器类型IE,Firefox*/
function ar_get_agent()
{
    var a = "";
    var n = navigator;

    if (n.userAgent) {
        a = n.userAgent;
    }

    return a;
}


/**返回浏览器是否支持(启用)cookie */
function ar_get_cookie_enabled()
{
    var c = "";
    var n = navigator;
    c = n.cookieEnabled ? 1 : 0;

    return c;
}




/**匹配顶级域名*/
function ar_c_ctry_top_domain(str)
{
    var pattern = "/^aero$|^cat$|^coop$|^int$|^museum$|^pro$|^travel$|^xxx$|^com$|^net$|^gov$|^org$|^mil$|^edu$|^biz$|^info$|^name$|^ac$|^mil$|^co$|^ed$|^gv$|^nt$|^bj$|^hz$|^sh$|^tj$|^cq$|^he$|^nm$|^ln$|^jl$|^hl$|^js$|^zj$|^ah$|^hb$|^hn$|^gd$|^gx$|^hi$|^sc$|^gz$|^yn$|^xz$|^sn$|^gs$|^qh$|^nx$|^xj$|^tw$|^hk$|^mo$|^fj$|^ha$|^jx$|^sd$|^sx$/i";

    if(str.match(pattern)){ return 1; }

    return 0;
}

/**处理域名地址*/
function ar_get_domain(host)
{
    //如果存在则截去域名开头的 "www."
    var d=host.replace(/^www\./, "");

    //剩余部分按照"."进行split操作,获取长度
    var ss=d.split(".");
    var l=ss.length;

    //如果长度为3,则为xxx.yyy.zz格式
    if(l == 3){
        //如果yyy为顶级域名,zz为次级域名,保留所有
        if(ar_c_ctry_top_domain(ss[1]) && ar_c_ctry_domain(ss[2])){
        }
        //否则只保留后两节
        else{
            d = ss[1]+"."+ss[2];
        }
    }
    //如果长度大于3
    else if(l >= 3){

        //如果host本身是个ip地址,则直接返回该ip地址为完整域名
        var ip_pat = "^[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*$";
        if(host.match(ip_pat)){
            return d;
        }
        //如果host后两节为顶级域名及次级域名,则保留后三节
        if(ar_c_ctry_top_domain(ss[l-2]) && ar_c_ctry_domain(ss[l-1])) {
            d = ss[l-3]+"."+ss[l-2]+"."+ss[l-1];
        }
        //否则保留后两节
        else{
            d = ss[l-2]+"."+ss[l-1];
        }
    }

    return d;
}


/**返回cookie信息*/
function ar_get_cookie(name)
{
    //获取所有cookie信息
    var co=document.cookie;

    //如果名字是个空 返回所有cookie信息
    if (name == "") {
        return co;
    }

    //名字不为空 则在所有的cookie中查找这个名字的cookie
    var mn=name+"=";
    var b,e;
    b=co.indexOf(mn);

    //没有找到这个名字的cookie 则返回空
    if (b < 0) {
        return "";
    }

    //找到了这个名字的cookie 获取cookie的值返回
    e=co.indexOf(";", b+name.length);
    if (e < 0) {
        return co.substring(b+name.length + 1);
    }
    else {
        return co.substring(b+name.length + 1, e);
    }
}

/**
 设置cookie信息
 操作符:
 0 表示不设置超时时间 cookie是一个会话级别的cookie  cookie信息保存在浏览器内存当中 浏览器关闭时cookie消失
 1 表示设置超时时间为10年以后 cookie会一直保存在浏览器的临时文件夹里 直到超时时间到来 或用户手动清空cookie为止
 2 表示设置超时时间为1个小时以后 cookie会一直保存在浏览器的临时文件夹里 直到超时时间到来 或用户手动清空cookie为止
 * */
function ar_set_cookie(name, val, cotp)
{
    var date=new Date;
    var year=date.getFullYear();
    var hour=date.getHours();

    var cookie="";

    if (cotp == 0) {
        cookie=name+"="+val+";";
    }
    else if (cotp == 1) {
        year=year+10;
        date.setYear(year);
        cookie=name+"="+val+";expires="+date.toGMTString()+";";
    }
    else if (cotp == 2) {
        hour=hour+1;
        date.setHours(hour);
        cookie=name+"="+val+";expires="+date.toGMTString()+";";
    }

    var d=ar_get_domain(document.domain);
    if(d != ""){
        cookie +="domain="+d+";";
    }
    cookie +="path="+"/;";

    document.cookie=cookie;
}



/**返回客户端时间*/
function ar_get_stm()
{
    return new Date().getTime();
}


/**返回指定个数的随机数字串*/
function ar_get_random(n) {
    var str = "";
    for (var i = 0; i < n; i ++) {
        str += String(parseInt(Math.random() * 10));
    }
    return str;
}

/* main function */
function ar_main() {
    
      
    //收集完日志 提交到的路径
    var dest_path   = "http://192.168.3.152/proxy/big.png?";
    var expire_time = 30 * 60 * 1000;//会话超时时长

    //处理uv
    //--获取cookie ar_stat_uv的值
    var uv_str = ar_get_cookie("ar_stat_uv");
    var uv_id = "";
    //--如果cookie ar_stat_uv的值为空
    if (uv_str == ""){
        //--为这个新uv配置id,为一个长度20的随机数字
        uv_id = ar_get_random(20);
        //--设置cookie ar_stat_uv 保存时间为10年
        ar_set_cookie("ar_stat_uv", uv_id, 1);
    }
    //--如果cookie ar_stat_uv的值不为空
    else{
        //--获取uv_id
        uv_id  = uv_str;
    }

    //处理ss
    //--获取cookie ar_stat_ss
    var ss_str = ar_get_cookie("ar_stat_ss");
    var ss_id = "";  //sessin id
    var ss_no = 0;   //session有效期内访问页面的次数

    //--如果cookie中不存在ar_stat_ss 说明是一次新的会话
    if (ss_str == ""){
        //--随机生成长度为10的session id
        ss_id = ar_get_random(10);
        //--session有效期内页面访问次数为0
        ss_no = 0;
        //--拼接cookie ar_stat_ss 值 格式为 会话编号_会话期内访问次数_客户端时间_网站id
        value = ss_id+"_"+ss_no+"_"+ar_get_stm();
        //--设置cookie ar_stat_ss
        ar_set_cookie("ar_stat_ss", value, 0);
    }
    //--如果cookie中存在ar_stat_ss
    else {
        //获取ss相关信息
        var items = ss_str.split("_");
        //--ss_id
        var cookie_ss_id  = items[0];
        //--ss_no
        var cookie_ss_no  = parseInt(items[1]);
        //--ss_stm
        var cookie_ss_stm = items[2];

        //如果当前时间-当前会话上一次访问页面的时间>30分钟,虽然cookie还存在,但是其实已经超时了!仍然需要重新生成cookie
        if (ar_get_stm() - cookie_ss_stm > expire_time) {
            //--重新生成会话id
            ss_id = ar_get_random(10);
            //--设置会话中的页面访问次数为0
            ss_no = 0;
        }
        //--如果会话没有超时
        else{
            //--会话id不变
            ss_id = cookie_ss_id;
            //--设置会话中的页面方位次数+1
            ss_no = cookie_ss_no + 1;
        }

        //--重新拼接cookie ar_stat_ss的值
        value = ss_id+"_"+ss_no+"_"+ar_get_stm();
        ar_set_cookie("ar_stat_ss", value, 0);
    }

    //当前地址
    var url = document.URL;
    url = ar_encode(String(url));

    //当前资源名
    var urlname = document.URL.substring(document.URL.lastIndexOf("/")+1);
    urlname = ar_encode(String(urlname));

    //返回导航到当前网页的超链接所在网页的URL
    var ref = document.referrer;
    ref = ar_encode(String(ref));

    //屏幕信息
    var screen = ar_get_screen();
    screen = ar_encode(String(screen)); 

    //浏览器是否支持并启用了cookie
    var cookie_enabled =ar_get_cookie_enabled();
    cookie_enabled =ar_encode(String(cookie_enabled));

    //当前ss状态 格式为"会话id_会话次数_当前时间"
    var stat_ss = ss_id+"_"+ss_no+"_"+ar_get_stm();
    //拼接访问地址 增加如上信息
    dest = dest_path
                + "url="+url
                + "&urlname=" + urlname
                + "&scr=" + screen
                + "&ce=" + cookie_enabled
                + "&cnv=" + String(Math.random())
                + "&ref=" + ref
                + "&stat_uv=" + uv_id
                + "&stat_ss=" + stat_ss ;

    //通过插入图片访问该地址
    document.getElementsByTagName("body")[0].innerHTML += "<img src=\""+dest+"\" border=\"0\" width=\"1\" height=\"1\" />";

}

window.onload = function(){
    //触发main方法
    ar_main();
}
View Code

说明:

     1. var dest_path   = "http://192.168.3.152/proxy/big.png?";     此处要改成日志服务器的地址,并且这个地址是能够被访问的,最后一个 "?“ 不要忘加,用来拼后续参数使用的。

     2. 埋点的原理:js代码会动态在页面中创建一个宽和高都是1px的图片,图片的地址指向了1中定义的日志服务器中的图片,

           document.getElementsByTagName("body")[0].innerHTML += "<img src=\""+dest+"\" border=\"0\" width=\"1\" height=\"1\" />";

 

二. 日志服务器日志格式定义

   日志服务器一般会使用nginx或apache ,此处以nginx为例,一般日志服务器会安装在linux Centos系统上,nginx日志格式定义的文件通常位于 /etc/nginx/nginx.conf ,如下:

worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" "$http_user_agent"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

说明:上述红色部分为日志格式定义部分,每一行用单引号括住,每一个字段列用又引号括住 , 当前样例只是用到部分日志列

 配置中具体可用到的参数说明:  

  $remote_addr:远程IP;
  $remote_user:远程用户;
  $stime_local:时间;
  $request:用来记录请求的url与http协议;
  $status:用来记录请求状态;成功是200;
  $body_bytes_sent:记录发送给客户端文件主体内容大小;
  $http_referer:用来记录从那个页面链接访问过来的;
  $http_user_agent:记录客户浏览器的相关信息;
  $http_x_forwarded_for:访问用户的真实 IP 地址;

 

三. 日志切割

   对于忙碌的服务器,日志文件大小会增长极快,服务器会很快消耗磁盘空间,这成了个问题。除此之外,处理一个单个的庞大日志文件也常常是件十分棘手的事。在Linux下提供了一个十分有用的工具Logrotate ,配合Linux定时任务它可以自动对日志进行截断(或轮循)、压缩以及删除旧的日志文件。配置完后,logrotate的运作完全自动化,不必进行任何进一步的人为干预。更为方便的是,Centos7系统安装完成,Logrotate 和 Crontab这样的工具都是自动安装好的,直接可以使用。

       日志首先要配置相应的文件,logrotate命令需要根据具体的切割指令文件去完成切割,切割的指令是一个JSON串的格式,这个串包括了具体的指令参数,具体可用到的参数如下:

       

       下面是 具体的一个配置指令的实例 (其中红色部分为我们自定义的执行语句):

       {

    daily
    missingok
    rotate 1
    delaycompress
    notifempty
    create 640 nginx adm
    sharedscripts
    postrotate
    if [ -f /var/run/nginx.pid ]; then
      kill -USR1 `cat /var/run/nginx.pid`
      mv /var/log/nginx/access.log.*   /root/logs/$(date +%Y%m%d-%H).log
    fi
    endscript
  }

          

       日志的定时切割可以两种方式来做:

       1. 通过crontab -e来创建定时任务

       2. 如果是按每小时或每天来触发的定时任务,可以在 /etc/cron.daily 或/etc/cron.hourly 目录下创时要执行的脚本:

            例: /etc/cron.daily/logrotate

      /usr/sbin/logrotate -s /var/lib/logrotate/logrotate.status /etc/logrotate.conf
      EXITVALUE=$?
      if [ $EXITVALUE != 0 ]; then
        /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]"
      fi
      exit 0

          

        测试时,我们可以通过手动执行脚本来完成切割 :  logrotate -vf /etc/logrotate.d/nginx  ,/etc/logrotate.d/nginx脚本内容如下:

 /var/log/nginx/*.log {
    daily
    missingok
    rotate 1
    delaycompress
    notifempty
    create 640 nginx adm
    sharedscripts
    postrotate
        if [ -f /var/run/nginx.pid ]; then
            kill -USR1 `cat /var/run/nginx.pid`
            mv /var/log/nginx/access.log.* /root/logs/$(date +%Y%m%d-%H).log
        fi
    endscript
}                

 

四.Flume收集日志到HDFS

agent.sources = spoolDirSrc
agent.channels = memoryChannel
agent.sinks = hdfsSink

# For each one of the sources, the type is defined
agent.sources.spoolDirSrc.type = spooldir
agent.sources.spoolDirSrc.spoolDir = /root/logs
# The channel can be defined as follows.
agent.sources.spoolDirSrc.channels = memoryChannel

# Each sink's type must be defined
agent.sinks.hdfsSink.type = hdfs
agent.sinks.hdfsSink.hdfs.path = hdfs://hadoop01:9000/test
agent.sinks.hdfsSink.hdfs.fileType = DataStream
#Specify the channel the sink should use
agent.sinks.hdfsSink.channels = memoryChannel

# Each channel's type is defined.
agent.channels.memoryChannel.type = memory

agent.channels.memoryChannel.capacity = 100

 

bin/flume-ng agent --conf ./conf/ -f conf/flume-conf  -n agent

 

posted @ 2019-07-28 14:18  杭州胡欣  阅读(1130)  评论(0编辑  收藏  举报