perf(telemetry): load telemetry asap (#1431)

Reviewed-on: daoyoucloud/tachybase#1431
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-08-02 11:57:29 +08:00 committed by sealday
parent e5156a44a9
commit d5a0f5640c
9 changed files with 933 additions and 1258 deletions

View File

@ -11,7 +11,8 @@
"@tachybase/preset-hera-sancongtou": "workspace:*", "@tachybase/preset-hera-sancongtou": "workspace:*",
"@tachybase/preset-mini": "workspace:*", "@tachybase/preset-mini": "workspace:*",
"@tachybase/preset-tachybase": "workspace:*", "@tachybase/preset-tachybase": "workspace:*",
"@tachybase/server": "workspace:*" "@tachybase/server": "workspace:*",
"@tachybase/telemetry": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@tachybase/cache": "workspace:*", "@tachybase/cache": "workspace:*",

View File

@ -1,9 +1,10 @@
import { telemetryOptions as telemetry } from '@tachybase/telemetry';
import { cacheManager } from './cache'; import { cacheManager } from './cache';
import { parseDatabaseOptions } from './database'; import { parseDatabaseOptions } from './database';
import logger from './logger'; import logger from './logger';
import plugins from './plugins'; import plugins from './plugins';
import resourcer from './resourcer'; import resourcer from './resourcer';
import { telemetry } from './telemetry';
export async function getConfig() { export async function getConfig() {
return { return {

View File

@ -1,3 +1,5 @@
import '@tachybase/telemetry'; // 尽早实例化以尽力保证插桩成功TODO: Koa 依然无法成功 hook
import { Gateway } from '@tachybase/server'; import { Gateway } from '@tachybase/server';
import { getConfig } from './config'; import { getConfig } from './config';

View File

@ -17,7 +17,7 @@ import {
SystemLoggerOptions, SystemLoggerOptions,
} from '@tachybase/logger'; } from '@tachybase/logger';
import { ResourceOptions, Resourcer } from '@tachybase/resourcer'; import { ResourceOptions, Resourcer } from '@tachybase/resourcer';
import { Telemetry, TelemetryOptions } from '@tachybase/telemetry'; import { AppTelemetryOptions, getTelemetry } from '@tachybase/telemetry';
import { applyMixins, AsyncEmitter, importModule, Toposort, ToposortOptions } from '@tachybase/utils'; import { applyMixins, AsyncEmitter, importModule, Toposort, ToposortOptions } from '@tachybase/utils';
import { Command, CommandOptions, ParseOptions } from 'commander'; import { Command, CommandOptions, ParseOptions } from 'commander';
@ -67,10 +67,6 @@ export interface AppLoggerOptions {
system: SystemLoggerOptions; system: SystemLoggerOptions;
} }
export interface AppTelemetryOptions extends TelemetryOptions {
enabled?: boolean;
}
export interface ApplicationOptions { export interface ApplicationOptions {
database?: IDatabaseOptions | Database; database?: IDatabaseOptions | Database;
cacheManager?: CacheManagerOptions; cacheManager?: CacheManagerOptions;
@ -310,10 +306,8 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
return this._locales; return this._locales;
} }
protected _telemetry: Telemetry;
get telemetry() { get telemetry() {
return this._telemetry; return getTelemetry();
} }
protected _version: ApplicationVersion; protected _version: ApplicationVersion;
@ -516,12 +510,12 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
await this.emitAsync('beforeLoad', this, options); await this.emitAsync('beforeLoad', this, options);
} }
// Telemetry is initialized after beforeLoad hook // Telemetry is already initialized in @tachybase/app
// since some configuration may be registered in beforeLoad hook
this.telemetry.init();
if (this.options.telemetry?.enabled) { if (this.options.telemetry?.enabled) {
// Start collecting telemetry data if enabled // Start collecting telemetry data if enabled
this.telemetry.start(); if (!this.telemetry.started) {
this.telemetry.start();
}
} }
await this.pm.load(options); await this.pm.load(options);
@ -1071,11 +1065,6 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
this.log.warn('TELEMETRY_SERVICE_NAME is not set, will use default service name, please set it in .env file!'); this.log.warn('TELEMETRY_SERVICE_NAME is not set, will use default service name, please set it in .env file!');
serviceName = `tachybase-${this.name}`; serviceName = `tachybase-${this.name}`;
} }
this._telemetry = new Telemetry({
serviceName,
version: this.getVersion(),
...options.telemetry,
});
this._authManager = new AuthManager({ this._authManager = new AuthManager({
authKey: 'X-Authenticator', authKey: 'X-Authenticator',

View File

@ -1,6 +1,4 @@
import { AppTelemetryOptions } from '@tachybase/server'; export const telemetryOptions = {
export const telemetry: AppTelemetryOptions = {
enabled: process.env.TELEMETRY_ENABLED === 'on', enabled: process.env.TELEMETRY_ENABLED === 'on',
metric: { metric: {
readerName: process.env.OTEL_METRICS_READER, readerName: process.env.OTEL_METRICS_READER,

View File

@ -1 +1,7 @@
import { getTelemetry } from './telemetry';
if (!getTelemetry()) {
throw new Error('Failed to load telemetry');
}
export * from './telemetry'; export * from './telemetry';

View File

@ -74,17 +74,31 @@ export class Metric {
console.warn('Prometheus server is disabled'); console.warn('Prometheus server is disabled');
} else { } else {
const port = Number(process.env.OTEL_PROMETHEUS_PORT) || 9464; const port = Number(process.env.OTEL_PROMETHEUS_PORT) || 9464;
const reader = () => if (port <= 0 || port >= 65536) {
new PrometheusExporter( throw new Error(`Invalid port: ${port}`);
{ }
port, // optional - default is 9464 if (port <= 1024) {
}, console.warn('Prometheus server will try to run on a privileged port, it may need root permission');
() => { }
console.log(`Prometheus exporter endpoint started on http://localhost:${port}/metrics`); if (port === 9464) {
}, console.warn('Prometheus server will run on default port 9464');
); }
// 注册 Prometheus 作为指标 Reader try {
this.registerReader('prometheus', reader); // 启动 Prometheus Exporter
const reader = () =>
new PrometheusExporter(
{
port,
},
() => {
console.log(`Prometheus exporter endpoint started on http://localhost:${port}/metrics`);
},
);
// 注册 Prometheus Exporter 作为指标 Reader
this.registerReader('prometheus', reader);
} catch (error) {
console.error('Failed to initialize Prometheus metrics reader:', error);
}
} }
} catch (error) { } catch (error) {
console.error('Failed to initialize Prometheus metrics reader:', error); console.error('Failed to initialize Prometheus metrics reader:', error);

View File

@ -4,9 +4,14 @@ import { InstrumentationOption, registerInstrumentations } from '@opentelemetry/
import { Resource } from '@opentelemetry/resources'; import { Resource } from '@opentelemetry/resources';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
// 防止加载什么奇怪的东西
import packageJson from '../../../../package.json';
import { telemetryOptions } from './config';
import { Metric, MetricOptions } from './metric'; import { Metric, MetricOptions } from './metric';
import { Trace, TraceOptions } from './trace'; import { Trace, TraceOptions } from './trace';
export { telemetryOptions } from './config';
export interface TelemetryOptions { export interface TelemetryOptions {
serviceName?: string; serviceName?: string;
version?: string; version?: string;
@ -14,6 +19,10 @@ export interface TelemetryOptions {
metric?: MetricOptions; metric?: MetricOptions;
} }
export interface AppTelemetryOptions extends TelemetryOptions {
enabled?: boolean;
}
export class Telemetry { export class Telemetry {
serviceName: string; serviceName: string;
version: string; version: string;
@ -23,14 +32,13 @@ export class Telemetry {
started = false; started = false;
constructor(options?: TelemetryOptions) { constructor(options?: TelemetryOptions) {
console.log('Create telemetry with options', options);
const { trace, metric, serviceName, version } = options || {}; const { trace, metric, serviceName, version } = options || {};
this.trace = new Trace({ tracerName: `${serviceName}-trace`, version, ...trace }); this.trace = new Trace({ tracerName: `${serviceName}-trace`, version, ...trace });
this.metric = new Metric({ meterName: `${serviceName}-meter`, version, ...metric }); this.metric = new Metric({ meterName: `${serviceName}-meter`, version, ...metric });
this.serviceName = serviceName || 'tachybase'; this.serviceName = serviceName || 'tachybase-main';
this.version = version || ''; this.version = version || '';
}
init() {
// 设置 OTel 日志等级 // 设置 OTel 日志等级
const diagLogLevel = process.env.OTEL_LOG_LEVEL; const diagLogLevel = process.env.OTEL_LOG_LEVEL;
if (diagLogLevel) { if (diagLogLevel) {
@ -51,6 +59,10 @@ export class Telemetry {
registerInstrumentations({ registerInstrumentations({
instrumentations: this.instrumentations, instrumentations: this.instrumentations,
}); });
}
init() {
console.log('Start init telemetry', this.serviceName, this.version);
const resource = Resource.default().merge( const resource = Resource.default().merge(
new Resource({ new Resource({
@ -80,3 +92,25 @@ export class Telemetry {
this.instrumentations.push(...instrumentation); this.instrumentations.push(...instrumentation);
} }
} }
let _telemetry: Telemetry;
let serviceName = process.env.TELEMETRY_SERVICE_NAME;
if (!serviceName) {
console.warn('TELEMETRY_SERVICE_NAME is not set, will use default service name, please set it in .env file!');
serviceName = `tachybase-main`;
}
export const getTelemetry = () => {
if (!_telemetry || typeof _telemetry === 'undefined') {
_telemetry = new Telemetry({
serviceName,
version: packageJson.version || 'UnkVer',
...telemetryOptions,
});
_telemetry.init();
}
// 这里只需要 initstart 保留在 application 里
return _telemetry;
};

File diff suppressed because it is too large Load Diff