之前的几篇文章都是关于之前提到的低代码平台
的。
这个大的项目以 low code 为核心,囊括了编辑器前端、编辑器后端、C 端 H5、组件库、组件平台、后台管理系统前端、后台管理系统后台、统计服务、自研 CLI 九大系统。
今天就来说一下其中的统计服务
:目的主要是为了实现 H5 页面的分渠道统计(其实不仅仅是分渠道统计,核心是想做一个自定义事件统计服务,只是目前有分渠道统计的需求),查看每个渠道具体的 PV 情况。(具体会在 url 上面体现,会带上页面名称、id、渠道类型等)
先放一下整体流程图吧:
常见的日志收集方式有手动埋点和自动埋点,这里我们不关注于如何收集日志,而是如何将收集的日志的发送到服务器。
在常见的埋点方案中,通过图片来发送埋点请求是一种经常被采纳的,它有很多优势:
这里的方案就是在 nginx
上放一张 1px * 1px
的静态图片,然后通过访问该图片(http://xxxx.png?env=xx&event=xxx
),并将埋点数据放在query
参数上,以此将埋点数据落到nginx
日志中。
iOS 上会限制 get 请求的 url 长度,但我们这里真实场景发送的数据不会太多,所以目前暂时采用这种方案
这里简单阐述一下为什么图片地址的query key
要这么设计,如果单纯是为了统计渠道和作品,很有可能会把key
设计为channel
、workId
这种,但上面也说到了,我们是想做一个自定义事件统计服务,那么就要考虑字段的可扩展性,字段应更有通用语义。所以参考了很多统计服务的设计,这里采用的字段为:
之后每次访问页面,nginx
就会自动记录日志到access_log
中。
有了日志,下面我们来看下如何来对其进行拆分。
access.log
日志默认不会拆分,会越积累越多,系统磁盘的空间会被消耗得越来越多,将来可能面临着日志写入失败、服务异常的问题。
日志文件内容过多,对于后续的问题排查和分析也会变得很困难。
所以日志的拆分是有必要也是必须的。
我们这里拆分日志的核心思路是:将当前的access.log
复制一份重命名为新的日志文件,之后清空老的日志文件。
视流量情况(流量越大日志文件积累的越快),按天、小时、分钟来拆分。可以把access.log
按天拆分到某个文件夹中。
log_by_day/2021-12-19.log
log_by_day/2021-12-20.log
log_by_day/2021-12-21.log
但上面的复制 -> 清空
操作肯定是要自动处理的,这里就需要启动定时任务,在每天固定的时间(我这里是在每天凌晨 00:00)来处理。
其实定时任务不仅在日志拆分的时候会用到,在后面的日志分析和日志清除都会用到,这里先简单介绍一下,最终会整合拆分、分析和清除。
linux
中内置的cron
进程就是来处理定时任务的。在node
中我们一般会用node-schedule
或cron
来处理定时任务。
这里使用的是cron[1]:
/**
cron 定时规则 https://www.npmjs.com/package/cron
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │
│ │ │ │ │ └ day of week (0 - 6) (Sun-Sat)
│ │ │ │ └───── month (1 - 12)
│ │ │ └────────── day of month (1 - 31)
│ │ └─────────────── hour (0 - 23)
│ └──────────────────── minute (0 - 59)
└───────────────────────── second (0 - 59)
*/
具体使用方式就不展开说明了。
有了上面这些储备,下面我就来写一下这块代码,首先梳理下逻辑:
1️⃣ 读取源文件 access.log
2️⃣ 创建拆分后的文件夹(不存在时需自动创建)
3️⃣ 创建日志文件(天维度,不存在时需自动创建)
4️⃣ 拷贝源日志至新文件
5️⃣ 清空 access.log
/**
* 拆分日志文件
*
* @param {*} accessLogPath
*/
function splitLogFile(accessLogPath) {
const accessLogFile = path.join(accessLogPath, "access.log");
const distFolder = path.join(accessLogPath, DIST_FOLDER_NAME);
fse.ensureDirSync(distFolder);
const distFile = path.join(distFolder, genYesterdayLogFileName());
fse.ensureFileSync(distFile);
fse.outputFileSync(distFile, ""); // 防止重复,先清空
fse.copySync(accessLogFile, distFile);
fse.outputFileSync(accessLogFile, "");
}
日志分析就是读取上一步拆分好的文件,然后按照一定规则去处理、落库。这里有一个很重要的点要提一下:node
在处理大文件或者未知内存文件大小的时候千万不要使用readFile
,会突破 V8 内存限制。正是考虑到这种情况,所以这里读取日志文件的方式应该是:createReadStream
创建一个可读流交给 readline[2] 逐行读取处理
readline
readline
模块提供了用于从可读流每次一行地读取数据的接口。可以使用以下方式访问它:
const readline = require("readline");
readline
的使用也非常简单:创建一个接口实例,传入对应的参数:
const readStream = fs.createReadStream(logFile);
const rl = readline.createInterface({
input: readStream,
});
然后监听对应事件即可:
rl.on("line", (line) => {
if (!line) return;
// 获取 url query
const query = getQueryFromLogLine(line);
if (_.isEmpty(query)) return;
// 累加逻辑
// ...
});
rl.on("close", () => {
// 逐行读取结束,存入数据库
const result = eventData.getResult();
resolve(result);
});
这里用到了line
和close
事件:
line
事件:每当 input 流接收到行尾输入(\n、\r 或 \r\n)时,则会触发 'line' 事件close
事件:一般在传输结束时会触发该事件了解了readline
的使用,下面让我们来逐行对日志结果进行分析吧。
首先来看下access.log
中日志的格式:
我们取其中一行来分析:
127.0.0.1 - - [19/Feb/2021:15:22:06 +0800] "GET /event.png?env=h5&event=pv&key=24&value=2 HTTP/1.1" 200 5233 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36" "-"
我们要拿到的就是url
的query
部分,也就是我们在h5
中自定义的数据。
通过正则匹配即可:
const reg = /GET\s\/event.png\?(.+?)\s/;
const matchResult = line.match(reg);
console.log("matchResult", matchResult);
const queryStr = matchResult[1];
console.log("queryStr", queryStr);
打印结果为:
queryStr
可通过node
中的querystring.parse()
来处理:
const query = querystring.parse(queryStr);
console.log('query', query)
{
env: 'h5',
event: 'pv',
key: '24',
value: '2'
}
剩下的就是对数据做累加处理了。
但如何去做累加,我们要想一下,最开始也说了是要去做分渠道统计,那么最终的结果应该可以清晰的看到两个数据:
只有这样的数据对于运营才是有价值的,数据的好坏也直接决定了后面在每个渠道投放的力度。
这里我参考了 Google Analytics
中的多渠道漏斗[3]的概念,由上到下分维度记录每个维度的数据,这样就可以清晰的知道每个渠道的情况了。
具体实现也不麻烦,我们先来看下刚刚从一条链接中得到的有用数据:
{
env: 'h5',
event: 'pv',
key: '24',
value: '2'
}
这里的env
代表环境,这里统计的都是来源于h5
页面,所以env
为h5
,但是为了扩展,所以设置了这个字段。
event
表示事件名称,这里主要是统计访问量,所以为pv
。
key
是作品 id。
value
是渠道 code,目前主要有:1-微信、2-小红书、3-抖音。
再来看下最终统计得到的结果吧:
{
date: '2021-12-21',
key: 'h5',
value: { num: 1276}
}
{
date: '2021-12-21',
key: 'h5.pv',
value: { num: 1000}
}
{
date: '2021-12-21',
key: 'h5.pv.12',
value: { num: 200}
}
{
date: '2021-12-21',
key: 'h5.pv.12.1',
value: { num: 56}
}
{
date: '2021-12-21',
key: 'h5.pv.12.2',
value: { num: 84}
}
{
date: '2021-12-21',
key: 'h5.pv.12.3',
value: { num: 60}
}
这是截取了2021-12-21
当天的数据,我给大家分析一波:
1️⃣ h5:当天 h5 页面的自定义事件上报总数为 1276
2️⃣ h5.pv:其中 所有 pv(也就是 h5.pv)为 1000
3️⃣ h5.pv.12:作品 id 为 12 的 pv 一共有 200
4️⃣ h5.pv.12.1:作品 id 为 12 的在微信渠道的 pv 为 56
5️⃣ h5.pv.12.2:作品 id 为 12 的在小红书渠道的 pv 为 84
6️⃣ h5.pv.12.2:作品 id 为 12 的在抖音渠道的 pv 为 60
这样就能清楚的得到某一天某个作品在某条渠道的访问情况了,后续再以这些数据为支撑做成可视化报表,效果就一目了然了。
目前这部分数据是放在了mongoDB
中,关于node
中使用mongoDB
就不展开说了,不熟悉的可以参考我另外一篇文章[Koa2+MongoDB+JWT 实战--Restful API 最佳实践]
这里贴下model
吧:
/**
* @description event 数据模型
*/
const mongoose = require("../db/mongoose");
const schema = mongoose.Schema(
{
date: Date,
key: String,
value: {
num: Number,
},
},
{
timestamps: true,
}
);
const EventModel = mongoose.model("event_analytics_data", schema);
module.exports = EventModel;
随着页面的持续访问,日志文件会快速增加,超过一定时间的日志文件存在的价值也不是很大,所以我们要定期清除日志文件。
这个其实比较简单,遍历文件,因为文件名都是以日期命名的(格式:2021-12-14.log
),所以只要判断时间间隔大于 90 天就删除日志文件。
贴一下核心实现:
// 读取日志文件
const fileNames = fse.readdirSync(distFolder);
fileNames.forEach((fileName) => {
try {
// fileName 格式 '2021-09-14.log'
const dateStr = fileName.split(".")[0];
const d = new Date(dateStr);
const t = Date.now() - d.getTime();
if (t / 1000 / 60 / 60 / 24 > 90) {
// 时间间隔,大于 90 天,则删除日志文件
const filePath = path.join(distFolder, fileName);
fse.removeSync(filePath);
}
} catch (error) {
console.error(`日志文件格式错误 ${fileName}`, error);
}
});
到这里,日志的拆分、分析和清除都说完了,现在要用cron
来对他们做整合了。
首先来创建定时任务:
function schedule(cronTime, onTick) {
if (!cronTime) return;
if (typeof onTick !== "function") return;
// 创建定时任务
const c = new CronJob(
cronTime,
onTick,
null, // onComplete 何时停止任务
true, // 初始化之后立刻执行
"Asia/Shanghai" // 时区
);
// 进程结束时,停止定时任务
process.on("exit", () => c.stop());
}
然后每一阶段都在不同的时间阶段去处理(定时拆分 -> 定时分析 -> 定时删除)
function splitLogFileTiming() {
const cronTime = "0 0 0 * * *"; // 每天的 00:00:00
schedule(cronTime, () => splitLogFile(accessLogPath));
console.log("定时拆分日志文件", cronTime);
}
function analysisLogsTiming() {
const cronTime = "0 0 3 * * *"; // 每天的 3:00:00 ,此时凌晨,访问量较少,服务器资源处于闲置状态
schedule(cronTime, () => analysisLogsAndWriteDB(accessLogPath));
console.log("定时分析日志并入库", cronTime);
}
function rmLogsTiming() {
const cronTime = "0 0 4 * * *"; // 每天的 4:00:00 ,此时凌晨,访问量较少,服务器资源处于闲置状态
schedule(cronTime, () => rmLogs(accessLogPath));
console.log("定时删除过期日志文件", cronTime);
}
然后在应用入口按序调用即可:
// 定时拆分日志文件
splitLogFileTiming();
// 定时分析日志并入库
analysisLogsTiming();
// 定时删除过期日志文件
rmLogsTiming();
ok,到这里,一个简易的统计服务就完成了。
[1]cron: https://www.npmjs.com/package/cron
[2]readline: http://nodejs.cn/api/readline.html
[3]多渠道漏斗: https://support.google.com/analytics/answer/1191180?hl=zh-Hans#zippy=%2C%E6%9C%AC%E6%96%87%E5%8C%85%E5%90%AB%E7%9A%84%E4%B8%BB%E9%A2%98
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/uvBZCS7zThXl08L-FyFE_w
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。