cenglu

Migrating from Winston

Complete guide to migrating from Winston to cenglu

Migrating from Winston

This guide helps you migrate from Winston to cenglu with code examples and feature mappings.

Quick Comparison

FeatureWinstonCenglu
DependenciesManyZero
Bundle Size~1MB< 500KB
PerformanceSlowFast (5x faster)
Built-in Redaction
TypeScript⚠️ Partial✅ Full
ConfigurationComplexSimple

Basic Migration

Creating a Logger

Winston:

import winston from "winston";

const logger = winston.createLogger({
  level: "info",
  format: winston.format.json(),
  defaultMeta: { service: "my-app" },
  transports: [
    new winston.transports.Console(),
    new winston.transports.File({ filename: "app.log" }),
  ],
});

Cenglu:

import { createLogger, createFileTransport } from "cenglu";

const logger = createLogger({
  level: "info",
  service: "my-app",
  structured: { type: "json" }, // JSON format built-in
  transports: [
    createFileTransport({ dir: "./logs" }),
  ],
});

Logging Methods

Winston:

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

Cenglu:

logger.info("User logged in");
logger.info("User logged in", { userId: 123 });
logger.error("Failed", new Error("Oops")); // Error as second argument

Child Loggers

Winston:

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

WinstonCengluNotes
errorerror✅ Same
warnwarn✅ Same
infoinfo✅ Same
httpinfoMap to info
verbosedebugMap to debug
debugdebug✅ Same
sillytraceMap to trace

Configuration

Winston OptionCenglu Equivalent
levellevel
levelsNot needed (standardized)
formatstructured.type
defaultMetabindings
transportstransports
exitOnErrorHandle manually
silentconsole.enabled: false

Format Migration

JSON Format

Winston:

const logger = winston.createLogger({
  format: winston.format.json(),
});

Cenglu:

const logger = createLogger({
  structured: { type: "json" }, // Default
});

Pretty Format

Winston:

const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.colorize(),
    winston.format.simple()
  ),
});

Cenglu:

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

Custom Format

Winston:

const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.printf(info => {
      return `${info.timestamp} [${info.level}]: ${info.message}`;
    })
  ),
});

Cenglu:

const logger = createLogger({
  pretty: {
    enabled: true,
    formatter: (record) => {
      return `${new Date(record.time).toISOString()} [${record.level}]: ${record.msg}`;
    },
  },
});

Transport Migration

Console Transport

Winston:

new winston.transports.Console({
  level: "info",
  format: winston.format.simple(),
})

Cenglu:

import { createConsoleTransport } from "cenglu";

createConsoleTransport({
  enabled: true,
  stream: process.stdout,
})

File Transport

Winston:

new winston.transports.File({
  filename: "error.log",
  level: "error",
  maxsize: 10485760, // 10MB
  maxFiles: 5,
})

Cenglu:

import { createRotatingFileTransport } from "cenglu";

createRotatingFileTransport({
  dir: "./logs",
  separateErrors: true,
  rotation: {
    maxBytes: 10485760, // 10MB
    maxFiles: 5,
    compress: "gzip",
  },
})

Daily Rotate File

Winston:

import DailyRotateFile from "winston-daily-rotate-file";

new DailyRotateFile({
  filename: "app-%DATE%.log",
  datePattern: "YYYY-MM-DD",
  maxSize: "20m",
  maxFiles: "14d",
})

Cenglu:

createRotatingFileTransport({
  dir: "./logs",
  filename: ({ date }) => `app-${date.toISOString().split("T")[0]}.log`,
  rotation: {
    intervalDays: 1,
    maxBytes: 20971520, // 20MB
    retentionDays: 14,
  },
})

HTTP Transport

Winston:

new winston.transports.Http({
  host: "logs.example.com",
  port: 443,
  path: "/logs",
})

Cenglu:

const logger = createLogger({
  adapters: [
    {
      name: "http",
      handle: async (record) => {
        await fetch("https://logs.example.com/logs", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(record),
        });
      },
    },
  ],
});

Feature Migration

Metadata

Winston:

const logger = winston.createLogger({
  defaultMeta: {
    service: "my-app",
    version: "1.0.0",
  },
});

logger.info("Message", { userId: 123 });
// Output includes service, version, userId

Cenglu:

const logger = createLogger({
  service: "my-app",
  version: "1.0.0",
  bindings: {
    region: "us-east-1",
  },
});

