cenglu

Migrating from Pino

Complete guide to migrating from Pino to cenglu

Migrating from Pino

This guide helps you migrate from Pino to cenglu with minimal code changes.

Basic Migration

Creating a Logger

Pino:

import pino from "pino";

const logger = pino({
  level: "info",
  name: "my-app",
});

Cenglu:

import { createLogger } from "cenglu";

const logger = createLogger({
  level: "info",
  service: "my-app", // 'service' instead of 'name'
});

Logging Methods

The logging API is nearly identical:

Pino:

logger.info("User logged in");
logger.info({ userId: 123 }, "User logged in");
logger.error(new Error("Failed"), "Operation failed");

Cenglu:

logger.info("User logged in");
logger.info("User logged in", { userId: 123 }); // Context after message
logger.error("Operation failed", new Error("Failed")); // Error after message

Key Difference: In cenglu, context comes after the message, and errors come after the message.

Child Loggers

Pino:

const child = logger.child({ requestId: "abc-123" });
child.info("Processing request");

Cenglu:

const child = logger.child({ requestId: "abc-123" });
child.info("Processing request");

✅ Same API

API Mapping

Log Levels

PinoCengluNotes
trace()trace()✅ Same
debug()debug()✅ Same
info()info()✅ Same
warn()warn()✅ Same
error()error()✅ Same
fatal()fatal()✅ Same

Configuration

Pino OptionCenglu Equivalent
levellevel
nameservice
enabledconsole.enabled
serializersCustom plugin
formattersstructured.transform
redactredaction.paths
timestampAlways included
basebindings
mixinenrichPlugin()

Methods

PinoCenglu
logger.child()logger.child()
logger.levellogger.getLevel() / logger.setLevel()
logger.isLevelEnabled()logger.isLevelEnabled()
logger.silent()logger.setLevel("fatal") then don't use fatal
logger.flush()logger.flush()

Feature Migration

Pretty Printing

Pino:

import pino from "pino";
import pretty from "pino-pretty";

const logger = pino(pretty({
  colorize: true,
  translateTime: "SYS:standard",
}));

Cenglu:

import { createLogger } from "cenglu";

const logger = createLogger({
  pretty: {
    enabled: true, // Built-in, no external dependency
  },
});

Redaction

Pino:

const logger = pino({
  redact: {
    paths: ["password", "*.password"],
    censor: "[REDACTED]",
  },
});

Cenglu:

const logger = createLogger({
  redaction: {
    enabled: true,
    paths: ["password", "user.password"],
    patterns: [
      {
        pattern: /Bearer\s+\S+/g,
        replacement: "Bearer [REDACTED]",
      },
    ],
  },
});

Serializers

Pino:

const logger = pino({
  serializers: {
    req: (req) => ({
      method: req.method,
      url: req.url,
    }),
    err: pino.stdSerializers.err,
  },
});

Cenglu:

import { enrichPlugin } from "cenglu";

const logger = createLogger({
  plugins: [
    enrichPlugin({
      compute: (record) => {
        if (record.context?.req) {
          return {
            req: {
              method: record.context.req.method,
              url: record.context.req.url,
            },
          };
        }
        return undefined;
      },
    }),
  ],
});

Mixins

Pino:

const logger = pino({
  mixin() {
    return { hostname: os.hostname() };
  },
});

Cenglu:

import { enrichPlugin } from "cenglu";

const logger = createLogger({
  plugins: [
    enrichPlugin({
      addHostname: true, // Built-in
      // Or custom:
      fields: {
        hostname: os.hostname(),
      },
    }),
  ],
});

Express Integration

Pino-HTTP

Pino:

import express from "express";
import pino from "pino";
import pinoHttp from "pino-http";

const app = express();
const logger = pino();

app.use(pinoHttp({ logger }));

app.get("/users", (req, res) => {
  req.log.info("Fetching users");
  res.json({ users: [] });
});

Cenglu:

import express from "express";
import { createLogger, expressMiddleware } from "cenglu";

const app = express();
const logger = createLogger({ service: "api" });

app.use(expressMiddleware(logger));

app.get("/users", (req, res) => {
  req.logger.info("Fetching users"); // req.logger instead of req.log
  res.json({ users: [] });
});

Transports

File Transport

Pino:

import pino from "pino";
import { createWriteStream } from "pino/file";

const logger = pino(createWriteStream("./logs/app.log"));

Cenglu:

import { createLogger, createRotatingFileTransport } from "cenglu";

const logger = createLogger({
  transports: [
    createRotatingFileTransport({
      dir: "./logs",
      rotation: {
        maxBytes: 10485760,
        maxFiles: 7,
      },
    }),
  ],
});

External Transports

Pino:

import pino from "pino";

const logger = pino(pino.transport({
  target: "pino-datadog",
  options: { apiKey: process.env.DD_API_KEY },
}));

Cenglu:

import { createLogger } from "cenglu";

const logger = createLogger({
  adapters: [
    {
      name: "datadog",
      handle: async (record) => {
        await datadogClient.log(record);
      },
    },
  ],
});

Advanced Features

Bindings

Pino:

const logger = pino({
  base: {
    pid: process.pid,
    hostname: os.hostname(),
  },
});

Cenglu:

const logger = createLogger({
  bindings: {
    pid: process.pid,
    hostname: os.hostname(),
  },
});

Custom Levels

Pino:

const logger = pino({
  customLevels: {
    audit: 35,
  },
});

logger.audit("Audit log");

Cenglu:

// Use standard levels, or create wrapper:
const logger = createLogger({ level: "info" });

function audit(msg: string, context?: any) {
  logger.info(`[AUDIT] ${msg}`, { ...context, audit: true });
}

audit("Audit log");

Level Labels

Pino:

const logger = pino({
  formatters: {
    level(label, number) {
      return { level: number };
    },
  },
});

Cenglu:

// Levels are always labels ("info", "error", etc.)
// Use transform if you need numbers:
const logger = createLogger({
  structured: {
    transform: (record) => ({
      ...record,
      levelNum: LEVEL_VALUES[record.level],
    }),
  },
});

Common Pitfalls

1. Argument Order

Pino:

logger.info({ userId: 123 }, "User action");
// Context first, message second

Cenglu:

logger.info("User action", { userId: 123 });
// Message first, context second

2. Error Logging

Pino:

logger.error({ err }, "Error occurred");

Cenglu:

logger.error("Error occurred", err, { additional: "context" });
// Error as second argument

3. Request Logger Access

Pino-HTTP:

req.log.info("message");

Cenglu:

req.logger.info("message");

Migration Checklist

  • Replace pino with cenglu in package.json
  • Update logger creation (pino()createLogger())
  • Swap argument order (context before message → message before context)
  • Update nameservice
  • Replace req.logreq.logger
  • Migrate custom serializers to plugins
  • Update redaction configuration
  • Replace pino-http with expressMiddleware
  • Update transport configuration
  • Test thoroughly in dev environment

Gradual Migration

You can migrate gradually by running both loggers:

import pino from "pino";
import { createLogger } from "cenglu";

const pinoLogger = pino();
const cengluLogger = createLogger({ service: "app" });

// Wrapper function
function log(level: string, message: string, context?: any) {
  pinoLogger[level](context, message); // Old format
  cengluLogger[level](message, context); // New format
}

// Use wrapper during migration
log("info", "Test", { data: 123 });

On this page