Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,5 @@ export const example = async (ctx: Context, next: () => Promise<void>) => {
Now, when you get a workspace up and running for your app with `vtex link`, you'll have this package linked as well.

> When done developing, don't forget to unlink it from `<your-app>/node`: `yarn unlink @vtex/api`

<!-- Test PR to validate SonarQube configuration -->
104 changes: 52 additions & 52 deletions src/service/telemetry/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,58 @@ import {
Logs,
Metrics,
Traces,
} from '@vtex/diagnostics-nodejs';
import { APP, OTEL_EXPORTER_OTLP_ENDPOINT, DK_APP_ID, DIAGNOSTICS_TELEMETRY_ENABLED, WORKSPACE, PRODUCTION, AttributeKeys } from '../../constants';
import { TelemetryClient } from '@vtex/diagnostics-nodejs/dist/telemetry';
import { KoaInstrumentation } from '@opentelemetry/instrumentation-koa';
import { HostMetricsInstrumentation } from '../metrics/instruments/hostMetrics';
} from '@vtex/diagnostics-nodejs'
import { APP, OTEL_EXPORTER_OTLP_ENDPOINT, DK_APP_ID, DIAGNOSTICS_TELEMETRY_ENABLED, WORKSPACE, PRODUCTION, AttributeKeys } from '../../constants'
import { TelemetryClient } from '@vtex/diagnostics-nodejs/dist/telemetry'
import { KoaInstrumentation } from '@opentelemetry/instrumentation-koa'
import { HostMetricsInstrumentation } from '../metrics/instruments/hostMetrics'

const APPLICATION_ID = APP.ID || 'vtex-io-app';
const APPLICATION_ID = APP.ID || 'vtex-io-app'

interface TelemetryClients {
logsClient: Logs.LogClient;
metricsClient: Metrics.MetricsClient;
tracesClient: Traces.TraceClient;
logsClient: Logs.LogClient
metricsClient: Metrics.MetricsClient
tracesClient: Traces.TraceClient
}

class TelemetryClientSingleton {
private static instance: TelemetryClientSingleton;
private telemetryClients: TelemetryClients | undefined;
private initializationPromise: Promise<TelemetryClients> | undefined = undefined;
private static instance: TelemetryClientSingleton
private telemetryClients: TelemetryClients | undefined
private initializationPromise: Promise<TelemetryClients> | undefined = undefined

private constructor() {}

public static getInstance(): TelemetryClientSingleton {
if (!TelemetryClientSingleton.instance) {
TelemetryClientSingleton.instance = new TelemetryClientSingleton();
TelemetryClientSingleton.instance = new TelemetryClientSingleton()
}
return TelemetryClientSingleton.instance;
return TelemetryClientSingleton.instance
}

public async getTelemetryClients(): Promise<TelemetryClients> {
if (this.telemetryClients) {
return this.telemetryClients
}

if (this.initializationPromise) {
return this.initializationPromise
}

this.initializationPromise = this.initializeTelemetryClients()
return this.initializationPromise
}

public reset(): void {
this.telemetryClients = undefined
this.initializationPromise = undefined
}

private initializeTracesClient = async (telemetryClient: TelemetryClient) =>
await telemetryClient.newTracesClient({
exporter: Exporters.CreateExporter(Exporters.CreateTracesExporterConfig({
endpoint: OTEL_EXPORTER_OTLP_ENDPOINT,
}), 'otlp'),
});
})

private initializeMetricsClient = async (telemetryClient: TelemetryClient) =>
await telemetryClient.newMetricsClient({
Expand All @@ -48,15 +66,15 @@ class TelemetryClientSingleton {
timeoutSeconds: 60,
temporality: 'delta',
}), 'otlp'),
});
})

