From fbcb39d52c03d40675dfa36ac21bfb238546cc8b Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Thu, 12 Feb 2026 15:35:27 -0800 Subject: [PATCH] feat(logger): add @bytelyst/logger shared package createLogger(config) factory for structured logging across dashboards and services. - Dev: pretty-prints to stderr/stdout - Prod: JSON structured output for log ingestion (Loki, Datadog, etc.) - Exports: createLogger, Logger, LoggerConfig, LogLevel, LogPayload --- packages/logger/package.json | 19 ++++++++ packages/logger/src/index.ts | 2 + packages/logger/src/logger.ts | 83 +++++++++++++++++++++++++++++++++++ packages/logger/src/types.ts | 25 +++++++++++ packages/logger/tsconfig.json | 8 ++++ 5 files changed, 137 insertions(+) create mode 100644 packages/logger/package.json create mode 100644 packages/logger/src/index.ts create mode 100644 packages/logger/src/logger.ts create mode 100644 packages/logger/src/types.ts create mode 100644 packages/logger/tsconfig.json diff --git a/packages/logger/package.json b/packages/logger/package.json new file mode 100644 index 00000000..c7b786bb --- /dev/null +++ b/packages/logger/package.json @@ -0,0 +1,19 @@ +{ + "name": "@bytelyst/logger", + "version": "0.1.0", + "description": "Structured logger factory for Next.js dashboards and Node.js services", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": ["dist"], + "scripts": { + "build": "tsc", + "test": "vitest run" + } +} diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts new file mode 100644 index 00000000..2b538076 --- /dev/null +++ b/packages/logger/src/index.ts @@ -0,0 +1,2 @@ +export { createLogger } from "./logger.js"; +export type { Logger, LoggerConfig, LogLevel, LogPayload } from "./types.js"; diff --git a/packages/logger/src/logger.ts b/packages/logger/src/logger.ts new file mode 100644 index 00000000..28591ada --- /dev/null +++ b/packages/logger/src/logger.ts @@ -0,0 +1,83 @@ +import type { LogLevel, LogPayload, LoggerConfig, Logger } from "./types.js"; + +function formatPayload( + service: string, + level: LogLevel, + message: string, + error?: unknown, + extra?: Record, +): LogPayload { + const payload: LogPayload = { + level, + message, + timestamp: new Date().toISOString(), + service, + ...extra, + }; + + if (error instanceof Error) { + payload.error = error.message; + payload.stack = error.stack; + } else if (error !== undefined) { + payload.error = String(error); + } + + return payload; +} + +function emit(payload: LogPayload, isDev: boolean): void { + if (isDev) { + const { level, message, error: err, stack: _stack, ...rest } = payload; + const prefix = `[${level.toUpperCase()}]`; + const extras = Object.keys(rest).length > 2 ? ` ${JSON.stringify(rest)}` : ""; + const errStr = err ? ` — ${err}` : ""; + + if (level === "error" || level === "warn") { + // eslint-disable-next-line no-console + console.error(`${prefix} ${message}${errStr}${extras}`); + } else { + // eslint-disable-next-line no-console + console.log(`${prefix} ${message}${extras}`); + } + } else { + const out = JSON.stringify(payload); + if (payload.level === "error" || payload.level === "warn") { + process.stderr.write(out + "\n"); + } else { + process.stdout.write(out + "\n"); + } + } +} + +/** + * Create a structured logger for a service or dashboard. + * + * @example + * ```ts + * import { createLogger } from "@bytelyst/logger"; + * const log = createLogger({ service: "admin-dashboard" }); + * log.error("Login failed", error, { userId }); + * log.info("Seed complete", { containers: 8 }); + * ``` + */ +export function createLogger(config: LoggerConfig): Logger { + const { service } = config; + const isDev = config.isDev ?? process.env.NODE_ENV !== "production"; + + return { + error(message: string, error?: unknown, extra?: Record): void { + emit(formatPayload(service, "error", message, error, extra), isDev); + }, + warn(message: string, extra?: Record): void { + emit(formatPayload(service, "warn", message, undefined, extra), isDev); + }, + info(message: string, extra?: Record): void { + emit(formatPayload(service, "info", message, undefined, extra), isDev); + }, + debug(message: string, extra?: Record): void { + if (isDev) { + emit(formatPayload(service, "debug", message, undefined, extra), isDev); + } + }, + }; +} diff --git a/packages/logger/src/types.ts b/packages/logger/src/types.ts new file mode 100644 index 00000000..61ac282f --- /dev/null +++ b/packages/logger/src/types.ts @@ -0,0 +1,25 @@ +export type LogLevel = "info" | "warn" | "error" | "debug"; + +export interface LogPayload { + level: LogLevel; + message: string; + timestamp: string; + service: string; + error?: string; + stack?: string; + [key: string]: unknown; +} + +export interface LoggerConfig { + /** Service name included in every log entry (e.g. "admin-dashboard", "billing-service") */ + service: string; + /** Override NODE_ENV detection for dev/prod mode. Default: reads process.env.NODE_ENV */ + isDev?: boolean; +} + +export interface Logger { + error(message: string, error?: unknown, extra?: Record): void; + warn(message: string, extra?: Record): void; + info(message: string, extra?: Record): void; + debug(message: string, extra?: Record): void; +} diff --git a/packages/logger/tsconfig.json b/packages/logger/tsconfig.json new file mode 100644 index 00000000..5a24989c --- /dev/null +++ b/packages/logger/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +}