@vtex/api 7.4.2 → 7.5.1-beta.0
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.
- package/CHANGELOG.md +23 -1
- package/lib/caches/DiskCache.d.ts +3 -1
- package/lib/caches/DiskCache.js +8 -4
- package/lib/caches/LRUCache.d.ts +3 -1
- package/lib/caches/LRUCache.js +16 -7
- package/lib/caches/LRUDiskCache.d.ts +3 -1
- package/lib/caches/LRUDiskCache.js +16 -7
- package/lib/caches/MultilayeredCache.d.ts +3 -2
- package/lib/caches/MultilayeredCache.js +11 -8
- package/lib/caches/typings.d.ts +8 -0
- package/lib/constants.d.ts +4 -0
- package/lib/constants.js +6 -2
- package/lib/metrics/DiagnosticsMetrics.d.ts +19 -3
- package/lib/metrics/DiagnosticsMetrics.js +173 -1
- package/lib/service/metrics/otelRequestMetricsMiddleware.js +7 -4
- package/lib/service/metrics/requestHandlerLabel.d.ts +26 -0
- package/lib/service/metrics/requestHandlerLabel.js +30 -0
- package/lib/service/metrics/requestMetricsMiddleware.js +5 -4
- package/lib/service/telemetry/client.js +2 -0
- package/lib/service/telemetry/resourceAttributes.d.ts +1 -0
- package/lib/service/telemetry/resourceAttributes.js +21 -0
- package/lib/service/worker/runtime/statusTrack.js +4 -1
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,29 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [7.5.0]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Kubernetes cluster identity and role (`VTEX_CLUSTER_ID` → `cluster_id`,
|
|
13
|
+
`VTEX_CLUSTER_ROLE` → `cluster_role`) as shared diagnostics resource
|
|
14
|
+
attributes, applied centrally to diagnostics metrics and logs. Values are
|
|
15
|
+
trimmed and omitted independently when empty, enabling cross-cluster
|
|
16
|
+
dashboards and incident isolation without consuming per-call custom
|
|
17
|
+
attribute limits.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- `runtime_http_*` metrics no longer emit samples without a `handler` label. Requests
|
|
22
|
+
that never reach a named handler (unmatched paths, replica-level rate limit
|
|
23
|
+
rejections, errors before the route pipeline) were counted with
|
|
24
|
+
`handler: undefined`; Node's cluster IPC serializes worker registries as JSON, which
|
|
25
|
+
drops `undefined` values, so the aggregated `/metrics` exposed a second, unnamed
|
|
26
|
+
series that Prometheus reads as `handler=""`. Those requests are now labelled
|
|
27
|
+
`handler="undefined"` — the same value prom-client rendered locally before cluster
|
|
28
|
+
aggregation — keeping dashboards and alerts that filter on it working.
|
|
29
|
+
- `/_status` requests are now reported as `handler="builtin:status-track"`, matching
|
|
30
|
+
the other builtin handlers, instead of falling into the unnamed bucket.
|
|
9
31
|
|
|
10
32
|
## [7.4.0] - 2026-06-22
|
|
11
33
|
### Changed
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CacheLayer } from './CacheLayer';
|
|
2
|
-
import { DiskStats } from './typings';
|
|
2
|
+
import { CumulativeStats, DiskStats } from './typings';
|
|
3
3
|
import { outputJSON, readJSON } from 'fs-extra';
|
|
4
4
|
export declare class DiskCache<V> implements CacheLayer<string, V> {
|
|
5
5
|
private cachePath;
|
|
@@ -8,9 +8,11 @@ export declare class DiskCache<V> implements CacheLayer<string, V> {
|
|
|
8
8
|
private hits;
|
|
9
9
|
private total;
|
|
10
10
|
private lock;
|
|
11
|
+
private reported;
|
|
11
12
|
constructor(cachePath: string, readFile?: typeof readJSON, writeFile?: typeof outputJSON);
|
|
12
13
|
has: (key: string) => boolean;
|
|
13
14
|
getStats: (name?: string) => DiskStats;
|
|
15
|
+
getCumulativeStats: () => CumulativeStats;
|
|
14
16
|
get: (key: string) => Promise<V | void>;
|
|
15
17
|
set: (key: string, value: V) => Promise<boolean>;
|
|
16
18
|
private getPathKey;
|
package/lib/caches/DiskCache.js
CHANGED
|
@@ -14,20 +14,24 @@ class DiskCache {
|
|
|
14
14
|
this.writeFile = writeFile;
|
|
15
15
|
this.hits = 0;
|
|
16
16
|
this.total = 0;
|
|
17
|
+
this.reported = { hits: 0, total: 0 };
|
|
17
18
|
this.has = (key) => {
|
|
18
19
|
const pathKey = this.getPathKey(key);
|
|
19
20
|
return (0, fs_extra_1.pathExistsSync)(pathKey);
|
|
20
21
|
};
|
|
21
22
|
this.getStats = (name = 'disk-cache') => {
|
|
22
23
|
const stats = {
|
|
23
|
-
hits: this.hits,
|
|
24
|
+
hits: this.hits - this.reported.hits,
|
|
24
25
|
name,
|
|
25
|
-
total: this.total,
|
|
26
|
+
total: this.total - this.reported.total,
|
|
26
27
|
};
|
|
27
|
-
this.
|
|
28
|
-
this.total = 0;
|
|
28
|
+
this.reported = { hits: this.hits, total: this.total };
|
|
29
29
|
return stats;
|
|
30
30
|
};
|
|
31
|
+
this.getCumulativeStats = () => ({
|
|
32
|
+
hits: this.hits,
|
|
33
|
+
total: this.total,
|
|
34
|
+
});
|
|
31
35
|
this.get = async (key) => {
|
|
32
36
|
const pathKey = this.getPathKey(key);
|
|
33
37
|
this.total += 1;
|
package/lib/caches/LRUCache.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import LRU from 'lru-cache';
|
|
2
2
|
import { CacheLayer } from './CacheLayer';
|
|
3
|
-
import { FetchResult, LRUStats } from './typings';
|
|
3
|
+
import { CumulativeStats, FetchResult, LRUStats } from './typings';
|
|
4
4
|
export declare class LRUCache<K, V> implements CacheLayer<K, V> {
|
|
5
5
|
private multilayer;
|
|
6
6
|
private storage;
|
|
7
7
|
private hits;
|
|
8
8
|
private total;
|
|
9
9
|
private disposed;
|
|
10
|
+
private reported;
|
|
10
11
|
constructor(options: LRU.Options<K, V>);
|
|
11
12
|
get: (key: K) => V | void;
|
|
12
13
|
getOrSet: (key: K, fetcher?: () => Promise<FetchResult<V>>) => Promise<V | void>;
|
|
13
14
|
set: (key: K, value: V, maxAge?: number) => boolean;
|
|
14
15
|
has: (key: K) => boolean;
|
|
15
16
|
getStats: (name?: string) => LRUStats;
|
|
17
|
+
getCumulativeStats: () => CumulativeStats;
|
|
16
18
|
}
|
package/lib/caches/LRUCache.js
CHANGED
|
@@ -20,24 +20,33 @@ class LRUCache {
|
|
|
20
20
|
this.set = (key, value, maxAge) => this.storage.set(key, value, maxAge);
|
|
21
21
|
this.has = (key) => this.storage.has(key);
|
|
22
22
|
this.getStats = (name = 'lru-cache') => {
|
|
23
|
+
const hits = this.hits - this.reported.hits;
|
|
24
|
+
const total = this.total - this.reported.total;
|
|
23
25
|
const stats = {
|
|
24
|
-
disposedItems: this.disposed,
|
|
25
|
-
hitRate:
|
|
26
|
-
hits
|
|
26
|
+
disposedItems: this.disposed - this.reported.disposed,
|
|
27
|
+
hitRate: total > 0 ? hits / total : undefined,
|
|
28
|
+
hits,
|
|
27
29
|
itemCount: this.storage.itemCount,
|
|
28
30
|
length: this.storage.length,
|
|
29
31
|
max: this.storage.max,
|
|
30
32
|
name,
|
|
31
|
-
total
|
|
33
|
+
total,
|
|
32
34
|
};
|
|
33
|
-
this.
|
|
34
|
-
this.total = 0;
|
|
35
|
-
this.disposed = 0;
|
|
35
|
+
this.reported = { disposed: this.disposed, hits: this.hits, total: this.total };
|
|
36
36
|
return stats;
|
|
37
37
|
};
|
|
38
|
+
this.getCumulativeStats = () => ({
|
|
39
|
+
disposedItems: this.disposed,
|
|
40
|
+
hits: this.hits,
|
|
41
|
+
itemCount: this.storage.itemCount,
|
|
42
|
+
length: this.storage.length,
|
|
43
|
+
max: this.storage.max,
|
|
44
|
+
total: this.total,
|
|
45
|
+
});
|
|
38
46
|
this.hits = 0;
|
|
39
47
|
this.total = 0;
|
|
40
48
|
this.disposed = 0;
|
|
49
|
+
this.reported = { disposed: 0, hits: 0, total: 0 };
|
|
41
50
|
this.storage = new lru_cache_1.default({
|
|
42
51
|
...options,
|
|
43
52
|
dispose: () => this.disposed += 1,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CacheLayer } from './CacheLayer';
|
|
2
|
-
import { LRUDiskCacheOptions, LRUStats } from './typings';
|
|
2
|
+
import { CumulativeStats, LRUDiskCacheOptions, LRUStats } from './typings';
|
|
3
3
|
import { outputJSON, readJSON } from 'fs-extra';
|
|
4
4
|
export declare class LRUDiskCache<V> implements CacheLayer<string, V> {
|
|
5
5
|
private cachePath;
|
|
@@ -11,9 +11,11 @@ export declare class LRUDiskCache<V> implements CacheLayer<string, V> {
|
|
|
11
11
|
private total;
|
|
12
12
|
private lruStorage;
|
|
13
13
|
private keyToBeDeleted;
|
|
14
|
+
private reported;
|
|
14
15
|
constructor(cachePath: string, options: LRUDiskCacheOptions, readFile?: typeof readJSON, writeFile?: typeof outputJSON);
|
|
15
16
|
has: (key: string) => boolean;
|
|
16
17
|
getStats: (name?: string) => LRUStats;
|
|
18
|
+
getCumulativeStats: () => CumulativeStats;
|
|
17
19
|
get: (key: string) => Promise<V | void>;
|
|
18
20
|
set: (key: string, value: V, maxAge?: number) => Promise<boolean>;
|
|
19
21
|
private getPathKey;
|
|
@@ -15,23 +15,32 @@ class LRUDiskCache {
|
|
|
15
15
|
this.writeFile = writeFile;
|
|
16
16
|
this.hits = 0;
|
|
17
17
|
this.total = 0;
|
|
18
|
+
this.reported = { disposed: 0, hits: 0, total: 0 };
|
|
18
19
|
this.has = (key) => this.lruStorage.has(key);
|
|
19
20
|
this.getStats = (name = 'disk-lru-cache') => {
|
|
21
|
+
const hits = this.hits - this.reported.hits;
|
|
22
|
+
const total = this.total - this.reported.total;
|
|
20
23
|
const stats = {
|
|
21
|
-
disposedItems: this.disposed,
|
|
22
|
-
hitRate:
|
|
23
|
-
hits
|
|
24
|
+
disposedItems: this.disposed - this.reported.disposed,
|
|
25
|
+
hitRate: total > 0 ? hits / total : undefined,
|
|
26
|
+
hits,
|
|
24
27
|
itemCount: this.lruStorage.itemCount,
|
|
25
28
|
length: this.lruStorage.length,
|
|
26
29
|
max: this.lruStorage.max,
|
|
27
30
|
name,
|
|
28
|
-
total
|
|
31
|
+
total,
|
|
29
32
|
};
|
|
30
|
-
this.
|
|
31
|
-
this.total = 0;
|
|
32
|
-
this.disposed = 0;
|
|
33
|
+
this.reported = { disposed: this.disposed, hits: this.hits, total: this.total };
|
|
33
34
|
return stats;
|
|
34
35
|
};
|
|
36
|
+
this.getCumulativeStats = () => ({
|
|
37
|
+
disposedItems: this.disposed,
|
|
38
|
+
hits: this.hits,
|
|
39
|
+
itemCount: this.lruStorage.itemCount,
|
|
40
|
+
length: this.lruStorage.length,
|
|
41
|
+
max: this.lruStorage.max,
|
|
42
|
+
total: this.total,
|
|
43
|
+
});
|
|
35
44
|
this.get = async (key) => {
|
|
36
45
|
const timeOfDeath = this.lruStorage.get(key);
|
|
37
46
|
this.total += 1;
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { CacheLayer } from './CacheLayer';
|
|
2
|
-
import { FetchResult, MultilayerStats } from './typings';
|
|
2
|
+
import { CumulativeStats, FetchResult, MultilayerStats } from './typings';
|
|
3
3
|
export declare class MultilayeredCache<K, V> implements CacheLayer<K, V> {
|
|
4
4
|
private caches;
|
|
5
5
|
private hits;
|
|
6
6
|
private total;
|
|
7
|
+
private reported;
|
|
7
8
|
constructor(caches: Array<CacheLayer<K, V>>);
|
|
8
9
|
get: (key: K, fetcher?: () => Promise<FetchResult<V>>) => Promise<V | void>;
|
|
9
10
|
set: (key: K, value: V, maxAge?: number) => Promise<boolean>;
|
|
10
11
|
has: (key: K) => Promise<boolean>;
|
|
11
12
|
getStats: (name?: string) => MultilayerStats;
|
|
13
|
+
getCumulativeStats: () => CumulativeStats;
|
|
12
14
|
private findIndex;
|
|
13
|
-
private resetCounters;
|
|
14
15
|
}
|
|
@@ -7,6 +7,7 @@ class MultilayeredCache {
|
|
|
7
7
|
this.caches = caches;
|
|
8
8
|
this.hits = 0;
|
|
9
9
|
this.total = 0;
|
|
10
|
+
this.reported = { hits: 0, total: 0 };
|
|
10
11
|
this.get = async (key, fetcher) => {
|
|
11
12
|
let value = undefined;
|
|
12
13
|
let maxAge;
|
|
@@ -40,15 +41,21 @@ class MultilayeredCache {
|
|
|
40
41
|
return (0, ramda_1.any)(item => item, hasList);
|
|
41
42
|
};
|
|
42
43
|
this.getStats = (name = 'multilayred-cache') => {
|
|
44
|
+
const hits = this.hits - this.reported.hits;
|
|
45
|
+
const total = this.total - this.reported.total;
|
|
43
46
|
const multilayerStats = {
|
|
44
|
-
hitRate:
|
|
45
|
-
hits
|
|
47
|
+
hitRate: total > 0 ? hits / total : undefined,
|
|
48
|
+
hits,
|
|
46
49
|
name,
|
|
47
|
-
total
|
|
50
|
+
total,
|
|
48
51
|
};
|
|
49
|
-
this.
|
|
52
|
+
this.reported = { hits: this.hits, total: this.total };
|
|
50
53
|
return multilayerStats;
|
|
51
54
|
};
|
|
55
|
+
this.getCumulativeStats = () => ({
|
|
56
|
+
hits: this.hits,
|
|
57
|
+
total: this.total,
|
|
58
|
+
});
|
|
52
59
|
this.findIndex = async (func, array) => {
|
|
53
60
|
this.total += 1;
|
|
54
61
|
for (let index = 0; index < array.length; index++) {
|
|
@@ -61,9 +68,5 @@ class MultilayeredCache {
|
|
|
61
68
|
return -1;
|
|
62
69
|
};
|
|
63
70
|
}
|
|
64
|
-
resetCounters() {
|
|
65
|
-
this.hits = 0;
|
|
66
|
-
this.total = 0;
|
|
67
|
-
}
|
|
68
71
|
}
|
|
69
72
|
exports.MultilayeredCache = MultilayeredCache;
|
package/lib/caches/typings.d.ts
CHANGED
|
@@ -17,6 +17,14 @@ export type LRUStats = {
|
|
|
17
17
|
name: string;
|
|
18
18
|
total: number;
|
|
19
19
|
};
|
|
20
|
+
export type CumulativeStats = {
|
|
21
|
+
hits: number;
|
|
22
|
+
total: number;
|
|
23
|
+
disposedItems?: number;
|
|
24
|
+
itemCount?: number;
|
|
25
|
+
length?: number;
|
|
26
|
+
max?: number;
|
|
27
|
+
};
|
|
20
28
|
export type MultilayerStats = {
|
|
21
29
|
hitRate: number | undefined;
|
|
22
30
|
hits: number;
|
package/lib/constants.d.ts
CHANGED
|
@@ -45,6 +45,8 @@ export declare const AttributeKeys: {
|
|
|
45
45
|
VTEX_IO_WORKSPACE_TYPE: "vtex_io.workspace.type";
|
|
46
46
|
VTEX_IO_APP_ID: "vtex_io.app.id";
|
|
47
47
|
VTEX_IO_APP_AUTHOR_TYPE: "vtex_io.app.author_type";
|
|
48
|
+
VTEX_IO_CLUSTER_ID: "vtex_io.cluster.id";
|
|
49
|
+
VTEX_IO_CLUSTER_ROLE: "vtex_io.cluster.role";
|
|
48
50
|
};
|
|
49
51
|
/** @deprecated Use HeaderKeys.CACHE_CONTROL instead */
|
|
50
52
|
export declare const CACHE_CONTROL_HEADER: string;
|
|
@@ -114,6 +116,8 @@ export declare const HTTP_SERVER_PORT = 5050;
|
|
|
114
116
|
export declare const MAX_WORKERS = 4;
|
|
115
117
|
export declare const LINKED: boolean;
|
|
116
118
|
export declare const REGION: string;
|
|
119
|
+
export declare const CLUSTER_ID: string;
|
|
120
|
+
export declare const CLUSTER_ROLE: string;
|
|
117
121
|
export declare const PUBLIC_ENDPOINT: string;
|
|
118
122
|
export declare const APP: {
|
|
119
123
|
ID: string;
|
package/lib/constants.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.DIAGNOSTICS_TELEMETRY_ENABLED = exports.DK_APP_ID = exports.OTEL_EXPORTER_OTLP_ENDPOINT = exports.METRIC_CLIENT_INIT_TIMEOUT_MS = void 0;
|
|
3
|
+
exports.INSPECT_DEBUGGER_PORT = exports.PRODUCTION = exports.WORKSPACE = exports.ACCOUNT = exports.NODE_ENV = exports.APP = exports.PUBLIC_ENDPOINT = exports.CLUSTER_ROLE = exports.CLUSTER_ID = 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 = exports.METRIC_CLIENT_INIT_TIMEOUT_MS = exports.LOG_CLIENT_INIT_TIMEOUT_MS = exports.cancellableMethods = 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
|
|
@@ -55,6 +55,8 @@ exports.AttributeKeys = {
|
|
|
55
55
|
VTEX_IO_WORKSPACE_TYPE: diagnostics_semconv_1.ATTR_VTEX_IO_WORKSPACE_TYPE,
|
|
56
56
|
VTEX_IO_APP_ID: diagnostics_semconv_1.ATTR_VTEX_IO_APP_ID,
|
|
57
57
|
VTEX_IO_APP_AUTHOR_TYPE: diagnostics_semconv_1.ATTR_VTEX_IO_APP_AUTHOR_TYPE,
|
|
58
|
+
VTEX_IO_CLUSTER_ID: diagnostics_semconv_1.ATTR_VTEX_IO_CLUSTER_ID,
|
|
59
|
+
VTEX_IO_CLUSTER_ROLE: diagnostics_semconv_1.ATTR_VTEX_IO_CLUSTER_ROLE,
|
|
58
60
|
};
|
|
59
61
|
/** @deprecated Use HeaderKeys.CACHE_CONTROL instead */
|
|
60
62
|
exports.CACHE_CONTROL_HEADER = exports.HeaderKeys.CACHE_CONTROL;
|
|
@@ -123,6 +125,8 @@ exports.HTTP_SERVER_PORT = 5050;
|
|
|
123
125
|
exports.MAX_WORKERS = 4;
|
|
124
126
|
exports.LINKED = !!process.env.VTEX_APP_LINK;
|
|
125
127
|
exports.REGION = process.env.VTEX_REGION;
|
|
128
|
+
exports.CLUSTER_ID = process.env.VTEX_CLUSTER_ID;
|
|
129
|
+
exports.CLUSTER_ROLE = process.env.VTEX_CLUSTER_ROLE;
|
|
126
130
|
exports.PUBLIC_ENDPOINT = process.env.VTEX_PUBLIC_ENDPOINT || 'myvtex.com';
|
|
127
131
|
exports.APP = {
|
|
128
132
|
ID: process.env.VTEX_APP_ID,
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import { Attributes } from '@opentelemetry/api';
|
|
1
|
+
import { Attributes, MetricOptions, ObservableCallback } from '@opentelemetry/api';
|
|
2
|
+
import { CumulativeStats } from '../caches/typings';
|
|
3
|
+
export interface TrackedCache {
|
|
4
|
+
getCumulativeStats(): CumulativeStats;
|
|
5
|
+
}
|
|
2
6
|
/**
|
|
3
7
|
* DiagnosticsMetrics provides a high-level API for recording metrics using
|
|
4
8
|
* the @vtex/diagnostics-nodejs library. It completely abstracts instrument
|
|
@@ -56,8 +60,12 @@ export declare class DiagnosticsMetrics {
|
|
|
56
60
|
private metricsClient;
|
|
57
61
|
private clientInitPromise;
|
|
58
62
|
private latencyHistogram;
|
|
59
|
-
private counters;
|
|
60
|
-
private gauges;
|
|
63
|
+
private readonly counters;
|
|
64
|
+
private readonly gauges;
|
|
65
|
+
private readonly observableRegistrations;
|
|
66
|
+
private readonly observableInstruments;
|
|
67
|
+
private readonly cacheRegistry;
|
|
68
|
+
private cacheInstruments;
|
|
61
69
|
constructor();
|
|
62
70
|
/**
|
|
63
71
|
* Initialize the metrics client with timeout handling.
|
|
@@ -186,4 +194,12 @@ export declare class DiagnosticsMetrics {
|
|
|
186
194
|
* ```
|
|
187
195
|
*/
|
|
188
196
|
setGauge(name: string, value: number, attributes?: Attributes): void;
|
|
197
|
+
registerObservableGauge(name: string, observe: ObservableCallback, options?: MetricOptions): () => void;
|
|
198
|
+
registerObservableCounter(name: string, observe: ObservableCallback, options?: MetricOptions): () => void;
|
|
199
|
+
private registerObservable;
|
|
200
|
+
private syncObservables;
|
|
201
|
+
private flushPendingObservables;
|
|
202
|
+
trackCache(name: string, cacheInstance: TrackedCache): () => void;
|
|
203
|
+
private ensureCacheInstruments;
|
|
204
|
+
private observeCaches;
|
|
189
205
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DiagnosticsMetrics = void 0;
|
|
4
4
|
const api_1 = require("@opentelemetry/api");
|
|
5
|
-
const client_1 = require("../service/metrics/client");
|
|
6
5
|
const constants_1 = require("../constants");
|
|
6
|
+
const client_1 = require("../service/metrics/client");
|
|
7
7
|
/**
|
|
8
8
|
* Maximum number of custom attributes allowed per metric call to control cardinality.
|
|
9
9
|
* This limit applies only to custom attributes provided by callers (VTEX IO Apps).
|
|
@@ -17,6 +17,11 @@ const MAX_CUSTOM_ATTRIBUTES = 7;
|
|
|
17
17
|
* These attributes are automatically merged with custom attributes in all metric methods.
|
|
18
18
|
*/
|
|
19
19
|
const BASE_ATTRIBUTES_KEY = (0, api_1.createContextKey)('vtex.metrics.baseAttributes');
|
|
20
|
+
// trackCache() metric names — one shared instrument set, differentiated by `cache`.
|
|
21
|
+
const CACHE_OPERATIONS_METRIC = 'io_app_cache_operations_total';
|
|
22
|
+
const CACHE_ITEMS_METRIC = 'io_app_cache_items_current';
|
|
23
|
+
const CACHE_CAPACITY_METRIC = 'io_app_cache_capacity';
|
|
24
|
+
const CACHE_DISPOSED_METRIC = 'io_app_cache_disposed_total';
|
|
20
25
|
/**
|
|
21
26
|
* Converts an hrtime tuple [seconds, nanoseconds] to milliseconds.
|
|
22
27
|
*/
|
|
@@ -46,6 +51,16 @@ function limitCustomAttributes(customAttributes) {
|
|
|
46
51
|
}
|
|
47
52
|
return Object.fromEntries(entries.slice(0, MAX_CUSTOM_ATTRIBUTES));
|
|
48
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Applies the same cardinality limit the push-based methods use to whatever an
|
|
56
|
+
* observable callback reports. Observables run outside any request, so there are no
|
|
57
|
+
* base attributes to merge — every attribute here is a custom one.
|
|
58
|
+
*/
|
|
59
|
+
function limitObservableResult(result) {
|
|
60
|
+
return {
|
|
61
|
+
observe: (value, attributes) => result.observe(value, limitCustomAttributes(attributes)),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
49
64
|
/**
|
|
50
65
|
* DiagnosticsMetrics provides a high-level API for recording metrics using
|
|
51
66
|
* the @vtex/diagnostics-nodejs library. It completely abstracts instrument
|
|
@@ -103,6 +118,9 @@ class DiagnosticsMetrics {
|
|
|
103
118
|
constructor() {
|
|
104
119
|
this.counters = new Map();
|
|
105
120
|
this.gauges = new Map();
|
|
121
|
+
this.observableRegistrations = { gauge: new Map(), counter: new Map() };
|
|
122
|
+
this.observableInstruments = { gauge: new Map(), counter: new Map() };
|
|
123
|
+
this.cacheRegistry = new Map();
|
|
106
124
|
this.initMetricClient();
|
|
107
125
|
}
|
|
108
126
|
/**
|
|
@@ -124,6 +142,9 @@ class DiagnosticsMetrics {
|
|
|
124
142
|
]);
|
|
125
143
|
// Create the single latency histogram after client is ready
|
|
126
144
|
this.createLatencyHistogram();
|
|
145
|
+
// Attach any observable registrations made before the client was ready.
|
|
146
|
+
// No-op if there are none.
|
|
147
|
+
this.flushPendingObservables();
|
|
127
148
|
return this.metricsClient;
|
|
128
149
|
}
|
|
129
150
|
catch (error) {
|
|
@@ -338,5 +359,156 @@ class DiagnosticsMetrics {
|
|
|
338
359
|
// Set the gauge value
|
|
339
360
|
this.gauges.get(name).set(value, mergedAttributes);
|
|
340
361
|
}
|
|
362
|
+
// Registers (or replaces) a pull-based gauge: OTel calls `observe` on its own
|
|
363
|
+
// schedule, with `result.observe(value, attributes?)`. No base-attribute merging —
|
|
364
|
+
// there's no request in progress when this runs — but the attributes reported are
|
|
365
|
+
// held to the same MAX_CUSTOM_ATTRIBUTES limit as the push-based methods.
|
|
366
|
+
registerObservableGauge(name, observe, options) {
|
|
367
|
+
return this.registerObservable('gauge', name, observe, options);
|
|
368
|
+
}
|
|
369
|
+
// Same as registerObservableGauge, but `observe` reports the cumulative total —
|
|
370
|
+
// the SDK derives the delta itself.
|
|
371
|
+
registerObservableCounter(name, observe, options) {
|
|
372
|
+
return this.registerObservable('counter', name, observe, options);
|
|
373
|
+
}
|
|
374
|
+
registerObservable(kind, name, observe, options) {
|
|
375
|
+
// The same name registered as both kinds produces two same-named streams of
|
|
376
|
+
// different types in one meter, which the SDK accepts silently and the collector
|
|
377
|
+
// then rejects. Refuse the second one instead of publishing a broken metric.
|
|
378
|
+
const otherKind = kind === 'gauge' ? 'counter' : 'gauge';
|
|
379
|
+
if (this.observableRegistrations[otherKind].has(name)) {
|
|
380
|
+
console.error(`DiagnosticsMetrics: '${name}' is already registered as an observable ${otherKind}; ` +
|
|
381
|
+
`ignoring the ${kind} registration. Pick a distinct metric name.`);
|
|
382
|
+
return () => undefined;
|
|
383
|
+
}
|
|
384
|
+
this.observableRegistrations[kind].set(name, { observe, options });
|
|
385
|
+
this.syncObservables(kind);
|
|
386
|
+
return () => {
|
|
387
|
+
var _a, _b;
|
|
388
|
+
// A later registration for the same name owns the instrument now; disposing
|
|
389
|
+
// this one must not tear that down.
|
|
390
|
+
if (((_a = this.observableRegistrations[kind].get(name)) === null || _a === void 0 ? void 0 : _a.observe) !== observe) {
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
this.observableRegistrations[kind].delete(name);
|
|
394
|
+
(_b = this.observableInstruments[kind].get(name)) === null || _b === void 0 ? void 0 : _b.remove();
|
|
395
|
+
this.observableInstruments[kind].delete(name);
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
// Attaches every `kind` registration to its instrument. A no-op if there's nothing
|
|
399
|
+
// registered or the client isn't ready — safe to call anytime, including from
|
|
400
|
+
// flushPendingObservables() once the client becomes ready.
|
|
401
|
+
//
|
|
402
|
+
// One instrument per name, carrying one callback for its whole life: the callback
|
|
403
|
+
// is a trampoline that reads whatever registration is current, so replacing the
|
|
404
|
+
// app's function is a map update and never touches the SDK. Re-creating the
|
|
405
|
+
// instrument instead would leave the meter holding a stream the collector sees as
|
|
406
|
+
// a duplicate type for the same name.
|
|
407
|
+
syncObservables(kind) {
|
|
408
|
+
if (this.observableRegistrations[kind].size === 0) {
|
|
409
|
+
return; // skip the meter entirely if this kind is unused
|
|
410
|
+
}
|
|
411
|
+
const client = this.metricsClient;
|
|
412
|
+
if (!client) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
for (const [name, { options }] of this.observableRegistrations[kind]) {
|
|
416
|
+
if (this.observableInstruments[kind].has(name)) {
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const attached = (result) => {
|
|
420
|
+
var _a;
|
|
421
|
+
(_a = this.observableRegistrations[kind].get(name)) === null || _a === void 0 ? void 0 : _a.observe(limitObservableResult(result));
|
|
422
|
+
};
|
|
423
|
+
const instrument = kind === 'gauge'
|
|
424
|
+
? client.createObservableGauge(name, attached, options)
|
|
425
|
+
: client.createObservableCounter(name, attached, options);
|
|
426
|
+
this.observableInstruments[kind].set(name, instrument);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// Runs once the client is ready. No-op for an app that never calls
|
|
430
|
+
// registerObservableGauge/Counter/trackCache.
|
|
431
|
+
flushPendingObservables() {
|
|
432
|
+
this.syncObservables('gauge');
|
|
433
|
+
this.syncObservables('counter');
|
|
434
|
+
if (this.cacheRegistry.size > 0) {
|
|
435
|
+
this.ensureCacheInstruments();
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
// Replacement for the legacy MetricsAccumulator.trackCache() — same cache instances,
|
|
439
|
+
// see METRICS_CATALOG.md for the metrics emitted. Reads getCumulativeStats(), which
|
|
440
|
+
// has no side effects, so this can run alongside the legacy trackCache().
|
|
441
|
+
trackCache(name, cacheInstance) {
|
|
442
|
+
this.cacheRegistry.set(name, cacheInstance);
|
|
443
|
+
this.ensureCacheInstruments();
|
|
444
|
+
return () => {
|
|
445
|
+
this.cacheRegistry.delete(name);
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
ensureCacheInstruments() {
|
|
449
|
+
if (this.cacheInstruments) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const client = this.metricsClient;
|
|
453
|
+
if (!client) {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
const operations = client.createObservableCounter(CACHE_OPERATIONS_METRIC, undefined, {
|
|
457
|
+
description: 'Hit/miss operations for a VTEX IO app in-memory cache',
|
|
458
|
+
unit: '1',
|
|
459
|
+
});
|
|
460
|
+
const items = client.createObservableGauge(CACHE_ITEMS_METRIC, undefined, {
|
|
461
|
+
description: 'Current number of items held by a VTEX IO app cache',
|
|
462
|
+
unit: '1',
|
|
463
|
+
});
|
|
464
|
+
const capacity = client.createObservableGauge(CACHE_CAPACITY_METRIC, undefined, {
|
|
465
|
+
description: 'Capacity of a VTEX IO app cache, in the units that cache uses (item count, unless the LRU was built with a length function)',
|
|
466
|
+
unit: '1',
|
|
467
|
+
});
|
|
468
|
+
const disposed = client.createObservableCounter(CACHE_DISPOSED_METRIC, undefined, {
|
|
469
|
+
description: 'Items disposed (evicted) from a VTEX IO app cache',
|
|
470
|
+
unit: '1',
|
|
471
|
+
});
|
|
472
|
+
client.addBatchObservableCallback((result) => this.observeCaches(result), [operations.instrument, items.instrument, capacity.instrument, disposed.instrument]);
|
|
473
|
+
// These live for the process, so only the handles are kept: the wrappers exist
|
|
474
|
+
// to attach the batch callback above.
|
|
475
|
+
this.cacheInstruments = {
|
|
476
|
+
operations: operations.instrument,
|
|
477
|
+
items: items.instrument,
|
|
478
|
+
capacity: capacity.instrument,
|
|
479
|
+
disposed: disposed.instrument,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
// Reads each registered cache's cumulative counters straight into the instruments.
|
|
483
|
+
observeCaches(result) {
|
|
484
|
+
if (!this.cacheInstruments) {
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const { operations, items, capacity, disposed } = this.cacheInstruments;
|
|
488
|
+
for (const [name, cache] of this.cacheRegistry) {
|
|
489
|
+
let stats;
|
|
490
|
+
try {
|
|
491
|
+
stats = cache.getCumulativeStats();
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
console.error('DiagnosticsMetrics: failed to read stats for cache', name, error);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
const attributes = { cache: name };
|
|
498
|
+
if (typeof stats.hits === 'number' && typeof stats.total === 'number') {
|
|
499
|
+
result.observe(operations, stats.hits, { ...attributes, cache_state: 'hit' });
|
|
500
|
+
result.observe(operations, Math.max(stats.total - stats.hits, 0), { ...attributes, cache_state: 'miss' });
|
|
501
|
+
}
|
|
502
|
+
if (typeof stats.itemCount === 'number') {
|
|
503
|
+
result.observe(items, stats.itemCount, attributes);
|
|
504
|
+
}
|
|
505
|
+
if (typeof stats.max === 'number') {
|
|
506
|
+
result.observe(capacity, stats.max, attributes);
|
|
507
|
+
}
|
|
508
|
+
if (typeof stats.disposedItems === 'number') {
|
|
509
|
+
result.observe(disposed, stats.disposedItems, attributes);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
341
513
|
}
|
|
342
514
|
exports.DiagnosticsMetrics = DiagnosticsMetrics;
|
|
@@ -4,6 +4,7 @@ exports.addOtelRequestMetricsMiddleware = void 0;
|
|
|
4
4
|
const stream_1 = require("stream");
|
|
5
5
|
const utils_1 = require("../../utils");
|
|
6
6
|
const metrics_1 = require("./metrics");
|
|
7
|
+
const requestHandlerLabel_1 = require("./requestHandlerLabel");
|
|
7
8
|
const INSTRUMENTS_INITIALIZATION_TIMEOUT = 500;
|
|
8
9
|
const addOtelRequestMetricsMiddleware = () => {
|
|
9
10
|
let instruments;
|
|
@@ -31,7 +32,9 @@ const addOtelRequestMetricsMiddleware = () => {
|
|
|
31
32
|
instruments.concurrentRequests.add(1);
|
|
32
33
|
ctx.req.once('aborted', () => {
|
|
33
34
|
if (instruments) {
|
|
34
|
-
instruments.abortedRequests.add(1, {
|
|
35
|
+
instruments.abortedRequests.add(1, {
|
|
36
|
+
[metrics_1.RequestsMetricLabels.REQUEST_HANDLER]: (0, requestHandlerLabel_1.requestHandlerLabel)(ctx.requestHandlerName),
|
|
37
|
+
});
|
|
35
38
|
}
|
|
36
39
|
});
|
|
37
40
|
let responseClosed = false;
|
|
@@ -43,14 +46,14 @@ const addOtelRequestMetricsMiddleware = () => {
|
|
|
43
46
|
const responseLength = ctx.response.length;
|
|
44
47
|
if (responseLength && instruments) {
|
|
45
48
|
instruments.responseSizes.record(responseLength, {
|
|
46
|
-
[metrics_1.RequestsMetricLabels.REQUEST_HANDLER]: ctx.requestHandlerName,
|
|
49
|
+
[metrics_1.RequestsMetricLabels.REQUEST_HANDLER]: (0, requestHandlerLabel_1.requestHandlerLabel)(ctx.requestHandlerName),
|
|
47
50
|
[metrics_1.RequestsMetricLabels.STATUS_CODE]: ctx.response.status,
|
|
48
51
|
[metrics_1.RequestsMetricLabels.ACCOUNT_NAME]: ((_a = ctx.vtex) === null || _a === void 0 ? void 0 : _a.account) || 'unknown',
|
|
49
52
|
});
|
|
50
53
|
}
|
|
51
54
|
if (instruments) {
|
|
52
55
|
instruments.totalRequests.add(1, {
|
|
53
|
-
[metrics_1.RequestsMetricLabels.REQUEST_HANDLER]: ctx.requestHandlerName,
|
|
56
|
+
[metrics_1.RequestsMetricLabels.REQUEST_HANDLER]: (0, requestHandlerLabel_1.requestHandlerLabel)(ctx.requestHandlerName),
|
|
54
57
|
[metrics_1.RequestsMetricLabels.STATUS_CODE]: ctx.response.status,
|
|
55
58
|
[metrics_1.RequestsMetricLabels.ACCOUNT_NAME]: ((_b = ctx.vtex) === null || _b === void 0 ? void 0 : _b.account) || 'unknown',
|
|
56
59
|
});
|
|
@@ -59,7 +62,7 @@ const addOtelRequestMetricsMiddleware = () => {
|
|
|
59
62
|
var _a;
|
|
60
63
|
if (instruments) {
|
|
61
64
|
instruments.requestTimings.record((0, utils_1.hrToMillisFloat)(process.hrtime(start)), {
|
|
62
|
-
[metrics_1.RequestsMetricLabels.REQUEST_HANDLER]: ctx.requestHandlerName,
|
|
65
|
+
[metrics_1.RequestsMetricLabels.REQUEST_HANDLER]: (0, requestHandlerLabel_1.requestHandlerLabel)(ctx.requestHandlerName),
|
|
63
66
|
[metrics_1.RequestsMetricLabels.STATUS_CODE]: ctx.response.status,
|
|
64
67
|
[metrics_1.RequestsMetricLabels.ACCOUNT_NAME]: ((_a = ctx.vtex) === null || _a === void 0 ? void 0 : _a.account) || 'unknown',
|
|
65
68
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Label value reported for requests that never reached a named handler: unmatched
|
|
3
|
+
* paths (Koa answers its default 404), requests rejected before the route pipeline
|
|
4
|
+
* (replica-level rate limiter, errors in compress/recorder), and handlers that
|
|
5
|
+
* don't set `ctx.requestHandlerName`.
|
|
6
|
+
*
|
|
7
|
+
* The value must be a non-empty string. `ctx.requestHandlerName` is `undefined`
|
|
8
|
+
* for those requests, and Node's cluster IPC serializes each worker's registry as
|
|
9
|
+
* JSON, which drops properties whose value is `undefined`. The sample then reaches
|
|
10
|
+
* the aggregated `/metrics` with the `handler` label missing altogether, which
|
|
11
|
+
* Prometheus reads as `handler=""` — a distinct, unnamed series that shows up as
|
|
12
|
+
* an extra "Value" line in dashboards.
|
|
13
|
+
*
|
|
14
|
+
* `'undefined'` is deliberate rather than a nicer word: prom-client's local
|
|
15
|
+
* exposition already rendered `handler: undefined` as `handler="undefined"` before
|
|
16
|
+
* the cluster aggregation was introduced, so keeping that value makes the
|
|
17
|
+
* aggregated output match the historical series identity and keeps existing
|
|
18
|
+
* dashboards, filters and alerts (e.g. `handler!~"builtin:.*|undefined"`) working.
|
|
19
|
+
*/
|
|
20
|
+
export declare const UNNAMED_REQUEST_HANDLER = "undefined";
|
|
21
|
+
/**
|
|
22
|
+
* Resolves the `handler` label value for a request, falling back to
|
|
23
|
+
* {@link UNNAMED_REQUEST_HANDLER} when the pipeline never named the handler.
|
|
24
|
+
* Empty strings fall back too, so the label is never emitted empty.
|
|
25
|
+
*/
|
|
26
|
+
export declare const requestHandlerLabel: (requestHandlerName?: string) => string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requestHandlerLabel = exports.UNNAMED_REQUEST_HANDLER = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Label value reported for requests that never reached a named handler: unmatched
|
|
6
|
+
* paths (Koa answers its default 404), requests rejected before the route pipeline
|
|
7
|
+
* (replica-level rate limiter, errors in compress/recorder), and handlers that
|
|
8
|
+
* don't set `ctx.requestHandlerName`.
|
|
9
|
+
*
|
|
10
|
+
* The value must be a non-empty string. `ctx.requestHandlerName` is `undefined`
|
|
11
|
+
* for those requests, and Node's cluster IPC serializes each worker's registry as
|
|
12
|
+
* JSON, which drops properties whose value is `undefined`. The sample then reaches
|
|
13
|
+
* the aggregated `/metrics` with the `handler` label missing altogether, which
|
|
14
|
+
* Prometheus reads as `handler=""` — a distinct, unnamed series that shows up as
|
|
15
|
+
* an extra "Value" line in dashboards.
|
|
16
|
+
*
|
|
17
|
+
* `'undefined'` is deliberate rather than a nicer word: prom-client's local
|
|
18
|
+
* exposition already rendered `handler: undefined` as `handler="undefined"` before
|
|
19
|
+
* the cluster aggregation was introduced, so keeping that value makes the
|
|
20
|
+
* aggregated output match the historical series identity and keeps existing
|
|
21
|
+
* dashboards, filters and alerts (e.g. `handler!~"builtin:.*|undefined"`) working.
|
|
22
|
+
*/
|
|
23
|
+
exports.UNNAMED_REQUEST_HANDLER = 'undefined';
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the `handler` label value for a request, falling back to
|
|
26
|
+
* {@link UNNAMED_REQUEST_HANDLER} when the pipeline never named the handler.
|
|
27
|
+
* Empty strings fall back too, so the label is never emitted empty.
|
|
28
|
+
*/
|
|
29
|
+
const requestHandlerLabel = (requestHandlerName) => requestHandlerName || exports.UNNAMED_REQUEST_HANDLER;
|
|
30
|
+
exports.requestHandlerLabel = requestHandlerLabel;
|
|
@@ -4,6 +4,7 @@ exports.addRequestMetricsMiddleware = void 0;
|
|
|
4
4
|
const stream_1 = require("stream");
|
|
5
5
|
const utils_1 = require("../../utils");
|
|
6
6
|
const instruments_1 = require("../tracing/metrics/instruments");
|
|
7
|
+
const requestHandlerLabel_1 = require("./requestHandlerLabel");
|
|
7
8
|
const addRequestMetricsMiddleware = () => {
|
|
8
9
|
const concurrentRequests = (0, instruments_1.createConcurrentRequestsInstrument)();
|
|
9
10
|
const requestTimings = (0, instruments_1.createRequestsTimingsInstrument)();
|
|
@@ -13,7 +14,7 @@ const addRequestMetricsMiddleware = () => {
|
|
|
13
14
|
return async function addRequestMetrics(ctx, next) {
|
|
14
15
|
const start = process.hrtime();
|
|
15
16
|
concurrentRequests.inc(1);
|
|
16
|
-
ctx.req.once('aborted', () => abortedRequests.inc({ ["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: ctx.requestHandlerName }, 1));
|
|
17
|
+
ctx.req.once('aborted', () => abortedRequests.inc({ ["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: (0, requestHandlerLabel_1.requestHandlerLabel)(ctx.requestHandlerName) }, 1));
|
|
17
18
|
let responseClosed = false;
|
|
18
19
|
ctx.res.once('close', () => (responseClosed = true));
|
|
19
20
|
try {
|
|
@@ -22,15 +23,15 @@ const addRequestMetricsMiddleware = () => {
|
|
|
22
23
|
finally {
|
|
23
24
|
const responseLength = ctx.response.length;
|
|
24
25
|
if (responseLength) {
|
|
25
|
-
responseSizes.observe({ ["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: ctx.requestHandlerName }, responseLength);
|
|
26
|
+
responseSizes.observe({ ["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: (0, requestHandlerLabel_1.requestHandlerLabel)(ctx.requestHandlerName) }, responseLength);
|
|
26
27
|
}
|
|
27
28
|
totalRequests.inc({
|
|
28
|
-
["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: ctx.requestHandlerName,
|
|
29
|
+
["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: (0, requestHandlerLabel_1.requestHandlerLabel)(ctx.requestHandlerName),
|
|
29
30
|
["status_code" /* RequestsMetricLabels.STATUS_CODE */]: ctx.response.status,
|
|
30
31
|
}, 1);
|
|
31
32
|
const onResFinished = () => {
|
|
32
33
|
requestTimings.observe({
|
|
33
|
-
["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: ctx.requestHandlerName,
|
|
34
|
+
["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: (0, requestHandlerLabel_1.requestHandlerLabel)(ctx.requestHandlerName),
|
|
34
35
|
}, (0, utils_1.hrToMillisFloat)(process.hrtime(start)));
|
|
35
36
|
concurrentRequests.dec(1);
|
|
36
37
|
};
|
|
@@ -5,6 +5,7 @@ const instrumentation_koa_1 = require("@opentelemetry/instrumentation-koa");
|
|
|
5
5
|
const diagnostics_nodejs_1 = require("@vtex/diagnostics-nodejs");
|
|
6
6
|
const constants_1 = require("../../constants");
|
|
7
7
|
const hostMetrics_1 = require("../metrics/instruments/hostMetrics");
|
|
8
|
+
const resourceAttributes_1 = require("./resourceAttributes");
|
|
8
9
|
const APPLICATION_ID = constants_1.APP.ID || 'vtex-io-app';
|
|
9
10
|
class TelemetryClientSingleton {
|
|
10
11
|
static getInstance() {
|
|
@@ -58,6 +59,7 @@ class TelemetryClientSingleton {
|
|
|
58
59
|
'version': constants_1.APP.VERSION || '',
|
|
59
60
|
[constants_1.AttributeKeys.VTEX_IO_WORKSPACE_NAME]: constants_1.WORKSPACE,
|
|
60
61
|
[constants_1.AttributeKeys.VTEX_IO_WORKSPACE_TYPE]: constants_1.PRODUCTION ? 'production' : 'development',
|
|
62
|
+
...(0, resourceAttributes_1.getClusterResourceAttributes)(constants_1.CLUSTER_ID, constants_1.CLUSTER_ROLE),
|
|
61
63
|
},
|
|
62
64
|
// Use built-in no-op functionality when telemetry is disabled
|
|
63
65
|
noop: !constants_1.DIAGNOSTICS_TELEMETRY_ENABLED,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getClusterResourceAttributes: (clusterId?: string, clusterRole?: string) => Record<string, string>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getClusterResourceAttributes = void 0;
|
|
4
|
+
const constants_1 = require("../../constants");
|
|
5
|
+
const normalizeAttribute = (value) => {
|
|
6
|
+
const normalized = value === null || value === void 0 ? void 0 : value.trim();
|
|
7
|
+
return normalized || undefined;
|
|
8
|
+
};
|
|
9
|
+
const getClusterResourceAttributes = (clusterId, clusterRole) => {
|
|
10
|
+
const attributes = {};
|
|
11
|
+
const normalizedClusterId = normalizeAttribute(clusterId);
|
|
12
|
+
const normalizedClusterRole = normalizeAttribute(clusterRole);
|
|
13
|
+
if (normalizedClusterId) {
|
|
14
|
+
attributes[constants_1.AttributeKeys.VTEX_IO_CLUSTER_ID] = normalizedClusterId;
|
|
15
|
+
}
|
|
16
|
+
if (normalizedClusterRole) {
|
|
17
|
+
attributes[constants_1.AttributeKeys.VTEX_IO_CLUSTER_ROLE] = normalizedClusterRole;
|
|
18
|
+
}
|
|
19
|
+
return attributes;
|
|
20
|
+
};
|
|
21
|
+
exports.getClusterResourceAttributes = getClusterResourceAttributes;
|
|
@@ -15,7 +15,10 @@ const isStatusTrackBroadcast = (message) => message === BROADCAST_STATUS_TRACK;
|
|
|
15
15
|
exports.isStatusTrackBroadcast = isStatusTrackBroadcast;
|
|
16
16
|
const statusTrackHandler = async (ctx) => {
|
|
17
17
|
var _a, _b, _c;
|
|
18
|
-
|
|
18
|
+
// Parity with the other builtin handlers: name the request so its samples don't
|
|
19
|
+
// land in the catch-all `handler="undefined"` bucket.
|
|
20
|
+
ctx.requestHandlerName = 'builtin:status-track';
|
|
21
|
+
(_b = (_a = ctx.tracing) === null || _a === void 0 ? void 0 : _a.currentSpan) === null || _b === void 0 ? void 0 : _b.setOperationName(ctx.requestHandlerName);
|
|
19
22
|
if (!constants_1.LINKED) {
|
|
20
23
|
(_c = process.send) === null || _c === void 0 ? void 0 : _c.call(process, BROADCAST_STATUS_TRACK);
|
|
21
24
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vtex/api",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.5.1-beta.0",
|
|
4
4
|
"description": "VTEX I/O API client",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"@opentelemetry/instrumentation-koa": "0.47.1",
|
|
54
54
|
"@types/koa": "^2.11.0",
|
|
55
55
|
"@types/koa-compose": "^3.2.3",
|
|
56
|
-
"@vtex/diagnostics-nodejs": "0.1.8-io",
|
|
57
|
-
"@vtex/diagnostics-semconv": "
|
|
56
|
+
"@vtex/diagnostics-nodejs": "0.1.8-io-beta.4",
|
|
57
|
+
"@vtex/diagnostics-semconv": "5.5.2",
|
|
58
58
|
"@vtex/node-error-report": "^0.0.3",
|
|
59
59
|
"@wry/equality": "^0.1.9",
|
|
60
60
|
"agentkeepalive": "^4.0.2",
|
|
@@ -95,6 +95,8 @@
|
|
|
95
95
|
"xss": "^1.0.6"
|
|
96
96
|
},
|
|
97
97
|
"devDependencies": {
|
|
98
|
+
"@opentelemetry/context-async-hooks": "^1.30.1",
|
|
99
|
+
"@opentelemetry/sdk-metrics": "^1.30.1",
|
|
98
100
|
"@tiagonapoli/opentracing-alternate-mock": "^0.0.3",
|
|
99
101
|
"@types/archiver": "^2.0.1",
|
|
100
102
|
"@types/bluebird": "^3.5.27",
|