@vtex/api 6.52.0 → 6.52.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.
- package/CHANGELOG.md +50 -24
- package/lib/HttpClient/middlewares/cache.js +2 -2
- package/lib/HttpClient/middlewares/request/index.js +1 -1
- package/lib/HttpClient/middlewares/request/setupAxios/interceptors/tracing/spanSetup.js +1 -1
- package/lib/caches/MultilayeredCache.js +1 -1
- package/lib/service/logger/client.js +3 -3
- package/lib/service/logger/logger.d.ts +1 -1
- package/lib/service/logger/logger.js +3 -3
- package/lib/service/master.d.ts +0 -2
- package/lib/service/master.js +3 -15
- package/lib/service/metrics/requestMetricsMiddleware.js +4 -5
- package/lib/service/telemetry/client.js +18 -18
- package/lib/service/worker/index.d.ts +0 -1
- package/lib/service/worker/index.js +7 -17
- package/lib/service/worker/listeners.js +5 -3
- package/lib/service/worker/runtime/builtIn/middlewares.d.ts +1 -1
- package/lib/service/worker/runtime/builtIn/middlewares.js +2 -7
- package/lib/service/worker/runtime/graphql/middlewares/error.js +8 -2
- package/lib/service/worker/runtime/graphql/schema/schemaDirectives/Auth.js +2 -2
- package/lib/service/worker/runtime/http/middlewares/error.js +20 -2
- package/lib/service/worker/runtime/http/middlewares/sanitizeResponse.d.ts +37 -0
- package/lib/service/worker/runtime/http/middlewares/sanitizeResponse.js +143 -0
- package/lib/service/worker/runtime/statusTrack.js +1 -4
- package/lib/utils/buildFullPath.js +2 -2
- package/lib/utils/responseSanitizer.d.ts +52 -0
- package/lib/utils/responseSanitizer.js +354 -0
- package/lib/utils/stringRedactor.d.ts +19 -0
- package/lib/utils/stringRedactor.js +94 -0
- package/package.json +1 -1
- package/lib/service/metrics/clusterMetricsAggregator.d.ts +0 -72
- package/lib/service/metrics/clusterMetricsAggregator.js +0 -181
- package/lib/service/metrics/requestHandlerLabel.d.ts +0 -26
- package/lib/service/metrics/requestHandlerLabel.js +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -7,30 +7,56 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [6.52.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
10
|
+
## [6.52.5]
|
|
11
|
+
|
|
12
|
+
### Security
|
|
13
|
+
|
|
14
|
+
- Error objects assigned to the response body no longer expose the request
|
|
15
|
+
configuration, credentials, connection agents or, outside link mode, the
|
|
16
|
+
stack. If the response cannot be sanitized it is withheld. A body the walk cannot
|
|
17
|
+
inspect completely is sent as it is, and the limit is reported.
|
|
18
|
+
- String response bodies built from an error (`` `failed: ${JSON.stringify(error)}` ``) no longer
|
|
19
|
+
expose its credentials: the request's tokens, the value of credential headers (`authorization`,
|
|
20
|
+
`proxy-authorization`, `VtexIdclientAutCookie`, `x-vtex-api-*`), the VTEX ID cookie and `Bearer`
|
|
21
|
+
tokens are replaced with `[Redacted]`. The same names are replaced when the request is
|
|
22
|
+
embedded as header lines (`Cookie`, `Set-Cookie`, `X-Vtex-Session`), not only as JSON keys.
|
|
23
|
+
- The body the runtime builds from a thrown value goes through the same sanitizer. An error that is
|
|
24
|
+
a property of the thrown one (`originalError`), or sits in a thrown object, no longer exposes its
|
|
25
|
+
request configuration, request or stack, and the upstream response headers are no longer
|
|
26
|
+
included. Linked apps keep the stack but no longer get the configuration in the body.
|
|
27
|
+
- Errors printed by the runtime no longer include the request configuration.
|
|
28
|
+
- GraphQL error responses no longer include the stack outside link mode.
|
|
29
|
+
|
|
30
|
+
## [6.52.4]
|
|
31
|
+
### Fixed
|
|
32
|
+
- `yarn test` no longer fails to even load `src/service/logger` and its transitive
|
|
33
|
+
dependents under `jest@25`: that resolver predates the package `exports` field, so
|
|
34
|
+
it couldn't resolve `@vtex/diagnostics-nodejs@0.1.0-beta.10`'s OpenTelemetry OTLP
|
|
35
|
+
exporter chain (reached via `@opentelemetry/otlp-exporter-base/node-http`), which is
|
|
36
|
+
loaded at module-eval time by nearly every service module. Stubbed the package for
|
|
37
|
+
tests via `jest.config.js` `moduleNameMapper` (see `jest/stubs/diagnosticsNodejs.js`);
|
|
38
|
+
the real telemetry/log-client paths are lazy and error-guarded, so this has no
|
|
39
|
+
behavioural effect on the code under test.
|
|
40
|
+
- `TestServer.closeServer()`'s `new Promise((resolve) => ...)` failed to type-check
|
|
41
|
+
under strict `Promise<void>` typing; typed the executor explicitly.
|
|
42
|
+
- The `axiosTracing.test.ts` "forcing ECONNREFUSED" suite pointed at `localhost`, which
|
|
43
|
+
Node's `autoSelectFamily` (Happy Eyeballs) resolves to both loopback addresses; since
|
|
44
|
+
both connections are refused, Node throws an `AggregateError` with an empty top-level
|
|
45
|
+
`message` instead of the plain `ECONNREFUSED` error the test asserts on. Uses the
|
|
46
|
+
literal `127.0.0.1` instead.
|
|
47
|
+
|
|
48
|
+
## [6.52.2]
|
|
49
|
+
### Fixed
|
|
50
|
+
- TSLint violations across `src/axios.d.ts`, `src/caches/MultilayeredCache.ts`,
|
|
51
|
+
`src/HttpClient/middlewares/cache.ts`, `src/HttpClient/middlewares/request/index.ts`,
|
|
52
|
+
`src/HttpClient/middlewares/request/setupAxios/interceptors/tracing/spanSetup.ts`,
|
|
53
|
+
`src/service/logger/client.ts`, `src/service/logger/logger.ts`,
|
|
54
|
+
`src/service/telemetry/client.ts`,
|
|
55
|
+
`src/service/worker/runtime/graphql/schema/schemaDirectives/Auth.ts`,
|
|
56
|
+
`src/tracing/utils.test.ts` and `src/utils/buildFullPath.ts` (semicolon usage,
|
|
57
|
+
member ordering, `==` vs `===`, unsorted object keys, import ordering, and
|
|
58
|
+
other `tslint-config-vtex` rule failures) that were blocking the CI lint gate
|
|
59
|
+
on unrelated PRs against the 6.x line.
|
|
34
60
|
|
|
35
61
|
## [6.51.0] - 2026-06-23
|
|
36
62
|
### Added
|
|
@@ -73,7 +73,7 @@ const cacheMiddleware = ({ type, storage, asyncSet }) => {
|
|
|
73
73
|
["key-with-segment" /* HttpCacheLogFields.KEY_WITH_SEGMENT */]: keyWithSegment,
|
|
74
74
|
});
|
|
75
75
|
const cacheReadSpan = createCacheSpan(cacheType, 'read', tracer, span);
|
|
76
|
-
let cached
|
|
76
|
+
let cached;
|
|
77
77
|
try {
|
|
78
78
|
const cacheHasWithSegment = await storage.has(keyWithSegment);
|
|
79
79
|
cached = cacheHasWithSegment ? await storage.get(keyWithSegment) : await storage.get(key);
|
|
@@ -114,7 +114,7 @@ const cacheMiddleware = ({ type, storage, asyncSet }) => {
|
|
|
114
114
|
if (cachedEtag && validateStatus(response.status)) {
|
|
115
115
|
ctx.config.headers = {
|
|
116
116
|
...ctx.config.headers,
|
|
117
|
-
'if-none-match': cachedEtag
|
|
117
|
+
'if-none-match': cachedEtag,
|
|
118
118
|
};
|
|
119
119
|
ctx.config.validateStatus = validateStatus;
|
|
120
120
|
}
|
|
@@ -4,9 +4,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.requestMiddleware = exports.routerCacheMiddleware = exports.defaultsMiddleware = void 0;
|
|
7
|
-
const buildFullPath_1 = __importDefault(require("../../../utils/buildFullPath"));
|
|
8
7
|
const qs_1 = require("qs");
|
|
9
8
|
const ramda_1 = require("ramda");
|
|
9
|
+
const buildFullPath_1 = __importDefault(require("../../../utils/buildFullPath"));
|
|
10
10
|
const Tags_1 = require("../../../tracing/Tags");
|
|
11
11
|
const renameBy_1 = require("../../../utils/renameBy");
|
|
12
12
|
const setupAxios_1 = require("./setupAxios");
|
|
@@ -4,10 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.injectResponseInfoOnSpan = exports.injectRequestInfoOnSpan = void 0;
|
|
7
|
-
const buildFullPath_1 = __importDefault(require("../../../../../../utils/buildFullPath"));
|
|
8
7
|
const constants_1 = require("../../../../../../constants");
|
|
9
8
|
const Tags_1 = require("../../../../../../tracing/Tags");
|
|
10
9
|
const utils_1 = require("../../../../../../tracing/utils");
|
|
10
|
+
const buildFullPath_1 = __importDefault(require("../../../../../../utils/buildFullPath"));
|
|
11
11
|
const injectRequestInfoOnSpan = (span, http, config) => {
|
|
12
12
|
span === null || span === void 0 ? void 0 : span.addTags({
|
|
13
13
|
[Tags_1.OpentracingTags.SPAN_KIND]: Tags_1.OpentracingTags.SPAN_KIND_RPC_CLIENT,
|
|
@@ -8,7 +8,7 @@ class MultilayeredCache {
|
|
|
8
8
|
this.hits = 0;
|
|
9
9
|
this.total = 0;
|
|
10
10
|
this.get = async (key, fetcher) => {
|
|
11
|
-
let value
|
|
11
|
+
let value;
|
|
12
12
|
let maxAge;
|
|
13
13
|
let successIndex = await this.findIndex(async (cache) => {
|
|
14
14
|
const [getValue, hasKey] = await Promise.all([cache.get(key), cache.has(key)]);
|
|
@@ -5,7 +5,7 @@ const diagnostics_nodejs_1 = require("@vtex/diagnostics-nodejs");
|
|
|
5
5
|
const telemetry_1 = require("../telemetry");
|
|
6
6
|
let logClient;
|
|
7
7
|
let isInitializing = false;
|
|
8
|
-
let initPromise
|
|
8
|
+
let initPromise;
|
|
9
9
|
async function getLogClient(account, workspace, appName) {
|
|
10
10
|
if (logClient) {
|
|
11
11
|
return logClient;
|
|
@@ -23,11 +23,11 @@ async function initializeClient(account, workspace, appName) {
|
|
|
23
23
|
const telemetryClient = await (0, telemetry_1.getTelemetryClient)();
|
|
24
24
|
const logsConfig = diagnostics_nodejs_1.Exporters.CreateLogsExporterConfig({
|
|
25
25
|
endpoint: process.env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
26
|
+
headers: { 'Content-Type': 'application/json' },
|
|
27
|
+
interval: 5,
|
|
26
28
|
path: process.env.OTEL_EXPORTER_OTLP_PATH || '/v1/logs',
|
|
27
29
|
protocol: 'http',
|
|
28
|
-
interval: 5,
|
|
29
30
|
timeoutSeconds: 5,
|
|
30
|
-
headers: { 'Content-Type': 'application/json' },
|
|
31
31
|
});
|
|
32
32
|
const logsExporter = diagnostics_nodejs_1.Exporters.CreateExporter(logsConfig, 'otlp');
|
|
33
33
|
await logsExporter.initialize();
|
|
@@ -9,10 +9,10 @@ export declare class Logger {
|
|
|
9
9
|
private logClient;
|
|
10
10
|
private clientInitPromise;
|
|
11
11
|
constructor(ctx: LoggerContext);
|
|
12
|
-
private initLogClient;
|
|
13
12
|
debug: (message: any) => void;
|
|
14
13
|
info: (message: any) => void;
|
|
15
14
|
warn: (warning: any) => void;
|
|
16
15
|
error: (error: any) => void;
|
|
17
16
|
log: (message: any, level: LogLevel) => void;
|
|
17
|
+
private initLogClient;
|
|
18
18
|
}
|
|
@@ -4,8 +4,8 @@ exports.Logger = void 0;
|
|
|
4
4
|
const constants_1 = require("../../constants");
|
|
5
5
|
const error_1 = require("../../utils/error");
|
|
6
6
|
const log_1 = require("../../utils/log");
|
|
7
|
-
const loggerTypes_1 = require("./loggerTypes");
|
|
8
7
|
const client_1 = require("./client");
|
|
8
|
+
const loggerTypes_1 = require("./loggerTypes");
|
|
9
9
|
const app = constants_1.APP.ID;
|
|
10
10
|
const EMPTY_MESSAGE = 'Logger.log was called with null or undefined message';
|
|
11
11
|
class Logger {
|
|
@@ -42,7 +42,7 @@ class Logger {
|
|
|
42
42
|
}
|
|
43
43
|
if (this.logClient) {
|
|
44
44
|
try {
|
|
45
|
-
|
|
45
|
+
const logMessage = typeof data === 'string' ? data : JSON.stringify(data);
|
|
46
46
|
switch (level) {
|
|
47
47
|
case loggerTypes_1.LogLevel.Debug:
|
|
48
48
|
this.logClient.debug(logMessage, inflatedLog);
|
|
@@ -90,7 +90,7 @@ class Logger {
|
|
|
90
90
|
});
|
|
91
91
|
this.logClient = await Promise.race([
|
|
92
92
|
(0, client_1.getLogClient)(this.account, this.workspace, constants_1.APP.NAME),
|
|
93
|
-
timeoutPromise
|
|
93
|
+
timeoutPromise,
|
|
94
94
|
]);
|
|
95
95
|
return this.logClient;
|
|
96
96
|
}
|
package/lib/service/master.d.ts
CHANGED
package/lib/service/master.js
CHANGED
|
@@ -3,12 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.startMaster =
|
|
6
|
+
exports.startMaster = void 0;
|
|
7
7
|
const cluster_1 = __importDefault(require("cluster"));
|
|
8
8
|
const os_1 = require("os");
|
|
9
9
|
const constants_1 = require("../constants");
|
|
10
10
|
const logger_1 = require("./logger");
|
|
11
|
-
const clusterMetricsAggregator_1 = require("./metrics/clusterMetricsAggregator");
|
|
12
11
|
const listeners_1 = require("./worker/listeners");
|
|
13
12
|
const statusTrack_1 = require("./worker/runtime/statusTrack");
|
|
14
13
|
let handledSignal;
|
|
@@ -20,12 +19,7 @@ const onMessage = (worker, message) => {
|
|
|
20
19
|
(0, statusTrack_1.trackStatus)();
|
|
21
20
|
(0, statusTrack_1.broadcastStatusTrack)();
|
|
22
21
|
}
|
|
23
|
-
else
|
|
24
|
-
(0, clusterMetricsAggregator_1.handleWorkerMetricsRequest)(worker, message);
|
|
25
|
-
}
|
|
26
|
-
else if (!(0, clusterMetricsAggregator_1.isPromClientMessage)(message)) {
|
|
27
|
-
// prom-client's own cluster messages are handled by its cluster listener;
|
|
28
|
-
// anything else that reaches here is genuinely unexpected.
|
|
22
|
+
else {
|
|
29
23
|
listeners_1.logger.warn({
|
|
30
24
|
content: message,
|
|
31
25
|
message: 'Worker sent message',
|
|
@@ -33,7 +27,6 @@ const onMessage = (worker, message) => {
|
|
|
33
27
|
});
|
|
34
28
|
}
|
|
35
29
|
};
|
|
36
|
-
exports.onMessage = onMessage;
|
|
37
30
|
const onExit = (worker, code, signal) => {
|
|
38
31
|
if (!constants_1.LINKED && worker.exitedAfterDisconnect === false) {
|
|
39
32
|
listeners_1.logger.error({
|
|
@@ -77,11 +70,6 @@ const startMaster = (service) => {
|
|
|
77
70
|
if (service.deterministicVary) {
|
|
78
71
|
process.env.DETERMINISTIC_VARY = 'true';
|
|
79
72
|
}
|
|
80
|
-
// Set up the master-side Prometheus aggregator so workers can request a
|
|
81
|
-
// merged, monotonic /metrics view across the whole cluster over IPC.
|
|
82
|
-
if (numWorkers > 1) {
|
|
83
|
-
(0, clusterMetricsAggregator_1.initMasterAggregatorRegistry)();
|
|
84
|
-
}
|
|
85
73
|
// Setup dubugger
|
|
86
74
|
if (constants_1.LINKED) {
|
|
87
75
|
cluster_1.default.setupMaster({ inspectPort: constants_1.INSPECT_DEBUGGER_PORT });
|
|
@@ -94,7 +82,7 @@ const startMaster = (service) => {
|
|
|
94
82
|
}
|
|
95
83
|
cluster_1.default.on('online', onOnline);
|
|
96
84
|
cluster_1.default.on('exit', onExit);
|
|
97
|
-
cluster_1.default.on('message',
|
|
85
|
+
cluster_1.default.on('message', onMessage);
|
|
98
86
|
process.on('SIGINT', handleSignal(SIGINT_TIMEOUT_S));
|
|
99
87
|
process.on('SIGTERM', handleSignal(shutdownTimeout));
|
|
100
88
|
};
|
|
@@ -4,7 +4,6 @@ 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");
|
|
8
7
|
const addRequestMetricsMiddleware = () => {
|
|
9
8
|
const concurrentRequests = (0, instruments_1.createConcurrentRequestsInstrument)();
|
|
10
9
|
const requestTimings = (0, instruments_1.createRequestsTimingsInstrument)();
|
|
@@ -14,7 +13,7 @@ const addRequestMetricsMiddleware = () => {
|
|
|
14
13
|
return async function addRequestMetrics(ctx, next) {
|
|
15
14
|
const start = process.hrtime();
|
|
16
15
|
concurrentRequests.inc(1);
|
|
17
|
-
ctx.req.once('aborted', () => abortedRequests.inc({ ["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]:
|
|
16
|
+
ctx.req.once('aborted', () => abortedRequests.inc({ ["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: ctx.requestHandlerName }, 1));
|
|
18
17
|
let responseClosed = false;
|
|
19
18
|
ctx.res.once('close', () => (responseClosed = true));
|
|
20
19
|
try {
|
|
@@ -23,15 +22,15 @@ const addRequestMetricsMiddleware = () => {
|
|
|
23
22
|
finally {
|
|
24
23
|
const responseLength = ctx.response.length;
|
|
25
24
|
if (responseLength) {
|
|
26
|
-
responseSizes.observe({ ["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]:
|
|
25
|
+
responseSizes.observe({ ["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: ctx.requestHandlerName }, responseLength);
|
|
27
26
|
}
|
|
28
27
|
totalRequests.inc({
|
|
29
|
-
["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]:
|
|
28
|
+
["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: ctx.requestHandlerName,
|
|
30
29
|
["status_code" /* RequestsMetricLabels.STATUS_CODE */]: ctx.response.status,
|
|
31
30
|
}, 1);
|
|
32
31
|
const onResFinished = () => {
|
|
33
32
|
requestTimings.observe({
|
|
34
|
-
["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]:
|
|
33
|
+
["handler" /* RequestsMetricLabels.REQUEST_HANDLER */]: ctx.requestHandlerName,
|
|
35
34
|
}, (0, utils_1.hrToMillisFloat)(process.hrtime(start)));
|
|
36
35
|
concurrentRequests.dec(1);
|
|
37
36
|
};
|
|
@@ -4,21 +4,35 @@ exports.resetTelemetryClient = exports.getTelemetryClient = void 0;
|
|
|
4
4
|
const diagnostics_nodejs_1 = require("@vtex/diagnostics-nodejs");
|
|
5
5
|
const constants_1 = require("../../constants");
|
|
6
6
|
class TelemetryClientSingleton {
|
|
7
|
-
constructor() {
|
|
8
|
-
this.initializationPromise = undefined;
|
|
9
|
-
}
|
|
10
7
|
static getInstance() {
|
|
11
8
|
if (!TelemetryClientSingleton.instance) {
|
|
12
9
|
TelemetryClientSingleton.instance = new TelemetryClientSingleton();
|
|
13
10
|
}
|
|
14
11
|
return TelemetryClientSingleton.instance;
|
|
15
12
|
}
|
|
13
|
+
constructor() {
|
|
14
|
+
this.initializationPromise = undefined;
|
|
15
|
+
}
|
|
16
|
+
async getClient() {
|
|
17
|
+
if (this.telemetryClient) {
|
|
18
|
+
return this.telemetryClient;
|
|
19
|
+
}
|
|
20
|
+
if (this.initializationPromise) {
|
|
21
|
+
return this.initializationPromise;
|
|
22
|
+
}
|
|
23
|
+
this.initializationPromise = this.initTelemetryClient();
|
|
24
|
+
return this.initializationPromise;
|
|
25
|
+
}
|
|
26
|
+
reset() {
|
|
27
|
+
this.telemetryClient = undefined;
|
|
28
|
+
this.initializationPromise = undefined;
|
|
29
|
+
}
|
|
16
30
|
async initTelemetryClient() {
|
|
17
31
|
try {
|
|
18
32
|
const telemetryClient = await (0, diagnostics_nodejs_1.NewTelemetryClient)('node-vtex-api', constants_1.APP.ID || 'vtex-app', {
|
|
19
33
|
additionalAttrs: {
|
|
20
|
-
'version': constants_1.APP.VERSION || '',
|
|
21
34
|
'environment': process.env.VTEX_WORKSPACE || 'development',
|
|
35
|
+
'version': constants_1.APP.VERSION || '',
|
|
22
36
|
},
|
|
23
37
|
});
|
|
24
38
|
this.telemetryClient = telemetryClient;
|
|
@@ -32,20 +46,6 @@ class TelemetryClientSingleton {
|
|
|
32
46
|
this.initializationPromise = undefined;
|
|
33
47
|
}
|
|
34
48
|
}
|
|
35
|
-
async getClient() {
|
|
36
|
-
if (this.telemetryClient) {
|
|
37
|
-
return this.telemetryClient;
|
|
38
|
-
}
|
|
39
|
-
if (this.initializationPromise) {
|
|
40
|
-
return this.initializationPromise;
|
|
41
|
-
}
|
|
42
|
-
this.initializationPromise = this.initTelemetryClient();
|
|
43
|
-
return this.initializationPromise;
|
|
44
|
-
}
|
|
45
|
-
reset() {
|
|
46
|
-
this.telemetryClient = undefined;
|
|
47
|
-
this.initializationPromise = undefined;
|
|
48
|
-
}
|
|
49
49
|
}
|
|
50
50
|
async function getTelemetryClient() {
|
|
51
51
|
return TelemetryClientSingleton.getInstance().getClient();
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Koa from 'koa';
|
|
2
2
|
import { MetricsAccumulator } from '../../metrics/MetricsAccumulator';
|
|
3
3
|
import { ServiceJSON } from './runtime/typings';
|
|
4
|
-
export declare const onMessage: (service: ServiceJSON) => (message: any) => void;
|
|
5
4
|
export declare const startWorker: (serviceJSON: ServiceJSON) => Koa<Koa.DefaultState, Koa.DefaultContext>;
|
|
6
5
|
declare global {
|
|
7
6
|
namespace NodeJS {
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.startWorker =
|
|
6
|
+
exports.startWorker = void 0;
|
|
7
7
|
const http_1 = require("http");
|
|
8
8
|
const koa_1 = __importDefault(require("koa"));
|
|
9
9
|
const koa_compress_1 = __importDefault(require("koa-compress"));
|
|
@@ -14,7 +14,6 @@ const MetricsAccumulator_1 = require("../../metrics/MetricsAccumulator");
|
|
|
14
14
|
const loaders_1 = require("../loaders");
|
|
15
15
|
const console_1 = require("../logger/console");
|
|
16
16
|
const loggerTypes_1 = require("../logger/loggerTypes");
|
|
17
|
-
const clusterMetricsAggregator_1 = require("../metrics/clusterMetricsAggregator");
|
|
18
17
|
const requestMetricsMiddleware_1 = require("../metrics/requestMetricsMiddleware");
|
|
19
18
|
const TracerSingleton_1 = require("../tracing/TracerSingleton");
|
|
20
19
|
const tracingMiddlewares_1 = require("../tracing/tracingMiddlewares");
|
|
@@ -27,6 +26,7 @@ const graphql_1 = require("./runtime/graphql");
|
|
|
27
26
|
const http_2 = require("./runtime/http");
|
|
28
27
|
const error_1 = require("./runtime/http/middlewares/error");
|
|
29
28
|
const rateLimit_1 = require("./runtime/http/middlewares/rateLimit");
|
|
29
|
+
const sanitizeResponse_1 = require("./runtime/http/middlewares/sanitizeResponse");
|
|
30
30
|
const router_2 = require("./runtime/http/router");
|
|
31
31
|
const routes_1 = require("./runtime/http/routes");
|
|
32
32
|
const statusTrack_1 = require("./runtime/statusTrack");
|
|
@@ -55,12 +55,7 @@ const onMessage = (service) => (message) => {
|
|
|
55
55
|
else if ((0, statusTrack_1.isStatusTrack)(message)) {
|
|
56
56
|
(0, statusTrack_1.trackStatus)();
|
|
57
57
|
}
|
|
58
|
-
else
|
|
59
|
-
(0, clusterMetricsAggregator_1.handleMasterMetricsResponse)(message);
|
|
60
|
-
}
|
|
61
|
-
else if (!(0, clusterMetricsAggregator_1.isPromClientMessage)(message)) {
|
|
62
|
-
// prom-client's own cluster messages are handled by its worker listener;
|
|
63
|
-
// anything else that reaches here is genuinely unexpected.
|
|
58
|
+
else {
|
|
64
59
|
listeners_1.logger.warn({
|
|
65
60
|
content: message,
|
|
66
61
|
message: 'Master sent message',
|
|
@@ -68,7 +63,6 @@ const onMessage = (service) => (message) => {
|
|
|
68
63
|
});
|
|
69
64
|
}
|
|
70
65
|
};
|
|
71
|
-
exports.onMessage = onMessage;
|
|
72
66
|
const createAppHttpHandlers = ({ config: { routes, clients } }, serviceJSON, globalLimiter) => {
|
|
73
67
|
if (routes && clients) {
|
|
74
68
|
return Object.keys(routes).reduce((acc, routeId) => {
|
|
@@ -160,22 +154,18 @@ const startWorker = (serviceJSON) => {
|
|
|
160
154
|
var _a, _b;
|
|
161
155
|
(0, listeners_1.addProcessListeners)();
|
|
162
156
|
const tracer = TracerSingleton_1.TracerSingleton.getTracer();
|
|
163
|
-
// In multi-worker mode install prom-client's worker-side cluster responder so
|
|
164
|
-
// the master can collect this worker's registry for the aggregated /metrics.
|
|
165
|
-
if (serviceJSON.workers > 1) {
|
|
166
|
-
(0, clusterMetricsAggregator_1.ensureWorkerAggregatorRegistry)();
|
|
167
|
-
}
|
|
168
157
|
const app = new koa_1.default();
|
|
169
158
|
app.proxy = true;
|
|
170
159
|
app
|
|
171
160
|
.use(error_1.error)
|
|
172
|
-
.use((0, middlewares_1.prometheusLoggerMiddleware)(
|
|
161
|
+
.use((0, middlewares_1.prometheusLoggerMiddleware)())
|
|
173
162
|
.use((0, tracingMiddlewares_1.addTracingMiddleware)(tracer))
|
|
174
163
|
.use((0, requestMetricsMiddleware_1.addRequestMetricsMiddleware)())
|
|
175
164
|
.use((0, middlewares_1.addMetricsLoggerMiddleware)())
|
|
176
165
|
.use((0, rateLimit_1.concurrentRateLimiter)((_a = serviceJSON === null || serviceJSON === void 0 ? void 0 : serviceJSON.rateLimitPerReplica) === null || _a === void 0 ? void 0 : _a.concurrent))
|
|
177
166
|
.use((0, koa_compress_1.default)())
|
|
178
|
-
.use(middlewares_1.recorderMiddleware)
|
|
167
|
+
.use(middlewares_1.recorderMiddleware)
|
|
168
|
+
.use(sanitizeResponse_1.sanitizeResponse);
|
|
179
169
|
const service = (0, loaders_1.getService)();
|
|
180
170
|
const { config: { clients } } = service;
|
|
181
171
|
if (clients) {
|
|
@@ -202,7 +192,7 @@ const startWorker = (serviceJSON) => {
|
|
|
202
192
|
const privateHandlersRouter = routerFromPrivateHttpHandlers(httpHandlers.pvt);
|
|
203
193
|
app.use(privateHandlersRouter.routes());
|
|
204
194
|
}
|
|
205
|
-
process.on('message',
|
|
195
|
+
process.on('message', onMessage(serviceJSON));
|
|
206
196
|
return app;
|
|
207
197
|
};
|
|
208
198
|
exports.startWorker = startWorker;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.addProcessListeners = exports.logger = void 0;
|
|
4
4
|
const os_1 = require("os");
|
|
5
5
|
const RequestCancelledError_1 = require("../../errors/RequestCancelledError");
|
|
6
|
+
const responseSanitizer_1 = require("../../utils/responseSanitizer");
|
|
6
7
|
const logger_1 = require("../logger");
|
|
7
8
|
exports.logger = new logger_1.Logger({ account: 'unhandled', workspace: 'unhandled', requestId: 'unhandled', operationId: 'unhandled', production: process.env.VTEX_PRODUCTION === 'true' });
|
|
8
9
|
let watched;
|
|
@@ -15,18 +16,19 @@ const addProcessListeners = () => {
|
|
|
15
16
|
return;
|
|
16
17
|
}
|
|
17
18
|
watched = process.on('uncaughtException', (err) => {
|
|
18
|
-
console.error('uncaughtException', err);
|
|
19
|
+
console.error('uncaughtException', (0, responseSanitizer_1.toLoggableError)(err));
|
|
19
20
|
if (err && exports.logger) {
|
|
20
21
|
err.type = 'uncaughtException';
|
|
21
22
|
exports.logger.error(err);
|
|
22
23
|
}
|
|
23
24
|
process.exit(420);
|
|
24
25
|
});
|
|
25
|
-
process.on('unhandledRejection', (reason
|
|
26
|
+
process.on('unhandledRejection', (reason) => {
|
|
26
27
|
if (reason instanceof RequestCancelledError_1.RequestCancelledError) {
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
29
|
-
|
|
30
|
+
// The promise is not printed: inspecting it shows the rejection reason again, unsanitized.
|
|
31
|
+
console.error('unhandledRejection', (0, responseSanitizer_1.toLoggableError)(reason));
|
|
30
32
|
if (reason && exports.logger) {
|
|
31
33
|
reason.type = 'unhandledRejection';
|
|
32
34
|
exports.logger.error(reason);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { ServiceContext } from '../typings';
|
|
2
2
|
export declare function recorderMiddleware(ctx: ServiceContext, next: () => Promise<void>): Promise<void>;
|
|
3
3
|
export declare const addMetricsLoggerMiddleware: () => (ctx: ServiceContext, next: () => Promise<void>) => Promise<void>;
|
|
4
|
-
export declare const prometheusLoggerMiddleware: (
|
|
4
|
+
export declare const prometheusLoggerMiddleware: () => (ctx: ServiceContext, next: () => Promise<void>) => Promise<void>;
|
|
@@ -4,7 +4,6 @@ exports.prometheusLoggerMiddleware = exports.addMetricsLoggerMiddleware = export
|
|
|
4
4
|
const prom_client_1 = require("prom-client");
|
|
5
5
|
const constants_1 = require("../../../../constants");
|
|
6
6
|
const metricsLogger_1 = require("../../../logger/metricsLogger");
|
|
7
|
-
const clusterMetricsAggregator_1 = require("../../../metrics/clusterMetricsAggregator");
|
|
8
7
|
const EventLoopLagMeasurer_1 = require("../../../tracing/metrics/measurers/EventLoopLagMeasurer");
|
|
9
8
|
const recorder_1 = require("../utils/recorder");
|
|
10
9
|
async function recorderMiddleware(ctx, next) {
|
|
@@ -23,14 +22,10 @@ const addMetricsLoggerMiddleware = () => {
|
|
|
23
22
|
};
|
|
24
23
|
};
|
|
25
24
|
exports.addMetricsLoggerMiddleware = addMetricsLoggerMiddleware;
|
|
26
|
-
const prometheusLoggerMiddleware = (
|
|
25
|
+
const prometheusLoggerMiddleware = () => {
|
|
27
26
|
(0, prom_client_1.collectDefaultMetrics)();
|
|
28
27
|
const eventLoopLagMeasurer = new EventLoopLagMeasurer_1.EventLoopLagMeasurer();
|
|
29
28
|
eventLoopLagMeasurer.start();
|
|
30
|
-
// In multi-worker mode each worker holds only its own registry, so /metrics
|
|
31
|
-
// must serve the cluster-wide aggregate requested from the master over IPC.
|
|
32
|
-
// In single-worker mode (LINKED / workers:1) the local registry is complete.
|
|
33
|
-
const isMultiWorker = workers > 1;
|
|
34
29
|
return async (ctx, next) => {
|
|
35
30
|
if (ctx.request.path !== '/metrics') {
|
|
36
31
|
return next();
|
|
@@ -41,7 +36,7 @@ const prometheusLoggerMiddleware = (workers = 1) => {
|
|
|
41
36
|
}
|
|
42
37
|
await eventLoopLagMeasurer.updateInstrumentsAndReset();
|
|
43
38
|
ctx.set('Content-Type', prom_client_1.register.contentType);
|
|
44
|
-
ctx.body =
|
|
39
|
+
ctx.body = await prom_client_1.register.metrics();
|
|
45
40
|
ctx.status = 200;
|
|
46
41
|
};
|
|
47
42
|
};
|
|
@@ -36,8 +36,14 @@ const createLogErrorToSplunk = (vtex) => (err) => {
|
|
|
36
36
|
logger.log(log, level);
|
|
37
37
|
}
|
|
38
38
|
finally {
|
|
39
|
-
if (!constants_1.LINKED
|
|
40
|
-
|
|
39
|
+
if (!constants_1.LINKED) {
|
|
40
|
+
// Already logged above. The response must not carry them.
|
|
41
|
+
if ((_f = (_e = err === null || err === void 0 ? void 0 : err.extensions) === null || _e === void 0 ? void 0 : _e.exception) === null || _f === void 0 ? void 0 : _f.sensitive) {
|
|
42
|
+
delete err.extensions.exception.sensitive;
|
|
43
|
+
}
|
|
44
|
+
if (err) {
|
|
45
|
+
delete err.stack;
|
|
46
|
+
}
|
|
41
47
|
}
|
|
42
48
|
}
|
|
43
49
|
};
|
|
@@ -50,7 +50,7 @@ async function auth(ctx, authArgs) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
function parseArgs(authArgs) {
|
|
53
|
-
if (authArgs.scope
|
|
53
|
+
if (authArgs.scope === 'PUBLIC') {
|
|
54
54
|
return authArgs;
|
|
55
55
|
}
|
|
56
56
|
if (!authArgs.productCode || !authArgs.resourceCode) {
|
|
@@ -63,7 +63,7 @@ class Auth extends graphql_tools_1.SchemaDirectiveVisitor {
|
|
|
63
63
|
const { resolve = graphql_1.defaultFieldResolver } = field;
|
|
64
64
|
field.resolve = async (root, args, ctx, info) => {
|
|
65
65
|
const authArgs = parseArgs(this.args);
|
|
66
|
-
if (!authArgs.scope || authArgs.scope
|
|
66
|
+
if (!authArgs.scope || authArgs.scope === 'PRIVATE') {
|
|
67
67
|
await auth(ctx, authArgs);
|
|
68
68
|
}
|
|
69
69
|
return resolve(root, args, ctx, info);
|
|
@@ -5,7 +5,9 @@ const constants_1 = require("../../../../../constants");
|
|
|
5
5
|
const RequestCancelledError_1 = require("../../../../../errors/RequestCancelledError");
|
|
6
6
|
const TooManyRequestsError_1 = require("../../../../../errors/TooManyRequestsError");
|
|
7
7
|
const error_1 = require("../../../../../utils/error");
|
|
8
|
+
const responseSanitizer_1 = require("../../../../../utils/responseSanitizer");
|
|
8
9
|
const logger_1 = require("../../../../logger");
|
|
10
|
+
const sanitizeResponse_1 = require("./sanitizeResponse");
|
|
9
11
|
const CACHE_CONTROL_HEADER = 'cache-control';
|
|
10
12
|
const META_HEADER = 'x-vtex-meta';
|
|
11
13
|
const ETAG_HEADER = 'etag';
|
|
@@ -45,6 +47,19 @@ const logAndRemoveSensitiveData = (ctx, err) => {
|
|
|
45
47
|
});
|
|
46
48
|
}
|
|
47
49
|
};
|
|
50
|
+
// What is sent to the client for a thrown value. It is built with the sanitizer that covers the
|
|
51
|
+
// bodies the app sets, so both paths expose the same: `cleanError` alone removes credentials only
|
|
52
|
+
// at the top level, and by a short list of header names, so an error that is a property of the
|
|
53
|
+
// thrown value (`originalError`) kept its config, request and stack.
|
|
54
|
+
const buildResponseBody = (thrown) => {
|
|
55
|
+
try {
|
|
56
|
+
return (0, error_1.cleanError)((0, responseSanitizer_1.sanitizeErrors)(thrown, { linked: constants_1.LINKED }).value);
|
|
57
|
+
}
|
|
58
|
+
catch (sanitizeError) {
|
|
59
|
+
console.error('[node-vtex-api error] the thrown value could not be sanitized', sanitizeError === null || sanitizeError === void 0 ? void 0 : sanitizeError.name);
|
|
60
|
+
return sanitizeResponse_1.WITHHELD_BODY;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
48
63
|
async function error(ctx, next) {
|
|
49
64
|
var _a;
|
|
50
65
|
try {
|
|
@@ -59,7 +74,8 @@ async function error(ctx, next) {
|
|
|
59
74
|
ctx.status = TooManyRequestsError_1.tooManyRequestsStatus;
|
|
60
75
|
return;
|
|
61
76
|
}
|
|
62
|
-
|
|
77
|
+
// The raw error carries the request config, whose headers include credentials.
|
|
78
|
+
console.error('[node-vtex-api error]', (0, responseSanitizer_1.toLoggableError)(e));
|
|
63
79
|
const err = (0, error_1.cleanError)(e);
|
|
64
80
|
// Add response
|
|
65
81
|
ctx.status = (e === null || e === void 0 ? void 0 : e.status) && e.status >= 400 && e.status <= 599
|
|
@@ -78,8 +94,10 @@ async function error(ctx, next) {
|
|
|
78
94
|
else {
|
|
79
95
|
ctx.set(CACHE_CONTROL_HEADER, `no-cache, no-store`);
|
|
80
96
|
}
|
|
97
|
+
// `err` is what is logged, which keeps the config and the request to debug with. It is not the
|
|
98
|
+
// body: the sanitized value is.
|
|
81
99
|
logAndRemoveSensitiveData(ctx, err);
|
|
82
|
-
ctx.body = ctx.body ||
|
|
100
|
+
ctx.body = ctx.body || buildResponseBody(e);
|
|
83
101
|
}
|
|
84
102
|
}
|
|
85
103
|
exports.error = error;
|