feat: telemetry init (#1378)

Reviewed-on: daoyoucloud/tachybase#1378
Reviewed-by: sealday <zhanglin@daoyoucloud.com>
Co-authored-by: TomyJan <TomyJan6@gmail.com>
Co-committed-by: TomyJan <TomyJan6@gmail.com>
This commit is contained in:
TomyJan 2024-07-25 10:55:37 +08:00 committed by sealday
parent c3c8b5b655
commit ac65c94730
20 changed files with 422 additions and 1 deletions

View File

@ -0,0 +1,2 @@
/node_modules
/src

View File

@ -0,0 +1,25 @@
# prometheus
English | [中文](./README.zh-CN.md)
## Install
First, edit `.env` to enable the system telemetry and add the prometheus collector
```
TELEMETRY_ENABLED=on
TELEMETRY_METRIC_READER=console,prometheus
TELEMETRY_PROMETHEUS_SERVER=on # If not on, the prometheus server will be registered to prometheus:metrics, note that the standalone prometheus server has NO permission restriction!
TELEMETRY_PROMETHEUS_PORT=9464 # The port of the prometheus server, default to 9464
```
Then enable the plugin
```shell
pnpm pm add @tachybase/plugin-prometheus
pnpm pm enable @tachybase/plugin-prometheus
```
## Usage
TODO

View File

@ -0,0 +1,25 @@
# prometheus
[English](./README.md) | 中文
## 安装激活
首先编辑 `.env`,启用系统的遥测功能并添加采集器 prometheus
```
TELEMETRY_ENABLED=on
TELEMETRY_METRIC_READER=console,prometheus
TELEMETRY_PROMETHEUS_SERVER=on # 如果不为 on则 prometheus 服务器将注册到 prometheus:metrics ,注意 prometheus 的独立服务器*没有*权限限制!
TELEMETRY_PROMETHEUS_PORT=9464 # prometheus 服务器的端口,默认为 9464
```
然后启用插件
```shell
pnpm pm add @tachybase/plugin-prometheus
pnpm pm enable @tachybase/plugin-prometheus
```
## 使用方法
TODO

View File

@ -0,0 +1,2 @@
export * from './dist/client';
export { default } from './dist/client';

View File

@ -0,0 +1 @@
module.exports = require('./dist/client/index.js');

View File

@ -0,0 +1,26 @@
{
"name": "@tachybase/plugin-prometheus",
"displayName": "Prometheus Monitoring",
"version": "0.21.77",
"description": "Prometheus collector for system telemetry",
"keywords": [
"Users & permissions"
],
"license": "Apache-2.0",
"main": "dist/server/index.js",
"dependencies": {
"@opentelemetry/exporter-prometheus": "^0.52.1",
"node-os-utils": "^1.3.7",
"prom-client": "^15.1.3"
},
"peerDependencies": {
"@tachybase/actions": "workspace:*",
"@tachybase/auth": "workspace:*",
"@tachybase/client": "workspace:*",
"@tachybase/database": "workspace:*",
"@tachybase/server": "workspace:*",
"@tachybase/test": "workspace:*"
},
"description.zh-CN": "系统遥测的 Prometheus 采集器",
"displayName.zh-CN": "Prometheus 监控"
}

View File

@ -0,0 +1,2 @@
export * from './dist/server';
export { default } from './dist/server';

View File

@ -0,0 +1 @@
module.exports = require('./dist/server/index.js');

View File

@ -0,0 +1,21 @@
import { Plugin } from '@tachybase/client';
export class PluginPrometheusClient extends Plugin {
async afterAdd() {
// await this.app.pm.add()
}
async beforeLoad() {}
// You can get and modify the app instance here
async load() {
console.log(this.app);
// this.app.addComponents({})
// this.app.addScopes({})
// this.app.addProvider()
// this.app.addProviders()
// this.app.router.add()
}
}
export default PluginPrometheusClient;

View File

@ -0,0 +1,2 @@
export * from './server';
export { default } from './server';

View File

@ -0,0 +1 @@
export { default } from './plugin';

View File

@ -0,0 +1,77 @@
import os from 'os';
import osUtils from 'node-os-utils';
export class hardware {
meter: any;
constructor(meter) {
this.meter = meter;
}
async start() {
console.log('hardware meter start');
const uptime = this.meter.createObservableGauge('environment.hardware.uptime', {
description: 'Uptime in seconds',
unit: 'seconds',
});
uptime.addCallback((result) => {
const uptime = os.uptime();
console.log('uptime', uptime);
result.observe(uptime);
});
const cpuUsage = this.meter.createObservableGauge('environment.hardware.cpuusage', {
description: 'CPU Usage',
unit: 'percent',
});
cpuUsage.addCallback(async (result) => {
const cpuPercentage = await osUtils.cpu.usage();
console.log('cpuPercentage', cpuPercentage);
result.observe(cpuPercentage);
});
const load1min = this.meter.createObservableGauge('environment.hardware.load_1min', {
description: 'Load Average in 1 minutes',
});
load1min.addCallback((result) => {
const load = os.loadavg();
const parsedLoad = Number(load[0].toFixed(2));
console.log('load_1min', parsedLoad);
result.observe(parsedLoad);
});
const load5min = this.meter.createObservableGauge('environment.hardware.load_5min', {
description: 'Load Average in 5 minutes',
});
load5min.addCallback((result) => {
const load = os.loadavg();
const parsedLoad = Number(load[1].toFixed(2));
console.log('load_5min', parsedLoad);
result.observe(parsedLoad);
});
const load15min = this.meter.createObservableGauge('environment.hardware.load_15min', {
description: 'Load Average in 15 minutes',
});
load15min.addCallback((result) => {
const load = os.loadavg();
const parsedLoad = Number(load[2].toFixed(2));
console.log('load_15min', parsedLoad);
result.observe(parsedLoad);
});
const memoryUsage = this.meter.createObservableGauge('environment.hardware.memoryusage', {
description: 'Memory Usage',
unit: 'percent',
});
memoryUsage.addCallback(async (result) => {
const mem = await osUtils.mem.used();
const memPercentage = Number(((mem.usedMemMb / mem.totalMemMb) * 100).toFixed(2));
console.log('memPercentage', memPercentage);
result.observe(memPercentage);
});
}
async shutdown() {
console.log('hardware meter shutdown');
}
}

View File

@ -0,0 +1,16 @@
import { hardware } from './hardware';
import { processSelf } from './process';
export default class initMeters {
meter: any;
constructor(meter) {
this.meter = meter;
}
async start() {
const hardwareMeter = new hardware(this.meter);
await hardwareMeter.start();
const processMeter = new processSelf(this.meter);
await processMeter.start();
}
}

View File

@ -0,0 +1,34 @@
export class processSelf {
meter: any;
constructor(meter) {
this.meter = meter;
}
async start() {
console.log('process meter start');
const selfUptime = this.meter.createObservableGauge('tachybase.basic.uptime', {
description: 'Process Uptime in seconds',
unit: 'seconds',
});
selfUptime.addCallback((result) => {
const uptime = Number(process.uptime().toFixed(2));
console.log('self uptime', uptime);
result.observe(uptime);
});
const selfMemoryUsage = this.meter.createObservableGauge('tachybase.basic.memoryusage', {
description: 'Process Memory Usage in MB',
unit: 'mb',
});
selfMemoryUsage.addCallback((result) => {
const mem = process.memoryUsage();
console.log('self mem', mem);
const memUsage = Number((mem.rss / 1024 / 1024).toFixed(2));
console.log('self memUsage', memUsage);
result.observe(memUsage);
});
}
async shutdown() {
console.log('hardware meter shutdown');
}
}

View File

@ -0,0 +1,47 @@
import { Plugin } from '@tachybase/server';
import initMeters from './meters';
import { InternalPrometheusExporter, PrometheusExporter } from './prometheus-exporters';
export class PluginPrometheusServer extends Plugin {
async afterAdd() {
this.app.on('beforeLoad', async (app) => {
const startServer = process.env.TELEMETRY_PROMETHEUS_SERVER === 'on';
let reader;
if (!startServer) {
reader = () => new InternalPrometheusExporter(app);
this.log.info('internal prometheus endpoint registered on /api/prometheus:metrics');
} else {
const port: number = Number(process.env.TELEMETRY_PROMETHEUS_PORT) || 9464;
reader = () =>
new PrometheusExporter(
{
port, // optional - default is 9464
},
() => {
this.log.info(`independent prometheus endpoint started on http://localhost:${port}/metrics`);
},
);
}
app.telemetry.metric.registerReader('prometheus', reader);
});
}
async beforeLoad() {}
async load() {
const meter = this.app.telemetry.metric.getMeter();
const meters = new initMeters(meter);
await meters.start();
}
async install() {}
async afterEnable() {}
async afterDisable() {}
async remove() {}
}
export default PluginPrometheusServer;

View File

@ -0,0 +1,2 @@
export * from './internal-prometheus-exporter';
export { PrometheusExporter } from '@opentelemetry/exporter-prometheus';

View File

@ -0,0 +1,35 @@
import { PrometheusExporter, PrometheusSerializer } from '@opentelemetry/exporter-prometheus';
export class InternalPrometheusExporter extends PrometheusExporter {
constructor(app) {
super({
preventServerStart: true,
});
app.resourcer.define({
name: 'prometheus',
actions: {
metrics: async (ctx) => {
ctx.withoutDataWrapping = true;
ctx.set('Content-Type', 'text/plain');
ctx.res.statusCode = 200;
try {
const collectionRes = await this.collect();
const { resourceMetrics, errors } = collectionRes;
if (errors.length) {
ctx.log.error(`metrics collection errors`, {
method: 'InternalPrometheusExporter',
errors,
});
}
ctx.body = new PrometheusSerializer().serialize(resourceMetrics);
} catch (err) {
ctx.body = `# failed to export metrics: ${err}`;
}
},
},
});
}
collect() {
return super.collect();
}
}

View File

@ -8,7 +8,7 @@ function banGuestActionMiddleware() {
// 注意由于已经注入访客 token, 还需要判断请求体里面用户是否为 guest
const reqBody = ctx.request.body;
if (
ctx.action.resourceName === 'auth' &&
ctx.action?.resourceName === 'auth' &&
!['check', 'signOut'].includes(ctx.action.actionName) &&
ctx.auth.user &&
(reqBody.account === 'guest' || reqBody.account === 'guest@tachybase.com')

View File

@ -3569,6 +3569,36 @@ importers:
specifier: ^0.6.2
version: 0.6.2
packages/plugins/@tachybase/plugin-prometheus:
dependencies:
'@opentelemetry/exporter-prometheus':
specifier: ^0.52.1
version: 0.52.1(@opentelemetry/api@1.7.0)
'@tachybase/actions':
specifier: workspace:*
version: link:../../../core/actions
'@tachybase/auth':
specifier: workspace:*
version: link:../../../core/auth
'@tachybase/client':
specifier: workspace:*
version: link:../../../core/client
'@tachybase/database':
specifier: workspace:*
version: link:../../../core/database
'@tachybase/server':
specifier: workspace:*
version: link:../../../core/server
'@tachybase/test':
specifier: workspace:*
version: link:../../../core/test
node-os-utils:
specifier: ^1.3.7
version: 1.3.7
prom-client:
specifier: ^15.1.3
version: 15.1.3
packages/plugins/@tachybase/plugin-saml:
dependencies:
'@tachybase/actions':
@ -10461,6 +10491,28 @@ packages:
'@opentelemetry/semantic-conventions': 1.19.0
dev: false
/@opentelemetry/core@1.25.1(@opentelemetry/api@1.7.0):
resolution: {integrity: sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
dependencies:
'@opentelemetry/api': 1.7.0
'@opentelemetry/semantic-conventions': 1.25.1
dev: false
/@opentelemetry/exporter-prometheus@0.52.1(@opentelemetry/api@1.7.0):
resolution: {integrity: sha512-hwK0QnjtqAxGpQAXMNUY+kTT5CnHyz1I0lBA8SFySvaFtExZm7yQg/Ua/i+RBqgun7WkUbkUVJzEi3lKpJ7WdA==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': ^1.3.0
dependencies:
'@opentelemetry/api': 1.7.0
'@opentelemetry/core': 1.25.1(@opentelemetry/api@1.7.0)
'@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.7.0)
'@opentelemetry/sdk-metrics': 1.25.1(@opentelemetry/api@1.7.0)
dev: false
/@opentelemetry/instrumentation@0.46.0(@opentelemetry/api@1.7.0):
resolution: {integrity: sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw==}
engines: {node: '>=14'}
@ -10508,6 +10560,17 @@ packages:
'@opentelemetry/semantic-conventions': 1.19.0
dev: false
/@opentelemetry/resources@1.25.1(@opentelemetry/api@1.7.0):
resolution: {integrity: sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.0.0 <1.10.0'
dependencies:
'@opentelemetry/api': 1.7.0
'@opentelemetry/core': 1.25.1(@opentelemetry/api@1.7.0)
'@opentelemetry/semantic-conventions': 1.25.1
dev: false
/@opentelemetry/sdk-metrics@1.19.0(@opentelemetry/api@1.7.0):
resolution: {integrity: sha512-FiMii40zr0Fmys4F1i8gmuCvbinBnBsDeGBr4FQemOf0iPCLytYQm5AZJ/nn4xSc71IgKBQwTFQRAGJI7JvZ4Q==}
engines: {node: '>=14'}
@ -10520,6 +10583,18 @@ packages:
lodash.merge: 4.6.2
dev: false
/@opentelemetry/sdk-metrics@1.25.1(@opentelemetry/api@1.7.0):
resolution: {integrity: sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==}
engines: {node: '>=14'}
peerDependencies:
'@opentelemetry/api': '>=1.3.0 <1.10.0'
dependencies:
'@opentelemetry/api': 1.7.0
'@opentelemetry/core': 1.25.1(@opentelemetry/api@1.7.0)
'@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.7.0)
lodash.merge: 4.6.2
dev: false
/@opentelemetry/sdk-trace-base@1.19.0(@opentelemetry/api@1.7.0):
resolution: {integrity: sha512-+IRvUm+huJn2KqfFW3yW/cjvRwJ8Q7FzYHoUNx5Fr0Lws0LxjMJG1uVB8HDpLwm7mg5XXH2M5MF+0jj5cM8BpQ==}
engines: {node: '>=14'}
@ -10552,6 +10627,11 @@ packages:
engines: {node: '>=14'}
dev: false
/@opentelemetry/semantic-conventions@1.25.1:
resolution: {integrity: sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==}
engines: {node: '>=14'}
dev: false
/@pkgjs/parseargs@0.11.0:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@ -15428,6 +15508,10 @@ packages:
chainsaw: 0.1.0
dev: true
/bintrees@1.0.2:
resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
dev: false
/bl@1.2.3:
resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==}
dependencies:
@ -23628,6 +23712,10 @@ packages:
util: 0.11.1
vm-browserify: 1.1.2
/node-os-utils@1.3.7:
resolution: {integrity: sha512-fvnX9tZbR7WfCG5BAy3yO/nCLyjVWD6MghEq0z5FDfN+ZXpLWNITBdbifxQkQ25ebr16G0N7eRWJisOcMEHG3Q==}
dev: false
/node-releases@2.0.14:
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
@ -25441,6 +25529,14 @@ packages:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
/prom-client@15.1.3:
resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==}
engines: {node: ^16 || ^18 || >=20}
dependencies:
'@opentelemetry/api': 1.7.0
tdigest: 0.1.2
dev: false
/promise-coalesce@1.1.1:
resolution: {integrity: sha512-k7+VaIwZc5dRfSF6RELqRY1+LCmcCkrnuNV9HzIpA6iwRHKke+j9yb0LBTTHQ2RRgf6AlMl9TntuTzcgV/BZwg==}
engines: {node: '>=18'}
@ -29143,6 +29239,12 @@ packages:
mkdirp: 1.0.4
yallist: 4.0.0
/tdigest@0.1.2:
resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
dependencies:
bintrees: 1.0.2
dev: false
/tencentcloud-sdk-nodejs@4.0.759:
resolution: {integrity: sha512-8xbPmz5LJc3S5+mAjGquiYQmQQlMi5baVHAy4IWHmwHJisL63jCprNvs5svKgSQEwIs3sv2gBrM5s3G3HWN7rQ==}
engines: {node: '>=10'}