@heroku/js-blanket 0.0.0 → 1.0.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.
Files changed (82) hide show
  1. package/README.md +4 -1
  2. package/dist/cjs/.tsbuildinfo +1 -0
  3. package/dist/cjs/adapters/logging/generic.js +23 -0
  4. package/dist/cjs/adapters/logging/generic.js.map +1 -0
  5. package/dist/cjs/adapters/logging/generic.test.js +432 -0
  6. package/dist/cjs/adapters/logging/generic.test.js.map +1 -0
  7. package/dist/cjs/core/patterns.js +17 -0
  8. package/dist/cjs/core/patterns.js.map +1 -0
  9. package/dist/cjs/core/presets.js +116 -0
  10. package/dist/cjs/core/presets.js.map +1 -0
  11. package/dist/cjs/core/scrubber.js +260 -0
  12. package/dist/cjs/core/scrubber.js.map +1 -0
  13. package/dist/cjs/core/scrubber.test.js +392 -0
  14. package/dist/cjs/core/scrubber.test.js.map +1 -0
  15. package/dist/cjs/core/types.js +3 -0
  16. package/dist/cjs/core/types.js.map +1 -0
  17. package/dist/cjs/core/types.test.js +326 -0
  18. package/dist/cjs/core/types.test.js.map +1 -0
  19. package/dist/cjs/index.js +16 -0
  20. package/dist/cjs/index.js.map +1 -0
  21. package/dist/cjs/index.test.js +31 -0
  22. package/dist/cjs/index.test.js.map +1 -0
  23. package/dist/cjs/package.json +1 -0
  24. package/dist/esm/.tsbuildinfo +1 -0
  25. package/{src/adapters/logging/generic.ts → dist/esm/adapters/logging/generic.d.ts} +1 -4
  26. package/dist/esm/adapters/logging/generic.js +20 -0
  27. package/dist/esm/adapters/logging/generic.js.map +1 -0
  28. package/dist/esm/adapters/logging/generic.test.d.ts +7 -0
  29. package/dist/esm/adapters/logging/generic.test.js +430 -0
  30. package/dist/esm/adapters/logging/generic.test.js.map +1 -0
  31. package/dist/esm/core/patterns.d.ts +4 -0
  32. package/dist/esm/core/patterns.js +14 -0
  33. package/dist/esm/core/patterns.js.map +1 -0
  34. package/dist/esm/core/presets.d.ts +64 -0
  35. package/{src/core/presets.ts → dist/esm/core/presets.js} +46 -55
  36. package/dist/esm/core/presets.js.map +1 -0
  37. package/dist/esm/core/scrubber.d.ts +131 -0
  38. package/dist/esm/core/scrubber.js +256 -0
  39. package/dist/esm/core/scrubber.js.map +1 -0
  40. package/dist/esm/core/scrubber.test.d.ts +1 -0
  41. package/dist/esm/core/scrubber.test.js +390 -0
  42. package/dist/esm/core/scrubber.test.js.map +1 -0
  43. package/dist/esm/core/types.d.ts +169 -0
  44. package/dist/esm/core/types.js +2 -0
  45. package/dist/esm/core/types.js.map +1 -0
  46. package/dist/esm/core/types.test.d.ts +9 -0
  47. package/dist/esm/core/types.test.js +324 -0
  48. package/dist/esm/core/types.test.js.map +1 -0
  49. package/{src/index.ts → dist/esm/index.d.ts} +0 -3
  50. package/dist/esm/index.js +7 -0
  51. package/dist/esm/index.js.map +1 -0
  52. package/dist/esm/index.test.d.ts +1 -0
  53. package/dist/esm/index.test.js +29 -0
  54. package/dist/esm/index.test.js.map +1 -0
  55. package/package.json +45 -47
  56. package/.c8rc.json +0 -11
  57. package/.editorconfig +0 -11
  58. package/.github/PULL_REQUEST_TEMPLATE.md +0 -41
  59. package/.github/copilot-instructions.md +0 -117
  60. package/.github/workflows/ci.yml +0 -25
  61. package/.husky/pre-commit +0 -1
  62. package/.lintstagedrc.json +0 -4
  63. package/.tool-versions +0 -1
  64. package/CODEOWNERS +0 -8
  65. package/CODE_OF_CONDUCT.md +0 -111
  66. package/CONTRIBUTING.md +0 -123
  67. package/SECURITY.md +0 -8
  68. package/docs/examples/logging-integration.md +0 -736
  69. package/eslint.config.mjs +0 -108
  70. package/prettier.config.mjs +0 -10
  71. package/scripts/test-setup.mjs +0 -24
  72. package/src/adapters/logging/generic.test.ts +0 -531
  73. package/src/core/patterns.ts +0 -22
  74. package/src/core/scrubber.test.ts +0 -465
  75. package/src/core/scrubber.ts +0 -284
  76. package/src/core/types.test.ts +0 -516
  77. package/src/core/types.ts +0 -176
  78. package/src/index.test.ts +0 -41
  79. package/tsconfig.cjs.json +0 -12
  80. package/tsconfig.esm.json +0 -12
  81. package/tsconfig.json +0 -32
  82. package/tsconfig.test.json +0 -9
