@anishsharma/betterlogs
Version:
A lightweight yet powerful logging library that makes console output elegant, expressive, and customizable
205 lines (171 loc) • 6 kB
text/typescript
import { LogEntry, LogLevel, BetterLogsConfig } from "./types";
import { ConfigManager } from "./config";
import { ThemeManager } from "./themes";
import { Formatter } from "./formatter";
import { FileLogger } from "./fileLogger";
import { EnvironmentDetector } from "./utils";
export class BetterLogger {
private configManager: ConfigManager;
private themeManager: ThemeManager;
private fileLogger?: FileLogger;
private labels: Map<string, BetterLogger> = new Map();
private customLevels: Map<string, { color: string; emoji: string }> =
new Map();
private activeTimers: Map<string, number> = new Map();
private label?: string;
constructor(
configManager: ConfigManager,
themeManager: ThemeManager,
label?: string
) {
this.configManager = configManager;
this.themeManager = themeManager;
this.label = label;
if (
EnvironmentDetector.isNode() &&
this.configManager.getConfig().file
) {
this.fileLogger = new FileLogger(
this.configManager.getConfig().file!
);
}
}
// Core logging methods
info(message: string, ...data: unknown[]): void {
this.log("info", message, data);
}
success(message: string, ...data: unknown[]): void {
this.log("success", message, data);
}
warn(message: string, ...data: unknown[]): void {
this.log("warn", message, data);
}
error(message: string, ...data: unknown[]): void {
this.log("error", message, data);
}
debug(message: string, ...data: unknown[]): void {
this.log("debug", message, data);
}
// Label system
withLabel(labelName: string): BetterLogger {
if (!this.labels.has(labelName)) {
this.labels.set(
labelName,
new BetterLogger(
this.configManager,
this.themeManager,
labelName
)
);
}
return this.labels.get(labelName)!;
}
// Configuration
config(newConfig: Partial<BetterLogsConfig>): void {
this.configManager.updateConfig(newConfig);
}
setLevel(level: LogLevel): void {
this.configManager.updateConfig({ level });
}
setMode(mode: "pretty" | "json"): void {
this.configManager.updateConfig({ mode });
}
// Custom levels
addLevel(name: string, config: { color: string; emoji: string }): void {
this.customLevels.set(name, config);
// Add the level to the current theme for proper formatting
const currentTheme = this.configManager.getCurrentTheme();
if (!currentTheme.levels[name]) {
currentTheme.levels[name] = config;
}
(this as any)[name] = (message: string, ...data: unknown[]) => {
this.log(name, message, data);
};
}
// Grouped logs
group(name: string): BetterLogger {
return this.withLabel(name);
}
// Table logging
table(data: unknown[] | object): void {
if (console.table) {
console.table(data);
} else {
// Fallback for environments without console.table
console.log(JSON.stringify(data, null, 2));
}
}
// Timer utilities
time(label: string): void {
this.activeTimers.set(label, Date.now());
}
timeEnd(label: string): void {
const startTime = this.activeTimers.get(label);
if (startTime) {
const duration = Date.now() - startTime;
this.info(`Timer '${label}': ${duration}ms`);
this.activeTimers.delete(label);
}
}
// File logging (Node.js only)
file(filePath: string): void {
if (EnvironmentDetector.isNode()) {
this.configManager.updateConfig({ file: filePath });
this.fileLogger = new FileLogger(filePath);
} else {
this.warn("File logging is only available in Node.js environment");
}
}
// Private methods
private log(level: string, message: string, data: unknown[]): void {
// For custom levels, check against 'debug' level to ensure they're logged
const levelToCheck = this.customLevels.has(level) ? "debug" : level;
if (!this.configManager.shouldLog(levelToCheck as LogLevel)) {
return;
}
const entry: LogEntry = {
level,
message,
timestamp: new Date(),
label: this.label,
data: data.length > 0 ? data : undefined
};
const config = this.configManager.getConfig();
const theme = this.configManager.getCurrentTheme();
// Ensure custom levels are available in the theme for formatting
if (!theme.levels[level] && this.customLevels.has(level)) {
theme.levels[level] = this.customLevels.get(level)!;
}
const formattedMessage = Formatter.format(entry, config, theme);
// Output to console
this.outputToConsole(level, formattedMessage, data);
// Output to file if configured
if (this.fileLogger) {
this.fileLogger.write(entry);
}
}
private outputToConsole(
level: string,
formattedMessage: string,
data: unknown[]
): void {
const consoleMethod = this.getConsoleMethod(level);
if (data.length > 0) {
consoleMethod(formattedMessage, ...data);
} else {
consoleMethod(formattedMessage);
}
}
private getConsoleMethod(level: string): (...args: unknown[]) => void {
switch (level) {
case "error":
return console.error;
case "warn":
return console.warn;
case "debug":
return console.debug;
default:
return console.log;
}
}
}