0
# Serializers
1
2
Custom object serialization and sensitive data redaction for secure and structured log output. Serializers control how objects are converted to JSON, while redaction ensures sensitive data is protected in log files.
3
4
## Capabilities
5
6
### Serializer Configuration
7
8
Configure custom serializers to control how specific object types are logged.
9
10
```typescript { .api }
11
interface LoggerOptions {
12
/** Custom serializers for object properties */
13
serializers?: { [key: string]: SerializerFn };
14
}
15
16
/**
17
* Function that transforms an object before logging
18
* @param value - The object to serialize
19
* @returns Serialized object suitable for JSON logging
20
*/
21
type SerializerFn = (value: any) => any;
22
```
23
24
**Usage Examples:**
25
26
```javascript
27
const logger = pino({
28
serializers: {
29
user: (user) => {
30
return {
31
id: user.id,
32
name: user.name,
33
// Exclude sensitive fields like password, email
34
};
35
},
36
request: (req) => {
37
return {
38
method: req.method,
39
url: req.url,
40
userAgent: req.headers['user-agent'],
41
// Exclude headers with sensitive data
42
};
43
},
44
error: (err) => {
45
return {
46
name: err.name,
47
message: err.message,
48
stack: err.stack,
49
code: err.code
50
};
51
}
52
}
53
});
54
55
// Serializers are applied to matching property names
56
logger.info({ user: userObject }, 'User action');
57
logger.error({ request: reqObject, error: errorObject }, 'Request failed');
58
```
59
60
### Standard Serializers
61
62
Use built-in serializers for common object types.
63
64
```typescript { .api }
65
const stdSerializers: {
66
/** HTTP request serializer */
67
req: SerializerFn;
68
69
/** HTTP response serializer */
70
res: SerializerFn;
71
72
/** Error object serializer */
73
err: SerializerFn;
74
75
/** Error with cause chain serializer */
76
errWithCause: SerializerFn;
77
78
/** Request serializer wrapper */
79
wrapRequestSerializer: (serializer: SerializerFn) => SerializerFn;
80
81
/** Response serializer wrapper */
82
wrapResponseSerializer: (serializer: SerializerFn) => SerializerFn;
83
84
/** Error serializer wrapper */
85
wrapErrorSerializer: (serializer: SerializerFn) => SerializerFn;
86
87
/** HTTP request mapping function */
88
mapHttpRequest: SerializerFn;
89
90
/** HTTP response mapping function */
91
mapHttpResponse: SerializerFn;
92
};
93
```
94
95
**Usage Examples:**
96
97
```javascript
98
// Use standard serializers
99
const logger = pino({
100
serializers: {
101
req: pino.stdSerializers.req,
102
res: pino.stdSerializers.res,
103
err: pino.stdSerializers.err
104
}
105
});
106
107
// Or use them directly
108
const logger = pino({
109
serializers: pino.stdSerializers
110
});
111
112
// Express.js example
113
app.use((req, res, next) => {
114
req.log = logger.child({ req });
115
res.log = logger.child({ res });
116
next();
117
});
118
119
app.get('/users', (req, res) => {
120
req.log.info('Fetching users'); // Includes serialized request
121
res.log.info('Users returned'); // Includes serialized response
122
});
123
```
124
125
## Redaction
126
127
### Redaction Configuration
128
129
Remove or censor sensitive data from log output automatically.
130
131
```typescript { .api }
132
interface LoggerOptions {
133
/** Redaction configuration */
134
redact?: string[] | RedactOptions;
135
}
136
137
interface RedactOptions {
138
/** Array of paths to redact using dot notation */
139
paths: string[];
140
141
/** Value to replace redacted data with (default: '[Redacted]') */
142
censor?: string | ((value: any, path: string[]) => any);
143
144
/** Remove the property entirely instead of censoring (default: false) */
145
remove?: boolean;
146
}
147
```
148
149
**Usage Examples:**
150
151
```javascript
152
// Simple redaction with array of paths
153
const logger = pino({
154
redact: ['password', 'creditCard', 'ssn']
155
});
156
157
logger.info({
158
username: 'john',
159
password: 'secret123',
160
creditCard: '4111-1111-1111-1111'
161
});
162
// Output: { username: 'john', password: '[Redacted]', creditCard: '[Redacted]' }
163
164
// Advanced redaction with options
165
const logger = pino({
166
redact: {
167
paths: ['user.password', 'payment.*.number', 'headers.authorization'],
168
censor: '***HIDDEN***',
169
remove: false
170
}
171
});
172
173
// Custom censor function
174
const logger = pino({
175
redact: {
176
paths: ['user.email'],
177
censor: (value) => {
178
if (typeof value === 'string' && value.includes('@')) {
179
const [local, domain] = value.split('@');
180
return `${local[0]}***@${domain}`;
181
}
182
return '[Redacted]';
183
}
184
}
185
});
186
```
187
188
### Redaction Path Syntax
189
190
Specify complex paths using dot notation and wildcards.
191
192
**Path Examples:**
193
194
```javascript
195
const logger = pino({
196
redact: {
197
paths: [
198
'password', // Top-level property
199
'user.password', // Nested property
200
'users.*.password', // Array elements
201
'config.database.password', // Deep nesting
202
'headers["x-api-key"]', // Bracket notation
203
'data[*].sensitive', // Array with wildcard
204
'nested.*.deep.secret' // Multiple levels with wildcard
205
]
206
}
207
});
208
209
// Test data
210
logger.info({
211
password: 'secret',
212
user: { password: 'user-secret' },
213
users: [
214
{ password: 'user1-secret' },
215
{ password: 'user2-secret' }
216
],
217
config: {
218
database: { password: 'db-secret' }
219
},
220
headers: {
221
'x-api-key': 'api-key-123'
222
}
223
});
224
// All specified paths will be redacted
225
```
226
227
## Custom Serializer Patterns
228
229
### Object Type Serializers
230
231
Create serializers for specific object types or classes.
232
233
**Usage Examples:**
234
235
```javascript
236
class User {
237
constructor(id, name, email, password) {
238
this.id = id;
239
this.name = name;
240
this.email = email;
241
this.password = password;
242
}
243
}
244
245
const logger = pino({
246
serializers: {
247
user: (user) => {
248
if (user instanceof User) {
249
return {
250
id: user.id,
251
name: user.name,
252
email: user.email.replace(/(.{2}).*@/, '$1***@')
253
};
254
}
255
return user;
256
},
257
database: (db) => {
258
return {
259
host: db.host,
260
port: db.port,
261
database: db.database,
262
// Never log connection strings or credentials
263
connected: db.connected
264
};
265
}
266
}
267
});
268
```
269
270
### Contextual Serializers
271
272
Create serializers that behave differently based on context.
273
274
**Usage Examples:**
275
276
```javascript
277
const logger = pino({
278
serializers: {
279
response: (res) => {
280
const serialized = {
281
statusCode: res.statusCode,
282
statusMessage: res.statusMessage,
283
headers: res.headers
284
};
285
286
// Include response body only for errors
287
if (res.statusCode >= 400 && res.body) {
288
serialized.body = res.body;
289
}
290
291
// Redact sensitive headers
292
if (serialized.headers) {
293
const { authorization, cookie, ...safeHeaders } = serialized.headers;
294
serialized.headers = safeHeaders;
295
}
296
297
return serialized;
298
},
299
performance: (perf) => {
300
return {
301
duration: perf.duration,
302
memory: Math.round(perf.memory / 1024 / 1024), // MB
303
cpu: Math.round(perf.cpu * 100) / 100 // Percentage
304
};
305
}
306
}
307
});
308
```
309
310
## Wrapper Serializers
311
312
### Extending Standard Serializers
313
314
Wrap standard serializers to add custom behavior while preserving base functionality.
315
316
**Usage Examples:**
317
318
```javascript
319
// Extend request serializer
320
const customReqSerializer = pino.stdSerializers.wrapRequestSerializer((req) => {
321
const serialized = pino.stdSerializers.req(req);
322
323
// Add custom fields
324
serialized.requestId = req.id;
325
serialized.userAgent = req.headers['user-agent'];
326
serialized.ip = req.ip || req.connection.remoteAddress;
327
328
// Remove sensitive headers
329
if (serialized.headers) {
330
delete serialized.headers.authorization;
331
delete serialized.headers.cookie;
332
}
333
334
return serialized;
335
});
336
337
// Extend error serializer
338
const customErrSerializer = pino.stdSerializers.wrapErrorSerializer((err) => {
339
const serialized = pino.stdSerializers.err(err);
340
341
// Add custom error properties
342
if (err.code) serialized.code = err.code;
343
if (err.statusCode) serialized.statusCode = err.statusCode;
344
if (err.context) serialized.context = err.context;
345
346
return serialized;
347
});
348
349
const logger = pino({
350
serializers: {
351
req: customReqSerializer,
352
err: customErrSerializer
353
}
354
});
355
```
356
357
## Advanced Redaction Patterns
358
359
### Dynamic Redaction
360
361
Configure redaction rules that change based on log level or context.
362
363
**Usage Examples:**
364
365
```javascript
366
function createLogger(logLevel) {
367
const redactPaths = ['password', 'token'];
368
369
// Add more redaction in production
370
if (process.env.NODE_ENV === 'production') {
371
redactPaths.push('user.email', 'user.phone', 'request.ip');
372
}
373
374
// Less redaction for debug level
375
if (logLevel === 'debug') {
376
return pino({
377
level: logLevel,
378
redact: {
379
paths: ['password'], // Only redact passwords for debugging
380
censor: '[DEBUG-REDACTED]'
381
}
382
});
383
}
384
385
return pino({
386
level: logLevel,
387
redact: {
388
paths: redactPaths,
389
remove: true // Remove entirely in production
390
}
391
});
392
}
393
```
394
395
### Conditional Redaction
396
397
Use censor functions to implement complex redaction logic.
398
399
**Usage Examples:**
400
401
```javascript
402
const logger = pino({
403
redact: {
404
paths: ['data.*'],
405
censor: (value, path) => {
406
// Different handling based on path
407
if (path.includes('email')) {
408
return value.replace(/(.{2}).*@(.*)/, '$1***@$2');
409
}
410
411
if (path.includes('phone')) {
412
return value.replace(/(\d{3})\d{3}(\d{4})/, '$1-***-$2');
413
}
414
415
if (path.includes('ssn')) {
416
return '***-**-' + value.slice(-4);
417
}
418
419
// Default redaction
420
return '[Redacted]';
421
}
422
}
423
});
424
425
logger.info({
426
data: {
427
email: 'user@example.com', // → us***@example.com
428
phone: '5551234567', // → 555-***-4567
429
ssn: '123456789' // → ***-**-6789
430
}
431
});
432
```
433
434
## Performance Considerations
435
436
### Serializer Performance
437
438
Optimize serializers for high-performance logging scenarios.
439
440
**Usage Examples:**
441
442
```javascript
443
// Efficient serializers avoid expensive operations
444
const logger = pino({
445
serializers: {
446
user: (user) => {
447
// Fast path for null/undefined
448
if (!user) return user;
449
450
// Object literal is faster than Object.assign
451
return {
452
id: user.id,
453
name: user.name
454
};
455
},
456
457
// Cache computed values when possible
458
request: (() => {
459
const headerCache = new WeakMap();
460
461
return (req) => {
462
if (!req) return req;
463
464
let headers = headerCache.get(req);
465
if (!headers) {
466
headers = {
467
'user-agent': req.headers['user-agent'],
468
'content-type': req.headers['content-type']
469
};
470
headerCache.set(req, headers);
471
}
472
473
return {
474
method: req.method,
475
url: req.url,
476
headers
477
};
478
};
479
})()
480
}
481
});
482
```
483
484
### Redaction Performance
485
486
Configure redaction for optimal performance with large objects.
487
488
**Usage Examples:**
489
490
```javascript
491
// Minimize redaction paths for better performance
492
const logger = pino({
493
redact: {
494
paths: [
495
'password', // Simple path
496
'user.password' // Avoid deep nesting when possible
497
],
498
censor: '[HIDDEN]', // String is faster than function
499
remove: false // Censoring is faster than removal
500
}
501
});
502
503
// For high-volume logging, consider pre-processing
504
function sanitizeForLogging(obj) {
505
const { password, secret, ...safe } = obj;
506
return safe;
507
}
508
509
logger.info(sanitizeForLogging(userData), 'User action');
510
```