@anishsharma/betterlogs
Version:
A lightweight yet powerful logging library that makes console output elegant, expressive, and customizable
481 lines (472 loc) • 13.7 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
BetterLogger: () => BetterLogger,
ConfigManager: () => ConfigManager,
ThemeManager: () => ThemeManager,
betterlogs: () => betterlogs,
default: () => index_default
});
module.exports = __toCommonJS(index_exports);
// src/utils.ts
var EnvironmentDetector = class {
static isNode() {
return typeof process !== "undefined" && process.versions != null && process.versions.node != null;
}
static isBrowser() {
return typeof window !== "undefined" && typeof document !== "undefined";
}
static supportsEmoji() {
if (this.isNode()) {
const isTTY = process.stdout?.isTTY;
const hasNoEmojiFlag = process.env.NO_EMOJI === "true";
return !!isTTY && !hasNoEmojiFlag;
}
if (this.isBrowser()) {
return true;
}
return true;
}
};
var Colorizer = class {
static applyColor(text, color, isBackground = false) {
if (EnvironmentDetector.isNode()) {
return this.applyNodeColor(text, color, isBackground);
} else {
return this.applyBrowserColor(text, color, isBackground);
}
}
static applyNodeColor(text, color, isBackground) {
const colors = {
red: "\x1B[31m",
green: "\x1B[32m",
yellow: "\x1B[33m",
blue: "\x1B[34m",
magenta: "\x1B[35m",
cyan: "\x1B[36m",
white: "\x1B[37m",
gray: "\x1B[90m",
reset: "\x1B[0m"
};
const bgColors = {
red: "\x1B[41m",
green: "\x1B[42m",
yellow: "\x1B[43m",
blue: "\x1B[44m",
magenta: "\x1B[45m",
cyan: "\x1B[46m",
white: "\x1B[47m",
reset: "\x1B[0m"
};
const code = isBackground ? bgColors[color] : colors[color];
return code ? `${code}${text}${colors.reset}` : text;
}
static applyBrowserColor(text) {
return text;
}
};
function formatTime(date, format) {
const hours = format === "12h" ? date.getHours() % 12 || 12 : date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");
const seconds = date.getSeconds().toString().padStart(2, "0");
if (format === "12h") {
const ampm = date.getHours() >= 12 ? "PM" : "AM";
return `${hours}:${minutes}:${seconds} ${ampm}`;
}
return `${hours}:${minutes}:${seconds}`;
}
// src/formatter.ts
var Formatter = class {
static formatPretty(entry, config, theme) {
const parts = [];
if (config.showTimestamp) {
const timestamp = formatTime(entry.timestamp, config.timestampFormat);
parts.push(`[${timestamp}]`);
}
if (config.showEmoji) {
const levelConfig2 = theme.levels[entry.level] || theme.levels.info;
parts.push(levelConfig2.emoji);
}
const levelConfig = theme.levels[entry.level] || theme.levels.info;
const levelStr = entry.level.toUpperCase();
const coloredLevel = Colorizer.applyColor(levelStr, levelConfig.color);
parts.push(coloredLevel);
if (entry.label) {
const labelStr = `[${entry.label}]`;
const coloredLabel = Colorizer.applyColor(labelStr, "gray");
parts.push(coloredLabel);
}
parts.push(entry.message);
return parts.join(" ");
}
static formatJson(entry) {
const jsonEntry = {
level: entry.level,
message: entry.message,
timestamp: entry.timestamp.toISOString(),
label: entry.label,
...entry.data && entry.data.length > 0 ? { data: entry.data } : {}
};
return JSON.stringify(jsonEntry);
}
static format(entry, config, theme) {
if (config.mode === "json") {
return this.formatJson(entry);
}
return this.formatPretty(entry, config, theme);
}
};
// src/fileLogger.ts
var import_fs = require("fs");
var import_path = require("path");
var FileLogger = class {
constructor(filePath) {
this.filePath = filePath;
this.ensureDirectoryExists();
this.initializeFile();
}
write(entry) {
const logLine = this.formatLogEntry(entry);
try {
(0, import_fs.appendFileSync)(this.filePath, logLine + "\n", "utf8");
} catch (error) {
console.error("Failed to write to log file:", error);
}
}
ensureDirectoryExists() {
const dir = (0, import_path.dirname)(this.filePath);
if (!(0, import_fs.existsSync)(dir)) {
(0, import_fs.mkdirSync)(dir, { recursive: true });
}
}
initializeFile() {
if (!(0, import_fs.existsSync)(this.filePath)) {
(0, import_fs.writeFileSync)(this.filePath, "", "utf8");
}
}
formatLogEntry(entry) {
return JSON.stringify({
timestamp: entry.timestamp.toISOString(),
level: entry.level,
message: entry.message,
label: entry.label,
...entry.data && { data: entry.data }
});
}
};
// src/logger.ts
var BetterLogger = class _BetterLogger {
constructor(configManager2, themeManager2, label) {
this.labels = /* @__PURE__ */ new Map();
this.customLevels = /* @__PURE__ */ new Map();
this.activeTimers = /* @__PURE__ */ new Map();
this.configManager = configManager2;
this.themeManager = themeManager2;
this.label = label;
if (EnvironmentDetector.isNode() && this.configManager.getConfig().file) {
this.fileLogger = new FileLogger(
this.configManager.getConfig().file
);
}
}
// Core logging methods
info(message, ...data) {
this.log("info", message, data);
}
success(message, ...data) {
this.log("success", message, data);
}
warn(message, ...data) {
this.log("warn", message, data);
}
error(message, ...data) {
this.log("error", message, data);
}
debug(message, ...data) {
this.log("debug", message, data);
}
// Label system
withLabel(labelName) {
if (!this.labels.has(labelName)) {
this.labels.set(
labelName,
new _BetterLogger(
this.configManager,
this.themeManager,
labelName
)
);
}
return this.labels.get(labelName);
}
// Configuration
config(newConfig) {
this.configManager.updateConfig(newConfig);
}
setLevel(level) {
this.configManager.updateConfig({ level });
}
setMode(mode) {
this.configManager.updateConfig({ mode });
}
// Custom levels
addLevel(name, config) {
this.customLevels.set(name, config);
const currentTheme = this.configManager.getCurrentTheme();
if (!currentTheme.levels[name]) {
currentTheme.levels[name] = config;
}
this[name] = (message, ...data) => {
this.log(name, message, data);
};
}
// Grouped logs
group(name) {
return this.withLabel(name);
}
// Table logging
table(data) {
if (console.table) {
console.table(data);
} else {
console.log(JSON.stringify(data, null, 2));
}
}
// Timer utilities
time(label) {
this.activeTimers.set(label, Date.now());
}
timeEnd(label) {
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) {
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
log(level, message, data) {
const levelToCheck = this.customLevels.has(level) ? "debug" : level;
if (!this.configManager.shouldLog(levelToCheck)) {
return;
}
const entry = {
level,
message,
timestamp: /* @__PURE__ */ new Date(),
label: this.label,
data: data.length > 0 ? data : void 0
};
const config = this.configManager.getConfig();
const theme = this.configManager.getCurrentTheme();
if (!theme.levels[level] && this.customLevels.has(level)) {
theme.levels[level] = this.customLevels.get(level);
}
const formattedMessage = Formatter.format(entry, config, theme);
this.outputToConsole(level, formattedMessage, data);
if (this.fileLogger) {
this.fileLogger.write(entry);
}
}
outputToConsole(level, formattedMessage, data) {
const consoleMethod = this.getConsoleMethod(level);
if (data.length > 0) {
consoleMethod(formattedMessage, ...data);
} else {
consoleMethod(formattedMessage);
}
}
getConsoleMethod(level) {
switch (level) {
case "error":
return console.error;
case "warn":
return console.warn;
case "debug":
return console.debug;
default:
return console.log;
}
}
};
// src/config.ts
var defaultConfig = {
showTimestamp: true,
showEmoji: true,
theme: "dark",
level: "info",
mode: "pretty",
timestampFormat: "24h"
};
var ConfigManager = class {
constructor(themeManager2) {
this.themeManager = themeManager2;
this.config = { ...defaultConfig };
}
updateConfig(newConfig) {
this.config = {
...this.config,
...newConfig
};
}
getConfig() {
return { ...this.config };
}
getCurrentTheme() {
const theme = this.config.theme;
if (typeof theme === "string") {
return this.themeManager.getTheme(theme) || this.themeManager.getDefaultTheme();
}
return theme;
}
shouldLog(level) {
const levelWeights = {
debug: 0,
info: 1,
success: 1,
warn: 2,
error: 3,
silent: 999
};
return levelWeights[level] >= levelWeights[this.config.level];
}
};
// src/themes.ts
var builtInThemes = {
dark: {
name: "dark",
levels: {
info: { color: "cyan", emoji: "\u{1F4A1}" },
success: { color: "green", emoji: "\u2705" },
warn: { color: "yellow", emoji: "\u26A0\uFE0F" },
error: { color: "red", emoji: "\u274C" },
debug: { color: "magenta", emoji: "\u{1F50D}" }
},
background: "#1a1a1a"
},
light: {
name: "light",
levels: {
info: { color: "blue", emoji: "\u{1F4A1}" },
success: { color: "green", emoji: "\u2705" },
warn: { color: "orange", emoji: "\u26A0\uFE0F" },
error: { color: "red", emoji: "\u274C" },
debug: { color: "purple", emoji: "\u{1F50D}" }
},
background: "#ffffff"
},
neon: {
name: "neon",
levels: {
info: { color: "#00FFFF", emoji: "\u{1F4A1}" },
success: { color: "#00FF7F", emoji: "\u2705" },
warn: { color: "#FFD700", emoji: "\u26A0\uFE0F" },
error: { color: "#FF5555", emoji: "\u274C" },
debug: { color: "#8888FF", emoji: "\u{1F50D}" }
},
background: "#111111"
},
minimal: {
name: "minimal",
levels: {
info: { color: "gray", emoji: "\u2022" },
success: { color: "gray", emoji: "\u2713" },
warn: { color: "gray", emoji: "!" },
error: { color: "gray", emoji: "\u2717" },
debug: { color: "gray", emoji: ">" }
}
}
};
var ThemeManager = class {
constructor() {
this.themes = /* @__PURE__ */ new Map();
Object.values(builtInThemes).forEach((theme) => {
this.registerTheme(theme);
});
}
registerTheme(theme) {
this.themes.set(theme.name, theme);
}
getTheme(name) {
return this.themes.get(name);
}
getDefaultTheme() {
return builtInThemes.dark;
}
};
// src/index.ts
var themeManager = new ThemeManager();
var configManager = new ConfigManager(themeManager);
var baseLogger = new BetterLogger(configManager, themeManager);
var betterlogs = {
// Core logging methods
info: baseLogger.info.bind(baseLogger),
success: baseLogger.success.bind(baseLogger),
warn: baseLogger.warn.bind(baseLogger),
error: baseLogger.error.bind(baseLogger),
debug: baseLogger.debug.bind(baseLogger),
// Label system
label: baseLogger.withLabel.bind(baseLogger),
// Configuration
config: baseLogger.config.bind(baseLogger),
setLevel: (level) => baseLogger.setLevel(level),
setMode: baseLogger.setMode.bind(baseLogger),
// Advanced features
addLevel: (name, config) => {
baseLogger.addLevel(name, config);
betterlogs[name] = (message, ...data) => {
const loggerWithCustomMethod = baseLogger;
loggerWithCustomMethod[name](message, ...data);
};
},
group: baseLogger.group.bind(baseLogger),
table: baseLogger.table.bind(baseLogger),
time: baseLogger.time.bind(baseLogger),
timeEnd: baseLogger.timeEnd.bind(baseLogger),
file: baseLogger.file.bind(baseLogger),
// Theme management
addTheme: (theme) => themeManager.registerTheme(theme),
// Create new instance
create: (config) => {
const newConfigManager = new ConfigManager(themeManager);
if (config) {
newConfigManager.updateConfig(config);
}
return new BetterLogger(newConfigManager, themeManager);
}
};
var index_default = betterlogs;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
BetterLogger,
ConfigManager,
ThemeManager,
betterlogs
});