ngnix+lua+js埋点 实现自定义日志采集
1 收集数据的页面
js埋点: 在想要手机数据的页面预先放一段js代码, 当用户发生行为时, 触发js方法, 收集数据,发到后端
http://192.168.4.101/index.html 这里的index.html page1.html page2.html 放在101的tomcat中
- 在页面js中添加点击事件 点击事件触发后, 引入外部js 外部js中有发送请求的代码
- js放到外部(可以放在静态资源服务器)的原因 : 解耦合 因为发送请求的js文件可能更改频繁
- ma.async = true 的意思是异步调用外部 js 文件
- 异步调用的好处: 不阻塞浏览器对页面的解析,待外部 js 下载完成后异步执行
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>日志采集</title>
<script src="/jquery-3.2.1.min.js">
</script>
<script type="text/javascript">
<!-- 收集数据的数组 -->
var _maq = _maq || [];
_maq.push(['_setAccount', 'QuietHR']);
<!-- jquery -->
$(function(){
<!-- 埋点 给页面的a标签添加点击事件 当点击事件触发后,
收集数据到_maq数组中, 引入外部js ,发送给后端 -->
$("a").click(function(){
<!-- 获取当前a标签的clstag属性 进行解析-->
var clstag = $(this).attr("clstag");
<!-- 获取当前a标签的文本 -->
var _a_value = $(this).text();
<!-- 封装数据 -->
_maq.push(['_a_value',_a_value]);
clstag = clstag.split('|');
for (i in clstag){
_maq.push(['type'+i, clstag[i]]);
}
<!-- 引入外部js 发送请求 -->
sendRequest();
})
});
<!-- 创建script标签 设置为异步处理 src js的路径 将js添加到页面中 -->
function sendRequest(){
var ma = document.createElement('script');
ma.type = 'text/javascript';
ma.async = true;
ma.src = 'http://192.168.4.103/ma.js';
<!-- 获得js标签数组的第一个 将引入的js放在他之前 -->
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ma, s);
}
</script>
</head>
<body>
<h1 align="center">bei cai ji de ye mian</h1>
<a href="http://192.168.4.101/page1.html" target="_blank" clstag="click|index|page1">点击后触发click方法,并跳转到page1.html</a><br/>
<a href="http://192.168.4.101/page2.html" target="_blank" clstag="click|index|page2">点击后触发click方法,并跳转到page2.html</a>
</body>
</html>
2 要跳转的页面(page1.html)
page1.html 在加载时, 自调用匿名函数触发, 引用外部js
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>page1</title>
<script type="text/javascript">
var _maq = _maq || [];
_maq.push(['_setAccount', 'QuietHR']);
(function() {
var ma = document.createElement('script');
ma.type = 'text/javascript';
ma.async = true;
ma.src = 'http://192.168.4.103/ma.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(ma, s);
})();
</script>
</head>
<body>
<h1 align="center">日志采集页面</h1>
<h1 align="center">Page 1</h1>
</body>
</html>
3 发送请求的js
http://192.168.4.103/ma.js 这里的js放在103服务器的nginx中
将_maq数组中的数据及其他信息(如document window) 封装到param对象中, 发送到日志采集服务器
js自调用匿名函数 (function(){})();
- 避免重名
- 自调用匿名函数 只在运行时调用一次, 一般用于初始化
- 在这里使用是因为在用户进入 page1.html 时, 就要采集用户信息, 发送给日志采集服务器
发送请求时,伪装成图片img发送
- 使用图片的原因 : 如果使用js的http请求, 会发生js跨域请求问题
- 请求一个图片,在请求URL中携带要采集的数据 所以重点在数据,而不是图片
- 图片设置为1*1像素大小
(function () {
var params = {};
//Document对象数据
if(document) {
params.domain = document.domain || '';
params.url = document.URL || '';
params.title = document.title || '';
params.referrer = document.referrer || '';
}
//Window对象数据
if(window && window.screen) {
params.sh = window.screen.height || 0;
params.sw = window.screen.width || 0;
params.cd = window.screen.colorDepth || 0;
}
//navigator对象数据
if(navigator) {
params.lang = navigator.language || '';
}
//解析_maq配置
if(_maq) {
for(var i in _maq) {
switch(_maq[i][0]) {
case '_setAccount':
params.account = _maq[i][1];
break;
case '_a_value':
params.avalue = _maq[i][1];
break;
case 'type0':
params.type0 = _maq[i][1];
break;
case 'type1':
params.type1 = _maq[i][1];
break;
case 'type2':
params.type2 = _maq[i][1];
break;
default:
break;
}
}
}
//将param对象中的属性 解析 拼接成字符串
var args = '';
for(var i in params) {
if(args != '') {
args += '&';
}
args += i + '=' + encodeURIComponent(params[i]);
}
//通过Image对象请求后端脚本
var img = new Image(1, 1);
img.src = 'http://192.168.4.103/log.gif?' + args;
})();
4 nginx后端脚本(lua)
nginx的配置文件 nginx.conf
- 定义一个自定义的日志文件 设置格式 log_format user_log_format
- location /log.gif
- 当有 192.168.4.103/log.gif 请求到达nginx时, 会触发这个脚本
- 通过subrequest到/i-log记录日志 把URL参数转发过去
- /i-log 中的internal表示这是一个内部location只能通过其他location转发子请求(subrequest)到自己
- i-log 将数据进行解析, 写入到user_defined.log 日志文件中
- i-log 响应空字符串给log.gif
- log.gif响应空图片给js
# nginx启动时 开启两个线程
worker_processes 2;
# 连接数
events {
worker_connections 1024;
}
# 重点
http {
include mime.types;
default_type application/octet-stream;
# 日志文件格式 access.log nginx默认的日志文件
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 日志文件格式 user_defined.log 数据会写到这个文件中
log_format user_log_format "$msec||$remote_addr||$status||$body_bytes_sent||$u_domain||$u_url||$u_title||$u_referrer||$u_sh||$u_sw||$u_cd||$u_lang||$http_user_agent||$u_account||$u_avalue||$u_type0||$u_type1||$u_type2";
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
# 接收 localhost/log.gif 请求
location /log.gif {
#伪装成gif文件
default_type image/gif;
#nginx本身记录的access_log,日志格式为main
access_log logs/access.log main;
access_by_lua "
-- 用户跟踪cookie名为__utrace
local uid = ngx.var.cookie___utrace
if not uid then
-- 如果没有则生成一个跟踪cookie,算法为md5(时间戳+IP+客户端信息)
uid = ngx.md5(ngx.now() .. ngx.var.remote_addr .. ngx.var.http_user_agent)
end
ngx.header['Set-Cookie'] = {'__utrace=' .. uid .. '; path=/'}
if ngx.var.arg_domain then
-- 通过subrequest到/i-log记录日志,将参数和用户跟踪cookie带过去
ngx.location.capture('/i-log?' .. ngx.var.args .. '&utrace=' .. uid)
end
";
#此请求不缓存
add_header Expires "Fri, 01 Jan 1980 00:00:00 GMT";
add_header Pragma "no-cache";
add_header Cache-Control "no-cache, max-age=0, must-revalidate";
#返回一个1×1的空gif图片
empty_gif;
}
location /i-log {
#内部location,不允许外部直接访问
internal;
#设置变量,注意需要unescape
set_unescape_uri $u_domain $arg_domain;
set_unescape_uri $u_url $arg_url;
set_unescape_uri $u_title $arg_title;
set_unescape_uri $u_referrer $arg_referrer;
set_unescape_uri $u_sh $arg_sh;
set_unescape_uri $u_sw $arg_sw;
set_unescape_uri $u_cd $arg_cd;
set_unescape_uri $u_lang $arg_lang;
set_unescape_uri $u_account $arg_account;
set_unescape_uri $u_avalue $arg_avalue;
set_unescape_uri $u_type0 $arg_type0;
set_unescape_uri $u_type1 $arg_type1;
set_unescape_uri $u_type2 $arg_type2;
#打开subrequest(子请求)日志
log_subrequest on;
#自定义采集的日志,记录数据到user_defined.log
access_log logs/user_defined.log user_log_format;
#输出空字符串
echo '';
}
}
}
测试
写入到日志文件的数据中, 中文变成了乱码
- \xE8\xBF\x99\xE6\x98\xAF\xE7\x82\xB9\xE5\x87\xBB
- 修改ngnix源码…
# 点击事件触发的日志采集
1542028871.526||192.168.4.130||200||0||192.168.4.101||http://192.168.4.101/index.html||bei cai ji de ye mian||||1080||1920||24||zh-CN||Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0||QuietHR||\xE8\xBF\x99\xE6\x98\xAF\xE7\x82\xB9\xE5\x87\xBB1||click||index||page1
# 进入page1页面 自动触发的日志
1542028871.601||192.168.4.130||200||0||192.168.4.101||http://192.168.4.101/page1.html||page1||http://192.168.4.101/index.html||1080||1920||24||zh-CN||Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0||QuietHR||||||||