Skip to content

Commit 2c35dbd

Browse files
committed
Updates logs instrumentation via diagnostics lib
Adds telemetry and log clients, integrating node-vtex-api with the new version of the diagnostics library that supports OpenTelemetry protocols. It also updates Typescript and Opentelemetry dependencies to enable support for node builder 6.x and IO apps using Node 16.x.
1 parent 23321f3 commit 2c35dbd

9 files changed

Lines changed: 220 additions & 31 deletions

File tree

package.json

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"dependencies": {
5050
"@types/koa": "^2.11.0",
5151
"@types/koa-compose": "^3.2.3",
52+
"@vtex/diagnostics-nodejs": "0.1.0-beta.8",
5253
"@vtex/node-error-report": "^0.0.3",
5354
"@wry/equality": "^0.1.9",
5455
"agentkeepalive": "^4.0.2",
@@ -121,7 +122,28 @@
121122
"tslint-config-vtex": "^2.1.0",
122123
"tslint-eslint-rules": "^5.4.0",
123124
"typemoq": "^2.1.0",
124-
"typescript": "^3.8.3",
125+
"typescript": "^4.4.4",
125126
"typescript-json-schema": "^0.40.0"
127+
},
128+
"resolutions": {
129+
"@opentelemetry/core": "1.30.1",
130+
"@opentelemetry/exporter-metrics-otlp-grpc": "0.57.2",
131+
"@opentelemetry/exporter-metrics-otlp-http": "0.57.2",
132+
"@opentelemetry/exporter-logs-otlp-grpc": "0.57.2",
133+
"@opentelemetry/exporter-logs-otlp-http": "0.57.2",
134+
"@opentelemetry/exporter-trace-otlp-grpc": "0.57.2",
135+
"@opentelemetry/exporter-trace-otlp-http": "0.57.2",
136+
"@opentelemetry/instrumentation": "0.57.2",
137+
"@opentelemetry/instrumentation-http": "0.57.2",
138+
"@opentelemetry/instrumentation-grpc": "0.57.2",
139+
"@opentelemetry/instrumentation-net": "0.43.1",
140+
"@opentelemetry/propagator-b3": "1.30.1",
141+
"@opentelemetry/resource-detector-aws": "1.12.0",
142+
"@opentelemetry/resources": "1.30.1",
143+
"@opentelemetry/sdk-node": "0.57.2",
144+
"@opentelemetry/sdk-logs": "0.57.2",
145+
"@opentelemetry/sdk-metrics": "1.30.1",
146+
"@opentelemetry/sdk-trace-base": "1.30.1",
147+
"@opentelemetry/sdk-trace-node": "1.30.1"
126148
}
127149
}

src/service/logger/client.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Exporters } from '@vtex/diagnostics-nodejs';
2+
import { LogClient } from '@vtex/diagnostics-nodejs/dist/types';
3+
import { getTelemetryClient } from '../telemetry';
4+
5+
let logClient: LogClient | undefined;
6+
let isInitializing = false;
7+
let initPromise: Promise<LogClient> | undefined = undefined;
8+
9+
export async function getLogClient(account: string, workspace: string): Promise<LogClient> {
10+
11+
if (logClient) {
12+
return logClient;
13+
}
14+
15+
if (initPromise) {
16+
return initPromise;
17+
}
18+
19+
isInitializing = true;
20+
initPromise = initializeClient(account, workspace);
21+
22+
return initPromise;
23+
}
24+
25+
async function initializeClient(account: string, workspace: string): Promise<LogClient> {
26+
try {
27+
const telemetryClient = await getTelemetryClient();
28+
29+
const logsConfig = Exporters.CreateLogsExporterConfig({
30+
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
31+
path: process.env.OTEL_EXPORTER_OTLP_PATH || '/v1/logs',
32+
protocol: 'http',
33+
interval: 5,
34+
timeoutSeconds: 5,
35+
headers: { 'Content-Type': 'application/json' },
36+
});
37+
38+
const logsExporter = Exporters.CreateExporter(logsConfig, 'otlp');
39+
await logsExporter.initialize();
40+
41+
const clientKey = `${account}-${workspace}`;
42+
logClient = await telemetryClient.newLogsClient({
43+
exporter: logsExporter,
44+
loggerName: `node-vtex-api-${clientKey}`,
45+
});
46+
47+
return logClient;
48+
} catch (error) {
49+
console.error('Failed to initialize logs client:', error);
50+
throw error;
51+
} finally {
52+
isInitializing = false;
53+
}
54+
}