@@ -1,736 +0,0 @@
1
- # Generic Logging Adapter - Integration Examples
2
-
3
- This guide provides comprehensive examples for integrating `@heroku/js-blanket`
4
- with popular logging libraries.
5
-
6
- ## Table of Contents
7
-
8
- - [Winston Integration](#winston-integration)
9
- - [Pino Integration](#pino-integration)
10
- - [Bunyan Integration](#bunyan-integration)
11
- - [Custom Logger Integration](#custom-logger-integration)
12
- - [oauth-provider-adapters Migration](#oauth-provider-adapters-migration)
13
-
14
- ---
15
-
16
- ## Winston Integration
17
-
18
- [Winston](https://github.com/winstonjs/winston) is a versatile logging library
19
- with support for multiple transports.
20
-
21
- ### Basic Integration
22
-
23
- ```typescript
24
- import winston from 'winston';
25
- import { createRedactor, HEROKU_FIELDS } from '@heroku/js-blanket';
26
-
27
- // Create the redactor
28
- const redactor = createRedactor({
29
- fields: HEROKU_FIELDS,
30
- paths: ['request.headers.authorization'],
31
- });
32
-
33
- // Custom format that scrubs sensitive data
34
- const scrubFormat = winston.format((info) => {
35
- const scrubbed = redactor.scrub(info);
36
- return scrubbed.data;
37
- });
38
-
39
- // Create Winston logger with scrubbing
40
- const logger = winston.createLogger({
41
- level: 'info',
42
- format: winston.format.combine(
43
- scrubFormat(), // Scrub sensitive data first
44
- winston.format.timestamp(),
45
- winston.format.json()
46
- ),
47
- transports: [
48
- new winston.transports.Console(),
49
- new winston.transports.File({ filename: 'app.log' }),
50
- ],
51
- });
52
-
53
- // Usage
54
- logger.info('User login', {
55
- user: 'john',
56
- email: 'john@example.com',
57
- password: 'secret123', // Will be scrubbed
58
- apiToken: 'token123', // Will be scrubbed
59
- });
60
- ```
61
-
62
- ### Advanced Winston Integration with Metadata
63
-
64
- ```typescript
65
- import winston from 'winston';
66
- import { createRedactor, HEROKU_FIELDS, GDPR_FIELDS } from '@heroku/js-blanket';
67
-
68
- const redactor = createRedactor({
69
- fields: [...HEROKU_FIELDS, ...GDPR_FIELDS],
70
- });
71
-
72
- // Helper function to scrub metadata
73
- function scrubMetadata(info: winston.Logform.TransformableInfo) {
74
- // Extract non-symbol properties (Winston uses Symbols for internal data)
75
- const data: Record<string, unknown> = {};
76
- for (const key in info) {
77
- if (typeof key === 'string' && !key.startsWith('Symbol(')) {
78
- data[key] = info[key];
79
- }
80
- }
81
-
82
- const scrubbed = redactor.scrub(data);
83
-
84
- // Replace info properties with scrubbed versions
85
- Object.assign(info, scrubbed.data);
86
- return info;
87
- }
88
-
89
- const logger = winston.createLogger({
90
- level: 'info',
91
- format: winston.format.combine(
92
- winston.format((info) => scrubMetadata(info))(),
93
- winston.format.timestamp(),
94
- winston.format.json()
95
- ),
96
- transports: [new winston.transports.Console()],
97
- });
98
-
99
- // Usage with rich metadata
100
- logger.info('API request completed', {
101
- request: {
102
- method: 'POST',
103
- url: '/api/users',
104
- headers: {
105
- authorization: 'Bearer secret', // Scrubbed
106
- 'user-agent': 'Mozilla/5.0',
107
- },
108
- },
109
- user: {
110
- id: 'user-123',
111
- email: 'user@example.com', // Scrubbed (GDPR_FIELDS)
112
- },
113
- duration: 145,
114
- });
115
- ```
116
-
117
- ### Winston with Child Loggers
118
-
119
- ```typescript
120
- import winston from 'winston';
121
- import { createRedactor, HEROKU_FIELDS } from '@heroku/js-blanket';
122
-
123
- const redactor = createRedactor({ fields: HEROKU_FIELDS });
124
-
125
- const scrubFormat = winston.format((info) => {
126
- const scrubbed = redactor.scrub(info);
127
- return scrubbed.data;
128
- });
129
-
130
- const logger = winston.createLogger({
131
- level: 'info',
132
- format: winston.format.combine(
133
- scrubFormat(),
134
- winston.format.timestamp(),
135
- winston.format.json()
136
- ),
137
- transports: [new winston.transports.Console()],
138
- });
139
-
140
- // Create child logger with default metadata
141
- const requestLogger = logger.child({ requestId: 'req-123' });
142
-
143
- // All logs from child logger are scrubbed
144
- requestLogger.info('User authenticated', {
145
- userId: 'user-456',
146
- password: 'secret', // Scrubbed
147
- });
148
- ```
149
-
150
- ---
151
-
152
- ## Pino Integration
153
-
154
- [Pino](https://github.com/pinojs/pino) is a fast, low-overhead logging library.
155
-
156
- ### Basic Integration
157
-
158
- ```typescript
159
- import pino from 'pino';
160
- import { createRedactor, HEROKU_FIELDS } from '@heroku/js-blanket';
161
-
162
- const redactor = createRedactor({
163
- fields: HEROKU_FIELDS,
164
- });
165
-
166
- // Custom serializer that scrubs sensitive data
167
- const scrubSerializer = (obj: unknown) => {
168
- const scrubbed = redactor.scrub(obj);
169
- return scrubbed.data;
170
- };
171
-
172
- // Create Pino logger with scrubbing
173
- const logger = pino({
174
- level: 'info',
175
- serializers: {
176
- // Scrub the entire log object
177
- log: scrubSerializer,
178
- // Or scrub specific fields
179
- user: scrubSerializer,
180
- request: scrubSerializer,
181
- },
182
- hooks: {
183
- // Scrub all log objects before they're written
184
- logMethod(args, method) {
185
- if (args.length >= 2) {
186
- const [obj, msg, ...rest] = args;
187
- const scrubbed = redactor.scrub(obj);
188
- method.apply(this, [scrubbed.data, msg, ...rest]);
189
- } else {
190
- method.apply(this, args);
191
- }
192
- },
193
- },
194
- });
195
-
196
- // Usage
197
- logger.info({
198
- user: 'john',
199
- password: 'secret123', // Will be scrubbed
200
- apiToken: 'token123', // Will be scrubbed
201
- msg: 'User login',
202
- });
203
- ```
204
-
205
- ### Advanced Pino Integration with Redaction
206
-
207
- ```typescript
208
- import pino from 'pino';
209
- import { createRedactor, HEROKU_FIELDS, GDPR_FIELDS } from '@heroku/js-blanket';
210
-
211
- const redactor = createRedactor({
212
- fields: [...HEROKU_FIELDS, ...GDPR_FIELDS],
213
- paths: ['request.headers.authorization', 'request.body.password'],
214
- });
215
-
216
- // Create Pino logger with comprehensive scrubbing
217
- const logger = pino({
218
- level: 'info',
219
- hooks: {
220
- logMethod(args, method) {
221
- if (args.length >= 1) {
222
- const [first, ...rest] = args;
223
-
224
- // Handle both obj-msg and msg-only formats
225
- if (typeof first === 'object' && first !== null) {
226
- const scrubbed = redactor.scrub(first);
227
- method.apply(this, [scrubbed.data, ...rest]);
228
- } else {
229
- method.apply(this, args);
230
- }
231
- } else {
232
- method.apply(this, args);
233
- }
234
- },
235
- },
236
- });
237
-
238
- // Usage with nested data
239
- logger.info({
240
- request: {
241
- method: 'POST',
242
- url: '/api/login',
243
- headers: {
244
- authorization: 'Bearer token123', // Scrubbed by path
245
- 'content-type': 'application/json',
246
- },
247
- body: {
248
- username: 'john',
249
- password: 'secret123', // Scrubbed by path
250
- },
251
- },
252
- user: {
253
- id: 'user-123',
254
- email: 'john@example.com', // Scrubbed by GDPR_FIELDS
255
- },
256
- msg: 'Login attempt',
257
- });
258
- ```
259
-
260
- ### Pino with Child Loggers and Bindings
261
-
262
- ```typescript
263
- import pino from 'pino';
264
- import { createRedactor, HEROKU_FIELDS } from '@heroku/js-blanket';
265
-
266
- const redactor = createRedactor({ fields: HEROKU_FIELDS });
267
-
268
- const logger = pino({
269
- hooks: {
270
- logMethod(args, method) {
271
- if (args.length >= 1 && typeof args[0] === 'object') {
272
- const scrubbed = redactor.scrub(args[0]);
273
- method.apply(this, [scrubbed.data, ...args.slice(1)]);
274
- } else {
275
- method.apply(this, args);
276
- }
277
- },
278
- },
279
- });
280
-
281
- // Create child logger with bindings
282
- const childLogger = logger.child({
283
- requestId: 'req-456',
284
- userId: 'user-789',
285
- });
286
-
287
- // Bindings are also scrubbed
288
- childLogger.info({
289
- password: 'secret', // Scrubbed
290
- action: 'update-profile',
291
- });
292
- ```
293
-
294
- ---
295
-
296
- ## Bunyan Integration
297
-
298
- [Bunyan](https://github.com/trentm/node-bunyan) is a JSON logging library for
299
- Node.js.
300
-
301
- ### Basic Integration
302
-
303
- ```typescript
304
- import bunyan from 'bunyan';
305
- import { createRedactor, HEROKU_FIELDS } from '@heroku/js-blanket';
306
-
307
- const redactor = createRedactor({
308
- fields: HEROKU_FIELDS,
309
- });
310
-
311
- // Custom stream that scrubs logs before writing
312
- class ScrubStream {
313
- write(rec: bunyan.LogRecord) {
314
- const scrubbed = redactor.scrub(rec);
315
- console.log(JSON.stringify(scrubbed.data));
316
- }
317
- }
318
-
319
- // Create Bunyan logger with scrubbing stream
320
- const logger = bunyan.createLogger({
321
- name: 'myapp',
322
- streams: [
323
- {
324
- level: 'info',
325
- stream: new ScrubStream(),
326
- },
327
- ],
328
- serializers: bunyan.stdSerializers, // Include standard serializers
329
- });
330
-
331
- // Usage
332
- logger.info(
333
- {
334
- user: 'john',
335
- password: 'secret123', // Will be scrubbed
336
- apiToken: 'token123', // Will be scrubbed
337
- },
338
- 'User login'
339
- );
340
- ```
341
-
342
- ### Advanced Bunyan Integration
343
-
344
- ```typescript
345
- import bunyan from 'bunyan';
346
- import {
347
- createRedactor,
348
- HEROKU_FIELDS,
349
- GDPR_FIELDS,
350
- PCI_FIELDS,
351
- } from '@heroku/js-blanket';
352
-
353
- const redactor = createRedactor({
354
- fields: [...HEROKU_FIELDS, ...GDPR_FIELDS, ...PCI_FIELDS],
355
- });
356
-
357
- // Custom serializers that scrub sensitive data
358
- const scrubSerializers = {
359
- ...bunyan.stdSerializers,
360
- // Scrub request data
361
- req: (req: Record<string, unknown>) => {
362
- const serialized = bunyan.stdSerializers.req(req);
363
- const scrubbed = redactor.scrub(serialized);
364
- return scrubbed.data;
365
- },
366
- // Scrub response data
367
- res: (res: Record<string, unknown>) => {
368
- const serialized = bunyan.stdSerializers.res(res);
369
- const scrubbed = redactor.scrub(serialized);
370
- return scrubbed.data;
371
- },
372
- // Scrub error data
373
- err: (err: Error) => {
374
- const serialized = bunyan.stdSerializers.err(err);
375
- const scrubbed = redactor.scrub(serialized);
376
- return scrubbed.data;
377
- },
378
- // Custom user serializer
379
- user: (user: Record<string, unknown>) => {
380
- const scrubbed = redactor.scrub(user);
381
- return scrubbed.data;
382
- },
383
- };
384
-
385
- class ScrubStream {
386
- write(rec: bunyan.LogRecord) {
387
- const scrubbed = redactor.scrub(rec);
388
- process.stdout.write(JSON.stringify(scrubbed.data) + '\n');
389
- }
390
- }
391
-
392
- const logger = bunyan.createLogger({
393
- name: 'myapp',
394
- streams: [
395
- {
396
- level: 'info',
397
- stream: new ScrubStream(),
398
- },
399
- ],
400
- serializers: scrubSerializers,
401
- });
402
-
403
- // Usage with serializers
404
- logger.info(
405
- {
406
- req: {
407
- method: 'POST',
408
- url: '/api/users',
409
- headers: { authorization: 'Bearer token' },
410
- },
411
- user: { id: 'user-123', email: 'user@example.com', password: 'secret' },
412
- },
413
- 'API request'
414
- );
415
- ```
416
-
417
- ---
418
-
419
- ## Custom Logger Integration
420
-
421
- ### Simple Custom Logger
422
-
423
- ```typescript
424
- import { createRedactor, HEROKU_FIELDS } from '@heroku/js-blanket';
425
-
426
- const redactor = createRedactor({
427
- fields: HEROKU_FIELDS,
428
- });
429
-
430
- class SimpleLogger {
431
- private redactor = redactor;
432
-
433
- info(message: string, data?: Record<string, unknown>) {
434
- this.log('INFO', message, data);
435
- }
436
-
437
- error(message: string, data?: Record<string, unknown>) {
438
- this.log('ERROR', message, data);
439
- }
440
-
441
- warn(message: string, data?: Record<string, unknown>) {
442
- this.log('WARN', message, data);
443
- }
444
-
445
- private log(level: string, message: string, data?: Record<string, unknown>) {
446
- const timestamp = new Date().toISOString();
447
-
448
- const logEntry = {
449
- timestamp,
450
- level,
451
- message,
452
- ...data,
453
- };
454
-
455
- const scrubbed = this.redactor.scrub(logEntry);
456
- console.log(JSON.stringify(scrubbed.data));
457
- }
458
- }
459
-
460
- // Usage
461
- const logger = new SimpleLogger();
462
- logger.info('User login', {
463
- user: 'john',
464
- password: 'secret123', // Scrubbed
465
- apiToken: 'token123', // Scrubbed
466
- });
467
- ```
468
-
469
- ### Advanced Custom Logger with Formatting
470
-
471
- ```typescript
472
- import { createRedactor, HEROKU_FIELDS, GDPR_FIELDS } from '@heroku/js-blanket';
473
-
474
- interface LoggerConfig {
475
- level: 'debug' | 'info' | 'warn' | 'error';
476
- format: 'json' | 'pretty';
477
- scrubConfig: Parameters<typeof createRedactor>[0];
478
- }
479
-
480
- class AdvancedLogger {
481
- private config: LoggerConfig;
482
- private redactor: ReturnType<typeof createRedactor>;
483
-
484
- constructor(config: LoggerConfig) {
485
- this.config = config;
486
- this.redactor = createRedactor(config.scrubConfig);
487
- }
488
-
489
- debug(message: string, meta?: Record<string, unknown>) {
490
- if (this.shouldLog('debug')) {
491
- this.log('DEBUG', message, meta);
492
- }
493
- }
494
-
495
- info(message: string, meta?: Record<string, unknown>) {
496
- if (this.shouldLog('info')) {
497
- this.log('INFO', message, meta);
498
- }
499
- }
500
-
501
- warn(message: string, meta?: Record<string, unknown>) {
502
- if (this.shouldLog('warn')) {
503
- this.log('WARN', message, meta);
504
- }
505
- }
506
-
507
- error(message: string, meta?: Record<string, unknown>) {
508
- if (this.shouldLog('error')) {
509
- this.log('ERROR', message, meta);
510
- }
511
- }
512
-
513
- private shouldLog(level: string): boolean {
514
- const levels = ['debug', 'info', 'warn', 'error'];
515
- return levels.indexOf(level) >= levels.indexOf(this.config.level);
516
- }
517
-
518
- private log(level: string, message: string, meta?: Record<string, unknown>) {
519
- const logEntry = {
520
- timestamp: new Date().toISOString(),
521
- level,
522
- message,
523
- ...meta,
524
- };
525
-
526
- const scrubbed = this.redactor.scrub(logEntry);
527
-
528
- if (this.config.format === 'json') {
529
- console.log(JSON.stringify(scrubbed.data));
530
- } else {
531
- this.prettyPrint(scrubbed.data);
532
- }
533
- }
534
-
535
- private prettyPrint(data: Record<string, unknown>) {
536
- const { timestamp, level, message, ...rest } = data;
537
- console.log(`[${timestamp}] ${level}: ${message}`);
538
- if (Object.keys(rest).length > 0) {
539
- console.log(' ', JSON.stringify(rest, null, 2));
540
- }
541
- }
542
- }
543
-
544
- // Usage
545
- const logger = new AdvancedLogger({
546
- level: 'info',
547
- format: 'pretty',
548
- scrubConfig: {
549
- fields: [...HEROKU_FIELDS, ...GDPR_FIELDS],
550
- paths: ['request.headers.authorization'],
551
- },
552
- });
553
-
554
- logger.info('User authenticated', {
555
- user: {
556
- id: 'user-123',
557
- email: 'john@example.com', // Scrubbed by GDPR_FIELDS
558
- password: 'secret', // Scrubbed by HEROKU_FIELDS
559
- },
560
- request: {
561
- method: 'POST',
562
- headers: {
563
- authorization: 'Bearer token', // Scrubbed by path
564
- },
565
- },
566
- });
567
- ```
568
-
569
- ---
570
-
571
- ## oauth-provider-adapters Migration Example
572
-
573
- If you're migrating from `oauth-provider-adapters-for-mcp`, this is a drop-in
574
- replacement for the `redaction.ts` utility.
575
-
576
- ### Before (oauth-provider-adapters)
577
-
578
- ```typescript
579
- // Old implementation in oauth-provider-adapters-for-mcp
580
- import { redactSensitiveData } from './utils/redaction';
581
-
582
- const logger = DefaultLogger.child();
583
- const sensitiveData = {
584
- client_id: 'my-client',
585
- client_secret: 'secret123',
586
- refresh_token: 'refresh123',
587
- };
588
-
589
- const redacted = redactSensitiveData(sensitiveData, [
590
- 'client_secret',
591
- 'refresh_token',
592
- 'access_token',
593
- ]);
594
-
595
- logger.info('OAuth data:', redacted);
596
- ```
597
-
598
- ### After (@heroku/js-blanket)
599
-
600
- ```typescript
601
- // New implementation with @heroku/js-blanket
602
- import { createRedactor } from '@heroku/js-blanket';
603
- import { DefaultLogger } from 'your-logger';
604
-
605
- const redactor = createRedactor({
606
- fields: ['client_secret', 'refresh_token', 'access_token'],
607
- });
608
-
609
- const logger = DefaultLogger.child();
610
- const sensitiveData = {
611
- client_id: 'my-client',
612
- client_secret: 'secret123',
613
- refresh_token: 'refresh123',
614
- };
615
-
616
- const { data: redacted } = redactor.scrub(sensitiveData);
617
-
618
- logger.info('OAuth data:', redacted);
619
- ```
620
-
621
- ### Migration Benefits
622
-
623
- 1. **More powerful scrubbing**: Field-based, path-based, and pattern-based modes
624
- 2. **Better performance**: 194k+ logs/sec average throughput
625
- 3. **Type safety**: Full TypeScript support with generic type preservation
626
- 4. **Presets available**: `HEROKU_FIELDS`, `GDPR_FIELDS`, `PCI_FIELDS`
627
- 5. **Metadata tracking**: Know what was scrubbed with `scrubbedPaths`
628
-
629
- ### Complete Migration Example
630
-
631
- ```typescript
632
- import { createRedactor, HEROKU_FIELDS } from '@heroku/js-blanket';
633
-
634
- // Create a global redactor instance
635
- const redactor = createRedactor({
636
- fields: [...HEROKU_FIELDS, 'client_secret', 'refresh_token', 'access_token'],
637
- paths: ['oauth.credentials.secret'],
638
- });
639
-
640
- // Replace all redactSensitiveData() calls
641
- function redactSensitiveData<T>(data: T): T {
642
- const result = redactor.scrub(data);
643
- return result.data;
644
- }
645
-
646
- // Usage remains the same
647
- const scrubbed = redactSensitiveData({
648
- client_id: 'my-client',
649
- client_secret: 'secret123',
650
- oauth: {
651
- credentials: {
652
- access_token: 'access123',
653
- secret: 'hidden',
654
- },
655
- },
656
- });
657
- ```
658
-
659
- ---
660
-
661
- ## Best Practices
662
-
663
- ### 1. Create Redactor Once
664
-
665
- ```typescript
666
- // ✅ Good: Create redactor once at module level
667
- const redactor = createRedactor({ fields: HEROKU_FIELDS });
668
-
669
- function logUserAction(data: Record<string, unknown>) {
670
- const { data: scrubbed } = redactor.scrub(data);
671
- logger.info(scrubbed);
672
- }
673
-
674
- // ❌ Bad: Creating redactor on every log
675
- function logUserAction(data: Record<string, unknown>) {
676
- const redactor = createRedactor({ fields: HEROKU_FIELDS }); // Inefficient!
677
- const { data: scrubbed } = redactor.scrub(data);
678
- logger.info(scrubbed);
679
- }
680
- ```
681
-
682
- ### 2. Use Presets
683
-
684
- ```typescript
685
- // ✅ Good: Use presets for common scenarios
686
- import { HEROKU_FIELDS, GDPR_FIELDS, PCI_FIELDS } from '@heroku/js-blanket';
687
-
688
- const redactor = createRedactor({
689
- fields: [...HEROKU_FIELDS, ...GDPR_FIELDS, ...PCI_FIELDS],
690
- });
691
-
692
- // ❌ Okay but verbose: Manually listing all fields
693
- const redactor = createRedactor({
694
- fields: ['password', 'apiToken', 'email', 'phone', 'cvv', ...],
695
- });
696
- ```
697
-
698
- ### 3. Combine Scrubbing Modes
699
-
700
- ```typescript
701
- // ✅ Good: Use multiple modes for comprehensive scrubbing
702
- const redactor = createRedactor({
703
- fields: HEROKU_FIELDS, // Scrub by field name
704
- paths: ['request.headers.authorization'], // Scrub specific paths
705
- patterns: [/\b\d{3}-\d{2}-\d{4}\b/g], // Scrub SSN patterns in text
706
- });
707
- ```
708
-
709
- ### 4. Monitor Scrubbing Activity
710
-
711
- ```typescript
712
- // ✅ Good: Track what was scrubbed for debugging
713
- const result = redactor.scrub(data);
714
-
715
- if (result.scrubbed) {
716
- console.log('Scrubbed paths:', result.scrubbedPaths);
717
- }
718
-
719
- logger.info(result.data);
720
- ```
721
-
722
- ---
723
-
724
- ## Performance Considerations
725
-
726
- - **Overhead**: <0.02ms p95 for typical log entries
727
- - **Throughput**: 194k+ logs/sec average
728
- - **Memory**: Scrubbing is immutable (creates new objects)
729
- - **Caching**: Redactor instances cache path lookups for O(1) performance
730
-
731
- ## Additional Resources
732
-
733
- - [API Documentation](../api/README.md)
734
- - [Core Scrubber Guide](../core/scrubber.md)
735
- - [Presets Reference](../core/presets.md)
736
- - [Performance Benchmarks](../benchmarks.md)