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 messageKey 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
| Pino | Cenglu | Notes |
|---|---|---|
trace() | trace() | ✅ Same |
debug() | debug() | ✅ Same |
info() | info() | ✅ Same |
warn() | warn() | ✅ Same |
error() | error() | ✅ Same |
fatal() | fatal() | ✅ Same |
Configuration
| Pino Option | Cenglu Equivalent |
|---|---|
level | level |
name | service |
enabled | console.enabled |
serializers | Custom plugin |
formatters | structured.transform |
redact | redaction.paths |
timestamp | Always included |
base | bindings |
mixin | enrichPlugin() |
Methods
| Pino | Cenglu |
|---|---|
logger.child() | logger.child() |
logger.level | logger.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 secondCenglu:
logger.info("User action", { userId: 123 });
// Message first, context second2. Error Logging
Pino:
logger.error({ err }, "Error occurred");Cenglu:
logger.error("Error occurred", err, { additional: "context" });
// Error as second argument3. Request Logger Access
Pino-HTTP:
req.log.info("message");Cenglu:
req.logger.info("message");Migration Checklist
- Replace
pinowithcengluin package.json - Update logger creation (
pino()→createLogger()) - Swap argument order (context before message → message before context)
- Update
name→service - Replace
req.log→req.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 });