refactor(server): config system (#11081)

This commit is contained in:
forehalo
2025-03-27 12:32:28 +00:00
parent 7091111f85
commit 0ea38680fa
274 changed files with 7583 additions and 5841 deletions

View File

@@ -1,33 +1,16 @@
import { defineStartupConfig, ModuleConfig } from '../config';
import { defineModuleConfig } from '../config';
declare module '../config' {
interface AppConfig {
metrics: ModuleConfig<{
/**
* Enable metric and tracing collection
*/
declare global {
interface AppConfigSchema {
metrics: {
enabled: boolean;
/**
* Enable telemetry
*/
telemetry: {
enabled: boolean;
token: string;
};
customerIo: {
token: string;
};
}>;
};
}
}
defineStartupConfig('metrics', {
enabled: false,
telemetry: {
enabled: false,
token: '',
},
customerIo: {
token: '',
defineModuleConfig('metrics', {
enabled: {
desc: 'Enable metric and tracing collection',
default: false,
},
});

View File

@@ -1,54 +1,14 @@
import './config';
import {
Global,
Module,
OnModuleDestroy,
OnModuleInit,
Provider,
} from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { Global, Module } from '@nestjs/common';
import { Config } from '../config';
import {
LocalOpentelemetryFactory,
OpentelemetryFactory,
registerCustomMetrics,
} from './opentelemetry';
const factorProvider: Provider = {
provide: OpentelemetryFactory,
useFactory: (config: Config) => {
return config.metrics.enabled ? new LocalOpentelemetryFactory() : null;
},
inject: [Config],
};
import { OpentelemetryFactory } from './opentelemetry';
@Global()
@Module({
providers: [factorProvider],
exports: [factorProvider],
providers: [OpentelemetryFactory],
})
export class MetricsModule implements OnModuleInit, OnModuleDestroy {
private sdk: NodeSDK | null = null;
constructor(private readonly ref: ModuleRef) {}
onModuleInit() {
const factor = this.ref.get(OpentelemetryFactory, { strict: false });
if (factor) {
this.sdk = factor.create();
this.sdk.start();
registerCustomMetrics();
}
}
async onModuleDestroy() {
if (this.sdk) {
await this.sdk.shutdown();
}
}
}
export class MetricsModule {}
export * from './metrics';
export * from './utils';

View File

@@ -2,11 +2,28 @@ import {
Gauge,
Histogram,
Meter,
MeterProvider,
MetricOptions,
metrics as otelMetrics,
UpDownCounter,
} from '@opentelemetry/api';
import { HostMetrics } from '@opentelemetry/host-metrics';
import { getMeter } from './opentelemetry';
function getMeterProvider() {
return otelMetrics.getMeterProvider();
}
export function registerCustomMetrics() {
const hostMetricsMonitoring = new HostMetrics({
name: 'instance-host-metrics',
meterProvider: getMeterProvider() as MeterProvider,
});
hostMetricsMonitoring.start();
}
export function getMeter(name = 'business') {
return getMeterProvider().getMeter(name);
}
type MetricType = 'counter' | 'gauge' | 'histogram';
type Metric<T extends MetricType> = T extends 'counter'
@@ -122,5 +139,3 @@ export const metrics = new Proxy<Record<KnownMetricScopes, ScopedMetrics>>(
},
}
);
export function stopMetrics() {}

View File

@@ -1,5 +1,4 @@
import { OnModuleDestroy } from '@nestjs/common';
import { metrics } from '@opentelemetry/api';
import { Injectable, Logger, OnModuleDestroy } from '@nestjs/common';
import {
CompositePropagator,
W3CBaggagePropagator,
@@ -7,7 +6,6 @@ import {
} from '@opentelemetry/core';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
import { HostMetrics } from '@opentelemetry/host-metrics';
import { Instrumentation } from '@opentelemetry/instrumentation';
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
@@ -15,7 +13,6 @@ import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis';
import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core';
import { SocketIoInstrumentation } from '@opentelemetry/instrumentation-socket.io';
import { Resource } from '@opentelemetry/resources';
import type { MeterProvider } from '@opentelemetry/sdk-metrics';
import { MetricProducer, MetricReader } from '@opentelemetry/sdk-metrics';
import { NodeSDK } from '@opentelemetry/sdk-node';
import {
@@ -30,11 +27,14 @@ import {
} from '@opentelemetry/semantic-conventions/incubating';
import prismaInstrument from '@prisma/instrumentation';
import { Config } from '../config';
import { OnEvent } from '../event/def';
import { registerCustomMetrics } from './metrics';
import { PrismaMetricProducer } from './prisma';
const { PrismaInstrumentation } = prismaInstrument;
export abstract class OpentelemetryFactory {
export abstract class BaseOpentelemetryFactory {
abstract getMetricReader(): MetricReader;
abstract getSpanExporter(): SpanExporter;
@@ -55,9 +55,9 @@ export abstract class OpentelemetryFactory {
getResource() {
return new Resource({
[ATTR_K8S_NAMESPACE_NAME]: AFFiNE.AFFINE_ENV,
[ATTR_SERVICE_NAME]: AFFiNE.flavor.type,
[ATTR_SERVICE_VERSION]: AFFiNE.version,
[ATTR_K8S_NAMESPACE_NAME]: env.NAMESPACE,
[ATTR_SERVICE_NAME]: env.FLAVOR,
[ATTR_SERVICE_VERSION]: env.version,
});
}
@@ -81,39 +81,58 @@ export abstract class OpentelemetryFactory {
}
}
export class LocalOpentelemetryFactory
extends OpentelemetryFactory
@Injectable()
export class OpentelemetryFactory
extends BaseOpentelemetryFactory
implements OnModuleDestroy
{
private readonly metricsExporter = new PrometheusExporter({
metricProducers: this.getMetricsProducers(),
});
private readonly logger = new Logger(OpentelemetryFactory.name);
#sdk: NodeSDK | null = null;
constructor(private readonly config: Config) {
super();
}
@OnEvent('config.init')
async init(event: Events['config.init']) {
if (event.config.metrics.enabled) {
await this.setup();
registerCustomMetrics();
}
}
@OnEvent('config.changed')
async onConfigChanged(event: Events['config.changed']) {
if ('metrics' in event.updates) {
await this.setup();
}
}
async onModuleDestroy() {
await this.metricsExporter.shutdown();
await this.#sdk?.shutdown();
}
override getMetricReader(): MetricReader {
return this.metricsExporter;
return new PrometheusExporter({
metricProducers: this.getMetricsProducers(),
});
}
override getSpanExporter(): SpanExporter {
return new ZipkinExporter();
}
}
function getMeterProvider() {
return metrics.getMeterProvider();
}
export function registerCustomMetrics() {
const hostMetricsMonitoring = new HostMetrics({
name: 'instance-host-metrics',
meterProvider: getMeterProvider() as MeterProvider,
});
hostMetricsMonitoring.start();
}
export function getMeter(name = 'business') {
return getMeterProvider().getMeter(name);
private async setup() {
if (this.config.metrics.enabled) {
if (!this.#sdk) {
this.#sdk = this.create();
}
this.#sdk.start();
this.logger.log('OpenTelemetry SDK started');
} else {
await this.#sdk?.shutdown();
this.#sdk = null;
this.logger.log('OpenTelemetry SDK stopped');
}
}
}

View File

@@ -10,7 +10,7 @@ import {
ScopeMetrics,
} from '@opentelemetry/sdk-metrics';
import { PrismaService } from '../prisma';
import { PrismaFactory } from '../prisma/factory';
function transformPrismaKey(key: string) {
// replace first '_' to '/' as a scope prefix
@@ -30,11 +30,11 @@ export class PrismaMetricProducer implements MetricProducer {
errors: [],
};
if (!PrismaService.INSTANCE) {
if (!PrismaFactory.INSTANCE) {
return result;
}
const prisma = PrismaService.INSTANCE;
const prisma = PrismaFactory.INSTANCE;
const endTime = hrTime();