You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现Bunyan日志通用对象序列化器且不修改调用方式?

Got it, let's figure out how to build a Bunyan serializer that handles any object type without changing how you call your logging functions. I've tackled this exact problem before, so here's a practical, step-by-step solution:

Step 1: Understand Bunyan's Default Serialization Quirks

Bunyan uses built-in serializers for common types like err, req, and res, but falls back to plain JSON.stringify for other objects. This breaks with:

  • Circular references
  • Non-enumerable properties
  • Special types like Date, Map, Set, or custom classes
  • Objects with nested complex structures

Our goal is to intercept all object processing before Bunyan does its default serialization—without touching existing log calls.

Step 2: Build a Universal Object Processor

First, write a recursive function that handles all edge cases while preserving object structure:

const bunyan = require('bunyan');

function processUniversalObject(value, seen = new WeakSet(), depthLimit = 10, currentDepth = 0) {
  // Stop recursion if we hit depth limit to avoid performance hits
  if (currentDepth >= depthLimit) return '[Depth Limit Reached]';
  
  // Non-object values get returned as-is
  if (value === null || typeof value !== 'object') return value;

  // Handle circular references
  if (seen.has(value)) return '[Circular Reference]';
  seen.add(value);

  // Reuse Bunyan's excellent built-in Error serializer
  if (value instanceof Error) {
    seen.delete(value);
    return bunyan.stdSerializers.err(value);
  }

  // Handle special native types
  switch (true) {
    case value instanceof Date:
      seen.delete(value);
      return value.toISOString();
    case value instanceof RegExp:
      seen.delete(value);
      return value.toString();
    case value instanceof Map:
      seen.delete(value);
      return Object.fromEntries(value);
    case value instanceof Set:
      seen.delete(value);
      return Array.from(value);
  }

  // Process plain objects (including non-enumerable properties and Symbols)
  const processedObj = {};
  
  // Handle regular properties
  Object.getOwnPropertyNames(value).forEach(key => {
    processedObj[key] = processUniversalObject(value[key], seen, depthLimit, currentDepth + 1);
  });
  
  // Handle Symbol properties
  Object.getOwnPropertySymbols(value).forEach(symbol => {
    processedObj[symbol.toString()] = processUniversalObject(value[symbol], seen, depthLimit, currentDepth + 1);
  });

  seen.delete(value);
  return processedObj;
}
Step 3: Extend Bunyan's Logger to Auto-Process Objects

We'll extend Bunyan's base Logger class to intercept log calls, process all fields automatically, and pass them to the original Bunyan logic. This way, you don't have to change any existing logger.info()/logger.error() calls:

class UniversalBunyanLogger extends bunyan.Logger {
  log(level, ...args) {
    let fields = {};
    let msg = '';
    const restArgs = [];

    // Parse arguments to match Bunyan's native API
    if (typeof args[0] === 'object' && args[0] !== null) {
      fields = args[0];
      if (args.length > 1 && typeof args[1] === 'string') {
        msg = args[1];
        restArgs.push(...args.slice(2));
      } else {
        restArgs.push(...args.slice(1));
      }
    } else {
      msg = args[0];
      restArgs.push(...args.slice(1));
    }

    // Process all fields with our universal serializer
    const processedFields = processUniversalObject(fields);

    // Call Bunyan's original log method with processed data
    return super.log(level, processedFields, msg, ...restArgs);
  }
}
Step 4: Create and Use Your Logger

Initialize your custom logger just like a regular Bunyan logger—all existing log calls will work exactly as before, but with proper serialization:

// Create your logger instance
const logger = new UniversalBunyanLogger({
  name: 'my-app',
  level: 'info',
  // Keep Bunyan's default serializers for req/res if needed
  serializers: bunyan.stdSerializers
});

// Test with all kinds of objects—no changes to your existing code!
const circularObj = {};
circularObj.self = circularObj;

logger.info({
  user: { id: 1, name: 'Alice', createdAt: new Date() },
  settings: new Map([['theme', 'dark'], ['notifications', true]]),
  error: new Error('Failed to load data'),
  circular: circularObj,
  // Non-enumerable property example
  hiddenProp: Object.defineProperty({}, 'secret', { value: '123', enumerable: false }).secret
}, 'User session initialized');

// Simple string message with attached object (works too!)
logger.info('Processing complete', { payload: { status: 'success', data: new Set([1, 2, 3]) } });
Bonus Optimization & Customization Tips
  • Adjust Depth Limit: Tweak the depthLimit parameter in processUniversalObject to balance detail and performance for your use case.
  • Add Custom Class Handling: Add a case for your own classes (e.g., case value instanceof User) to serialize only relevant properties instead of the entire object.
  • Performance Tuning: If you're logging high volumes of data, add checks to skip processing for simple objects (e.g., plain objects with only primitive values).

内容的提问来源于stack exchange,提问作者Diego

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:49:11