日志服务(SLS) 的桌面端 Node.js SDK 封装

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: 日志服务(SLS) 的桌面端 Node.js SDK 封装

项目需求

项目上为了更好地掌握产品的使用数据,综合对比各种埋点框架之后决定基于自由度更高的日志服务(以下简称“SLS”)实现数据埋点。

SLS 官方提供的 SDK 非常丰富,包含十几种语言,而 JavaScript 更是受到了优厚待遇,相关 SDK 已经达到了 3 种:浏览器 JavaScript SDK、小程序 JavaScript SDK、Node.js SDK。

目前我们项目没有小程序,只有 Node.js 的桌面端以及 JavaScript 的浏览器端,所以不考虑小程序 JavaScript SDK。

浏览器 JavaScript SDK

浏览器 JavaScript SDK,可以通过 npm 方便地引入,在 web 页面上进行数据上报。

importSlsTrackerfrom'@aliyun-sls/web-track-browser';
constopts= {
host: '${host}', // 所在地域的服务入口。例如cn-hangzhou.log.aliyuncs.comproject: '${project}', // Project名称。logstore: '${logstore}', // Logstore名称。}

API 也相当简单,4个函数实现了同步和异步、单条和多条的数据上报。

image.png

符合项目需求,很顺利地就被引用了。

Node.js SDK

Node.js SDK 和浏览器 JavaScript SDK 提供的 API 相比,则提供了更为丰富的功能,不仅有日志数据上报,还加入了对日志库的管理操作。

image.png

不仅如此,使用的时候增加必传参数 AccessKeyID 和 AccessKeySecret。

constALY=require('aliyun-sdk')
varsls=newALY.SLS({
accessKeyId: "11****ut",                         //阿里云访问密钥AccessKey ID。更多信息,请参见访问密钥。阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维。 secretAccessKey: "TS****7Y",                     //阿里云访问密钥AccessKey Secret。 endpoint: 'http://cn-hangzhou.log.aliyuncs.com', //日志服务的域名。更多信息,请参见服务入口。此处以杭州为例,其它地域请根据实际情况填写。apiVersion: '2015-06-01'//SDK版本号,固定值。  })
constprojectName="you_project_name"// 必选,Project名称。constlogstoreName="your_logstore_name"// 必选,Project描述。// 创建Project。functioncreateProject () {
constparam= {
projectDetail: {
projectName,                                  
description: "description about project"    }
  }
sls.createProject(param, function(err, data) {
if (err) {
console.error('error:', err)
    } else {
console.log('创建project', data)
    }
  })
}
// 运行function。createProject()

这两个特点就和桌面端的使用场景有些相悖了。

一方面桌面端也只需要进行数据上报,并不需要对日志库管理,丰富的功能有些冗余。另一方面作为支持公共写的日志库实际上并不需要这些配置信息,而且这些信息放在端上也存在安全风险。

那在桌面端上该怎么方便地上报日志数据呢?

解决思路

借用浏览器 JavaScript SDK

既然桌面端和浏览器一样都是用 JavaScript 写的,那是否可以直接在桌面端用浏览器 JavaScript SDK 呢?

很遗憾并不行,由于浏览器 JavaScript SDK 依赖了浏览器一些全局方法,会导致在 Node.js 中直接报错。

如果要强行在 Node.js 中运行,对其进行改造也是可以。

但是改造流程可能会比较长:

  • 拉取代码仓库。
  • 修改代码。
  • 提交 PR 合并后等新版本发布或者自行再建一个仓库发布到 (T)NPM。

这一番操作下来已是远水救不了近火,所以换个思路自行封装一个轻量的用于客户端的 Node.js SDK

基于官方 API 进行封装

首先考虑的是基于官方的 API 进行封装。

关于日志数据上报,官方提供了2个的方法: PostLogStoreLogsPutLogs 。这两个方法各有一些限制:

  • PostLogStoreLogs 只支持 PB格式的日志压缩数据。
  • PutLogs 需要传入 LogGroup、LogTag 这些参数。

除此之外,它们都有一个共同点:需要 AK 授权。

这和浏览器 JavaScript SDK 相比明显麻烦很多,既然这样,我们只能往更底层去挖掘可能性了~

基于官方 SDK 逆向研发

既然我们想要在桌面端实现一个功能和浏览器 JavaScript SDK 一样的 SDK,那么不妨先来了解它做了什么。

当我们调用 send 函数的时候,实际上并没有立即发送日志,而是在随后的轮询间隔中,以数组的形式将日志数据进行上报。

image.png

所以这里推测采用了队列来缓存日志数据,减少网络请求次数。

同时仔细观察还可以发现为了不占用网络线程,这里采用了浏览器 sendBeacon 方法而不是 XMLHTTPRequest 或者 fetch 的方式,如果浏览器不支持这个方法则降级为 POST 请求。但由于这个 API 是浏览器提供的,所以桌面端只考虑采用 POST 请求方式。

image.png

通过浏览器调试工具观察请求网址可以发现其规律

https://{project}.{host}/logstores/{logstore}/track?APIVersion=0.6.0

image.png

总结一下:

  1. 1.建立日志队列,延迟、定时发送。
  2. 2.按照固定格式发送 POST 请求。
  3. 3.支持立即发送,对应 SDK 的 sendImmediate 方法。(这一点是后面开发中才发现的需求,比如页面离开时需要立即发送日志,避免缓存的日志数据丢失)

具体实现

一般而言,对于无状态的封装可以考虑用(纯)函数。

而 SDK 不仅要传入 project、host、logstore 这些配置数据,还需要建立内部的缓存队列,所以对于这种有状态的场景考虑使用类 Class。

对应代码如下:

classSlsTracker {
_timeout=null; // 定时器实现延迟发送__logs__= []; // 缓存队列constructor({
host,
project,
logstore  }) {
// 配置信息this._host=host;
this._project=project;
this._logstore=logstore  }
}

