or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

cli-tool.mdcolorization.mdconfiguration.mdcustom-prettifiers.mdindex.mdmessage-formatting.mdprettifier-factory.mdstream-transport.md
tile.json

message-formatting.mddocs/

Message Formatting

The message formatting system provides flexible control over how log messages are displayed. It supports both template-based formatting with token substitution and custom function-based formatting.

Capabilities

Message Format Option

Configure custom message formatting.

/**
 * Custom message format
 * - false: use default format (just the message)
 * - string: template with {tokens} and conditional blocks
 * - function: custom formatter function
 * @default false
 */
messageFormat?: false | string | MessageFormatFunc;

/**
 * Custom message format function
 * @param log - The complete log object
 * @param messageKey - The key containing the message
 * @param levelLabel - The level label string (e.g., "INFO", "ERROR")
 * @param extras - Additional context including colors
 * @returns Formatted message string
 */
type MessageFormatFunc = (
  log: Record<string, unknown>,
  messageKey: string,
  levelLabel: string,
  extras: PrettifierExtras
) => string;

interface PrettifierExtras {
  colors: Colorette;
}

Template String Formatting

Basic Token Substitution

Use {property} tokens to reference log properties.

/**
 * Template tokens:
 * - {property} - Access top-level properties
 * - {nested.property} - Access nested properties with dot notation
 * - {levelLabel} - Special token for level label
 * - {msg} - Message property (configurable via messageKey)
 */

Usage Examples:

const pinoPretty = require('pino-pretty');

// Basic template
const stream1 = pinoPretty({
  messageFormat: '{levelLabel} - {msg}'
});
// Input: { level: 30, msg: 'hello' }
// Output: INFO - hello

// Include PID
const stream2 = pinoPretty({
  messageFormat: '{levelLabel} - {pid} - {msg}'
});
// Input: { level: 30, pid: 1234, msg: 'hello' }
// Output: INFO - 1234 - hello

// Nested properties
const stream3 = pinoPretty({
  messageFormat: '{levelLabel} - url:{req.url} - {msg}'
});
// Input: { level: 30, req: { url: '/api/users' }, msg: 'Request' }
// Output: INFO - url:/api/users - Request

// Custom label token
const stream4 = pinoPretty({
  levelLabel: 'severity',
  messageFormat: '{severity}: {msg}'
});

Conditional Blocks

Use {if property}...{end} blocks for conditional output.

/**
 * Conditional syntax:
 * {if property}content{end} - Show content if property exists
 * Nested conditions are not supported
 * Else statements are not supported
 */

Usage Examples:

// Conditional PID
pinoPretty({
  messageFormat: '{levelLabel} - {if pid}{pid} - {end}{msg}'
});
// With pid: INFO - 1234 - hello
// Without pid: INFO - hello

// Multiple conditionals
pinoPretty({
  messageFormat: '{levelLabel}{if pid} [{pid}]{end}{if hostname} @{hostname}{end} - {msg}'
});
// Full: INFO [1234] @server1 - hello
// Partial: INFO [1234] - hello
// Minimal: INFO - hello

// Conditional nested property
pinoPretty({
  messageFormat: '{if req.id}[{req.id}] {end}{msg}'
});
// With req.id: [abc123] hello
// Without: hello

// Conditional with text
pinoPretty({
  messageFormat: '{levelLabel}{if userId} (user:{userId}){end} - {msg}'
});
// With userId: INFO (user:42) - hello
// Without: INFO - hello

Complex Template Examples

Real-world message format templates.

// HTTP request logging
messageFormat: '{levelLabel} {method} {url}{if statusCode} -> {statusCode}{end}{if duration} ({duration}ms){end}'
// Output: INFO GET /api/users -> 200 (45ms)

// Service logging
messageFormat: '[{levelLabel}]{if service} {service}{end}{if requestId} [{requestId}]{end} {msg}'
// Output: [INFO] auth-service [abc-123] User logged in

// Database logging
messageFormat: '{levelLabel} - {operation}{if table} on {table}{end}{if duration} - {duration}ms{end}: {msg}'
// Output: INFO - SELECT on users - 12ms: Fetched user records