src/service/logger/console.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { isMaster, isWorker } from 'cluster'
22

33
import { LRUCache } from '../../caches'
4-
import { LogLevel } from './logger'
4+
import { LogLevel } from './loggerTypes'
55

66
export interface LogMessage {
77
cmd: typeof LOG_ONCE

src/service/logger/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export * from './console'
22
export * from './logger'
3+
export * from './loggerTypes'
4+
export * from './client'

src/service/logger/logger.ts

Lines changed: 52 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,23 @@
11
import { APP } from '../../constants'
2-
import { IUserLandTracer } from '../../tracing'
32
import { cleanError } from '../../utils/error'
43
import { cleanLog } from '../../utils/log'
5-
import { IOContext } from '../worker/runtime/typings'
6-
import { logOnceToDevConsole } from './console'
4+
import { LogClient } from '@vtex/diagnostics-nodejs/dist/types';
5+
import { LoggerContext, LogLevel, TracingState } from './loggerTypes'
6+
import { getLogClient } from './client'
77

8-
const linked = !!process.env.VTEX_APP_LINK
98
const app = APP.ID
109
const EMPTY_MESSAGE = 'Logger.log was called with null or undefined message'
1110

12-
export interface LoggerTracingContext {
13-
requestTracer: IUserLandTracer
14-
}
15-
16-
export enum LogLevel {
17-
Debug = 'debug',
18-
Info = 'info',
19-
Warn = 'warn',
20-
Error = 'error',
21-
}
22-
23-
interface LoggerContext extends Pick<IOContext, 'account'|'workspace'|'requestId'|'operationId'|'production'> {
24-
tracer?: IOContext['tracer']
25-
}
26-
27-
interface TracingState {
28-
isTraceSampled: boolean,
29-
traceId?: string
30-
}
31-
3211
export class Logger {
3312
private account: string
3413
private workspace: string
3514
private operationId: string
3615
private requestId: string
3716
private production: boolean
3817
private tracingState?: TracingState
18+
private logClient: LogClient | undefined = undefined
19+
private clientInitialized: boolean = false
20+
private clientInitializing: boolean = false
3921

4022
constructor(ctx: LoggerContext) {
4123
this.account = ctx.account
@@ -50,6 +32,25 @@ export class Logger {
5032
traceId: ctx.tracer.traceId,
5133
}
5234
}
35+
36+
this.initLogClient();
37+
}
38+
39+
private async initLogClient() {
40+
if (this.clientInitialized || this.clientInitializing) {
41+
return;
42+
}
43+
44+
this.clientInitializing = true;
45+
try {
46+
this.logClient = await getLogClient(this.account, this.workspace);
47+
this.clientInitialized = true;
48+
} catch (error) {
49+
console.error('Failed to initialize log client:', error);
50+
throw error;
51+
} finally {
52+
this.clientInitializing = false;
53+
}
5354
}
5455

5556
public debug = (message: any) =>
@@ -86,11 +87,34 @@ export class Logger {
8687
// Mark third-party apps logs to send to skidder
8788
if (APP.IS_THIRD_PARTY()) {
8889
Object.assign(inflatedLog, {
89-
__SKIDDER_TOPIC_1: `skidder.vendor.${APP.VENDOR}`,
90-
__SKIDDER_TOPIC_2: `skidder.app.${APP.VENDOR}.${APP.NAME}`,
91-
})
90+
'__SKIDDER_TOPIC_1': `skidder.vendor.${APP.VENDOR}`,
91+
'__SKIDDER_TOPIC_2': `skidder.app.${APP.VENDOR}.${APP.NAME}`,
92+
});
9293
}
9394