至于延迟、定时发送的实现可以依赖 setTimeout 或 setInterval,由于发送应该是队列有值的时候才进行,所以 setInterval 轮询这种性能消耗较大的方式并不适用。

下面来考虑核心函数 send 的实现。

send 接收一个对象参数作为日志数据。所以现将它推送到队列,然后创立一个延迟的 POST 请求,但很可能之前已经创建了一个 POST 请求,所以通过 this._timeout 来判断,如果存在则不再创建。定到定时器触发时将队列所有数据发送。

这里需要注意的是,上报的数据值需要转换为字符串。

考虑到程序的鲁棒性,可以考虑限制队列长度和字符长度。

classSlsTracker {
......send(info){
this.__logs__.push(this._transString(info));    
constoptions= {
hostname: `${this._project}.${this._host}`,
port: 443,
path: `/logstores/${this._logstore}/track?APIVersion=0.6.0`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
      }
    }
if (this._timeout) return;
this._timeout=setTimeout((o) => {
constpayload=JSON.stringify({ __logs__: this.__logs__ });
o.headers['Content-Length'] =Buffer.byteLength(payload)
constreq=https.request(o)
req.write(payload);
req.end();
this.__logs__= [];
this._timeout=null;
    }, this._time, options);
  }
.......
}

至于立即发送日志数据实现就相对容易了,取消之前定时器直接发送请求即可。

classSlsTracker {
......sendImmediate() {
constpayload=JSON.stringify({ __logs__: this.__logs__ });
consto= {
hostname: `${this._project}.${this._host}`,
port: 443,
path: `/logstores/${this._logstore}/track?APIVersion=0.6.0`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload)
      }
    }
constreq=https.request(o)
req.write(payload);
req.end();
this.__logs__= [];
clearTimeout(this._timeout);
this._timeout=null;
  }
}

总结

在依赖的代码库或平台不能满足要求时,首先考虑基于已有的信息自行进行研发,实在不行逆向研发也未尝不可。

相关实践学习
日志服务之数据清洗与入湖
本教程介绍如何使用日志服务接入NGINX模拟数据,通过数据加工对数据进行清洗并归档至OSS中进行存储。
相关文章
|
21天前
|
数据库连接 开发工具 数据库
逆向学习 SDK 篇:通过封装来简化代码编写,提高代码复用性
逆向学习 SDK 篇:通过封装来简化代码编写,提高代码复用性
11 0
|
1月前
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的公司员工工作日志办公系统附带文章和源代码部署视频讲解等
基于ssm+vue.js+uniapp小程序的公司员工工作日志办公系统附带文章和源代码部署视频讲解等
16 0
|
2月前
|
JavaScript 前端开发
autox.js如何打印日志?
autox.js如何打印日志?
|
2月前
|
开发工具 Python
【SLS开源兼容系列】使用ES SDK 访问SLS
本文介绍如何用es sdk访问sls
121 0
|
8月前
|
编解码
node封装一个图片拼接插件
node封装一个图片拼接插件
93 0
|
8月前
|
移动开发 算法 前端开发
node封装一个控制台进度条插件
node封装一个控制台进度条插件
91 0
|
11月前
|
资源调度 监控
[Nestjs] 使用log4js-node实现日志生成
安装依赖:使用 npm 或 yarn 安装 log4js。
300 0
|
12月前
|
Web App开发 JavaScript 测试技术
工银e生活开发脱坑日志(4)工行页面及jsAPI交互接口hybrid_app.js登录情况说明
工银e生活开发脱坑日志(4)工行页面及jsAPI交互接口hybrid_app.js登录情况说明
152 0
|
NoSQL JavaScript 前端开发
【Node.js实战】一文带你开发博客项目之Koa2重构(实现session、开发路由、联调、日志)
【Node.js实战】一文带你开发博客项目之Koa2重构(实现session、开发路由、联调、日志)
213 0
|
JavaScript 前端开发 Cloud Native
SAP Cloud SDK for JavaScript 概述
SAP Cloud SDK for JavaScript 概述
109 0
SAP Cloud SDK for JavaScript 概述

相关产品

  • 日志服务