@elisra-devops/docgen-data-provider 1.140.0 → 1.141.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/.github/workflows/ci.yml +2 -0
- package/bin/helpers/tfs.js +21 -14
- package/bin/helpers/tfs.js.map +1 -1
- package/bin/modules/JfrogDataProvider.js +1 -1
- package/bin/modules/JfrogDataProvider.js.map +1 -1
- package/bin/modules/PipelinesDataProvider.js +1 -1
- package/bin/modules/PipelinesDataProvider.js.map +1 -1
- package/bin/modules/ResultDataProvider.js +21 -15
- package/bin/modules/ResultDataProvider.js.map +1 -1
- package/bin/modules/TicketsDataProvider.js +13 -10
- package/bin/modules/TicketsDataProvider.js.map +1 -1
- package/bin/tests/modules/JfrogDataProvider.test.js +3 -1
- package/bin/tests/modules/JfrogDataProvider.test.js.map +1 -1
- package/bin/tests/modules/ResultDataProvider.test.js +6 -2
- package/bin/tests/modules/ResultDataProvider.test.js.map +1 -1
- package/bin/tests/utils/logger.test.d.ts +1 -0
- package/bin/tests/utils/logger.test.js +238 -0
- package/bin/tests/utils/logger.test.js.map +1 -0
- package/bin/tests/utils/runContext.test.d.ts +1 -0
- package/bin/tests/utils/runContext.test.js +51 -0
- package/bin/tests/utils/runContext.test.js.map +1 -0
- package/bin/utils/logSink.d.ts +25 -0
- package/bin/utils/logSink.js +16 -0
- package/bin/utils/logSink.js.map +1 -0
- package/bin/utils/logger.d.ts +6 -0
- package/bin/utils/logger.js +219 -27
- package/bin/utils/logger.js.map +1 -1
- package/bin/utils/runContext.d.ts +8 -0
- package/bin/utils/runContext.js +12 -0
- package/bin/utils/runContext.js.map +1 -0
- package/eslint.config.mjs +40 -0
- package/package.json +8 -3
- package/src/helpers/tfs.ts +19 -15
- package/src/modules/JfrogDataProvider.ts +1 -1
- package/src/modules/PipelinesDataProvider.ts +1 -1
- package/src/modules/ResultDataProvider.ts +20 -15
- package/src/modules/TicketsDataProvider.ts +15 -14
- package/src/tests/modules/JfrogDataProvider.test.ts +3 -1
- package/src/tests/modules/ResultDataProvider.test.ts +6 -2
- package/src/tests/utils/logger.test.ts +270 -0
- package/src/tests/utils/runContext.test.ts +48 -0
- package/src/utils/logSink.ts +44 -0
- package/src/utils/logger.ts +229 -29
- package/src/utils/runContext.ts +25 -0
package/bin/utils/logger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
var _a;
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.runContextStore = void 0;
|
|
5
|
+
const async_hooks_1 = require("async_hooks");
|
|
6
|
+
// Symbol.for uses the global symbol registry, so every duplicated copy of this file across
|
|
7
|
+
// the DocGen packages — hoisted or nested at any depth by npm — converges on the same
|
|
8
|
+
// AsyncLocalStorage instance. Keying by module identity instead would silently split into
|
|
9
|
+
// two stores and runId would go missing with no visible error.
|
|
10
|
+
const KEY = Symbol.for('elisradevops.docgen.runContext');
|
|
11
|
+
exports.runContextStore = (_a = globalThis[KEY]) !== null && _a !== void 0 ? _a : (globalThis[KEY] = new async_hooks_1.AsyncLocalStorage());
|
|
12
|
+
//# sourceMappingURL=runContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runContext.js","sourceRoot":"","sources":["../../src/utils/runContext.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;;;;AACb,6CAAgD;AAehD,2FAA2F;AAC3F,sFAAsF;AACtF,0FAA0F;AAC1F,+DAA+D;AAC/D,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;AAE5C,QAAA,eAAe,GAC1B,MAAE,UAAsC,CAAC,GAAG,CAA+C,mCAC3F,CAAE,UAAsC,CAAC,GAAG,CAAC,GAAG,IAAI,+BAAiB,EAAc,CAAC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import tseslint from 'typescript-eslint';
|
|
2
|
+
import globals from 'globals';
|
|
3
|
+
|
|
4
|
+
// Phase 4 regression guard: bans the two log-record-corrupting patterns the observability plan
|
|
5
|
+
// found and fixed by hand across all four backend repos. Deliberately does NOT extend
|
|
6
|
+
// js.configs.recommended/tseslint's recommended rule sets — this repo has never had a linter,
|
|
7
|
+
// and pulling in a general rule set now would surface a large, unrelated pre-existing backlog
|
|
8
|
+
// (unused vars, etc.) as new lint failures. Scope is exactly the one regression this guards
|
|
9
|
+
// against. Keep this file identical in shape across docgen-api-gate / docgen-content-control /
|
|
10
|
+
// docgen-data-provider-package / docgen-dg-skins-package — see each repo's CLAUDE.md "keep
|
|
11
|
+
// these files in sync" note.
|
|
12
|
+
export default tseslint.config(
|
|
13
|
+
{
|
|
14
|
+
ignores: ['bin/**', 'coverage/**', '**/test/**', '**/tests/**'],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
files: ['src/**/*.ts'],
|
|
18
|
+
extends: [tseslint.configs.base],
|
|
19
|
+
languageOptions: {
|
|
20
|
+
globals: globals.node,
|
|
21
|
+
},
|
|
22
|
+
rules: {
|
|
23
|
+
'no-restricted-syntax': [
|
|
24
|
+
'error',
|
|
25
|
+
{
|
|
26
|
+
selector:
|
|
27
|
+
"CallExpression[callee.object.name='logger'] > :matches(TemplateLiteral, BinaryExpression[operator='+']).arguments:first-child MemberExpression[property.name='stack']",
|
|
28
|
+
message:
|
|
29
|
+
'Do not embed .stack in the log message string — errors({stack:true}) attaches it automatically when the Error is passed as an argument. Pass the Error itself as a positional/meta argument instead.',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
selector:
|
|
33
|
+
"CallExpression[callee.object.name='logger'] CallExpression[callee.object.name='JSON'][callee.property.name='stringify'] > Identifier.arguments:first-child[name=/^(e|err|error|ex)$/]",
|
|
34
|
+
message:
|
|
35
|
+
"Don't JSON.stringify an Error — message/stack are non-enumerable and it serializes to {}. Pass the Error object directly as a log argument.",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elisra-devops/docgen-data-provider",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.141.0",
|
|
4
4
|
"description": "A document generator data provider, aimed to retrive data from azure devops",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
"types": "./bin/index.d.ts",
|
|
12
12
|
"scripts": {
|
|
13
13
|
"test": "jest --coverage --runInBand --forceExit",
|
|
14
|
-
"build": "tsc"
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"lint": "eslint ."
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
18
|
+
"@eslint/js": "^9.39.5",
|
|
17
19
|
"@types/babel__generator": "^7.6.8",
|
|
18
20
|
"@types/babel__template": "^7.4.4",
|
|
19
21
|
"@types/caseless": "^0.12.5",
|
|
@@ -24,11 +26,14 @@
|
|
|
24
26
|
"@types/yargs-parser": "^21.0.3",
|
|
25
27
|
"baseline-browser-mapping": "^2.9.0",
|
|
26
28
|
"dotenv": "^16.4.5",
|
|
29
|
+
"eslint": "^9.39.5",
|
|
30
|
+
"globals": "^15.15.0",
|
|
27
31
|
"jest": "^29.7.0",
|
|
28
32
|
"jest-junit": "^16.0.0",
|
|
29
33
|
"npm": "^10.8.2",
|
|
30
34
|
"ts-jest": "^29.2.3",
|
|
31
|
-
"typescript": "^5.5.4"
|
|
35
|
+
"typescript": "^5.5.4",
|
|
36
|
+
"typescript-eslint": "^8.71.0"
|
|
32
37
|
},
|
|
33
38
|
"dependencies": {
|
|
34
39
|
"@types/xml2js": "^0.4.5",
|
package/src/helpers/tfs.ts
CHANGED
|
@@ -248,7 +248,7 @@ export class TFSServices {
|
|
|
248
248
|
|
|
249
249
|
// Log error if needed
|
|
250
250
|
if (printError) {
|
|
251
|
-
this.logDetailedError(e, url);
|
|
251
|
+
this.logDetailedError(e, url, attempts);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
throw e;
|
|
@@ -286,23 +286,27 @@ export class TFSServices {
|
|
|
286
286
|
/**
|
|
287
287
|
* Log detailed error information
|
|
288
288
|
*/
|
|
289
|
-
private static logDetailedError(error: any, url: string): void {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
error.response.data.message || JSON.stringify(error.response.data).substring(0, 200);
|
|
300
|
-
logger.error(`Response: ${dataMessage}`);
|
|
289
|
+
private static logDetailedError(error: any, url: string, attempt?: number): void {
|
|
290
|
+
let responseExcerpt: string | undefined;
|
|
291
|
+
if (error.response?.data) {
|
|
292
|
+
if (typeof error.response.data === 'string') {
|
|
293
|
+
responseExcerpt = error.response.data.substring(0, 200);
|
|
294
|
+
} else {
|
|
295
|
+
try {
|
|
296
|
+
responseExcerpt = error.response.data.message || JSON.stringify(error.response.data).substring(0, 200);
|
|
297
|
+
} catch {
|
|
298
|
+
responseExcerpt = '[unserializable response data]';
|
|
301
299
|
}
|
|
302
300
|
}
|
|
303
|
-
} else {
|
|
304
|
-
logger.error(`Error for ${url} - ${error.message}`);
|
|
305
301
|
}
|
|
302
|
+
logger.error('ADO request failed', {
|
|
303
|
+
message: error.message,
|
|
304
|
+
stack: error.stack,
|
|
305
|
+
url,
|
|
306
|
+
status: error.response?.status,
|
|
307
|
+
responseExcerpt,
|
|
308
|
+
attempt,
|
|
309
|
+
});
|
|
306
310
|
}
|
|
307
311
|
|
|
308
312
|
private static getErrorMessage(error: any): string {
|
|
@@ -19,7 +19,7 @@ export default class JfrogDataProvider {
|
|
|
19
19
|
if (!serviceConnectionResponse?.url) {
|
|
20
20
|
throw new Error(`Service connection ${connectionId} returned no URL — identity may lack service-endpoint read permission`);
|
|
21
21
|
}
|
|
22
|
-
logger.debug(`service connection url ${
|
|
22
|
+
logger.debug(`service connection url ${serviceConnectionResponse.url}`);
|
|
23
23
|
return serviceConnectionResponse.url;
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -1252,7 +1252,7 @@ export default class PipelinesDataProvider {
|
|
|
1252
1252
|
},
|
|
1253
1253
|
parameters: parameter, //'{"Test":"123"}'
|
|
1254
1254
|
};
|
|
1255
|
-
logger.info(
|
|
1255
|
+
logger.info('Triggering build', data);
|
|
1256
1256
|
let url = `${this.orgUrl}${projectName}/_apis/build/builds?api-version=5.0`;
|
|
1257
1257
|
let res = await TFSServices.postRequest(url, this.token, 'post', data, null);
|
|
1258
1258
|
return res;
|
|
@@ -259,10 +259,22 @@ export default class ResultDataProvider {
|
|
|
259
259
|
|
|
260
260
|
return { combinedResults, openPcrToTestCaseTraceMap, testCaseToOpenPcrTraceMap };
|
|
261
261
|
} catch (error: any) {
|
|
262
|
-
|
|
262
|
+
// One event, one record — this used to be two separate calls (message, then
|
|
263
|
+
// conditionally the response body), which interleave with other requests' output
|
|
264
|
+
// under concurrency and become two unrelated entries in the dashboard's error grouping.
|
|
265
|
+
let responseSummary: string | undefined;
|
|
263
266
|
if (error.response) {
|
|
264
|
-
|
|
267
|
+
// Truncated to match the bound already used for this in tfs.ts — an untruncated ADO
|
|
268
|
+
// response body isn't a credential, but it can still be large/sensitive work-item content
|
|
269
|
+
// and doesn't need to go into logs unbounded. JSON.stringify itself (not just the
|
|
270
|
+
// output) can throw on a circular value, so that has to be guarded too, not just capped.
|
|
271
|
+
try {
|
|
272
|
+
responseSummary = JSON.stringify(error.response.data).substring(0, 200);
|
|
273
|
+
} catch {
|
|
274
|
+
responseSummary = '[unserializable response data]';
|
|
275
|
+
}
|
|
265
276
|
}
|
|
277
|
+
logger.error(`Error during getCombinedResultsSummary: ${error.message}`, { responseSummary });
|
|
266
278
|
// Ensure the error is rethrown to propagate it correctly
|
|
267
279
|
throw new Error(error.message || 'Unknown error occurred during getCombinedResultsSummary');
|
|
268
280
|
}
|
|
@@ -3261,8 +3273,7 @@ export default class ResultDataProvider {
|
|
|
3261
3273
|
);
|
|
3262
3274
|
return detailedPoints;
|
|
3263
3275
|
} catch (err: any) {
|
|
3264
|
-
logger.error(
|
|
3265
|
-
logger.error(`Error stack: ${err.stack}`);
|
|
3276
|
+
logger.error('Error during fetching Cross Test Points', err);
|
|
3266
3277
|
return [];
|
|
3267
3278
|
}
|
|
3268
3279
|
}
|
|
@@ -3903,10 +3914,7 @@ export default class ResultDataProvider {
|
|
|
3903
3914
|
relatedCRs,
|
|
3904
3915
|
};
|
|
3905
3916
|
} catch (error: any) {
|
|
3906
|
-
logger.error(
|
|
3907
|
-
if (isTestReporter) {
|
|
3908
|
-
logger.error(`Error stack: ${error.stack}`);
|
|
3909
|
-
}
|
|
3917
|
+
logger.error('Error while fetching run result', error);
|
|
3910
3918
|
return null;
|
|
3911
3919
|
}
|
|
3912
3920
|
}
|
|
@@ -4595,7 +4603,7 @@ export default class ResultDataProvider {
|
|
|
4595
4603
|
? testCase.workItem.workItemFields
|
|
4596
4604
|
: [];
|
|
4597
4605
|
if (testCaseWorkItemFields.length === 0) {
|
|
4598
|
-
logger.warn(`Could not fetch the steps from WI ${
|
|
4606
|
+
logger.warn(`Could not fetch the steps from WI ${testCase.workItem.id}`);
|
|
4599
4607
|
if (!isTestReporter) {
|
|
4600
4608
|
continue;
|
|
4601
4609
|
}
|
|
@@ -5058,8 +5066,7 @@ export default class ResultDataProvider {
|
|
|
5058
5066
|
? createResponseObject(resultData, testSuiteId, point, ...additionalArgs)
|
|
5059
5067
|
: null;
|
|
5060
5068
|
} catch (error: any) {
|
|
5061
|
-
logger.error(`Error occurred for point ${point.testCaseId}
|
|
5062
|
-
logger.error(`Stack trace: ${error.stack}`);
|
|
5069
|
+
logger.error(`Error occurred for point ${point.testCaseId}`, error);
|
|
5063
5070
|
return null;
|
|
5064
5071
|
}
|
|
5065
5072
|
}
|
|
@@ -5153,8 +5160,7 @@ export default class ResultDataProvider {
|
|
|
5153
5160
|
summarizedItemMap.set(wi.id, mappedItem);
|
|
5154
5161
|
}
|
|
5155
5162
|
} catch (error: any) {
|
|
5156
|
-
logger.error(
|
|
5157
|
-
logger.error(`Error Stack: ${error.stack}`);
|
|
5163
|
+
logger.error('Error occurred while fetching linked work items', error);
|
|
5158
5164
|
}
|
|
5159
5165
|
}
|
|
5160
5166
|
|
|
@@ -5826,8 +5832,7 @@ export default class ResultDataProvider {
|
|
|
5826
5832
|
}
|
|
5827
5833
|
return resultDataResponse;
|
|
5828
5834
|
} catch (err: any) {
|
|
5829
|
-
logger.error(
|
|
5830
|
-
logger.error(`Error stack: ${err.stack}`);
|
|
5835
|
+
logger.error('Error occurred while fetching result data', err);
|
|
5831
5836
|
return null;
|
|
5832
5837
|
}
|
|
5833
5838
|
},
|
|
@@ -783,11 +783,9 @@ export default class TicketsDataProvider {
|
|
|
783
783
|
: null;
|
|
784
784
|
} catch (err: any) {
|
|
785
785
|
logger.error(
|
|
786
|
-
`Error occurred while constructing the customer query list
|
|
787
|
-
|
|
788
|
-
)}`,
|
|
786
|
+
`Error occurred while constructing the customer query list with query ${JSON.stringify(rootQuery)}`,
|
|
787
|
+
err,
|
|
789
788
|
);
|
|
790
|
-
logger.error(`Error stack ${err.message}`);
|
|
791
789
|
return null;
|
|
792
790
|
}
|
|
793
791
|
}
|
|
@@ -2697,8 +2695,10 @@ export default class TicketsDataProvider {
|
|
|
2697
2695
|
const subRes = await postBatch(currentIds);
|
|
2698
2696
|
res.push(...subRes.value);
|
|
2699
2697
|
} catch (error) {
|
|
2700
|
-
|
|
2701
|
-
|
|
2698
|
+
// JSON.stringify(error) on an AxiosError calls its own .toJSON(), which serializes
|
|
2699
|
+
// config.auth.password / config.headers.Authorization — the PAT, in plaintext. Pass
|
|
2700
|
+
// the error as meta instead and let the logger's redact format scrub it.
|
|
2701
|
+
logger.error(`error populating workitems array`, error);
|
|
2702
2702
|
return [];
|
|
2703
2703
|
}
|
|
2704
2704
|
}
|
|
@@ -2708,8 +2708,10 @@ export default class TicketsDataProvider {
|
|
|
2708
2708
|
const subRes = await postBatch(currentIds);
|
|
2709
2709
|
res.push(...subRes.value);
|
|
2710
2710
|
} catch (error) {
|
|
2711
|
-
|
|
2712
|
-
|
|
2711
|
+
// JSON.stringify(error) on an AxiosError calls its own .toJSON(), which serializes
|
|
2712
|
+
// config.auth.password / config.headers.Authorization — the PAT, in plaintext. Pass
|
|
2713
|
+
// the error as meta instead and let the logger's redact format scrub it.
|
|
2714
|
+
logger.error(`error populating workitems array`, error);
|
|
2713
2715
|
return [];
|
|
2714
2716
|
}
|
|
2715
2717
|
}
|
|
@@ -2843,8 +2845,9 @@ export default class TicketsDataProvider {
|
|
|
2843
2845
|
);
|
|
2844
2846
|
return attachmentList;
|
|
2845
2847
|
} catch (e) {
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
+
// JSON.stringify(e) on an Error returns "{}" — own enumerable props only, message/stack
|
|
2849
|
+
// are non-enumerable — so this logged literally nothing useful. Pass it as meta instead.
|
|
2850
|
+
logger.error(`error fetching attachments for work item ${id}`, e);
|
|
2848
2851
|
return [];
|
|
2849
2852
|
}
|
|
2850
2853
|
}
|
|
@@ -3161,11 +3164,9 @@ export default class TicketsDataProvider {
|
|
|
3161
3164
|
return { tree1: tree1Node, tree2: tree2Node };
|
|
3162
3165
|
} catch (err: any) {
|
|
3163
3166
|
logger.error(
|
|
3164
|
-
`Error occurred while constructing the query list
|
|
3165
|
-
|
|
3166
|
-
)}`,
|
|
3167
|
+
`Error occurred while constructing the query list with query ${JSON.stringify(rootQuery)}`,
|
|
3168
|
+
err,
|
|
3167
3169
|
);
|
|
3168
|
-
logger.error(`Error stack ${err.message}`);
|
|
3169
3170
|
}
|
|
3170
3171
|
}
|
|
3171
3172
|
|
|
@@ -36,7 +36,9 @@ describe('JfrogDataProvider', () => {
|
|
|
36
36
|
`${mockOrgUrl}${mockTeamProject}/_apis/serviceendpoint/endpoints/${mockConnectionId}?api-version=7.1`,
|
|
37
37
|
mockTfsToken
|
|
38
38
|
);
|
|
39
|
-
|
|
39
|
+
// The log used JSON.stringify(url), which wraps a plain string in quotes for no reason —
|
|
40
|
+
// fixed to interpolate the string directly (see docgen-log-redaction / Phase 2).
|
|
41
|
+
expect(logger.debug).toHaveBeenCalledWith(`service connection url ${mockResponse.url}`);
|
|
40
42
|
expect(result).toBe(mockResponse.url);
|
|
41
43
|
});
|
|
42
44
|
|
|
@@ -6027,8 +6027,12 @@ describe('ResultDataProvider', () => {
|
|
|
6027
6027
|
resultDataProvider.getCombinedResultsSummary(mockTestPlanId, mockProjectName)
|
|
6028
6028
|
).rejects.toThrow('boom');
|
|
6029
6029
|
|
|
6030
|
-
|
|
6031
|
-
|
|
6030
|
+
// Was two separate calls (message, then the response body) — consolidated into one
|
|
6031
|
+
// record with the response summary as meta (docgen-log-redaction / Phase 2/4).
|
|
6032
|
+
expect(logger.error).toHaveBeenCalledWith('Error during getCombinedResultsSummary: boom', {
|
|
6033
|
+
responseSummary: '{"detail":"bad"}',
|
|
6034
|
+
});
|
|
6035
|
+
expect(logger.error).toHaveBeenCalledTimes(1);
|
|
6032
6036
|
});
|
|
6033
6037
|
});
|
|
6034
6038
|
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import * as winston from 'winston';
|
|
2
|
+
import Transport = require('winston-transport');
|
|
3
|
+
import { redact, DiagnosticsTransport } from '../../utils/logger';
|
|
4
|
+
import { installLogSink, LogSink, DiagnosticEvent } from '../../utils/logSink';
|
|
5
|
+
import { runContextStore } from '../../utils/runContext';
|
|
6
|
+
|
|
7
|
+
const applyRedact = (info: Record<string, unknown>) => (redact() as any).transform({ ...info });
|
|
8
|
+
|
|
9
|
+
describe('logger redact format', () => {
|
|
10
|
+
test('scrubs a top-level token/password/secret regardless of key casing', () => {
|
|
11
|
+
const out = applyRedact({ level: 'info', message: 'x', token: 'abc', Password: 'p', SECRET: 's' });
|
|
12
|
+
expect(out.token).toBe('[REDACTED]');
|
|
13
|
+
expect(out.Password).toBe('[REDACTED]');
|
|
14
|
+
expect(out.SECRET).toBe('[REDACTED]');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('scrubs nested config.auth.password and config.headers.Authorization — the AxiosError.toJSON() leak', () => {
|
|
18
|
+
// This is the exact shape that was leaking via logger.error(JSON.stringify(error))
|
|
19
|
+
// in TicketsDataProvider.ts on ADO batch work-item failures.
|
|
20
|
+
const axiosLikeError = {
|
|
21
|
+
message: 'Request failed',
|
|
22
|
+
config: {
|
|
23
|
+
auth: { username: '', password: 'super-secret-pat' },
|
|
24
|
+
headers: { Authorization: 'Bearer abc.def.ghi' },
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
const out: any = applyRedact({ level: 'error', message: 'error populating workitems array', err: axiosLikeError });
|
|
28
|
+
expect(out.err.config.auth.password).toBe('[REDACTED]');
|
|
29
|
+
expect(out.err.config.headers.Authorization).toBe('[REDACTED]');
|
|
30
|
+
expect(out.err.message).toBe('Request failed');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('leaves non-sensitive fields untouched', () => {
|
|
34
|
+
const out = applyRedact({ level: 'info', message: 'ok', project: 'Cube-ADCS', docType: 'SVD' });
|
|
35
|
+
expect(out.project).toBe('Cube-ADCS');
|
|
36
|
+
expect(out.docType).toBe('SVD');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('never throws on a circular object', () => {
|
|
40
|
+
const circular: any = { name: 'x' };
|
|
41
|
+
circular.self = circular;
|
|
42
|
+
expect(() => applyRedact({ level: 'info', message: 'x', circular })).not.toThrow();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('never throws and passes info through on null/undefined meta', () => {
|
|
46
|
+
expect(() => applyRedact({ level: 'info', message: 'x', meta: null })).not.toThrow();
|
|
47
|
+
expect(() => applyRedact({ level: 'info', message: 'x', meta: undefined })).not.toThrow();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// A capturing transport + the exact JSON format chain logger.ts uses (errors -> timestamp ->
|
|
52
|
+
// redact -> splat -> json), so these tests exercise the real pipeline, not just redact() alone.
|
|
53
|
+
class CaptureTransport extends Transport {
|
|
54
|
+
lines: Record<string, unknown>[] = [];
|
|
55
|
+
log(info: Record<string, unknown>, callback: () => void) {
|
|
56
|
+
this.lines.push(JSON.parse((info as any)[Symbol.for('message')] ?? JSON.stringify(info)));
|
|
57
|
+
callback();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function makeTestLogger() {
|
|
61
|
+
const capture = new CaptureTransport();
|
|
62
|
+
const logger = winston.createLogger({
|
|
63
|
+
level: 'silly',
|
|
64
|
+
format: winston.format.combine(
|
|
65
|
+
winston.format.errors({ stack: true }),
|
|
66
|
+
winston.format.timestamp(),
|
|
67
|
+
redact(),
|
|
68
|
+
winston.format.splat(),
|
|
69
|
+
winston.format.json()
|
|
70
|
+
),
|
|
71
|
+
transports: [capture],
|
|
72
|
+
});
|
|
73
|
+
return { logger, capture };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('logger pipeline — errors({stack:true})', () => {
|
|
77
|
+
test('logger.error(err) (single-arg Error) still produces a non-empty message and a stack', () => {
|
|
78
|
+
// This is the regression errors({stack:true}) exists to prevent: without it, winston's
|
|
79
|
+
// single-arg path makes `info` *be* the Error, and message/stack are non-enumerable, so
|
|
80
|
+
// json() would emit {"level":"error","timestamp":"…"} — content-free.
|
|
81
|
+
const { logger, capture } = makeTestLogger();
|
|
82
|
+
logger.error(new Error('boom'));
|
|
83
|
+
expect(capture.lines).toHaveLength(1);
|
|
84
|
+
expect(capture.lines[0].message).toBe('boom');
|
|
85
|
+
expect(typeof capture.lines[0].stack).toBe('string');
|
|
86
|
+
expect((capture.lines[0].stack as string).length).toBeGreaterThan(0);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('logger pipeline — hostile inputs never throw', () => {
|
|
91
|
+
test.each([
|
|
92
|
+
['null', null],
|
|
93
|
+
['undefined', undefined],
|
|
94
|
+
['NaN', NaN],
|
|
95
|
+
['a circular object', (() => { const c: any = { a: 1 }; c.self = c; return c; })()],
|
|
96
|
+
['an object with a throwing getter', { get boom() { throw new Error('nope'); } }],
|
|
97
|
+
['a BigInt', BigInt(9007199254740993)],
|
|
98
|
+
['a Symbol', Symbol('x')],
|
|
99
|
+
['an Error with no message', new Error()],
|
|
100
|
+
['a 10MB string', 'x'.repeat(10 * 1024 * 1024)],
|
|
101
|
+
])('logger.error(%s) does not throw', (_label, value) => {
|
|
102
|
+
const { logger } = makeTestLogger();
|
|
103
|
+
expect(() => logger.error(value as any)).not.toThrow();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('DiagnosticsTransport (Phase 6a)', () => {
|
|
108
|
+
function makeDiagnosticsLogger() {
|
|
109
|
+
const capture = new CaptureTransport();
|
|
110
|
+
const logger = winston.createLogger({
|
|
111
|
+
level: 'silly',
|
|
112
|
+
format: winston.format.combine(
|
|
113
|
+
winston.format.errors({ stack: true }),
|
|
114
|
+
winston.format.timestamp(),
|
|
115
|
+
redact(),
|
|
116
|
+
winston.format.splat(),
|
|
117
|
+
winston.format.json()
|
|
118
|
+
),
|
|
119
|
+
transports: [capture, new DiagnosticsTransport()],
|
|
120
|
+
});
|
|
121
|
+
return { logger, capture };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
test('pushes warn/error records to whatever sink is installed', () => {
|
|
125
|
+
const events: DiagnosticEvent[] = [];
|
|
126
|
+
const sink: LogSink = { push: (e) => events.push(e) };
|
|
127
|
+
installLogSink(sink);
|
|
128
|
+
const { logger } = makeDiagnosticsLogger();
|
|
129
|
+
logger.warn('a stable warning');
|
|
130
|
+
logger.error('a stable error', Object.assign(new Error('boom'), { code: 'ECONN' }));
|
|
131
|
+
expect(events).toHaveLength(2);
|
|
132
|
+
expect(events[0].level).toBe('warn');
|
|
133
|
+
expect(events[1].err?.code).toBe('ECONN');
|
|
134
|
+
expect(events[1].err?.stack).toEqual(expect.any(String));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('does not push info/debug records', () => {
|
|
138
|
+
const events: DiagnosticEvent[] = [];
|
|
139
|
+
installLogSink({ push: (e) => events.push(e) });
|
|
140
|
+
const { logger } = makeDiagnosticsLogger();
|
|
141
|
+
logger.info('not interesting to the dashboard');
|
|
142
|
+
expect(events).toHaveLength(0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test('a throwing sink cannot break the log call', () => {
|
|
146
|
+
installLogSink({
|
|
147
|
+
push: () => {
|
|
148
|
+
throw new Error('sink is down');
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
const { logger } = makeDiagnosticsLogger();
|
|
152
|
+
expect(() => logger.error('still must not throw')).not.toThrow();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test.each([
|
|
156
|
+
['null', null],
|
|
157
|
+
['undefined', undefined],
|
|
158
|
+
['NaN', NaN],
|
|
159
|
+
['a circular object', (() => { const c: any = { a: 1 }; c.self = c; return c; })()],
|
|
160
|
+
['an object with a throwing getter', { get boom() { throw new Error('nope'); } }],
|
|
161
|
+
['a BigInt', BigInt(9007199254740993)],
|
|
162
|
+
['a Symbol', Symbol('x')],
|
|
163
|
+
['an Error with no message', new Error()],
|
|
164
|
+
['a 10MB string', 'x'.repeat(10 * 1024 * 1024)],
|
|
165
|
+
])('logger.error(%s) with a sink installed does not throw', (_label, value) => {
|
|
166
|
+
installLogSink({ push: () => undefined });
|
|
167
|
+
const { logger } = makeDiagnosticsLogger();
|
|
168
|
+
expect(() => logger.error(value as any)).not.toThrow();
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('textFormat safety (regression)', () => {
|
|
173
|
+
// The hostile-input matrix above exercises makeTestLogger()'s json() pipeline — it never
|
|
174
|
+
// touched the real singleton logger's text-format branch (LOG_FORMAT's default), which is
|
|
175
|
+
// where a Symbol message crashed: a template literal's implicit ToString throws on a Symbol,
|
|
176
|
+
// and this formatter sits with no try/catch around it.
|
|
177
|
+
test('logger.error(Symbol) does not throw through the real default-exported logger', () => {
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
179
|
+
const logger = require('../../utils/logger').default;
|
|
180
|
+
expect(() => logger.error(Symbol('x'))).not.toThrow();
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
describe('DiagnosticsTransport capture policy (Phase 6b)', () => {
|
|
185
|
+
function makeDiagnosticsLogger() {
|
|
186
|
+
const capture = new CaptureTransport();
|
|
187
|
+
const logger = winston.createLogger({
|
|
188
|
+
level: 'silly',
|
|
189
|
+
format: winston.format.combine(
|
|
190
|
+
winston.format.errors({ stack: true }),
|
|
191
|
+
winston.format.timestamp(),
|
|
192
|
+
redact(),
|
|
193
|
+
winston.format.splat(),
|
|
194
|
+
winston.format.json()
|
|
195
|
+
),
|
|
196
|
+
transports: [capture, new DiagnosticsTransport()],
|
|
197
|
+
});
|
|
198
|
+
return { logger, capture };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
test('normal mode (no captureMode) does not persist debug/info', () => {
|
|
202
|
+
const events: DiagnosticEvent[] = [];
|
|
203
|
+
installLogSink({ push: (e) => events.push(e) });
|
|
204
|
+
const { logger } = makeDiagnosticsLogger();
|
|
205
|
+
runContextStore.run({ runId: 'run-1' }, () => {
|
|
206
|
+
logger.debug('a debug line');
|
|
207
|
+
logger.info('an info line');
|
|
208
|
+
});
|
|
209
|
+
expect(events).toHaveLength(0);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('verbose mode persists debug and info', () => {
|
|
213
|
+
const events: DiagnosticEvent[] = [];
|
|
214
|
+
installLogSink({ push: (e) => events.push(e) });
|
|
215
|
+
const { logger } = makeDiagnosticsLogger();
|
|
216
|
+
runContextStore.run({ runId: 'run-2', captureMode: 'verbose' }, () => {
|
|
217
|
+
logger.debug('a debug line');
|
|
218
|
+
logger.info('an info line');
|
|
219
|
+
});
|
|
220
|
+
expect(events).toHaveLength(2);
|
|
221
|
+
expect(events[0].retainPending).toBeUndefined();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('warn/error persist regardless of capture mode', () => {
|
|
225
|
+
const events: DiagnosticEvent[] = [];
|
|
226
|
+
installLogSink({ push: (e) => events.push(e) });
|
|
227
|
+
const { logger } = makeDiagnosticsLogger();
|
|
228
|
+
runContextStore.run({ runId: 'run-3' }, () => {
|
|
229
|
+
logger.warn('a warning');
|
|
230
|
+
logger.error('an error');
|
|
231
|
+
});
|
|
232
|
+
expect(events).toHaveLength(2);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('retain-on-failure persists debug/info tagged retainPending: true', () => {
|
|
236
|
+
const events: DiagnosticEvent[] = [];
|
|
237
|
+
installLogSink({ push: (e) => events.push(e) });
|
|
238
|
+
const { logger } = makeDiagnosticsLogger();
|
|
239
|
+
runContextStore.run({ runId: 'run-4', captureMode: 'retain-on-failure' }, () => {
|
|
240
|
+
logger.debug('a debug line');
|
|
241
|
+
});
|
|
242
|
+
expect(events).toHaveLength(1);
|
|
243
|
+
expect(events[0].retainPending).toBe(true);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('retain-on-failure does not tag warn/error as retainPending', () => {
|
|
247
|
+
const events: DiagnosticEvent[] = [];
|
|
248
|
+
installLogSink({ push: (e) => events.push(e) });
|
|
249
|
+
const { logger } = makeDiagnosticsLogger();
|
|
250
|
+
runContextStore.run({ runId: 'run-5', captureMode: 'retain-on-failure' }, () => {
|
|
251
|
+
logger.error('an error');
|
|
252
|
+
});
|
|
253
|
+
expect(events).toHaveLength(1);
|
|
254
|
+
expect(events[0].retainPending).toBeUndefined();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test('a concurrent normal-mode run is unaffected by a sibling verbose run', () => {
|
|
258
|
+
const events: DiagnosticEvent[] = [];
|
|
259
|
+
installLogSink({ push: (e) => events.push(e) });
|
|
260
|
+
const { logger } = makeDiagnosticsLogger();
|
|
261
|
+
runContextStore.run({ runId: 'verbose-run', captureMode: 'verbose' }, () => {
|
|
262
|
+
logger.debug('verbose debug');
|
|
263
|
+
});
|
|
264
|
+
runContextStore.run({ runId: 'normal-run' }, () => {
|
|
265
|
+
logger.debug('normal debug');
|
|
266
|
+
});
|
|
267
|
+
expect(events).toHaveLength(1);
|
|
268
|
+
expect(events[0].message).toBe('verbose debug');
|
|
269
|
+
});
|
|
270
|
+
});
|