CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-pino

Super fast, all natural JSON logger with exceptional performance for structured logging applications.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

serializers.mddocs/

Serializers

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.

Capabilities

Serializer Configuration

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');

Standard Serializers

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
});

Redaction

Redaction Configuration

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]';
    }
  }
});

Redaction Path Syntax

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 redacted

Custom Serializer Patterns

Object Type Serializers

Create 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
      };
    }
  }
});

Contextual Serializers

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
      };
    }
  }
});

Wrapper Serializers

Extending Standard Serializers

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
  }
});

Advanced Redaction Patterns

Dynamic Redaction

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
    }
  });
}

Conditional Redaction

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
  }
});

Performance Considerations

Serializer Performance

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
        };
      };
    })()
  }
});

Redaction Performance

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

docs

browser.md

child-loggers.md

index.md

logger-configuration.md

logger-methods.md

serializers.md

streams.md

transports.md

tile.json