Skip to content

Commit

Permalink
fix(server): wrong usage of otel
Browse files Browse the repository at this point in the history
  • Loading branch information
forehalo committed May 6, 2024
1 parent ec73f69 commit cf5045d
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 150 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@
"@reforged/maker-appimage/@electron-forge/maker-base": "7.3.1",
"macos-alias": "npm:@napi-rs/macos-alias@0.0.4",
"fs-xattr": "npm:@napi-rs/xattr@latest",
"@radix-ui/react-dialog": "npm:@radix-ui/react-dialog@latest"
"@radix-ui/react-dialog": "npm:@radix-ui/react-dialog@latest",
"import-in-the-middle": "npm:import-in-the-middle@latest"
}
}
15 changes: 7 additions & 8 deletions packages/backend/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
},
"scripts": {
"build": "tsc",
"start": "node --loader ts-node/esm/transpile-only.mjs ./src/index.ts",
"start": "node --import ./scripts/register.js ./src/index.ts",
"dev": "nodemon ./src/index.ts",
"test": "ava --concurrency 1 --serial",
"test:coverage": "c8 ava --concurrency 1 --serial",
"postinstall": "prisma generate",
"data-migration": "node --loader ts-node/esm/transpile-only.mjs ./src/data/index.ts",
"data-migration": "node --import ./scripts/register.js ./src/data/index.ts",
"predeploy": "yarn prisma migrate deploy && node --import ./scripts/register.js ./dist/data/index.js run"
},
"dependencies": {
Expand All @@ -40,8 +40,8 @@
"@node-rs/jsonwebtoken": "^0.5.2",
"@opentelemetry/api": "^1.8.0",
"@opentelemetry/core": "^1.23.0",
"@opentelemetry/exporter-prometheus": "^0.50.0",
"@opentelemetry/exporter-zipkin": "^1.23.0",
"@opentelemetry/exporter-metrics-otlp-proto": "^0.50.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.50.0",
"@opentelemetry/host-metrics": "^0.35.0",
"@opentelemetry/instrumentation": "^0.50.0",
"@opentelemetry/instrumentation-graphql": "^0.39.0",
Expand All @@ -55,7 +55,7 @@
"@opentelemetry/sdk-trace-node": "^1.23.0",
"@opentelemetry/semantic-conventions": "^1.23.0",
"@prisma/client": "^5.12.1",
"@prisma/instrumentation": "^5.12.1",
"@prisma/instrumentation": "^5.13",
"@socket.io/redis-adapter": "^8.3.0",
"cookie-parser": "^1.4.6",
"dotenv": "^16.4.5",
Expand Down Expand Up @@ -154,9 +154,8 @@
"exec": "node",
"script": "./src/index.ts",
"nodeArgs": [
"--loader",
"ts-node/esm.mjs",
"--es-module-specifier-resolution=node"
"--import",
"./scripts/register.js"
],
"ignore": [
"**/__tests__/**",
Expand Down
31 changes: 26 additions & 5 deletions packages/backend/server/scripts/loader.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
import { create, createEsmHooks } from 'ts-node';
import * as otel from '@opentelemetry/instrumentation/hook.mjs';
import { createEsmHooks, register } from 'ts-node';

const service = create({
const service = register({
experimentalSpecifierResolution: 'node',
transpileOnly: true,
logError: true,
skipProject: true,
});
const hooks = createEsmHooks(service);

export const resolve = hooks.resolve;
/**
* @type {import('ts-node').NodeLoaderHooksAPI2}
*/
const ts = createEsmHooks(service);

/**
* @type {import('ts-node').NodeLoaderHooksAPI2.ResolveHook}
*/
export const resolve = (specifier, context, defaultResolver) => {
return ts.resolve(specifier, context, (s, c) => {
return otel.resolve(s, c, defaultResolver);
});
};

/**
* @type {import('ts-node').NodeLoaderHooksAPI2.LoadHook}
*/
export const load = async (url, context, defaultLoader) => {
return await otel.load(url, context, (u, c) => {
return ts.load(u, c, defaultLoader);
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ export class UnamedAccount1703756315970 {
const users = await db.$queryRaw<
User[]
>`SELECT * FROM users WHERE name ~ E'^[\\s\\u2000-\\u200F]*$';`;
console.log(
`renaming ${users.map(({ email }) => email).join('|')} users`
);

await Promise.all(
users.map(({ id, email }) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Instrumentation } from '@opentelemetry/instrumentation';
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { IORedisInstrumentation } from '@opentelemetry/instrumentation-ioredis';
import { NestInstrumentation } from '@opentelemetry/instrumentation-nestjs-core';
import { SocketIoInstrumentation } from '@opentelemetry/instrumentation-socket.io';
import prismaInstrument from '@prisma/instrumentation';

const { PrismaInstrumentation } = prismaInstrument;

let instrumentations: Instrumentation[] = [];

export function registerInstrumentations(): void {
if (!AFFiNE.node.test) {
instrumentations = [
new NestInstrumentation(),
new IORedisInstrumentation(),
new SocketIoInstrumentation({ traceReserved: true }),
new GraphQLInstrumentation({
mergeItems: true,
ignoreTrivialResolveSpans: true,
depth: 10,
}),
new HttpInstrumentation(),
new PrismaInstrumentation({ middleware: false }),
];
}
}

export function getRegisteredInstrumentations(): Instrumentation[] {
return instrumentations;
}
131 changes: 86 additions & 45 deletions packages/backend/server/src/fundamentals/metrics/opentelemetry.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,82 @@
import { OnModuleDestroy } from '@nestjs/common';
import { metrics } from '@opentelemetry/api';
import { Attributes, metrics } from '@opentelemetry/api';
import {
CompositePropagator,
W3CBaggagePropagator,
W3CTraceContextPropagator,
} from '@opentelemetry/core';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';
import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-proto';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto';
import { HostMetrics } from '@opentelemetry/host-metrics';
import { Instrumentation } from '@opentelemetry/instrumentation';
import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
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 {
MetricProducer,
MetricReader,
PeriodicExportingMetricReader,
} from '@opentelemetry/sdk-metrics';
import { NodeSDK } from '@opentelemetry/sdk-node';
import {
BatchSpanProcessor,
SpanExporter,
TraceIdRatioBasedSampler,
} from '@opentelemetry/sdk-trace-node';
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions';
import prismaInstrument from '@prisma/instrumentation';
import {
SEMRESATTRS_K8S_CLUSTER_NAME,
SEMRESATTRS_K8S_NAMESPACE_NAME,
SEMRESATTRS_K8S_POD_NAME,
SEMRESATTRS_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';

import { getRegisteredInstrumentations } from './instrumentations';
import { PrismaMetricProducer } from './prisma';

const { PrismaInstrumentation } = prismaInstrument;
function withBuiltinAttributesMetricReader(
reader: MetricReader,
attrs: Attributes
) {
const collect = reader.collect;
reader.collect = async options => {
const result = await collect.call(reader, options);

result.resourceMetrics.scopeMetrics.forEach(metrics => {
metrics.metrics.forEach(metric => {
metric.dataPoints.forEach(dataPoint => {
// @ts-expect-error allow
dataPoint.attributes = Object.assign({}, attrs, dataPoint.attributes);
});
});
});

return result;
};

return reader;
}

function withBuiltinAttributesSpanExporter(
exporter: SpanExporter,
attrs: Attributes
) {
const exportSpans = exporter.export;
exporter.export = (spans, callback) => {
spans.forEach(span => {
// patch span attributes
// @ts-expect-error allow
span.attributes = Object.assign({}, attrs, span.attributes);
});

return exportSpans.call(exporter, spans, callback);
};

return exporter;
}

export abstract class OpentelemetryFactory {
abstract getMetricReader(): MetricReader;
abstract getSpanExporter(): SpanExporter;

getInstractions(): Instrumentation[] {
return [
new NestInstrumentation(),
new IORedisInstrumentation(),
new SocketIoInstrumentation({ traceReserved: true }),
new GraphQLInstrumentation({ mergeItems: true }),
new HttpInstrumentation(),
new PrismaInstrumentation(),
];
return getRegisteredInstrumentations();
}

getMetricsProducers(): MetricProducer[] {
Expand All @@ -51,20 +85,32 @@ export abstract class OpentelemetryFactory {

getResource() {
return new Resource({
[SemanticResourceAttributes.K8S_NAMESPACE_NAME]: AFFiNE.AFFINE_ENV,
[SemanticResourceAttributes.SERVICE_NAME]: AFFiNE.flavor.type,
[SemanticResourceAttributes.SERVICE_VERSION]: AFFiNE.version,
[SEMRESATTRS_K8S_CLUSTER_NAME]: AFFiNE.flavor.type,
[SEMRESATTRS_K8S_NAMESPACE_NAME]: AFFiNE.AFFINE_ENV,
[SEMRESATTRS_K8S_POD_NAME]: process.env.HOSTNAME ?? process.env.HOST,
});
}

getBuiltinAttributes(): Attributes {
return {
[SEMRESATTRS_SERVICE_VERSION]: AFFiNE.version,
};
}

create() {
const traceExporter = this.getSpanExporter();
const builtinAttributes = this.getBuiltinAttributes();

return new NodeSDK({
resource: this.getResource(),
sampler: new TraceIdRatioBasedSampler(0.1),
traceExporter,
metricReader: this.getMetricReader(),
spanProcessor: new BatchSpanProcessor(traceExporter),
traceExporter: withBuiltinAttributesSpanExporter(
this.getSpanExporter(),
builtinAttributes
),
metricReader: withBuiltinAttributesMetricReader(
this.getMetricReader(),
builtinAttributes
),
textMapPropagator: new CompositePropagator({
propagators: [
new W3CBaggagePropagator(),
Expand All @@ -77,24 +123,19 @@ export abstract class OpentelemetryFactory {
}
}

export class LocalOpentelemetryFactory
extends OpentelemetryFactory
implements OnModuleDestroy
{
private readonly metricsExporter = new PrometheusExporter({
metricProducers: this.getMetricsProducers(),
});

async onModuleDestroy() {
await this.metricsExporter.shutdown();
}

override getMetricReader(): MetricReader {
return this.metricsExporter;
export class LocalOpentelemetryFactory extends OpentelemetryFactory {
override getMetricReader() {
return new PeriodicExportingMetricReader({
// requires jeager service running in 'http://localhost:4318'
// with metrics feature enabled.
// see https://www.jaegertracing.io/docs/1.56/spm
exporter: new OTLPMetricExporter(),
});
}

override getSpanExporter(): SpanExporter {
return new ZipkinExporter();
override getSpanExporter() {
// requires jeager service running in 'http://localhost:4318'
return new OTLPTraceExporter();
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/backend/server/src/fundamentals/metrics/register.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { registerInstrumentations } from './instrumentations';

registerInstrumentations();
1 change: 1 addition & 0 deletions packages/backend/server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// <reference types="./global.d.ts" />
import './prelude';
import './fundamentals/metrics/register';

import { Logger } from '@nestjs/common';

Expand Down
2 changes: 1 addition & 1 deletion tests/affine-cloud/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const config: PlaywrightTestConfig = {
DATABASE_URL:
process.env.DATABASE_URL ??
'postgresql://affine:affine@localhost:5432/affine',
NODE_ENV: 'development',
NODE_ENV: 'test',
AFFINE_ENV: process.env.AFFINE_ENV ?? 'dev',
DEBUG: 'affine:*',
FORCE_COLOR: 'true',
Expand Down
2 changes: 1 addition & 1 deletion tests/affine-desktop-cloud/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const config: PlaywrightTestConfig = {
DATABASE_URL:
process.env.DATABASE_URL ??
'postgresql://affine:affine@localhost:5432/affine',
NODE_ENV: 'development',
NODE_ENV: 'test',
AFFINE_ENV: process.env.AFFINE_ENV ?? 'dev',
DEBUG: 'affine:*',
FORCE_COLOR: 'true',
Expand Down

0 comments on commit cf5045d

Please sign in to comment.