Super fast, all natural JSON logger with exceptional performance for structured logging applications.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Configure custom serializers to control how specific object types are logged.
interface LoggerOptions {
/** Custom serializers for object properties */
serializers?: { [key: string]: SerializerFn };
}
/**
* Function that transforms an object before logging
* @param value - The object to serialize
* @returns Serialized object suitable for JSON logging
*/
type SerializerFn = (value: any) => any;Usage Examples:
const logger = pino({
serializers: {
user: (user) => {
return {
id: user.id,
name: user.name,
// Exclude sensitive fields like password, email
};
},
request: (req) => {
return {
method: req.method,
url: req.url,
userAgent: req.headers['user-agent'],
// Exclude headers with sensitive data
};
},
error: (err) => {
return {
name: err.name,
message: err.message,
stack: err.stack,
code: err.code
};
}
}
});
// Serializers are applied to matching property names
logger.info({ user: userObject }, 'User action');
logger.error({ request: reqObject, error: errorObject }, 'Request failed');Use built-in serializers for common object types.
const stdSerializers: {
/** HTTP request serializer */
req: SerializerFn;
/** HTTP response serializer */
res: SerializerFn;
/** Error object serializer */
err: SerializerFn;
/** Error with cause chain serializer */
errWithCause: SerializerFn;
/** Request serializer wrapper */
wrapRequestSerializer: (serializer: SerializerFn) => SerializerFn;
/** Response serializer wrapper */
wrapResponseSerializer: (serializer: SerializerFn) => SerializerFn;
/** Error serializer wrapper */
wrapErrorSerializer: (serializer: SerializerFn) => SerializerFn;
/** HTTP request mapping function */
mapHttpRequest: SerializerFn;
/** HTTP response mapping function */
mapHttpResponse: SerializerFn;
};Usage Examples:
// Use standard serializers
const logger = pino({
serializers: {
req: pino.stdSerializers.req,
res: pino.stdSerializers.res,
err: pino.stdSerializers.err
}
});
// Or use them directly
const logger = pino({
serializers: pino.stdSerializers
});
// Express.js example
app.use((req, res, next) => {
req.log = logger.child({ req });
res.log = logger.child({ res });
next();
});
app.get('/users', (req, res) => {
req.log.info('Fetching users'); // Includes serialized request
res.log.info('Users returned'); // Includes serialized response
});Remove or censor sensitive data from log output automatically.
interface LoggerOptions {
/** Redaction configuration */
redact?: string[] | RedactOptions;
}
interface RedactOptions {
/** Array of paths to redact using dot notation */
paths: string[];
/** Value to replace redacted data with (default: '[Redacted]') */
censor?: string | ((value: any, path: string[]) => any);
/** Remove the property entirely instead of censoring (default: false) */
remove?: boolean;
}Usage Examples:
// Simple redaction with array of paths
const logger = pino({
redact: ['password', 'creditCard', 'ssn']
});
logger.info({
username: 'john',
password: 'secret123',
creditCard: '4111-1111-1111-1111'
});
// Output: { username: 'john', password: '[Redacted]', creditCard: '[Redacted]' }
// Advanced redaction with options
const logger = pino({
redact: {
paths: ['user.password', 'payment.*.number', 'headers.authorization'],
censor: '***HIDDEN***',
remove: false
}
});
// Custom censor function
const logger = pino({
redact: {
paths: ['user.email'],
censor: (value) => {
if (typeof value === 'string' && value.includes('@')) {
const [local, domain] = value.split('@');
return `${local[0]}***@${domain}`;
}
return '[Redacted]';
}
}
});Specify complex paths using dot notation and wildcards.
Path Examples:
const logger = pino({
redact: {
paths: [
'password', // Top-level property
'user.password', // Nested property
'users.*.password', // Array elements
'config.database.password', // Deep nesting
'headers["x-api-key"]', // Bracket notation
'data[*].sensitive', // Array with wildcard
'nested.*.deep.secret' // Multiple levels with wildcard
]
}
});
// Test data
logger.info({
password: 'secret',
user: { password: 'user-secret' },
users: [
{ password: 'user1-secret' },
{ password: 'user2-secret' }
],
config: {
database: { password: 'db-secret' }
},
headers: {
'x-api-key': 'api-key-123'
}
});
// All specified paths will be redactedCreate serializers for specific object types or classes.
Usage Examples:
class User {
constructor(id, name, email, password) {
this.id = id;
this.name = name;
this.email = email;
this.password = password;
}
}
const logger = pino({
serializers: {
user: (user) => {
if (user instanceof User) {
return {
id: user.id,
name: user.name,
email: user.email.replace(/(.{2}).*@/, '$1***@')
};
}
return user;
},
database: (db) => {
return {
host: db.host,
port: db.port,
database: db.database,
// Never log connection strings or credentials
connected: db.connected
};
}
}
});Create serializers that behave differently based on context.
Usage Examples:
const logger = pino({
serializers: {
response: (res) => {
const serialized = {
statusCode: res.statusCode,
statusMessage: res.statusMessage,
headers: res.headers
};
// Include response body only for errors
if (res.statusCode >= 400 && res.body) {
serialized.body = res.body;
}
// Redact sensitive headers
if (serialized.headers) {
const { authorization, cookie, ...safeHeaders } = serialized.headers;
serialized.headers = safeHeaders;
}
return serialized;
},
performance: (perf) => {
return {
duration: perf.duration,
memory: Math.round(perf.memory / 1024 / 1024), // MB
cpu: Math.round(perf.cpu * 100) / 100 // Percentage
};
}
}
});Wrap standard serializers to add custom behavior while preserving base functionality.
Usage Examples:
// Extend request serializer
const customReqSerializer = pino.stdSerializers.wrapRequestSerializer((req) => {
const serialized = pino.stdSerializers.req(req);
// Add custom fields
serialized.requestId = req.id;
serialized.userAgent = req.headers['user-agent'];
serialized.ip = req.ip || req.connection.remoteAddress;
// Remove sensitive headers
if (serialized.headers) {
delete serialized.headers.authorization;
delete serialized.headers.cookie;
}
return serialized;
});
// Extend error serializer
const customErrSerializer = pino.stdSerializers.wrapErrorSerializer((err) => {
const serialized = pino.stdSerializers.err(err);
// Add custom error properties
if (err.code) serialized.code = err.code;
if (err.statusCode) serialized.statusCode = err.statusCode;
if (err.context) serialized.context = err.context;
return serialized;
});
const logger = pino({
serializers: {
req: customReqSerializer,
err: customErrSerializer
}
});Configure redaction rules that change based on log level or context.
Usage Examples:
function createLogger(logLevel) {
const redactPaths = ['password', 'token'];
// Add more redaction in production
if (process.env.NODE_ENV === 'production') {
redactPaths.push('user.email', 'user.phone', 'request.ip');
}
// Less redaction for debug level
if (logLevel === 'debug') {
return pino({
level: logLevel,
redact: {
paths: ['password'], // Only redact passwords for debugging
censor: '[DEBUG-REDACTED]'
}
});
}
return pino({
level: logLevel,
redact: {
paths: redactPaths,
remove: true // Remove entirely in production
}
});
}Use censor functions to implement complex redaction logic.
Usage Examples:
const logger = pino({
redact: {
paths: ['data.*'],
censor: (value, path) => {
// Different handling based on path
if (path.includes('email')) {
return value.replace(/(.{2}).*@(.*)/, '$1***@$2');
}
if (path.includes('phone')) {
return value.replace(/(\d{3})\d{3}(\d{4})/, '$1-***-$2');
}
if (path.includes('ssn')) {
return '***-**-' + value.slice(-4);
}
// Default redaction
return '[Redacted]';
}
}
});
logger.info({
data: {
email: 'user@example.com', // → us***@example.com
phone: '5551234567', // → 555-***-4567
ssn: '123456789' // → ***-**-6789
}
});Optimize serializers for high-performance logging scenarios.
Usage Examples:
// Efficient serializers avoid expensive operations
const logger = pino({
serializers: {
user: (user) => {
// Fast path for null/undefined
if (!user) return user;
// Object literal is faster than Object.assign
return {
id: user.id,
name: user.name
};
},
// Cache computed values when possible
request: (() => {
const headerCache = new WeakMap();
return (req) => {
if (!req) return req;
let headers = headerCache.get(req);
if (!headers) {
headers = {
'user-agent': req.headers['user-agent'],
'content-type': req.headers['content-type']
};
headerCache.set(req, headers);
}
return {
method: req.method,
url: req.url,
headers
};
};
})()
}
});Configure redaction for optimal performance with large objects.
Usage Examples:
// Minimize redaction paths for better performance
const logger = pino({
redact: {
paths: [
'password', // Simple path
'user.password' // Avoid deep nesting when possible
],
censor: '[HIDDEN]', // String is faster than function
remove: false // Censoring is faster than removal
}
});
// For high-volume logging, consider pre-processing
function sanitizeForLogging(obj) {
const { password, secret, ...safe } = obj;
return safe;
}
logger.info(sanitizeForLogging(userData), 'User action');Install with Tessl CLI
npx tessl i tessl/npm-pino