private initializeLogsClient = async (telemetryClient: TelemetryClient) =>
await telemetryClient.newLogsClient({
exporter: Exporters.CreateExporter(Exporters.CreateLogsExporterConfig({
endpoint: OTEL_EXPORTER_OTLP_ENDPOINT,
}), 'otlp'),
loggerName: `node-vtex-api-${APPLICATION_ID}`,
});
})

private async initializeTelemetryClients(): Promise<TelemetryClients> {

Expand All @@ -66,78 +84,60 @@ class TelemetryClientSingleton {
'node-vtex-api',
APPLICATION_ID,
{
// Use built-in no-op functionality when telemetry is disabled
noop: !DIAGNOSTICS_TELEMETRY_ENABLED,
additionalAttrs: {
[AttributeKeys.VTEX_IO_APP_ID]: APPLICATION_ID,
'vendor': APP.VENDOR,
'version': APP.VERSION || '',
[AttributeKeys.VTEX_IO_WORKSPACE_NAME]: WORKSPACE,
[AttributeKeys.VTEX_IO_WORKSPACE_TYPE]: PRODUCTION ? 'production' : 'development',
},
// Use built-in no-op functionality when telemetry is disabled
noop: !DIAGNOSTICS_TELEMETRY_ENABLED,
}
);
)

const [tracesClient, metricsClient, logsClient] = await Promise.all([
this.initializeTracesClient(telemetryClient),
this.initializeMetricsClient(telemetryClient),
this.initializeLogsClient(telemetryClient),
]);
])

if (DIAGNOSTICS_TELEMETRY_ENABLED) {
console.log(`Telemetry enabled for app: ${APP.ID} (vendor: ${APP.VENDOR})`);
console.log(`Telemetry enabled for app: ${APP.ID} (vendor: ${APP.VENDOR})`)

const instrumentations = [
...Instrumentation.CommonInstrumentations.minimal(),
new KoaInstrumentation(),
new HostMetricsInstrumentation({
name: 'host-metrics-instrumentation',
meterProvider: metricsClient.provider(),
name: 'host-metrics-instrumentation',
}),
];
]

telemetryClient.registerInstrumentations(instrumentations);
telemetryClient.registerInstrumentations(instrumentations)
}

const clients: TelemetryClients = {
logsClient,
metricsClient,
tracesClient,
};
}

this.telemetryClients = clients;
return clients;
this.telemetryClients = clients
return clients
} catch (error) {
console.error('Failed to initialize telemetry clients:', error);
throw error;
console.error('Failed to initialize telemetry clients:', error)
throw error
} finally {
this.initializationPromise = undefined;
}
}

public async getTelemetryClients(): Promise<TelemetryClients> {
if (this.telemetryClients) {
return this.telemetryClients;
}

if (this.initializationPromise) {
return this.initializationPromise;
this.initializationPromise = undefined
}

this.initializationPromise = this.initializeTelemetryClients();
return this.initializationPromise;
}

public reset(): void {
this.telemetryClients = undefined;
this.initializationPromise = undefined;
}
}

export async function initializeTelemetry(): Promise<TelemetryClients> {
return TelemetryClientSingleton.getInstance().getTelemetryClients();
return TelemetryClientSingleton.getInstance().getTelemetryClients()
}

export function resetTelemetry(): void {
TelemetryClientSingleton.getInstance().reset();
TelemetryClientSingleton.getInstance().reset()
}
4 changes: 2 additions & 2 deletions src/service/worker/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Instrumentation } from '@vtex/diagnostics-nodejs';
import { Instrumentation } from '@vtex/diagnostics-nodejs'
import { request } from 'http'
import Koa from 'koa'
import compress from 'koa-compress'
Expand All @@ -13,8 +13,8 @@ import { MetricsAccumulator } from '../../metrics/MetricsAccumulator'
import { getService } from '../loaders'
import { logOnceToDevConsole } from '../logger/console'
import { LogLevel } from '../logger/loggerTypes'
import { addRequestMetricsMiddleware } from '../metrics/requestMetricsMiddleware'
import { addOtelRequestMetricsMiddleware } from '../metrics/otelRequestMetricsMiddleware'
import { addRequestMetricsMiddleware } from '../metrics/requestMetricsMiddleware'
import { TracerSingleton } from '../tracing/TracerSingleton'
import { addTracingMiddleware } from '../tracing/tracingMiddlewares'
import { addProcessListeners, logger } from './listeners'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ async function auth (ctx: ServiceContext, authArgs: AuthDirectiveArgs): Promise<
}