// Error logging
messageFormat: '{levelLabel}{if errorCode} ({errorCode}){end}{if userId} [user:{userId}]{end} - {msg}'
// Output: ERROR (AUTH_FAILED) [user:123] - Invalid credentials

Function-Based Formatting

Custom Format Function

Implement custom message formatting logic.

/**
 * Custom message format function
 * @param log - Complete log object
 * @param messageKey - Key for message (usually 'msg')
 * @param levelLabel - Level label string
 * @param extras - Colors and other context
 * @returns Formatted message string
 */
type MessageFormatFunc = (
  log: Record<string, unknown>,
  messageKey: string,
  levelLabel: string,
  extras: { colors: Colorette }
) => string;

Usage Examples:

const pinoPretty = require('pino-pretty');

// Simple function
const stream1 = pinoPretty({
  messageFormat: (log, messageKey) => {
    const msg = log[messageKey];
    return `>> ${msg}`;
  }
});

// With level
const stream2 = pinoPretty({
  messageFormat: (log, messageKey, levelLabel) => {
    return `[${levelLabel}] ${log[messageKey]}`;
  }
});

// With colors
const stream3 = pinoPretty({
  messageFormat: (log, messageKey, levelLabel, { colors }) => {
    const msg = log[messageKey];
    return colors.bold(`${levelLabel}: `) + msg;
  }
});

// Conditional logic
const stream4 = pinoPretty({
  messageFormat: (log, messageKey, levelLabel, { colors }) => {
    const msg = log[messageKey];
    if (log.requestId) {
      return colors.cyan(`[${log.requestId}] `) + msg;
    }
    return msg;
  }
});

// Complex formatting
const stream5 = pinoPretty({
  messageFormat: (log, messageKey, levelLabel, { colors }) => {
    const msg = log[messageKey];
    const parts = [levelLabel];

    if (log.service) {
      parts.push(colors.blue(log.service));
    }

    if (log.requestId) {
      parts.push(colors.gray(`[${log.requestId}]`));
    }

    return parts.join(' ') + ': ' + msg;
  }
});
// Output: INFO auth-service [abc-123]: User logged in

HTTP Request Formatting

Format HTTP request logs.

const stream = pinoPretty({
  messageFormat: (log, messageKey, levelLabel, { colors }) => {
    const msg = log[messageKey];

    // HTTP request format
    if (log.req && log.res) {
      const method = log.req.method || 'GET';
      const url = log.req.url || '/';
      const status = log.res.statusCode || '???';
      const duration = log.responseTime || log.duration;

      // Color status code
      let statusColor;
      if (status >= 500) statusColor = colors.red;
      else if (status >= 400) statusColor = colors.yellow;
      else if (status >= 300) statusColor = colors.cyan;
      else statusColor = colors.green;

      const parts = [
        colors.bold(method),
        url,
        '->',
        statusColor(status)
      ];

      if (duration) {
        parts.push(colors.gray(`(${duration}ms)`));
      }

      return parts.join(' ') + (msg ? ` - ${msg}` : '');
    }

    return msg;
  }
});
// Output: GET /api/users -> 200 (45ms) - Request successful

Error Context Formatting

Add contextual information to error messages.

const stream = pinoPretty({
  messageFormat: (log, messageKey, levelLabel, { colors }) => {
    const msg = log[messageKey];

    // Error format with context
    if (log.err || log.error) {
      const error = log.err || log.error;
      const errorMsg = error.message || msg;
      const errorType = error.type || error.name || 'Error';

      const parts = [colors.red(colors.bold(errorType))];

      if (log.errorCode) {
        parts.push(colors.yellow(`[${log.errorCode}]`));
      }

      if (log.userId) {
        parts.push(colors.gray(`(user:${log.userId})`));
      }

      return parts.join(' ') + ': ' + errorMsg;
    }

    return msg;
  }
});
// Output: AuthenticationError [AUTH_001] (user:123): Invalid credentials

Structured Data Formatting

Format logs with structured metadata.

