@vtex/api 7.2.4 → 7.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,6 +14,7 @@ const metricsMiddleware = ({ metrics, serverTiming, name }) => {
14
14
  target: name || 'unknown',
15
15
  }));
16
16
  return async (ctx, next) => {
17
+ var _a;
17
18
  const start = process.hrtime();
18
19
  let status = 'unknown';
19
20
  let errorCode;
@@ -57,29 +58,70 @@ const metricsMiddleware = ({ metrics, serverTiming, name }) => {
57
58
  const label = `http-client-${ctx.config.metric}`;
58
59
  const extensions = {};
59
60
  Object.assign(extensions, { [status]: 1 });
61
+ // Determine cache state for diagnostics metrics
62
+ let cacheState = 'none';
60
63
  if (ctx.cacheHit) {
61
64
  Object.assign(extensions, ctx.cacheHit, { [`${status}-hit`]: 1 });
65
+ cacheState = 'hit';
62
66
  }
63
67
  else if (!ctx.inflightHit && !ctx.memoizedHit) {
64
68
  // Lets us know how many calls passed through to origin
65
69
  Object.assign(extensions, { [`${status}-miss`]: 1 });
70
+ cacheState = 'miss';
66
71
  }
67
72
  if (ctx.inflightHit) {
68
73
  Object.assign(extensions, { [`${status}-inflight`]: 1 });
74
+ cacheState = 'inflight';
69
75
  }
70
76
  if (ctx.memoizedHit) {
71
77
  Object.assign(extensions, { [`${status}-memoized`]: 1 });
78
+ cacheState = 'memoized';
72
79
  }
73
- if (ctx.config.retryCount) {
74
- const retryCount = ctx.config.retryCount;
75
- if (retryCount > 0) {
76
- extensions[`retry-${status}-${retryCount}`] = 1;
77
- }
80
+ const retryCount = ctx.config.retryCount || 0;
81
+ if (retryCount > 0) {
82
+ extensions[`retry-${status}-${retryCount}`] = 1;
78
83
  }
79
84
  const end = status === 'success' && !ctx.cacheHit && !ctx.inflightHit && !ctx.memoizedHit
80
85
  ? process.hrtime(start)
81
86
  : undefined;
87
+ // Legacy metrics (backward compatibility)
82
88
  metrics.batch(label, end, extensions);
89
+ // New diagnostics metrics with stable names and attributes
90
+ if (global.diagnosticsMetrics) {
91
+ const elapsed = process.hrtime(start);
92
+ const rawStatusCode = ((_a = ctx.response) === null || _a === void 0 ? void 0 : _a.status) || errorStatus;
93
+ const baseAttributes = {
94
+ component: 'http-client',
95
+ client_metric: ctx.config.metric,
96
+ status_code: rawStatusCode,
97
+ status,
98
+ };
99
+ // Record latency histogram with all context
100
+ global.diagnosticsMetrics.recordLatency(elapsed, {
101
+ ...baseAttributes,
102
+ cache_state: cacheState,
103
+ });
104
+ // Increment counters for different event types (replaces extensions behavior)
105
+ // Main request counter with status as attribute
106
+ global.diagnosticsMetrics.incrementCounter('http_client_requests_total', 1, baseAttributes);
107
+ // Cache counter with cache_state as attribute (replaces extensions like 'success-hit', 'error-miss')
108
+ if (cacheState !== 'none') {
109
+ global.diagnosticsMetrics.incrementCounter('http_client_cache_total', 1, {
110
+ ...baseAttributes,
111
+ cache_state: cacheState,
112
+ });
113
+ }
114
+ // Retry counter (replaces 'retry-{status}-{count}' extensions)
115
+ if (retryCount > 0) {
116
+ global.diagnosticsMetrics.incrementCounter('http_client_requests_retried_total', 1, {
117
+ ...baseAttributes,
118
+ retry_count: retryCount,
119
+ });
120
+ }
121
+ }
122
+ else {
123
+ console.warn('DiagnosticsMetrics not available. HTTP client metrics not reported.');
124
+ }
83
125
  if (ctx.config.verbose) {
84
126
  console.log(`VERBOSE: ${name}.${ctx.config.label}`, {
85
127
  ...extensions,
@@ -6,6 +6,11 @@ export declare class HttpAgentSingleton {
6
6
  pendingRequests: number;
7
7
  sockets: number;
8
8
  };
9
+ /**
10
+ * Update HTTP agent statistics as diagnostics metrics (gauges).
11
+ * This method should be called periodically to export current HTTP agent state.
12
+ */
13
+ static updateHttpAgentMetrics(): void;
9
14
  private static httpAgent;
10
15
  private static count;
11
16
  }
@@ -19,6 +19,21 @@ class HttpAgentSingleton {
19
19
  sockets,
20
20
  };
21
21
  }
22
+ /**
23
+ * Update HTTP agent statistics as diagnostics metrics (gauges).
24
+ * This method should be called periodically to export current HTTP agent state.
25
+ */
26
+ static updateHttpAgentMetrics() {
27
+ if (!global.diagnosticsMetrics) {
28
+ console.warn('DiagnosticsMetrics not available. HTTP agent metrics not reported.');
29
+ return;
30
+ }
31
+ const stats = HttpAgentSingleton.httpAgentStats();
32
+ // Report HTTP agent stats as gauges (current state)
33
+ global.diagnosticsMetrics.setGauge('http_agent_sockets_current', stats.sockets, {});
34
+ global.diagnosticsMetrics.setGauge('http_agent_free_sockets_current', stats.freeSockets, {});
35
+ global.diagnosticsMetrics.setGauge('http_agent_pending_requests_current', stats.pendingRequests, {});
36
+ }
22
37
  static count(obj) {
23
38
  try {
24
39
  return Object.values(obj).reduce((acc, val) => acc += val.length, 0);
@@ -130,6 +130,7 @@ export declare const PRODUCTION: boolean;
130
130
  export declare const INSPECT_DEBUGGER_PORT = 5858;
131
131
  export declare const cancellableMethods: Set<string>;
132
132
  export declare const LOG_CLIENT_INIT_TIMEOUT_MS = 5000;
133
+ export declare const METRIC_CLIENT_INIT_TIMEOUT_MS = 5000;
133
134
  export declare const OTEL_EXPORTER_OTLP_ENDPOINT: string;
134
135
  export declare const DK_APP_ID: string;
135
136
  export declare const DIAGNOSTICS_TELEMETRY_ENABLED: boolean;
package/lib/constants.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.LOG_CLIENT_INIT_TIMEOUT_MS = exports.cancellableMethods = exports.INSPECT_DEBUGGER_PORT = exports.PRODUCTION = exports.WORKSPACE = exports.ACCOUNT = exports.NODE_ENV = exports.APP = exports.PUBLIC_ENDPOINT = exports.REGION = exports.LINKED = exports.MAX_WORKERS = exports.HTTP_SERVER_PORT = exports.MAX_AGE = exports.UP_SIGNAL = exports.BODY_HASH = exports.PROVIDER_HEADER = exports.TRACE_ID_HEADER = exports.COLOSSUS_PARAMS_HEADER = exports.COLOSSUS_ROUTE_ID_HEADER = exports.COLOSSUS_ROUTE_DECLARER_HEADER = exports.EVENT_HANDLER_ID_HEADER = exports.EVENT_SUBJECT_HEADER = exports.EVENT_SENDER_HEADER = exports.EVENT_KEY_HEADER = exports.WORKSPACE_HEADER = exports.WORKSPACE_IS_PRODUCTION_HEADER = exports.PLATFORM_HEADER = exports.OPERATION_ID_HEADER = exports.ROUTER_CACHE_HEADER = exports.REQUEST_ID_HEADER = exports.CREDENTIAL_HEADER = exports.ACCOUNT_HEADER = exports.ETAG_HEADER = exports.META_HEADER_BUCKET = exports.META_HEADER = exports.BINDING_HEADER = exports.TENANT_HEADER = exports.FORWARDED_HOST_HEADER = exports.LOCALE_HEADER = exports.PRODUCT_HEADER = exports.SESSION_HEADER = exports.SEGMENT_HEADER = exports.CACHE_CONTROL_HEADER = exports.AttributeKeys = exports.HeaderKeys = exports.PID = exports.IS_IO = exports.DEFAULT_WORKSPACE = exports.NODE_VTEX_API_VERSION = void 0;
4
- exports.DIAGNOSTICS_TELEMETRY_ENABLED = exports.DK_APP_ID = exports.OTEL_EXPORTER_OTLP_ENDPOINT = void 0;
4
+ exports.DIAGNOSTICS_TELEMETRY_ENABLED = exports.DK_APP_ID = exports.OTEL_EXPORTER_OTLP_ENDPOINT = exports.METRIC_CLIENT_INIT_TIMEOUT_MS = void 0;
5
5
  const app_1 = require("./utils/app");
6
6
  const diagnostics_semconv_1 = require("@vtex/diagnostics-semconv");
7
7
  // tslint:disable-next-line
@@ -141,6 +141,7 @@ exports.PRODUCTION = process.env.VTEX_PRODUCTION === 'true';
141
141
  exports.INSPECT_DEBUGGER_PORT = 5858;
142
142
  exports.cancellableMethods = new Set(['GET', 'OPTIONS', 'HEAD']);
143
143
  exports.LOG_CLIENT_INIT_TIMEOUT_MS = 5000;
144
+ exports.METRIC_CLIENT_INIT_TIMEOUT_MS = 5000;
144
145
  exports.OTEL_EXPORTER_OTLP_ENDPOINT = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
145
146
  exports.DK_APP_ID = process.env.NODE_VTEX_API_DK_APP_ID || "apps-team";
146
147
  exports.DIAGNOSTICS_TELEMETRY_ENABLED = process.env.VTEX_DIAGNOSTICS_TELEMETRY_ENABLED === 'true';
package/lib/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from './clients';
3
3
  export * from './errors';
4
4
  export * from './HttpClient';
5
5
  export * from './metrics/MetricsAccumulator';
6
+ export * from './metrics/DiagnosticsMetrics';
6
7
  export * from './responses';
7
8
  export * from './service/worker/runtime/Service';
8
9
  export * from './service/worker/runtime/method';
package/lib/index.js CHANGED
@@ -20,6 +20,7 @@ __exportStar(require("./clients"), exports);
20
20
  __exportStar(require("./errors"), exports);
21
21
  __exportStar(require("./HttpClient"), exports);
22
22
  __exportStar(require("./metrics/MetricsAccumulator"), exports);
23
+ __exportStar(require("./metrics/DiagnosticsMetrics"), exports);
23
24
  __exportStar(require("./responses"), exports);
24
25
  __exportStar(require("./service/worker/runtime/Service"), exports);
25
26
  __exportStar(require("./service/worker/runtime/method"), exports);
@@ -0,0 +1,94 @@
1
+ import { Attributes } from '@opentelemetry/api';
2
+ /**
3
+ * DiagnosticsMetrics provides a high-level API for recording metrics using
4
+ * the @vtex/diagnostics-nodejs library. It completely abstracts instrument
5
+ * management, bucket configuration, and lifecycle.
6
+ *
7
+ * Uses a single histogram for all latency measurements with attributes to differentiate.
8
+ * This follows OpenTelemetry best practices and reduces metric cardinality.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const diagnosticsMetrics = new DiagnosticsMetrics()
13
+ * diagnosticsMetrics.initMetricClient()
14
+ *
15
+ * // Record latency with operation type in attributes
16
+ * const start = process.hrtime()
17
+ * // ... do work ...
18
+ * diagnosticsMetrics.recordLatency(process.hrtime(start), { operation: 'api-call', status: '2xx' })
19
+ *
20
+ * // Or from milliseconds
21
+ * diagnosticsMetrics.recordLatency(42.5, { operation: 'db-query', status: 'success' })
22
+ *
23
+ * // Increment a counter
24
+ * diagnosticsMetrics.incrementCounter('http_requests_total', 1, { method: 'GET', status: '2xx' })
25
+ *
26
+ * // Set a gauge value
27
+ * diagnosticsMetrics.setGauge('cache_items_current', 1024, { cache: 'pages' })
28
+ * ```
29
+ */
30
+ export declare class DiagnosticsMetrics {
31
+ private metricsClient;
32
+ private clientInitPromise;
33
+ private latencyHistogram;
34
+ private counters;
35
+ private gauges;
36
+ constructor();
37
+ /**
38
+ * Initialize the metrics client with timeout handling.
39
+ * Called automatically in constructor.
40
+ */
41
+ private initMetricClient;
42
+ /**
43
+ * Create the single shared histogram for all latency measurements.
44
+ * Called after metric client is initialized.
45
+ */
46
+ private createLatencyHistogram;
47
+ /**
48
+ * Record a latency measurement using the single shared histogram.
49
+ * Accepts either an hrtime tuple from process.hrtime() or milliseconds as a number.
50
+ * Use attributes to differentiate between different operations.
51
+ *
52
+ * @param value Either [seconds, nanoseconds] from process.hrtime() or milliseconds
53
+ * @param attributes Attributes including 'operation' to identify the operation type (max of MAX_ATTRIBUTES attributes)
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const start = process.hrtime()
58
+ * // ... do work ...
59
+ * diagnosticsMetrics.recordLatency(process.hrtime(start), { operation: 'api-call', status: '2xx' })
60
+ *
61
+ * // Or with milliseconds
62
+ * diagnosticsMetrics.recordLatency(42.5, { operation: 'db-query', status: 'success' })
63
+ * ```
64
+ */
65
+ recordLatency(value: [number, number] | number, attributes?: Attributes): void;
66
+ /**
67
+ * Increment a counter by a specific value.
68
+ * Multiple counters are stored by name since counters represent different types of events.
69
+ *
70
+ * @param name Counter name (e.g., 'http_requests_total', 'cache_hits_total')
71
+ * @param value Amount to increment by (typically 1)
72
+ * @param attributes Optional attributes for the counter (max of MAX_ATTRIBUTES attributes, e.g., { method: 'GET', status: '2xx' })
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * diagnosticsMetrics.incrementCounter('http_requests_total', 1, { method: 'GET', status: '2xx' })
77
+ * ```
78
+ */
79
+ incrementCounter(name: string, value: number, attributes?: Attributes): void;
80
+ /**
81
+ * Set a gauge to a specific value (current state).
82
+ * Multiple gauges are stored by name since gauges represent different types of measurements.
83
+ *
84
+ * @param name Gauge name (e.g., 'cache_items_current', 'memory_usage_bytes')
85
+ * @param value Current value
86
+ * @param attributes Optional attributes for the gauge (max of MAX_ATTRIBUTES attributes, e.g., { cache: 'pages' })
87
+ *
88
+ * @example
89
+ * ```typescript
90
+ * diagnosticsMetrics.setGauge('cache_items_current', 1024, { cache: 'pages' })
91
+ * ```
92
+ */
93
+ setGauge(name: string, value: number, attributes?: Attributes): void;
94
+ }
@@ -0,0 +1,206 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DiagnosticsMetrics = void 0;
4
+ const client_1 = require("../service/metrics/client");
5
+ const constants_1 = require("../constants");
6
+ /**
7
+ * Maximum number of attributes allowed per metric to control cardinality.
8
+ * This limit applies to external usage (VTEX IO Apps) to prevent unbounded metric dimensions.
9
+ */
10
+ const MAX_ATTRIBUTES = 5;
11
+ /**
12
+ * Converts an hrtime tuple [seconds, nanoseconds] to milliseconds.
13
+ */
14
+ function hrtimeToMillis(hrtime) {
15
+ return (hrtime[0] * 1e3) + (hrtime[1] / 1e6);
16
+ }
17
+ /**
18
+ * Limits the number of attributes to prevent high cardinality.
19
+ * Takes the first MAX_ATTRIBUTES entries if the limit is exceeded.
20
+ *
21
+ * @param attributes Optional attributes object
22
+ * @returns Limited attributes object or undefined
23
+ */
24
+ function limitAttributes(attributes) {
25
+ if (!attributes) {
26
+ return undefined;
27
+ }
28
+ const entries = Object.entries(attributes);
29
+ if (entries.length <= MAX_ATTRIBUTES) {
30
+ return attributes;
31
+ }
32
+ if (constants_1.LINKED) {
33
+ console.warn(`Attribute limit exceeded: ${entries.length} attributes provided, using only the first ${MAX_ATTRIBUTES}. ` +
34
+ `Consider reducing the number of attributes to avoid high cardinality.`);
35
+ }
36
+ return Object.fromEntries(entries.slice(0, MAX_ATTRIBUTES));
37
+ }
38
+ /**
39
+ * DiagnosticsMetrics provides a high-level API for recording metrics using
40
+ * the @vtex/diagnostics-nodejs library. It completely abstracts instrument
41
+ * management, bucket configuration, and lifecycle.
42
+ *
43
+ * Uses a single histogram for all latency measurements with attributes to differentiate.
44
+ * This follows OpenTelemetry best practices and reduces metric cardinality.
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const diagnosticsMetrics = new DiagnosticsMetrics()
49
+ * diagnosticsMetrics.initMetricClient()
50
+ *
51
+ * // Record latency with operation type in attributes
52
+ * const start = process.hrtime()
53
+ * // ... do work ...
54
+ * diagnosticsMetrics.recordLatency(process.hrtime(start), { operation: 'api-call', status: '2xx' })
55
+ *
56
+ * // Or from milliseconds
57
+ * diagnosticsMetrics.recordLatency(42.5, { operation: 'db-query', status: 'success' })
58
+ *
59
+ * // Increment a counter
60
+ * diagnosticsMetrics.incrementCounter('http_requests_total', 1, { method: 'GET', status: '2xx' })
61
+ *
62
+ * // Set a gauge value
63
+ * diagnosticsMetrics.setGauge('cache_items_current', 1024, { cache: 'pages' })
64
+ * ```
65
+ */
66
+ class DiagnosticsMetrics {
67
+ constructor() {
68
+ this.counters = new Map();
69
+ this.gauges = new Map();
70
+ this.initMetricClient();
71
+ }
72
+ /**
73
+ * Initialize the metrics client with timeout handling.
74
+ * Called automatically in constructor.
75
+ */
76
+ initMetricClient() {
77
+ if (this.clientInitPromise) {
78
+ return this.clientInitPromise;
79
+ }
80
+ this.clientInitPromise = (async () => {
81
+ try {
82
+ const timeoutPromise = new Promise((_, reject) => {
83
+ setTimeout(() => reject(new Error('Metric client initialization timeout')), constants_1.METRIC_CLIENT_INIT_TIMEOUT_MS);
84
+ });
85
+ this.metricsClient = await Promise.race([
86
+ (0, client_1.getMetricClient)(),
87
+ timeoutPromise
88
+ ]);
89
+ // Create the single latency histogram after client is ready
90
+ this.createLatencyHistogram();
91
+ return this.metricsClient;
92
+ }
93
+ catch (error) {
94
+ console.error('Failed to initialize metric client:', error);
95
+ return undefined;
96
+ }
97
+ })();
98
+ return this.clientInitPromise;
99
+ }
100
+ /**
101
+ * Create the single shared histogram for all latency measurements.
102
+ * Called after metric client is initialized.
103
+ */
104
+ createLatencyHistogram() {
105
+ if (!this.metricsClient) {
106
+ return;
107
+ }
108
+ this.latencyHistogram = this.metricsClient.createHistogram('io_app_operation_duration_milliseconds', {
109
+ description: 'Duration of VTEX IO app operations in milliseconds',
110
+ unit: 'ms',
111
+ });
112
+ }
113
+ /**
114
+ * Record a latency measurement using the single shared histogram.
115
+ * Accepts either an hrtime tuple from process.hrtime() or milliseconds as a number.
116
+ * Use attributes to differentiate between different operations.
117
+ *
118
+ * @param value Either [seconds, nanoseconds] from process.hrtime() or milliseconds
119
+ * @param attributes Attributes including 'operation' to identify the operation type (max of MAX_ATTRIBUTES attributes)
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * const start = process.hrtime()
124
+ * // ... do work ...
125
+ * diagnosticsMetrics.recordLatency(process.hrtime(start), { operation: 'api-call', status: '2xx' })
126
+ *
127
+ * // Or with milliseconds
128
+ * diagnosticsMetrics.recordLatency(42.5, { operation: 'db-query', status: 'success' })
129
+ * ```
130
+ */
131
+ recordLatency(value, attributes) {
132
+ if (!this.latencyHistogram) {
133
+ console.warn('DiagnosticsMetrics not initialized. Call initialize() first.');
134
+ return;
135
+ }
136
+ // Convert hrtime to milliseconds if needed
137
+ const milliseconds = Array.isArray(value) ? hrtimeToMillis(value) : value;
138
+ // Limit attributes to prevent high cardinality
139
+ const limitedAttributes = limitAttributes(attributes);
140
+ // Record to the single shared histogram with attributes
141
+ this.latencyHistogram.record(milliseconds, limitedAttributes);
142
+ }
143
+ /**
144
+ * Increment a counter by a specific value.
145
+ * Multiple counters are stored by name since counters represent different types of events.
146
+ *
147
+ * @param name Counter name (e.g., 'http_requests_total', 'cache_hits_total')
148
+ * @param value Amount to increment by (typically 1)
149
+ * @param attributes Optional attributes for the counter (max of MAX_ATTRIBUTES attributes, e.g., { method: 'GET', status: '2xx' })
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * diagnosticsMetrics.incrementCounter('http_requests_total', 1, { method: 'GET', status: '2xx' })
154
+ * ```
155
+ */
156
+ incrementCounter(name, value, attributes) {
157
+ if (!this.metricsClient) {
158
+ console.warn('DiagnosticsMetrics not initialized. Call initialize() first.');
159
+ return;
160
+ }
161
+ // Get or create counter instrument
162
+ if (!this.counters.has(name)) {
163
+ const counter = this.metricsClient.createCounter(name, {
164
+ description: `Counter for ${name}`,
165
+ unit: '1',
166
+ });
167
+ this.counters.set(name, counter);
168
+ }
169
+ // Limit attributes to prevent high cardinality
170
+ const limitedAttributes = limitAttributes(attributes);
171
+ // Increment the counter
172
+ this.counters.get(name).add(value, limitedAttributes);
173
+ }
174
+ /**
175
+ * Set a gauge to a specific value (current state).
176
+ * Multiple gauges are stored by name since gauges represent different types of measurements.
177
+ *
178
+ * @param name Gauge name (e.g., 'cache_items_current', 'memory_usage_bytes')
179
+ * @param value Current value
180
+ * @param attributes Optional attributes for the gauge (max of MAX_ATTRIBUTES attributes, e.g., { cache: 'pages' })
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * diagnosticsMetrics.setGauge('cache_items_current', 1024, { cache: 'pages' })
185
+ * ```
186
+ */
187
+ setGauge(name, value, attributes) {
188
+ if (!this.metricsClient) {
189
+ console.warn('DiagnosticsMetrics not initialized. Call initialize() first.');
190
+ return;
191
+ }
192
+ // Get or create gauge instrument
193
+ if (!this.gauges.has(name)) {
194
+ const gauge = this.metricsClient.createGauge(name, {
195
+ description: `Gauge for ${name}`,
196
+ unit: '1',
197
+ });
198
+ this.gauges.set(name, gauge);
199
+ }
200
+ // Limit attributes to prevent high cardinality
201
+ const limitedAttributes = limitAttributes(attributes);
202
+ // Set the gauge value
203
+ this.gauges.get(name).set(value, limitedAttributes);
204
+ }
205
+ }
206
+ exports.DiagnosticsMetrics = DiagnosticsMetrics;
@@ -1,10 +1,12 @@
1
1
  import { MetricsAccumulator } from '../metrics/MetricsAccumulator';
2
+ import { DiagnosticsMetrics } from '../metrics/DiagnosticsMetrics';
2
3
  export declare const startApp: () => Promise<void>;
3
4
  export { appPath } from './loaders';
4
5
  declare global {
5
6
  namespace NodeJS {
6
7
  interface Global {
7
8
  metrics: MetricsAccumulator;
9
+ diagnosticsMetrics: DiagnosticsMetrics;
8
10
  }
9
11
  }
10
12
  }
@@ -31,12 +31,17 @@ const telemetry_1 = require("./telemetry");
31
31
  const cluster_1 = __importDefault(require("cluster"));
32
32
  const constants_1 = require("../constants");
33
33
  const MetricsAccumulator_1 = require("../metrics/MetricsAccumulator");
34
+ const DiagnosticsMetrics_1 = require("../metrics/DiagnosticsMetrics");
34
35
  const loaders_1 = require("./loaders");
35
36
  const logger_1 = require("./logger");
36
37
  const startApp = async () => {
37
38
  await (0, telemetry_1.initializeTelemetry)();
38
- // Initialize global.metrics for both master and worker processes
39
+ // Initialize both legacy and new metrics systems independently
40
+ // Legacy: MetricsAccumulator for backward compatibility
41
+ // External apps and internal code can continue using this during migration
39
42
  global.metrics = new MetricsAccumulator_1.MetricsAccumulator();
43
+ // New: DiagnosticsMetrics for new diagnostics-based instrumentation
44
+ global.diagnosticsMetrics = new DiagnosticsMetrics_1.DiagnosticsMetrics();
40
45
  const serviceJSON = (0, loaders_1.getServiceJSON)();
41
46
  try {
42
47
  // if it is a master process then call setting up worker process
@@ -21,11 +21,28 @@ class Metric extends graphql_tools_1.SchemaDirectiveVisitor {
21
21
  result = error;
22
22
  failedToResolve = true;
23
23
  }
24
- ctx.graphql.status = failedToResolve ? 'error' : 'success';
24
+ const status = failedToResolve ? 'error' : 'success';
25
+ ctx.graphql.status = status;
25
26
  const payload = {
26
- [ctx.graphql.status]: 1,
27
+ [status]: 1,
27
28
  };
29
+ // Legacy metrics (backward compatibility)
28
30
  metrics.batch(`graphql-metric-${name}`, failedToResolve ? undefined : ellapsed, payload);
31
+ // New diagnostics metrics with stable names and attributes
32
+ if (global.diagnosticsMetrics) {
33
+ const attributes = {
34
+ component: 'graphql',
35
+ field_name: name,
36
+ status,
37
+ };
38
+ // Record latency histogram (record all requests, not just successful ones)
39
+ global.diagnosticsMetrics.recordLatency(ellapsed, attributes);
40
+ // Increment counter (status is an attribute, not in metric name)
41
+ global.diagnosticsMetrics.incrementCounter('graphql_field_requests_total', 1, attributes);
42
+ }
43
+ else {
44
+ console.warn('DiagnosticsMetrics not available. GraphQL field metrics not reported.');
45
+ }
29
46
  if (failedToResolve) {
30
47
  throw result;
31
48
  }
@@ -22,20 +22,56 @@ class IncomingRequestStats {
22
22
  }
23
23
  }
24
24
  exports.incomingRequestStats = new IncomingRequestStats();
25
- const requestClosed = () => {
25
+ const requestClosed = (ctx) => () => {
26
26
  exports.incomingRequestStats.closed++;
27
+ // Report to diagnostics metrics (cumulative counter)
28
+ const { status: statusCode, vtex: { route: { id, type } } } = ctx;
29
+ if (global.diagnosticsMetrics) {
30
+ global.diagnosticsMetrics.incrementCounter('http_server_requests_closed_total', 1, {
31
+ route_id: id,
32
+ route_type: type,
33
+ status_code: statusCode,
34
+ });
35
+ }
36
+ else {
37
+ console.warn('DiagnosticsMetrics not available. Request closed metric not reported.');
38
+ }
27
39
  };
28
40
  const requestAborted = (ctx) => () => {
29
41
  exports.incomingRequestStats.aborted++;
42
+ // Report to diagnostics metrics (cumulative counter)
43
+ const { status: statusCode, vtex: { route: { id, type } } } = ctx;
44
+ if (global.diagnosticsMetrics) {
45
+ global.diagnosticsMetrics.incrementCounter('http_server_requests_aborted_total', 1, {
46
+ route_id: id,
47
+ route_type: type,
48
+ status_code: statusCode,
49
+ });
50
+ }
51
+ else {
52
+ console.warn('DiagnosticsMetrics not available. Request aborted metric not reported.');
53
+ }
30
54
  if (ctx.vtex.cancellation && ctx.vtex.cancellation.cancelable) {
31
55
  ctx.vtex.cancellation.source.cancel(exports.cancelMessage);
32
56
  ctx.vtex.cancellation.cancelled = true;
33
57
  }
34
58
  };
35
59
  async function trackIncomingRequestStats(ctx, next) {
36
- ctx.req.on('close', requestClosed);
60
+ ctx.req.on('close', requestClosed(ctx));
37
61
  ctx.req.on('aborted', requestAborted(ctx));
38
62
  exports.incomingRequestStats.total++;
63
+ // Report total requests to diagnostics metrics (cumulative counter)
64
+ const { status: statusCode, vtex: { route: { id, type } } } = ctx;
65
+ if (global.diagnosticsMetrics) {
66
+ global.diagnosticsMetrics.incrementCounter('http_server_requests_total', 1, {
67
+ route_id: id,
68
+ route_type: type,
69
+ status_code: statusCode,
70
+ });
71
+ }
72
+ else {
73
+ console.warn('DiagnosticsMetrics not available. Request total metric not reported.');
74
+ }
39
75
  await next();
40
76
  }
41
77
  exports.trackIncomingRequestStats = trackIncomingRequestStats;
@@ -35,12 +35,30 @@ const logBillingInfo = ({ account, workspace, production, route: { id, type } },
35
35
  async function timings(ctx, next) {
36
36
  // Errors will be caught by the next middleware so we don't have to catch.
37
37
  await next();
38
- const { status: statusCode, vtex: { route: { id } }, timings: { total }, vtex } = ctx;
38
+ const { status: statusCode, vtex: { route: { id, type } }, timings: { total }, vtex } = ctx;
39
39
  const totalMillis = (0, time_1.hrToMillis)(total);
40
40
  console.log(log(ctx, totalMillis));
41
41
  console.log(logBillingInfo(vtex, totalMillis));
42
42
  const status = (0, status_1.statusLabel)(statusCode);
43
+ // Legacy metrics (backward compatibility)
43
44
  // Only batch successful responses so metrics don't consider errors
44
45
  metrics.batch(`http-handler-${id}`, status === 'success' ? total : undefined, { [status]: 1 });
46
+ // New diagnostics metrics with stable names and attributes
47
+ if (global.diagnosticsMetrics) {
48
+ const attributes = {
49
+ component: 'http-handler',
50
+ route_id: id,
51
+ route_type: type,
52
+ status_code: statusCode,
53
+ status,
54
+ };
55
+ // Record latency histogram (record all requests, not just successful ones)
56
+ global.diagnosticsMetrics.recordLatency(total, attributes);
57
+ // Increment counter (status is an attribute, not in metric name)
58
+ global.diagnosticsMetrics.incrementCounter('http_handler_requests_total', 1, attributes);
59
+ }
60
+ else {
61
+ console.warn('DiagnosticsMetrics not available. HTTP handler metrics not reported.');
62
+ }
45
63
  }
46
64
  exports.timings = timings;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.broadcastStatusTrack = exports.trackStatus = exports.statusTrackHandler = exports.isStatusTrackBroadcast = exports.isStatusTrack = void 0;
7
7
  const cluster_1 = __importDefault(require("cluster"));
8
8
  const constants_1 = require("../../../constants");
9
+ const HttpAgentSingleton_1 = require("../../../HttpClient/middlewares/request/HttpAgentSingleton");
9
10
  const BROADCAST_STATUS_TRACK = 'broadcastStatusTrack';
10
11
  const STATUS_TRACK = 'statusTrack';
11
12
  const isStatusTrack = (message) => message === STATUS_TRACK;
@@ -23,6 +24,9 @@ const statusTrackHandler = async (ctx) => {
23
24
  };
24
25
  exports.statusTrackHandler = statusTrackHandler;
25
26
  const trackStatus = () => {
27
+ // Update diagnostics metrics (gauges for HTTP agent stats)
28
+ HttpAgentSingleton_1.HttpAgentSingleton.updateHttpAgentMetrics();
29
+ // Legacy status tracking (console.log export)
26
30
  global.metrics.statusTrack().forEach(status => {
27
31
  logStatus(status);
28
32
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vtex/api",
3
- "version": "7.2.4",
3
+ "version": "7.2.5",
4
4
  "description": "VTEX I/O API client",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",