function parseArgs (authArgs: AuthDirectiveArgs): AuthDirectiveArgs {
if (authArgs.scope == 'PUBLIC') {
if (authArgs.scope === 'PUBLIC') {
return authArgs
}

Expand All @@ -84,7 +84,7 @@ export class Auth extends SchemaDirectiveVisitor {
const {resolve = defaultFieldResolver} = field
field.resolve = async (root, args, ctx, info) => {
const authArgs = parseArgs(this.args as AuthDirectiveArgs)
if (!authArgs.scope || authArgs.scope == 'PRIVATE') {
if (!authArgs.scope || authArgs.scope === 'PRIVATE') {
await auth(ctx, authArgs)
}
return resolve(root, args, ctx, info)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ describe('Metric Schema Directive', () => {
batch: jest.fn(),
}
mockDiagnosticsMetrics = {
recordLatency: jest.fn(),
incrementCounter: jest.fn(),
recordLatency: jest.fn(),
}
;(global as any).metrics = mockMetricsAccumulator
;(global as any).diagnosticsMetrics = mockDiagnosticsMetrics
Expand All @@ -42,15 +42,15 @@ describe('Metric Schema Directive', () => {
// Simulate what the Metric directive does
const wrappedResolver = async (root: any, args: any, ctx: any, info: any) => {
let failedToResolve = false
let result: any = null
let resolverResult: any = null
let ellapsed: [number, number] = [0, 0]

try {
const start = process.hrtime()
result = await mockResolver(root, args, ctx, info)
resolverResult = await mockResolver(root, args, ctx, info)
ellapsed = process.hrtime(start)
} catch (error) {
result = error
resolverResult = error
failedToResolve = true
}

Expand Down Expand Up @@ -78,10 +78,10 @@ describe('Metric Schema Directive', () => {
}

if (failedToResolve) {
throw result
throw resolverResult
}

return result
return resolverResult
}

// Execute the wrapped resolver
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Attributes } from '@opentelemetry/api'
import { defaultFieldResolver, GraphQLField } from 'graphql'
import { SchemaDirectiveVisitor } from 'graphql-tools'
import { Attributes } from '@opentelemetry/api'
import { APP, AttributeKeys } from '../../../../../..'
import { GraphQLServiceContext } from '../../typings'

Expand Down
14 changes: 7 additions & 7 deletions src/service/worker/runtime/http/middlewares/requestStats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ describe('requestStats', () => {
beforeEach(() => {
// Create mock DiagnosticsMetrics instance
mockDiagnosticsMetrics = {
setGauge: jest.fn(),
incrementCounter: jest.fn(),
recordLatency: jest.fn(),
setGauge: jest.fn(),
} as any

// Set global.diagnosticsMetrics for the tests
Expand Down Expand Up @@ -58,9 +58,9 @@ describe('requestStats', () => {

const stats = incomingRequestStats.get()
expect(stats).toEqual({
total: 0,
aborted: 0,
closed: 0,
total: 0,
})
})
})
Expand All @@ -77,14 +77,14 @@ describe('requestStats', () => {
req: mockRequest,
status: 200,
vtex: {
route: {
id: 'test-route',
type: 'public',
},
cancellation: {
cancelable: true,
source: { cancel: jest.fn() },
cancelled: false,
source: { cancel: jest.fn() },
},
route: {
id: 'test-route',
type: 'public',
},
},
}
Expand Down
Loading