const stream = pinoPretty({
  messageFormat: (log, messageKey, levelLabel, { colors }) => {
    const msg = log[messageKey];
    const parts = [];

    // Add level with color
    const levelColors = {
      TRACE: colors.gray,
      DEBUG: colors.blue,
      INFO: colors.green,
      WARN: colors.yellow,
      ERROR: colors.red,
      FATAL: colors.bgRed
    };
    const colorize = levelColors[levelLabel] || colors.white;
    parts.push(colorize(levelLabel));

    // Add service
    if (log.service) {
      parts.push(colors.blue(`[${log.service}]`));
    }

    // Add trace ID
    if (log.traceId) {
      parts.push(colors.gray(`trace:${log.traceId.slice(0, 8)}`));
    }

    // Add user context
    if (log.userId) {
      parts.push(colors.magenta(`user:${log.userId}`));
    }

    return parts.join(' ') + ' - ' + msg;
  }
});
// Output: INFO [auth-service] trace:a1b2c3d4 user:42 - User logged in

Default Message Format

Standard Format Behavior

When messageFormat is false (default).

/**
 * Default format:
 * [time] LEVEL (metadata): message
 *   object properties...
 *
 * Structure:
 * - Time (if translated)
 * - Level (colored if colorize enabled)
 * - Metadata (pid, hostname, name, etc. unless ignored)
 * - Message
 * - Additional object properties
 */

Default Output Examples:

// Basic log
{ level: 30, time: 1522431328992, msg: 'hello' }
// Output: [10:30:45] INFO: hello

// With metadata
{ level: 30, time: 1522431328992, msg: 'hello', pid: 1234, hostname: 'server1' }
// Output: [10:30:45] INFO (1234 on server1): hello

// With objects
{ level: 30, msg: 'User login', userId: 42, action: 'login' }
// Output: [10:30:45] INFO: User login
//     userId: 42
//     action: "login"

// Error with stack
{ level: 50, msg: 'Error occurred', err: { type: 'Error', message: 'Failed', stack: '...' } }
// Output: [10:30:45] ERROR: Error occurred
//     Error: Failed
//     <stack trace>

MessageFormat Best Practices

Performance Tips

Keep message formatting efficient.

/**
 * Best practices:
 * - Keep template strings simple
 * - Avoid complex computations in functions
 * - Cache color functions if used repeatedly
 * - Return strings directly, avoid string building
 */

Examples:

// Good: Simple and fast
messageFormat: '{levelLabel} - {msg}'

// Good: Efficient function
messageFormat: (log, messageKey) => {
  return `[${log.service || 'app'}] ${log[messageKey]}`;
}

// Bad: Expensive computation
messageFormat: (log, messageKey) => {
  // Don't do heavy processing here
  const processed = expensiveOperation(log);
  return processed + log[messageKey];
}

Error Handling

Handle missing properties gracefully.

/**
 * Handle missing or invalid properties:
 * - Check property existence before use
 * - Provide fallback values
 * - Avoid throwing errors
 */

Examples:

messageFormat: (log, messageKey, levelLabel, { colors }) => {
  const msg = log[messageKey] || '(no message)';

  // Safe property access
  const userId = log.userId || log.user?.id || 'unknown';
  const requestId = log.requestId || log.req?.id;

  const parts = [levelLabel];

  if (requestId) {
    parts.push(colors.cyan(`[${requestId}]`));
  }

  return parts.join(' ') + ` (user:${userId}) - ${msg}`;
}

Colorization

Use colors effectively in custom formats.

/**
 * Color usage tips:
 * - Use colors.bold for emphasis
 * - Use colors.dim/gray for metadata
 * - Use semantic colors (red for errors, green for success)
 * - Don't overuse colors - keep it readable
 */

Examples:

messageFormat: (log, messageKey, levelLabel, { colors }) => {
  const msg = log[messageKey];

  // Semantic coloring
  const levelColor =
    levelLabel === 'ERROR' ? colors.red :
    levelLabel === 'WARN' ? colors.yellow :
    levelLabel === 'INFO' ? colors.green :
    colors.white;

  // Emphasis on important parts
  const level = colors.bold(levelColor(levelLabel));

  // Dim/gray for metadata
  const metadata = log.requestId
    ? colors.gray(` [${log.requestId}]`)
    : '';

  return `${level}${metadata} - ${msg}`;
}