autotel 2.26.0 → 2.26.1
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/dist/attribute-redacting-processor.cjs +14 -6
- package/dist/attribute-redacting-processor.d.cts +63 -1
- package/dist/attribute-redacting-processor.d.ts +63 -1
- package/dist/attribute-redacting-processor.js +1 -1
- package/dist/attributes.cjs +21 -21
- package/dist/attributes.js +2 -2
- package/dist/auto.cjs +8 -8
- package/dist/auto.js +6 -6
- package/dist/{chunk-RUD7KS4R.js → chunk-3SDILILG.js} +3 -3
- package/dist/{chunk-RUD7KS4R.js.map → chunk-3SDILILG.js.map} +1 -1
- package/dist/{chunk-B33XPEKY.js → chunk-55ER2KD5.js} +4 -4
- package/dist/chunk-55ER2KD5.js.map +1 -0
- package/dist/{chunk-UJJPTSEI.cjs → chunk-563EL6O6.cjs} +81 -14
- package/dist/chunk-563EL6O6.cjs.map +1 -0
- package/dist/{chunk-TS7IHIRW.cjs → chunk-6YGUN7IY.cjs} +5 -5
- package/dist/{chunk-TS7IHIRW.cjs.map → chunk-6YGUN7IY.cjs.map} +1 -1
- package/dist/{chunk-XDKK53OL.js → chunk-A4E5AQFK.js} +3 -3
- package/dist/{chunk-XDKK53OL.js.map → chunk-A4E5AQFK.js.map} +1 -1
- package/dist/{chunk-WAB4CHBU.js → chunk-BJ2XPN77.js} +3 -3
- package/dist/{chunk-WAB4CHBU.js.map → chunk-BJ2XPN77.js.map} +1 -1
- package/dist/{chunk-KZEC4CHV.cjs → chunk-CEAQK2QY.cjs} +5 -5
- package/dist/{chunk-KZEC4CHV.cjs.map → chunk-CEAQK2QY.cjs.map} +1 -1
- package/dist/chunk-CMNGGTQL.cjs +349 -0
- package/dist/chunk-CMNGGTQL.cjs.map +1 -0
- package/dist/{chunk-VYA6QDNA.js → chunk-DPSA4QLA.js} +4 -2
- package/dist/chunk-DPSA4QLA.js.map +1 -0
- package/dist/{chunk-M4US3P4K.js → chunk-ER43K7ES.js} +3 -3
- package/dist/{chunk-M4US3P4K.js.map → chunk-ER43K7ES.js.map} +1 -1
- package/dist/{chunk-AZ24DJAG.cjs → chunk-FU6R566Y.cjs} +4 -4
- package/dist/chunk-FU6R566Y.cjs.map +1 -0
- package/dist/{chunk-4PTCDOZY.js → chunk-HPUGKUMZ.js} +4 -4
- package/dist/{chunk-4PTCDOZY.js.map → chunk-HPUGKUMZ.js.map} +1 -1
- package/dist/{chunk-XRBP4RYL.cjs → chunk-JKIMEPI2.cjs} +4 -4
- package/dist/{chunk-XRBP4RYL.cjs.map → chunk-JKIMEPI2.cjs.map} +1 -1
- package/dist/{chunk-N344PVE5.cjs → chunk-OBWXM4NN.cjs} +9 -9
- package/dist/{chunk-N344PVE5.cjs.map → chunk-OBWXM4NN.cjs.map} +1 -1
- package/dist/{chunk-OFPZULMQ.cjs → chunk-OC6X2VIN.cjs} +8 -8
- package/dist/{chunk-OFPZULMQ.cjs.map → chunk-OC6X2VIN.cjs.map} +1 -1
- package/dist/{chunk-GTD3NXOS.js → chunk-QC5MNKVF.js} +4 -4
- package/dist/{chunk-GTD3NXOS.js.map → chunk-QC5MNKVF.js.map} +1 -1
- package/dist/chunk-TDNKIHKT.js +341 -0
- package/dist/chunk-TDNKIHKT.js.map +1 -0
- package/dist/{chunk-DGPUZ6TE.js → chunk-U54FTVFH.js} +3 -3
- package/dist/{chunk-DGPUZ6TE.js.map → chunk-U54FTVFH.js.map} +1 -1
- package/dist/{chunk-ZJ5GXCOT.cjs → chunk-UTZR7P7E.cjs} +36 -36
- package/dist/{chunk-ZJ5GXCOT.cjs.map → chunk-UTZR7P7E.cjs.map} +1 -1
- package/dist/{chunk-7FIGORWI.cjs → chunk-VH77IPJN.cjs} +4 -2
- package/dist/chunk-VH77IPJN.cjs.map +1 -0
- package/dist/{chunk-EXOXDI5A.js → chunk-W35FVJBC.js} +73 -8
- package/dist/chunk-W35FVJBC.js.map +1 -0
- package/dist/{chunk-II7GFVAF.cjs → chunk-WZOKY3PW.cjs} +13 -13
- package/dist/{chunk-II7GFVAF.cjs.map → chunk-WZOKY3PW.cjs.map} +1 -1
- package/dist/{chunk-CMADDTHY.cjs → chunk-YEVCD6DR.cjs} +7 -7
- package/dist/{chunk-CMADDTHY.cjs.map → chunk-YEVCD6DR.cjs.map} +1 -1
- package/dist/{chunk-RXFZKLRQ.js → chunk-YN7USLHW.js} +3 -3
- package/dist/{chunk-RXFZKLRQ.js.map → chunk-YN7USLHW.js.map} +1 -1
- package/dist/decorators.cjs +7 -7
- package/dist/decorators.js +7 -7
- package/dist/event.cjs +10 -10
- package/dist/event.js +7 -7
- package/dist/functional.cjs +14 -14
- package/dist/functional.js +7 -7
- package/dist/index.cjs +214 -97
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +141 -33
- package/dist/index.js.map +1 -1
- package/dist/{init-QSj7X6zU.d.cts → init-CMuTaFAV.d.cts} +26 -1
- package/dist/{init-FiR_glVc.d.ts → init-D6JfWEjL.d.ts} +26 -1
- package/dist/instrumentation.cjs +14 -14
- package/dist/instrumentation.js +6 -6
- package/dist/logger.cjs +8 -8
- package/dist/logger.js +1 -1
- package/dist/messaging.cjs +11 -11
- package/dist/messaging.js +8 -8
- package/dist/metric.cjs +1 -1
- package/dist/metric.js +1 -1
- package/dist/sampling.cjs +15 -15
- package/dist/sampling.js +2 -2
- package/dist/semantic-helpers.cjs +12 -12
- package/dist/semantic-helpers.js +8 -8
- package/dist/tail-sampling-processor.cjs +4 -4
- package/dist/tail-sampling-processor.js +3 -3
- package/dist/testing.cjs +1 -1
- package/dist/testing.js +1 -1
- package/dist/webhook.cjs +9 -8
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.js +8 -7
- package/dist/webhook.js.map +1 -1
- package/dist/workflow-distributed.cjs +9 -9
- package/dist/workflow-distributed.js +7 -7
- package/dist/workflow.cjs +12 -12
- package/dist/workflow.js +8 -8
- package/dist/yaml-config.cjs +6 -6
- package/dist/yaml-config.d.cts +1 -1
- package/dist/yaml-config.d.ts +1 -1
- package/dist/yaml-config.js +3 -3
- package/package.json +1 -1
- package/src/attribute-redacting-processor.test.ts +81 -16
- package/src/attribute-redacting-processor.ts +278 -24
- package/src/autotel-logger.ts +2 -2
- package/src/index.ts +2 -1
- package/src/init.ts +117 -2
- package/src/request-logger.test.ts +266 -1
- package/src/request-logger.ts +115 -16
- package/src/structured-error.ts +54 -1
- package/dist/chunk-7FIGORWI.cjs.map +0 -1
- package/dist/chunk-AZ24DJAG.cjs.map +0 -1
- package/dist/chunk-B33XPEKY.js.map +0 -1
- package/dist/chunk-ELW34S4C.cjs +0 -173
- package/dist/chunk-ELW34S4C.cjs.map +0 -1
- package/dist/chunk-EXOXDI5A.js.map +0 -1
- package/dist/chunk-SNINLBEE.js +0 -167
- package/dist/chunk-SNINLBEE.js.map +0 -1
- package/dist/chunk-UJJPTSEI.cjs.map +0 -1
- package/dist/chunk-VYA6QDNA.js.map +0 -1
|
@@ -46,6 +46,11 @@ export type AttributeRedactorFn = (
|
|
|
46
46
|
*/
|
|
47
47
|
export type AttributeRedactorPreset = 'default' | 'strict' | 'pci-dss';
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Masker function type - receives the matched string and returns a masked version
|
|
51
|
+
*/
|
|
52
|
+
export type MaskFn = (match: string) => string;
|
|
53
|
+
|
|
49
54
|
/**
|
|
50
55
|
* Value pattern configuration
|
|
51
56
|
*/
|
|
@@ -56,8 +61,15 @@ export interface ValuePatternConfig {
|
|
|
56
61
|
pattern: RegExp;
|
|
57
62
|
/** Custom replacement (default: uses global replacement) */
|
|
58
63
|
replacement?: string;
|
|
64
|
+
/** Mask function for smart partial masking (overrides replacement) */
|
|
65
|
+
mask?: MaskFn;
|
|
59
66
|
}
|
|
60
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Built-in PII pattern names
|
|
70
|
+
*/
|
|
71
|
+
export type BuiltinPatternName = keyof typeof builtinPatterns;
|
|
72
|
+
|
|
61
73
|
/**
|
|
62
74
|
* Attribute redactor configuration
|
|
63
75
|
*/
|
|
@@ -68,6 +80,15 @@ export interface AttributeRedactorConfig {
|
|
|
68
80
|
/** Patterns to match against attribute values (redacts matched portion) */
|
|
69
81
|
valuePatterns?: ValuePatternConfig[];
|
|
70
82
|
|
|
83
|
+
/** Dot-notation paths to redact (e.g. 'user.password', 'payment.card') */
|
|
84
|
+
paths?: string[];
|
|
85
|
+
|
|
86
|
+
/** Built-in PII patterns to enable. `true` enables all, `false` disables all, array selects specific ones. */
|
|
87
|
+
builtins?: boolean | BuiltinPatternName[];
|
|
88
|
+
|
|
89
|
+
/** Custom RegExp patterns for string-level redaction */
|
|
90
|
+
patterns?: RegExp[];
|
|
91
|
+
|
|
71
92
|
/** Default replacement string (default: '[REDACTED]') */
|
|
72
93
|
replacement?: string;
|
|
73
94
|
|
|
@@ -100,14 +121,112 @@ export const REDACTOR_PATTERNS = {
|
|
|
100
121
|
/^(password|passwd|pwd|secret|token|api[_-]?key|auth|credential|private[_-]?key|authorization)$/i,
|
|
101
122
|
} as const;
|
|
102
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Built-in PII detection patterns with smart masking.
|
|
126
|
+
* Each builtin preserves just enough signal for debugging while scrubbing PII.
|
|
127
|
+
*/
|
|
128
|
+
export const builtinPatterns = {
|
|
129
|
+
/** Credit card numbers → ****1111 (PCI DSS: last 4 allowed) */
|
|
130
|
+
creditCard: {
|
|
131
|
+
pattern: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g,
|
|
132
|
+
mask: (m: string) => `****${m.replace(/[\s-]/g, '').slice(-4)}`,
|
|
133
|
+
},
|
|
134
|
+
/** Email addresses → a***@***.com */
|
|
135
|
+
email: {
|
|
136
|
+
pattern: /[\w.+-]+@[\w-]+\.[\w.]+/g,
|
|
137
|
+
mask: (m: string) => {
|
|
138
|
+
const at = m.indexOf('@');
|
|
139
|
+
if (at < 1) return '***@***';
|
|
140
|
+
const tld = m.slice(m.lastIndexOf('.'));
|
|
141
|
+
return `${m[0]}***@***${tld}`;
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
/** IPv4 addresses → ***.***.***.100 (last octet only) */
|
|
145
|
+
ipv4: {
|
|
146
|
+
pattern:
|
|
147
|
+
/\b(?!0\.0\.0\.0\b)(?!127\.0\.0\.1\b)\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g,
|
|
148
|
+
mask: (m: string) => `***.***.***.${m.split('.').pop()}`,
|
|
149
|
+
},
|
|
150
|
+
/** International phone numbers → +33******78 (country code + last 2 digits) */
|
|
151
|
+
phone: {
|
|
152
|
+
pattern:
|
|
153
|
+
/(?:\+\d{1,3}[\s.-]?)?\(?\d{1,4}\)?[\s.-]?\d{2,4}[\s.-]?\d{2,4}[\s.-]?\d{2,4}\b/g,
|
|
154
|
+
mask: (m: string) => {
|
|
155
|
+
const digits = m.replace(/[^\d]/g, '');
|
|
156
|
+
const hasPlus = m.startsWith('+');
|
|
157
|
+
if (hasPlus && digits.length > 4) {
|
|
158
|
+
const ccMatch = m.match(/^\+\d{1,3}/);
|
|
159
|
+
const cc = ccMatch ? ccMatch[0] : '+';
|
|
160
|
+
return `${cc}******${digits.slice(-2)}`;
|
|
161
|
+
}
|
|
162
|
+
if (digits.length > 2) {
|
|
163
|
+
return `${'*'.repeat(digits.length - 2)}${digits.slice(-2)}`;
|
|
164
|
+
}
|
|
165
|
+
return '***';
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
/** JWT tokens → eyJ***.*** */
|
|
169
|
+
jwt: {
|
|
170
|
+
pattern: /\beyJ[\w-]*\.[\w-]*\.[\w-]*\b/g,
|
|
171
|
+
mask: () => 'eyJ***.***',
|
|
172
|
+
},
|
|
173
|
+
/** Bearer tokens → Bearer *** */
|
|
174
|
+
bearer: {
|
|
175
|
+
pattern: /\bBearer\s+[\w\-.~+/]{8,}=*/gi,
|
|
176
|
+
mask: () => 'Bearer ***',
|
|
177
|
+
},
|
|
178
|
+
/** IBAN → FR76****189 (country + check digits + last 3) */
|
|
179
|
+
iban: {
|
|
180
|
+
pattern:
|
|
181
|
+
/\b[A-Z]{2}\d{2}[\s-]?[\dA-Z]{4}[\s-]?[\dA-Z]{4}[\s-]?[\dA-Z]{4}[\s-]?[\dA-Z]{0,4}[\s-]?[\dA-Z]{0,4}[\s-]?[\dA-Z]{0,4}\b/g,
|
|
182
|
+
mask: (m: string) => {
|
|
183
|
+
const clean = m.replace(/[\s-]/g, '');
|
|
184
|
+
return `${clean.slice(0, 4)}****${clean.slice(-3)}`;
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
} as const;
|
|
188
|
+
|
|
189
|
+
function cloneRegex(re: RegExp): RegExp {
|
|
190
|
+
return new RegExp(re.source, re.flags);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
194
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function toRegExp(value: unknown): RegExp | undefined {
|
|
198
|
+
if (value instanceof RegExp) return value;
|
|
199
|
+
if (typeof value === 'string') return new RegExp(value, 'g');
|
|
200
|
+
if (isPlainObject(value) && typeof value.source === 'string') {
|
|
201
|
+
const flags = typeof value.flags === 'string' ? value.flags : 'g';
|
|
202
|
+
return new RegExp(value.source, flags);
|
|
203
|
+
}
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function toRegExpArray(value: unknown): RegExp[] | undefined {
|
|
208
|
+
if (!Array.isArray(value)) return undefined;
|
|
209
|
+
const out: RegExp[] = [];
|
|
210
|
+
for (const item of value) {
|
|
211
|
+
const re = toRegExp(item);
|
|
212
|
+
if (re) out.push(re);
|
|
213
|
+
}
|
|
214
|
+
return out.length > 0 ? out : [];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function builtinToValuePattern(name: BuiltinPatternName): ValuePatternConfig {
|
|
218
|
+
const b = builtinPatterns[name];
|
|
219
|
+
return { name, pattern: cloneRegex(b.pattern), mask: b.mask };
|
|
220
|
+
}
|
|
221
|
+
|
|
103
222
|
/**
|
|
104
223
|
* Default value patterns for the 'default' preset
|
|
105
224
|
*/
|
|
106
225
|
const DEFAULT_VALUE_PATTERNS: ValuePatternConfig[] = [
|
|
107
|
-
|
|
108
|
-
|
|
226
|
+
builtinToValuePattern('email'),
|
|
227
|
+
builtinToValuePattern('phone'),
|
|
109
228
|
{ name: 'ssn', pattern: REDACTOR_PATTERNS.ssn },
|
|
110
|
-
|
|
229
|
+
builtinToValuePattern('creditCard'),
|
|
111
230
|
];
|
|
112
231
|
|
|
113
232
|
/**
|
|
@@ -118,61 +237,158 @@ export const REDACTOR_PRESETS: Record<
|
|
|
118
237
|
AttributeRedactorConfig
|
|
119
238
|
> = {
|
|
120
239
|
/**
|
|
121
|
-
* Default preset - covers common PII patterns
|
|
122
|
-
* Detects: emails, phone numbers, SSNs, credit cards
|
|
240
|
+
* Default preset - covers common PII patterns with smart masking
|
|
241
|
+
* Detects: emails (a***@***.com), phone numbers, SSNs, credit cards (****1111)
|
|
123
242
|
* Redacts keys: password, secret, token, apiKey, auth, credential
|
|
124
243
|
*/
|
|
125
244
|
default: {
|
|
126
245
|
keyPatterns: [REDACTOR_PATTERNS.sensitiveKey],
|
|
127
246
|
valuePatterns: DEFAULT_VALUE_PATTERNS,
|
|
247
|
+
builtins: true,
|
|
128
248
|
replacement: '[REDACTED]',
|
|
129
249
|
},
|
|
130
250
|
|
|
131
251
|
/**
|
|
132
252
|
* Strict preset - more aggressive redaction for high-security environments
|
|
133
|
-
* Includes everything in default plus: Bearer tokens, JWTs, API keys in values
|
|
253
|
+
* Includes everything in default plus: Bearer tokens, JWTs, IBAN, API keys in values
|
|
134
254
|
*/
|
|
135
255
|
strict: {
|
|
136
256
|
keyPatterns: [REDACTOR_PATTERNS.sensitiveKey, /bearer/i, /jwt/i],
|
|
137
257
|
valuePatterns: [
|
|
138
258
|
...DEFAULT_VALUE_PATTERNS,
|
|
139
|
-
|
|
259
|
+
builtinToValuePattern('jwt'),
|
|
260
|
+
builtinToValuePattern('bearer'),
|
|
261
|
+
builtinToValuePattern('iban'),
|
|
140
262
|
{ name: 'apiKeyInValue', pattern: REDACTOR_PATTERNS.apiKeyInValue },
|
|
141
|
-
{ name: 'jwt', pattern: REDACTOR_PATTERNS.jwt },
|
|
142
263
|
],
|
|
264
|
+
builtins: true,
|
|
143
265
|
replacement: '[REDACTED]',
|
|
144
266
|
},
|
|
145
267
|
|
|
146
268
|
/**
|
|
147
269
|
* PCI-DSS preset - focused on payment card industry compliance
|
|
148
|
-
* Redacts: credit card numbers, CVV-like patterns, card-related keys
|
|
270
|
+
* Redacts: credit card numbers (****1111), CVV-like patterns, card-related keys
|
|
149
271
|
*/
|
|
150
272
|
'pci-dss': {
|
|
151
273
|
keyPatterns: [/card/i, /cvv/i, /cvc/i, /pan/i, /expir/i, /ccn/i],
|
|
152
|
-
valuePatterns: [
|
|
153
|
-
|
|
154
|
-
],
|
|
274
|
+
valuePatterns: [builtinToValuePattern('creditCard')],
|
|
275
|
+
builtins: ['creditCard'],
|
|
155
276
|
replacement: '[REDACTED]',
|
|
156
277
|
},
|
|
157
278
|
};
|
|
158
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Normalize redactor config that may have been deserialized from JSON/YAML.
|
|
282
|
+
* Converts regex-like values back to RegExp instances.
|
|
283
|
+
*/
|
|
284
|
+
export function normalizeAttributeRedactorConfig(
|
|
285
|
+
raw: AttributeRedactorConfig | AttributeRedactorPreset | unknown,
|
|
286
|
+
): AttributeRedactorConfig | AttributeRedactorPreset | undefined {
|
|
287
|
+
if (raw === undefined || raw === null) return undefined;
|
|
288
|
+
if (typeof raw === 'string') return raw as AttributeRedactorPreset;
|
|
289
|
+
if (!isPlainObject(raw)) return undefined;
|
|
290
|
+
|
|
291
|
+
const config: AttributeRedactorConfig = {};
|
|
292
|
+
|
|
293
|
+
if (Array.isArray(raw.paths)) {
|
|
294
|
+
config.paths = raw.paths.filter(
|
|
295
|
+
(value): value is string => typeof value === 'string',
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (typeof raw.replacement === 'string') {
|
|
300
|
+
config.replacement = raw.replacement;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (typeof raw.builtins === 'boolean') {
|
|
304
|
+
config.builtins = raw.builtins;
|
|
305
|
+
} else if (Array.isArray(raw.builtins)) {
|
|
306
|
+
config.builtins = raw.builtins.filter(
|
|
307
|
+
(name): name is BuiltinPatternName => typeof name === 'string',
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (typeof raw.redactor === 'function') {
|
|
312
|
+
config.redactor = raw.redactor as AttributeRedactorFn;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const keyPatterns = toRegExpArray(raw.keyPatterns);
|
|
316
|
+
if (keyPatterns) config.keyPatterns = keyPatterns;
|
|
317
|
+
|
|
318
|
+
const patterns = toRegExpArray(raw.patterns);
|
|
319
|
+
if (patterns) config.patterns = patterns;
|
|
320
|
+
|
|
321
|
+
if (Array.isArray(raw.valuePatterns)) {
|
|
322
|
+
const valuePatterns: ValuePatternConfig[] = [];
|
|
323
|
+
for (const item of raw.valuePatterns) {
|
|
324
|
+
if (!isPlainObject(item) || typeof item.name !== 'string') continue;
|
|
325
|
+
const pattern = toRegExp(item.pattern);
|
|
326
|
+
if (!pattern) continue;
|
|
327
|
+
valuePatterns.push({
|
|
328
|
+
name: item.name,
|
|
329
|
+
pattern,
|
|
330
|
+
replacement:
|
|
331
|
+
typeof item.replacement === 'string' ? item.replacement : undefined,
|
|
332
|
+
mask:
|
|
333
|
+
typeof item.mask === 'function' ? (item.mask as MaskFn) : undefined,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
config.valuePatterns = valuePatterns;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return config;
|
|
340
|
+
}
|
|
341
|
+
|
|
159
342
|
/**
|
|
160
343
|
* Resolve config to a normalized form
|
|
161
344
|
*/
|
|
162
345
|
function resolveConfig(
|
|
163
346
|
config: AttributeRedactorConfig | AttributeRedactorPreset,
|
|
164
347
|
): AttributeRedactorConfig {
|
|
165
|
-
|
|
166
|
-
|
|
348
|
+
const normalized = normalizeAttributeRedactorConfig(config);
|
|
349
|
+
if (!normalized) {
|
|
350
|
+
throw new Error('Invalid attribute redactor config');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (typeof normalized === 'string') {
|
|
354
|
+
const preset = REDACTOR_PRESETS[normalized];
|
|
167
355
|
if (!preset) {
|
|
168
356
|
throw new Error(
|
|
169
|
-
`Unknown attribute redactor preset: "${
|
|
357
|
+
`Unknown attribute redactor preset: "${normalized}". ` +
|
|
170
358
|
`Available presets: ${Object.keys(REDACTOR_PRESETS).join(', ')}`,
|
|
171
359
|
);
|
|
172
360
|
}
|
|
173
361
|
return preset;
|
|
174
362
|
}
|
|
175
|
-
|
|
363
|
+
|
|
364
|
+
const resolvedConfig: AttributeRedactorConfig = {
|
|
365
|
+
...normalized,
|
|
366
|
+
keyPatterns: normalized.keyPatterns
|
|
367
|
+
? [...normalized.keyPatterns]
|
|
368
|
+
: undefined,
|
|
369
|
+
valuePatterns: normalized.valuePatterns
|
|
370
|
+
? [...normalized.valuePatterns]
|
|
371
|
+
: undefined,
|
|
372
|
+
paths: normalized.paths ? [...normalized.paths] : undefined,
|
|
373
|
+
patterns: normalized.patterns ? [...normalized.patterns] : undefined,
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// Merge built-in patterns if enabled
|
|
377
|
+
if (resolvedConfig.builtins !== false) {
|
|
378
|
+
const builtinNames = Array.isArray(resolvedConfig.builtins)
|
|
379
|
+
? resolvedConfig.builtins
|
|
380
|
+
: (Object.keys(builtinPatterns) as BuiltinPatternName[]);
|
|
381
|
+
const builtinValuePatterns = builtinNames
|
|
382
|
+
.filter((name) => name in builtinPatterns)
|
|
383
|
+
.map(builtinToValuePattern);
|
|
384
|
+
|
|
385
|
+
resolvedConfig.valuePatterns = [
|
|
386
|
+
...(resolvedConfig.valuePatterns ?? []),
|
|
387
|
+
...builtinValuePatterns,
|
|
388
|
+
];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return resolvedConfig;
|
|
176
392
|
}
|
|
177
393
|
|
|
178
394
|
/**
|
|
@@ -188,27 +404,40 @@ function createRedactorFromConfig(
|
|
|
188
404
|
|
|
189
405
|
const keyPatterns = config.keyPatterns ?? [];
|
|
190
406
|
const valuePatterns = config.valuePatterns ?? [];
|
|
407
|
+
const paths = config.paths ?? [];
|
|
408
|
+
const pathSet = new Set(paths);
|
|
409
|
+
const customPatterns = config.patterns ?? [];
|
|
191
410
|
const defaultReplacement = config.replacement ?? '[REDACTED]';
|
|
192
411
|
|
|
412
|
+
// Build masker list from valuePatterns that have mask functions
|
|
413
|
+
const maskers: [RegExp, MaskFn][] = valuePatterns
|
|
414
|
+
.filter((vp) => vp.mask)
|
|
415
|
+
.map((vp) => [cloneRegex(vp.pattern), vp.mask!]);
|
|
416
|
+
|
|
193
417
|
return (key: string, value: AttributeValue): AttributeValue => {
|
|
194
418
|
// Check if key matches any sensitive key pattern
|
|
195
419
|
for (const pattern of keyPatterns) {
|
|
196
|
-
// Reset lastIndex for global regexes
|
|
197
420
|
pattern.lastIndex = 0;
|
|
198
421
|
if (pattern.test(key)) {
|
|
199
422
|
return defaultReplacement;
|
|
200
423
|
}
|
|
201
424
|
}
|
|
202
425
|
|
|
203
|
-
//
|
|
426
|
+
// Check if key matches any path-based redaction
|
|
427
|
+
if (pathSet.has(key)) {
|
|
428
|
+
return defaultReplacement;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// For non-string values, return as-is
|
|
204
432
|
if (typeof value !== 'string') {
|
|
205
|
-
// Handle arrays of strings
|
|
206
433
|
if (Array.isArray(value)) {
|
|
207
434
|
return value.map((item) => {
|
|
208
435
|
if (typeof item === 'string') {
|
|
209
436
|
return redactStringValue(
|
|
210
437
|
item,
|
|
211
438
|
valuePatterns,
|
|
439
|
+
maskers,
|
|
440
|
+
customPatterns,
|
|
212
441
|
defaultReplacement,
|
|
213
442
|
) as string;
|
|
214
443
|
}
|
|
@@ -218,25 +447,50 @@ function createRedactorFromConfig(
|
|
|
218
447
|
return value;
|
|
219
448
|
}
|
|
220
449
|
|
|
221
|
-
//
|
|
222
|
-
return redactStringValue(
|
|
450
|
+
// Three-tier strategy: path-based → masker-based → pattern-based
|
|
451
|
+
return redactStringValue(
|
|
452
|
+
value,
|
|
453
|
+
valuePatterns,
|
|
454
|
+
maskers,
|
|
455
|
+
customPatterns,
|
|
456
|
+
defaultReplacement,
|
|
457
|
+
);
|
|
223
458
|
};
|
|
224
459
|
}
|
|
225
460
|
|
|
226
461
|
/**
|
|
227
|
-
* Apply
|
|
462
|
+
* Apply three-tier redaction strategy to a string
|
|
463
|
+
* 1. Masker-based: built-in patterns with smart partial masking
|
|
464
|
+
* 2. Pattern-based: custom RegExp patterns replaced with replacement
|
|
228
465
|
*/
|
|
229
466
|
function redactStringValue(
|
|
230
467
|
value: string,
|
|
231
468
|
patterns: ValuePatternConfig[],
|
|
469
|
+
maskers: [RegExp, MaskFn][],
|
|
470
|
+
customPatterns: RegExp[],
|
|
232
471
|
defaultReplacement: string,
|
|
233
472
|
): string {
|
|
234
473
|
let result = value;
|
|
235
|
-
|
|
236
|
-
|
|
474
|
+
|
|
475
|
+
// Tier 1: Apply maskers (smart partial masking)
|
|
476
|
+
for (const [pattern, mask] of maskers) {
|
|
477
|
+
pattern.lastIndex = 0;
|
|
478
|
+
result = result.replace(pattern, mask);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Tier 2: Apply value patterns without mask (full replacement)
|
|
482
|
+
for (const { pattern, replacement, mask } of patterns) {
|
|
483
|
+
if (mask) continue; // Already handled by maskers
|
|
237
484
|
pattern.lastIndex = 0;
|
|
238
485
|
result = result.replaceAll(pattern, replacement ?? defaultReplacement);
|
|
239
486
|
}
|
|
487
|
+
|
|
488
|
+
// Tier 3: Apply custom patterns
|
|
489
|
+
for (const pattern of customPatterns) {
|
|
490
|
+
pattern.lastIndex = 0;
|
|
491
|
+
result = result.replaceAll(pattern, defaultReplacement);
|
|
492
|
+
}
|
|
493
|
+
|
|
240
494
|
return result;
|
|
241
495
|
}
|
|
242
496
|
|
package/src/autotel-logger.ts
CHANGED
|
@@ -232,7 +232,7 @@ export function createBuiltinLogger(
|
|
|
232
232
|
log(level, extraOrMessage, message as Record<string, unknown>);
|
|
233
233
|
} else {
|
|
234
234
|
// Pure string-only call: logger.info('message')
|
|
235
|
-
log(level, extraOrMessage
|
|
235
|
+
log(level, extraOrMessage);
|
|
236
236
|
}
|
|
237
237
|
} else {
|
|
238
238
|
// Pino style: logger.info({ extra }, 'message')
|
|
@@ -303,7 +303,7 @@ export function createBuiltinLogger(
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
// Pure string-only call: logger.error('message')
|
|
306
|
-
log('error', extraOrMessage
|
|
306
|
+
log('error', extraOrMessage);
|
|
307
307
|
return;
|
|
308
308
|
}
|
|
309
309
|
|
package/src/index.ts
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
31
|
// Core initialization
|
|
32
|
-
export { init, type AutotelConfig } from './init';
|
|
32
|
+
export { init, lockLogger, isLoggerLocked, type AutotelConfig } from './init';
|
|
33
33
|
|
|
34
34
|
// Baggage span processor
|
|
35
35
|
export {
|
|
@@ -126,6 +126,7 @@ export {
|
|
|
126
126
|
// Structured errors
|
|
127
127
|
export {
|
|
128
128
|
createStructuredError,
|
|
129
|
+
structuredErrorToJSON,
|
|
129
130
|
getStructuredErrorAttributes,
|
|
130
131
|
recordStructuredError,
|
|
131
132
|
type StructuredError,
|
package/src/init.ts
CHANGED
|
@@ -60,6 +60,7 @@ import {
|
|
|
60
60
|
} from './span-name-normalizer';
|
|
61
61
|
import {
|
|
62
62
|
AttributeRedactingProcessor,
|
|
63
|
+
normalizeAttributeRedactorConfig,
|
|
63
64
|
type AttributeRedactorConfig,
|
|
64
65
|
type AttributeRedactorPreset,
|
|
65
66
|
} from './attribute-redacting-processor';
|
|
@@ -1142,10 +1143,28 @@ export interface AutotelConfig {
|
|
|
1142
1143
|
*/
|
|
1143
1144
|
pretty?: boolean;
|
|
1144
1145
|
};
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* Suppress console output while keeping OTel exporters running.
|
|
1149
|
+
* Useful for platforms like GCP Cloud Run / AWS Lambda where stdout
|
|
1150
|
+
* is managed externally by the platform's log collector.
|
|
1151
|
+
*
|
|
1152
|
+
* @default false
|
|
1153
|
+
*/
|
|
1154
|
+
silent?: boolean;
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* Minimum log level for internal autotel diagnostic messages.
|
|
1158
|
+
* Messages below this level are dropped before processing.
|
|
1159
|
+
*
|
|
1160
|
+
* @default 'info'
|
|
1161
|
+
*/
|
|
1162
|
+
minLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
1145
1163
|
}
|
|
1146
1164
|
|
|
1147
1165
|
// Internal state
|
|
1148
1166
|
let initialized = false;
|
|
1167
|
+
let locked = false;
|
|
1149
1168
|
let config: AutotelConfig | null = null;
|
|
1150
1169
|
let sdk: NodeSDK | null = null;
|
|
1151
1170
|
let warnedOnce = false;
|
|
@@ -1156,6 +1175,85 @@ let _stringRedactor: StringRedactor | null = null;
|
|
|
1156
1175
|
let _optionalRequire: typeof safeRequire = safeRequire;
|
|
1157
1176
|
let _devtoolsClose: (() => Promise<void> | void) | null = null;
|
|
1158
1177
|
|
|
1178
|
+
const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 } as const;
|
|
1179
|
+
type LogLevelKey = keyof typeof LOG_LEVELS;
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Lock the logger to prevent further `init()` calls.
|
|
1183
|
+
* Use this when framework plugins set up instrumentation and you want
|
|
1184
|
+
* to prevent accidental re-initialization from user code.
|
|
1185
|
+
*/
|
|
1186
|
+
export function lockLogger(): void {
|
|
1187
|
+
locked = true;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Check if the logger has been locked.
|
|
1192
|
+
*/
|
|
1193
|
+
export function isLoggerLocked(): boolean {
|
|
1194
|
+
return locked;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function createSilentLogger(): Logger {
|
|
1198
|
+
return {
|
|
1199
|
+
info: () => {},
|
|
1200
|
+
warn: () => {},
|
|
1201
|
+
error: () => {},
|
|
1202
|
+
debug: () => {},
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function wrapLogger(
|
|
1207
|
+
base: Logger,
|
|
1208
|
+
silent: boolean,
|
|
1209
|
+
minLevel: LogLevelKey,
|
|
1210
|
+
): Logger {
|
|
1211
|
+
if (silent) return createSilentLogger();
|
|
1212
|
+
const threshold = LOG_LEVELS[minLevel];
|
|
1213
|
+
const wrap = (fn: Logger['info'], level: LogLevelKey): Logger['info'] => {
|
|
1214
|
+
if (LOG_LEVELS[level] < threshold) {
|
|
1215
|
+
return (() => {}) as Logger['info'];
|
|
1216
|
+
}
|
|
1217
|
+
return ((...args: Parameters<Logger['info']>) =>
|
|
1218
|
+
fn(...args)) as Logger['info'];
|
|
1219
|
+
};
|
|
1220
|
+
return {
|
|
1221
|
+
debug: wrap(base.debug, 'debug'),
|
|
1222
|
+
info: wrap(base.info, 'info'),
|
|
1223
|
+
warn: wrap(base.warn, 'warn'),
|
|
1224
|
+
error: wrap(base.error, 'error'),
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function detectEnvironmentAttributes(): Record<string, string> {
|
|
1229
|
+
const attrs: Record<string, string> = {};
|
|
1230
|
+
|
|
1231
|
+
const commitSha =
|
|
1232
|
+
process.env.COMMIT_SHA ||
|
|
1233
|
+
process.env.GITHUB_SHA ||
|
|
1234
|
+
process.env.VERCEL_GIT_COMMIT_SHA ||
|
|
1235
|
+
process.env.CF_PAGES_COMMIT_SHA ||
|
|
1236
|
+
process.env.AWS_CODEPIPELINE_EXECUTION_ID;
|
|
1237
|
+
if (commitSha) attrs['service.commit.sha'] = commitSha;
|
|
1238
|
+
|
|
1239
|
+
const region =
|
|
1240
|
+
process.env.VERCEL_REGION ||
|
|
1241
|
+
process.env.AWS_REGION ||
|
|
1242
|
+
process.env.AWS_DEFAULT_REGION ||
|
|
1243
|
+
process.env.FLY_REGION ||
|
|
1244
|
+
process.env.CF_REGION ||
|
|
1245
|
+
process.env.GOOGLE_CLOUD_REGION;
|
|
1246
|
+
if (region) attrs['service.region'] = region;
|
|
1247
|
+
|
|
1248
|
+
const version =
|
|
1249
|
+
process.env.APP_VERSION ||
|
|
1250
|
+
process.env.HEROKU_RELEASE_VERSION ||
|
|
1251
|
+
process.env.VERCEL_GIT_COMMIT_REF;
|
|
1252
|
+
if (version) attrs['service.deploy.version'] = version;
|
|
1253
|
+
|
|
1254
|
+
return attrs;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1159
1257
|
/**
|
|
1160
1258
|
* Resolve metrics flag with env var override support
|
|
1161
1259
|
*/
|
|
@@ -1295,6 +1393,10 @@ function normalizeOtlpHeaders(
|
|
|
1295
1393
|
*/
|
|
1296
1394
|
|
|
1297
1395
|
export function init(cfg: AutotelConfig): void {
|
|
1396
|
+
if (locked) {
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1298
1400
|
// Resolve configs in priority order: explicit > yaml > env > defaults
|
|
1299
1401
|
const envConfig = resolveConfigFromEnv();
|
|
1300
1402
|
const yamlConfig = loadYamlConfig() ?? {};
|
|
@@ -1308,19 +1410,32 @@ export function init(cfg: AutotelConfig): void {
|
|
|
1308
1410
|
resourceAttributes: {
|
|
1309
1411
|
...envConfig.resourceAttributes,
|
|
1310
1412
|
...yamlConfig.resourceAttributes,
|
|
1413
|
+
...detectEnvironmentAttributes(),
|
|
1311
1414
|
...cfg.resourceAttributes,
|
|
1312
1415
|
},
|
|
1313
1416
|
// Handle headers merge (can be string or object)
|
|
1314
1417
|
headers: cfg.headers ?? yamlConfig.headers ?? envConfig.headers,
|
|
1315
1418
|
} as AutotelConfig;
|
|
1316
1419
|
|
|
1420
|
+
if (mergedConfig.attributeRedactor !== undefined) {
|
|
1421
|
+
const normalizedRedactor = normalizeAttributeRedactorConfig(
|
|
1422
|
+
mergedConfig.attributeRedactor,
|
|
1423
|
+
);
|
|
1424
|
+
if (!normalizedRedactor) {
|
|
1425
|
+
throw new Error('Invalid attributeRedactor config');
|
|
1426
|
+
}
|
|
1427
|
+
mergedConfig.attributeRedactor = normalizedRedactor;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1317
1430
|
const devtoolsConfig = resolveDevtoolsConfig(mergedConfig.devtools);
|
|
1318
1431
|
if (devtoolsConfig.enabled && mergedConfig.logs === undefined) {
|
|
1319
1432
|
mergedConfig.logs = true;
|
|
1320
1433
|
}
|
|
1321
1434
|
|
|
1322
|
-
|
|
1323
|
-
|
|
1435
|
+
const silent = mergedConfig.silent ?? false;
|
|
1436
|
+
const minLevel = mergedConfig.minLevel ?? 'info';
|
|
1437
|
+
const baseLogger = mergedConfig.logger || silentLogger;
|
|
1438
|
+
logger = wrapLogger(baseLogger, silent, minLevel);
|
|
1324
1439
|
|
|
1325
1440
|
// Warn if re-initializing (same behavior in all environments)
|
|
1326
1441
|
if (initialized) {
|