利用Nginx实现用户数据收集

 2017-03-28 22:21:04     Nginx  数据收集   1883


导读: 用户数据主要分客户端主动上报和业务服务数据。

客户端主动上报的需要在前段做数据埋点,服务端部署服务收集上报的数据,我们这里用Nginx做日志收集服务,因为对日志收集服务来说,最重要的稳定、高并发和吞吐量,不涉及业务逻辑,保证数据不丢失。而Nginx是不错的选择。

业务服务数据分很多种,一种是Nginx的访问日志,一种是业务数据库中存储的数据,还有业务服务输出的日志等。

这里介绍的用户数据主要基于浏览器和移动APP。

网站数据收集


默认能够收集的信息如下:

名字 途径 备注
访问时间 web service Nginx $msec
IP web service Nginx $remote_addr
域名 javascript document.domain
URL javascript / web service document.URL / $request
页面标签 javascript document.title
分辨率 javascript windows.screen.height & width
颜色深度 javascript windows.screen.colorDepth
Referrer javascript / web service document.referrer / $http_referer
客户端信息 javascript / web service navigator.userAgent / $http_user_agent
客户端语言 javascript navigator.language

其他数据,需要根据业务来添加,另外,用户唯一标识必须要有,用来统计用户数。

用户唯一标识有两种实现方式,一种是客户端生成,一种是服务端生成。Google和Baidu貌似是服务端生成,实现方式无法知道。

以下为客户端生成用户以为标识的流程图:

以下为JavaScript代码:

(function () {
    var params = {};
    var uuid = "";

    function getCookie(c_name) {
        if (document.cookie.length > 0) {
            c_start = document.cookie.indexOf(c_name + "=");
            if (c_start != -1) {
                c_start = c_start + c_name.length + 1;
                c_end = document.cookie.indexOf(";", c_start);
                if (c_end == -1)
                    c_end = document.cookie.length;
                return unescape(document.cookie.substring(c_start, c_end));
            }
        }
        return "";
    };

    function generateUUID(){
        var d = new Date().getTime();
        uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = (d + Math.random()*16)%16 | 0;
            d = Math.floor(d/16);
            return (c=='x' ? r : (r&0x7|0x8)).toString(16);
        });
        return uuid.replace(new RegExp("-","gm"), "");
    };

    function checkUUID() {
        uuid = getCookie('uuid');
        if (uuid.length <= 0) {
            var date = new Date();
            date.setFullYear(date.getFullYear() + 10);
            uuid = generateUUID();
            document.cookie = "uuid=" + uuid + "; expires=" + date.toGMTString() + "; path=/; domain=.ssports";
        }
        return uuid;
    };

    params.uuid = checkUUID();

    function getQueryParameter(q_name) {
        var reg = new RegExp("(^|&)" + q_name + "=([^&]*)(&|$)", "i");
        var result = window.location.search.substr(1).match(reg);
        if (result != null) {
            return result[2];
        } else {
            return null;
        }
        ;
    }

    if (document) {
        //params.domain = document.domain || '';
        params.page = document.URL || '';
        params.title = document.title || '';
        //params.referrer = document.referrer || '';

        var s = getQueryParameter('source');
        if (s != undefined && s.length > 0) {
            document.cookie = "source=" + s + "; path=/; domain=.ssports";
        }
    }
    if (window) {
        if (window.screen) {
            params.sh = window.screen.height || 0;
            params.sw = window.screen.width || 0;
            params.cd = window.screen.colorDepth || 0;
        }
    }
    if (navigator) {
        params.lang = navigator.language || '';
        //params.acn = navigator.appCodeName || '';
        // params.an = navigator.appName || '';
        //params.av = navigator.appVersion || '';
        //params.plat = navigator.platform || '';
        //params.agent = navigator.userAgent || '';
    }

    var args = '';
    for (var i in params) {
        if (args != '') {
            args += '&';
        }
        args += i + '=' + encodeURIComponent(params[i]);
    }
    var img = new Image(1, 1);
    img.src = 'https://kiswo.com/logs/1.gif?' + args;
})();

由于客户端在上报数据,很容易涉及到跨域问题,而用gif作为接口,对浏览器就不存在跨越问题。

  • 天猫PC版的数据上报:
https://log.mmstat.com/2.gif?logtype=1&title=%u5929%u732Btmall.com--%u4E0A%u5929%u732B%uFF0C%u5C31%u591F%u4E86&pre=&cache=c90e9ea&scr=1366x768&category=&uidaplus=&aplus&isbeta=7&p=1&o=win7&b=firefox47&s=1366x768&w=gecko&ism=pc&lver=6.1.16&jsver=aplus_v2
  • 爱奇艺PC版的数据上报:
