or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

child-loggers-serializers.mdcli-tools.mdcore-logging.mdindex.mdstream-management.md
tile.json

child-loggers-serializers.mddocs/

Child Loggers and Serializers

Create specialized logger instances with additional context and configure custom serialization for complex objects like HTTP requests, responses, and errors.

Capabilities

Child Logger Creation

Create child loggers that inherit parent configuration while adding specific contextual fields.

class Logger {
  /**
   * Create a child logger with additional fields and optional configuration
   * @param options - Additional fields and configuration options
   * @param simple - Use simple child creation (performance optimization)
   * @returns New child logger instance
   */
  child(options: ChildOptions, simple?: boolean): Logger;
}

interface ChildOptions {
  [key: string]: any;          // Additional fields for all log records
  level?: string | number;     // Override parent level
  serializers?: Serializers;  // Additional or override serializers
}

Serializer Management

Add and configure custom serializers for converting complex objects to JSON-safe representations.

class Logger {
  /**
   * Add custom serializers to the logger
   * @param serializers - Object mapping field names to serializer functions
   */
  addSerializers(serializers: Serializers): void;
}

interface Serializers {
  [fieldName: string]: SerializerFunction;
}

type SerializerFunction = (obj: any) => any;

Standard Serializers

Built-in serializers for common objects like HTTP requests, responses, and errors.

// Access to standard serializers
const stdSerializers: {
  req: SerializerFunction;    // HTTP request serializer
  res: SerializerFunction;    // HTTP response serializer  
  err: SerializerFunction;    // Error object serializer
};

/**
 * HTTP request serializer - extracts safe request information
 * @param req - HTTP request object
 * @returns Serialized request data
 */
function reqSerializer(req: any): {
  method: string;
  url: string;
  headers: object;
  remoteAddress: string;
  remotePort: number;
} | any;

/**
 * HTTP response serializer - extracts safe response information  
 * @param res - HTTP response object
 * @returns Serialized response data
 */
function resSerializer(res: any): {
  statusCode: number;
  header: string;
} | any;

/**
 * Error serializer - safely serializes Error objects with stack traces
 * @param err - Error object
 * @returns Serialized error data
 */
function errSerializer(err: Error): {
  message: string;
  name: string;
  stack: string;
  code?: string;
  signal?: string;
} | any;

Safe JSON Handling

Utilities for handling circular references and non-serializable objects.

/**
 * Safe JSON stringification that handles circular references
 * @returns Replacer function for JSON.stringify
 */
function safeCycles(): (key: string, value: any) => any;

Usage Examples:

const bunyan = require('bunyan');

// Create parent logger
const log = bunyan.createLogger({
  name: 'parent-app',
  serializers: bunyan.stdSerializers
});

// Create child logger with request context
const reqLog = log.child({
  req_id: '12345-67890',
  user_id: 'alice',
  component: 'auth'
});

// Child inherits parent configuration but adds context
reqLog.info('Processing authentication');
// Output: {"name":"parent-app","req_id":"12345-67890","user_id":"alice","component":"auth","level":30,"msg":"Processing authentication",...}

// Create child with level override
const debugChild = log.child({
  component: 'database'
}, true); // simple=true for performance

debugChild.level('debug');
debugChild.debug('Database query executed');

// Child with additional serializers
const apiLog = log.child({
  component: 'api',
  serializers: {
    body: function(body) {
      // Custom serializer for request body
      if (body && body.password) {
        const safe = Object.assign({}, body);
        safe.password = '[REDACTED]';
        return safe;
      }
      return body;
    }
  }
});

// Using standard serializers
const express = require('express');
const app = express();

app.use((req, res, next) => {
  req.log = log.child({req_id: generateId()});
  req.log.info({req: req}, 'Request started');
  next();
});

app.get('/users/:id', (req, res) => {
  req.log.info('Fetching user');
  
  try {
    const user = getUserById(req.params.id);
    req.log.info({res: res}, 'Request completed successfully');
    res.json(user);
  } catch (err) {
    req.log.error({err: err}, 'Request failed');
    res.status(500).json({error: 'Internal server error'});
  }
});

// Custom serializers for domain objects
log.addSerializers({
  user: function(user) {
    return {
      id: user.id,
      username: user.username,
      // Don't log sensitive fields like passwords
      role: user.role
    };
  },
  
  query: function(query) {
    return {
      sql: query.sql,
      duration: query.duration,
      // Redact potentially sensitive query parameters
      params: query.params ? '[REDACTED]' : undefined
    };
  }
});

// Use custom serializers
log.info({user: currentUser, query: dbQuery}, 'User operation completed');

Child Logger Inheritance

Child loggers inherit from their parents:

  • Streams: Same output streams as parent
  • Serializers: Parent serializers plus any additional ones
  • Level: Parent level unless explicitly overridden
  • Fields: Parent fields plus child-specific fields
const parentLog = bunyan.createLogger({
  name: 'parent',
  level: 'info',
  serializers: {custom: customSerializer}
});

const childLog = parentLog.child({
  component: 'auth',
  serializers: {
    user: userSerializer  // Adds to parent serializers
  }
});

// Child has access to both 'custom' and 'user' serializers
childLog.info({custom: obj1, user: obj2}, 'Message');

Serializer Best Practices

Security Considerations

Always sanitize sensitive data in custom serializers:

const sensitiveSerializer = {
  user: function(user) {
    return {
      id: user.id,
      username: user.username,
      // Never log passwords, tokens, or other secrets
      email: user.email ? user.email.replace(/(.{2})[^@]*(@.*)/, '$1***$2') : undefined
    };
  },
  
  req: function(req) {
    const safe = bunyan.stdSerializers.req(req);
    // Remove sensitive headers
    if (safe.headers) {
      delete safe.headers.authorization;
      delete safe.headers.cookie;
    }
    return safe;
  }
};

Performance Considerations

  • Serializers are called for every log record containing the relevant field
  • Keep serialization logic lightweight
  • Consider caching expensive serialization results
  • Use simple=true for child loggers when possible for better performance
// Performance-optimized child creation
const fastChild = log.child({req_id: id}, true);

Error Handling in Serializers

Serializers should handle errors gracefully to avoid breaking logging:

const robustSerializer = {
  complexObject: function(obj) {
    try {
      return {
        id: obj.id,
        status: obj.getStatus(),
        data: obj.safeData()
      };
    } catch (err) {
      // Return safe fallback if serialization fails
      return {
        id: obj.id || 'unknown',
        error: 'Serialization failed'
      };
    }
  }
};