日志服务(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模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
4月前
|
前端开发 数据安全/隐私保护
crypto-js中AES的加解密封装
文章介绍了如何在前端使用crypto-js库进行AES加密和解密,提供了加解密的函数封装示例,并演示了如何加密和解密字符串或对象。
357 1
crypto-js中AES的加解密封装
|
5月前
|
Java Apache 开发工具
【Azure 事件中心】 org.slf4j.Logger 收集 Event Hub SDK(Java) 输出日志并以文件形式保存
【Azure 事件中心】 org.slf4j.Logger 收集 Event Hub SDK(Java) 输出日志并以文件形式保存
|
5月前
|
缓存 中间件
Nest.js 实战 (九):使用拦截器记录用户 CURD 操作日志
这篇文章介绍了在Nest.js中如何实现记录用户CURD操作的需求。首先解释了什么是拦截器以及拦截器的作用,然后通过创建Prisma模型,添加Log模型,并通过编写LoggerInterceptor拦截器,实现了记录用户操作的功能。最后通过效果演示和总结,强调了使用拦截器实现此功能的有效性。
100 0
|
2月前
|
JSON 监控 JavaScript
Node.js-API 限流与日志优化
Node.js-API 限流与日志优化
|
3月前
|
JavaScript 前端开发 开发工具
【Azure Developer】使用JavaScript通过SDK进行monitor-query的client认证报错问题
AADSTS90002: Tenant 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' not found. Check to make sure you have the correct tenant ID and are signing into the correct cloud. Check with your subscription administrator, this may happen if there are no active subscriptions for the tenant.
|
3月前
FFmpeg【SDK01】日志和字典的使用
FFmpeg中日志功能的使用方法,包括日志级别的设置和AVDictionary的基本操作,同时展示了字符串解析函数如av_parse_video_size、av_parse_video_rate和av_parse_time的应用。
47 2
|
5月前
|
SQL 运维 监控
Nest.js 实战 (十):使用 winston 打印和收集日志记录
这篇文章介绍了在Nest服务中如何使用Winston记录日志。文章首先强调了日志记录在后台服务中的重要性,接着提到Nest默认的内部日志记录器,并指出可以通过@nestjs/common包中的Logger类来全面控制日志系统的行为。文章还提到,为了在生产环境中实现更高级的日志功能,可以使用如Winston之类的Node.js日志包。接下来,文章介绍了如何在Nest服务中使用Winston记录日志,包括安装相关依赖、创建winston配置文件以及实现简单的日志记录示例。最后,文章指出更高级的自定义日志功能需要读者自己去探索。
179 2
Nest.js 实战 (十):使用 winston 打印和收集日志记录
|
4月前
|
设计模式 JavaScript
JS发布订阅模式封装(纯手工)
发布订阅模式是JS常用的设计模式,在面试中也会经常遇到,以下是我的手写实现方式,经测试效果不错,小伙伴们们可以直接拷贝使用。
|
5月前
|
JavaScript Serverless Linux
函数计算产品使用问题之遇到Node.js环境下的请求日志没有正常输出时,该如何排查
函数计算产品作为一种事件驱动的全托管计算服务,让用户能够专注于业务逻辑的编写,而无需关心底层服务器的管理与运维。你可以有效地利用函数计算产品来支撑各类应用场景,从简单的数据处理到复杂的业务逻辑,实现快速、高效、低成本的云上部署与运维。以下是一些关于使用函数计算产品的合集和要点,帮助你更好地理解和应用这一服务。
|
6月前
|
存储 并行计算 开发工具
SLS Prometheus存储问题之相比客户端SDK聚合写入,SLS网关侧聚合写入有什么优势
SLS Prometheus存储问题之相比客户端SDK聚合写入,SLS网关侧聚合写入有什么优势

相关产品

  • 日志服务