http://msg.71.am/cp2.gif?s=1468927518661&av=ares-2.18.6&y=qc_100001_100025&a=e2babb37798994618a7f1186c7fad602&p=t&rc=-1&t=s&rd=794&ai=0%7C%7C%7C%7C5000000839545%7C%7C5000000929733%7C%7C&d=54000&g=0&l=MTI0LjE5Mi4yMDYuNTg%3D

并不是所有参数都在URI,有些公共参数是放在Cookie或Header中的。

APP数据收集

APP数据收集应当尽量从业务服务Nginx日志来获取,减少客户端的上报,以减轻客户端的工作和减少性能影响。

APP数据上报接口和业务服务接口差不多。

  • 友盟SDK的数据上报:
http://alog.umeng.com/app_logs?...

Nginx配置

无论是PC的上报方式,还是APP的上报方式,都需要注意缓存问题,不能让接口请求出现缓存。

PC的上报方式接口一般都是返回一个空图片(小图片)。

APP的上报方式接口可以没有返回值,也可以返回其它内容。

日志记录格式:

 log_format main_gif '$remote_addr | $http_x_forwarded_for | $msec | [$time_local] | "$request" | "$cookie_source" | $status | "$http_referer" | "$http_user_agent"';

请求接口配置:

location /logs/1.gif {
    access_log logs/access_gif_1.log main_gif;
    default_type image/gif;

    #可以获取Cookie中的值
    set $cookie_source "";
    if ($http_cookie ~* "source=(.+)(?:;|$)" ) {
        set $cookie_source $1;
    }

    #etag on;
    expires off;
    add_header Last-Modified '';
    add_header Cache-Control 'no-cache';
    add_header Pragma "no-cache";

    empty_gif;
}

location /logs/app {
    default_type image/gif;

    expires off;
    add_header Last-Modified '';
    add_header Cache-Control 'no-cache';
    add_header Pragma "no-cache";

    empty_gif;
}

数据上报的请求服务,一般都是GET方式,如果有POST方式,就需要获取request_body的内容。

由于Nginx是为了解决负载均衡场景诞生的,所以它默认是不读取body的行为,会对API Server和Web Application场景造成一些影响。根据需要正确读取、丢弃bodyOpenResty开发是至关重要的。

以下为POST的请求方式,并获取request_body

日志记录格式:

 log_format main_gif '$remote_addr | $http_x_forwarded_for | $msec | [$time_local] | "$request" | "$request_body" | $status | "$http_referer" | "$http_user_agent"';

请求接口配置:

location /collect {
    access_log logs/access_old.log main_old;
    lua_code_cache off;
    set $body_data '';

    content_by_lua_block {
        ngx.req.read_body()
        local data = ngx.req.get_body_data()
        ngx.var.body_data = data
        ngx.say("body: ", data)
    }
}

Nginx日志切割

Nginx运行时间久了,日志会变的很大,如果不切割,处理起来会很不方便。

我们一般按小时来切割日志。对应其他日志,例如Nginx的错误日志或不是很重要的日志,可以采取按天或月切割。

#!/bin/bash
#nginx_log_division.sh

##############################
# Nginx 日志切割              #
# Author: wangr              #
# Date: 2016-09-23           #
##############################


CUT_TIME=$1
if [ ! -n "$CUT_TIME" ]; then
    CUT_TIME=`date -d "1 hours ago" +%Y%m%d%H`
fi

#DATE_DAY=${NOW_DATE//'-'/''}
CUT_HOUR=${CUT_TIME:8:10}

echo "`date +"%Y-%m-%d %H:%M:%S"` start execute script '$0', time is: $CUT_TIME"
echo "$CUT_HOUR"

LOGS_PATH=/usr/local/nginx/logs

mv ${LOGS_PATH}/access.log  ${LOGS_PATH}/access_${CUT_TIME}.log

if [ "$CUT_HOUR" -eq "23" ]; then
    echo "division error.log"
    #mv ${LOGS_PATH}/error.log  ${LOGS_PATH}/error_${CUT_TIME}.log
fi

kill -USR1 $(cat /usr/local/nginx/logs/nginx.pid)


echo "`date +"%Y-%m-%d %H:%M:%S"` over execute script '$0', time is: $CUT_TIME"

阿里老鱼的用户行为分析解决方案

下面为阿里的老鱼写的一篇文章,有好几年了,网站已经打不开了,只在他的微博上找到了几张图。膜拜一下。

用户行为分析解决方案

用户行为分析解决方案

网站监控

网站监控

用户行为还原1

用户行为还原2

相关文章