94-
console.log(JSON.stringify(inflatedLog))
95+
if (this.logClient) {
96+
try {
97+
let logMessage = typeof data === 'string' ? data : JSON.stringify(data)
98+
switch (level) {
99+
case LogLevel.Debug:
100+
this.logClient.debug(logMessage, inflatedLog);
101+
break;
102+
case LogLevel.Info:
103+
this.logClient.info(logMessage, inflatedLog);
104+
break;
105+
case LogLevel.Warn:
106+
this.logClient.warn(logMessage, inflatedLog);
107+
break;
108+
case LogLevel.Error:
109+
this.logClient.error(logMessage, inflatedLog);
110+
break;
111+
default:
112+
this.logClient.info(logMessage, inflatedLog);
113+
}
114+
} catch (e) {
115+
console.error('Error using diagnostics client for logging:', e);
116+
}
117+
}
118+
console.log(typeof inflatedLog === 'string' ? JSON.stringify(inflatedLog) : inflatedLog)
95119
}
96120
}

src/service/logger/loggerTypes.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { IOContext } from '../worker/runtime/typings'
2+
3+
export interface LoggerContext extends Pick<IOContext, 'account'|'workspace'|'requestId'|'operationId'|'production'> {
4+
tracer?: IOContext['tracer']
5+
}
6+
7+
export interface TracingState {
8+
isTraceSampled: boolean,
9+
traceId?: string
10+
}
11+
12+
export enum LogLevel {
13+
Debug = 'debug',
14+
Info = 'info',
15+
Warn = 'warn',
16+
Error = 'error',
17+
}

src/service/telemetry/client.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { NewTelemetryClient } from '@vtex/diagnostics-nodejs';
2+
import { TelemetryClient } from '@vtex/diagnostics-nodejs/dist/telemetry';
3+
import { APP } from '../../constants';
4+
5+
class TelemetryClientSingleton {
6+
private static instance: TelemetryClientSingleton;
7+
private telemetryClient: TelemetryClient | undefined;
8+
private initializationPromise: Promise<TelemetryClient> | undefined = undefined;
9+
10+
private constructor() {}
11+
12+
public static getInstance(): TelemetryClientSingleton {
13+
if (!TelemetryClientSingleton.instance) {
14+
TelemetryClientSingleton.instance = new TelemetryClientSingleton();
15+
}
16+
return TelemetryClientSingleton.instance;
17+
}
18+
19+
private async initTelemetryClient(): Promise<TelemetryClient> {
20+
try {
21+
const telemetryClient = await NewTelemetryClient(
22+
'node-vtex-api',
23+
APP.ID || 'vtex-app',
24+
{
25+
additionalAttrs: {
26+
'version': APP.VERSION || '',
27+
'environment': process.env.VTEX_WORKSPACE || 'development',
28+
},
29+
}
30+
);
31+
32+
this.telemetryClient = telemetryClient;
33+
return telemetryClient;
34+
} catch (error) {
35+
console.error('Failed to initialize telemetry client:', error);
36+
throw error;
37+
} finally {
38+
this.initializationPromise = undefined;
39+
}
40+
}
41+
42+
public async getClient(): Promise<TelemetryClient> {
43+
if (this.telemetryClient) {
44+
return this.telemetryClient;
45+
}
46+
47+
if (this.initializationPromise) {
48+
return this.initializationPromise;
49+
}
50+
51+
this.initializationPromise = this.initTelemetryClient();
52+
53+
return this.initializationPromise;
54+
}
55+
56+
public reset(): void {
57+
this.telemetryClient = undefined;
58+
this.initializationPromise = undefined;
59+
}
60+
61+
}
62+
63+
export async function getTelemetryClient(): Promise<TelemetryClient> {
64+
return TelemetryClientSingleton.getInstance().getClient();
65+
}
66+
67+
export function resetTelemetryClient(): void {
68+
TelemetryClientSingleton.getInstance().reset();
69+
}

src/service/telemetry/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './client'

src/service/worker/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { InstanceOptions } from '../../HttpClient/typings'
1111
import { MetricsAccumulator } from '../../metrics/MetricsAccumulator'
1212
import { getService } from '../loaders'
1313
import { logOnceToDevConsole } from '../logger/console'
14-
import { LogLevel } from '../logger/logger'
14+
import { LogLevel } from '../logger/loggerTypes'
1515
import { addRequestMetricsMiddleware } from '../metrics/requestMetricsMiddleware'
1616
import { TracerSingleton } from '../tracing/TracerSingleton'
1717
import { addTracingMiddleware } from '../tracing/tracingMiddlewares'

0 commit comments

Comments
 (0)