logger.info("Message", { userId: 123 });
// Output includes all fields

Logging Errors

Winston:

try {
  throw new Error("Something went wrong");
} catch (error) {
  logger.error("Error occurred", { error });
}

Cenglu:

try {
  throw new Error("Something went wrong");
} catch (error) {
  logger.error("Error occurred", error); // Error as second argument
}

Filters

Winston:

const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format((info) => {
      if (info.level === "http") {
        return false; // Drop
      }
      return info;
    })()
  ),
});

Cenglu:

import { filterPlugin } from "cenglu";

const logger = createLogger({
  plugins: [
    filterPlugin({
      filter: (record) => {
        return record.level !== "debug"; // Keep all except debug
      },
    }),
  ],
});

Exception Handling

Winston:

const logger = winston.createLogger({
  exceptionHandlers: [
    new winston.transports.File({ filename: "exceptions.log" }),
  ],
  rejectionHandlers: [
    new winston.transports.File({ filename: "rejections.log" }),
  ],
});

Cenglu:

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

process.on("uncaughtException", (error) => {
  logger.fatal("Uncaught exception", error);
  process.exit(1);
});

process.on("unhandledRejection", (reason, promise) => {
  logger.fatal("Unhandled rejection", reason as Error, {
    promise: String(promise),
  });
});

Express Integration

Winston:

import express from "express";
import expressWinston from "express-winston";

const app = express();

app.use(expressWinston.logger({
  transports: [new winston.transports.Console()],
  format: winston.format.json(),
  meta: true,
  msg: "HTTP {{req.method}} {{req.url}}",
}));

Cenglu:

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

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

app.use(expressMiddleware(logger, {
  logRequests: true,
  logResponses: true,
}));

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

Advanced Features

Log Streaming

Winston:

const stream = logger.stream({
  level: "info",
});

stream.on("log", (info) => {
  console.log(info);
});

Cenglu:

// Use adapters for streaming
const logger = createLogger({
  adapters: [
    {
      name: "stream",
      handle: (record) => {
        myStream.write(record);
      },
    },
  ],
});

Query Logs

Winston supports querying logs from transports. In cenglu, query your log storage directly (files, databases, etc.).

Winston:

logger.query({ limit: 10 }, (err, results) => {
  console.log(results);
});

Cenglu:

// Query your log storage directly
import fs from "fs/promises";

const logs = await fs.readFile("./logs/app.log", "utf-8");
const lines = logs.split("\n")
  .filter(Boolean)
  .map(line => JSON.parse(line))
  .slice(-10); // Last 10 logs

Profiling

Winston:

logger.profile("test");
// ... do work
logger.profile("test"); // Logs duration

Cenglu:

const done = logger.time("test");
// ... do work
done(); // Logs duration

Common Pitfalls

1. Format Combiners

Winston uses format combiners:

// Winston
winston.format.combine(
  winston.format.timestamp(),
  winston.format.json()
)

// Cenglu: simpler
{ structured: { type: "json" } }

2. Transport Arrays

Winston:

transports: [
  new winston.transports.Console(),
  new winston.transports.File({ filename: "app.log" }),
]

Cenglu:

transports: [
  createConsoleTransport(),
  createFileTransport({ dir: "./logs" }),
]

3. Custom Levels

Winston allows custom levels:

// Winston
levels: {
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  debug: 4,
}

Cenglu uses standardized levels. Map your custom levels to standard ones.

Migration Checklist

  • Replace winston with cenglu in package.json
  • Update logger creation
  • Simplify format configuration
  • Replace transport instantiation
  • Update error logging (error as second arg)
  • Migrate exception handlers to process events
  • Replace expressWinston with expressMiddleware
  • Update profiling to use logger.time()
  • Remove format combiners
  • Test thoroughly

Gradual Migration

Run both loggers during migration:

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

const winstonLogger = winston.createLogger({...});
const cengluLogger = createLogger({...});

// Wrapper
function log(level: string, message: string, meta?: any) {
  winstonLogger[level](message, meta);
  cengluLogger[level](message, meta);
}

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

// Gradually replace with cengluLogger.info()

Why Migrate?

  1. 5x Faster: Significantly better performance
  2. Zero Dependencies: Smaller bundle, fewer vulnerabilities
  3. Built-in Features: Redaction, middleware, TypeScript
  4. Simpler API: Less configuration, more conventions
  5. Better DX: Full TypeScript support, better error messages

On this page