@untools/logger
Version:
An enhanced logger for JavaScript/TypeScript that handles DOM elements and circular references
271 lines (270 loc) • 10.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.logger = exports.Logger = void 0;
class Logger {
constructor(options = {}) {
var _a, _b, _c, _d, _e, _f;
this.showInProd = (_a = options.showInProd) !== null && _a !== void 0 ? _a : false;
this.includeTimestamp = (_b = options.includeTimestamp) !== null && _b !== void 0 ? _b : true;
this.maxDepth = (_c = options.maxDepth) !== null && _c !== void 0 ? _c : 5;
this.maxStringLength = (_d = options.maxStringLength) !== null && _d !== void 0 ? _d : 10000;
this.enableCircularHandling = (_e = options.enableCircularHandling) !== null && _e !== void 0 ? _e : true;
this.domElementFormat = (_f = options.domElementFormat) !== null && _f !== void 0 ? _f : "summary";
this.env = this.getEnvironment();
}
getEnvironment() {
const isNode = typeof process !== "undefined" &&
process.versions != null &&
process.versions.node != null;
let isDevelopment = true;
if (isNode) {
isDevelopment = process.env.NODE_ENV !== "production";
}
else {
isDevelopment =
!window.location.hostname.includes("production") &&
process.env.NODE_ENV !== "production";
}
return { isDevelopment, isNode };
}
getCallerInfo() {
var _a, _b;
const callerInfo = { file: "unknown", line: "unknown", column: "unknown" };
try {
const stack = new Error().stack;
if (stack) {
// Split the stack trace into lines
const lines = stack.split("\n");
// Find the first line that doesn't include our logger file
let callerLine = "";
for (let i = 1; i < lines.length; i++) {
if (!lines[i].includes("debugLogger.ts") &&
!lines[i].includes("at Logger.")) {
callerLine = lines[i];
break;
}
}
if (this.env.isNode) {
// Node.js style stack trace
const matches = callerLine === null || callerLine === void 0 ? void 0 : callerLine.match(/\((.*):(\d+):(\d+)\)$/);
if (matches) {
callerInfo.file = ((_a = matches[1]) === null || _a === void 0 ? void 0 : _a.split("/").pop()) || "unknown";
callerInfo.line = matches[2] || "unknown";
callerInfo.column = matches[3] || "unknown";
}
}
else {
// Browser style stack trace
const matches = callerLine === null || callerLine === void 0 ? void 0 : callerLine.match(/at (?:.*? \()?(.+):(\d+):(\d+)/);
if (matches) {
callerInfo.file = ((_b = matches[1]) === null || _b === void 0 ? void 0 : _b.split("/").pop()) || "unknown";
callerInfo.line = matches[2] || "unknown";
callerInfo.column = matches[3] || "unknown";
}
}
}
}
catch (_c) {
// If stack trace parsing fails, we'll use default unknown values
}
return callerInfo;
}
formatMetadata(level) {
const callerInfo = this.getCallerInfo();
const timestamp = this.includeTimestamp ? new Date().toISOString() : "";
return [
`[${level.toUpperCase()}]`,
this.includeTimestamp ? `[${timestamp}]` : "",
`[${callerInfo.file}:${callerInfo.line}]`,
].filter(Boolean);
}
formatDOMElement(element) {
if (this.domElementFormat === "disabled") {
return "[DOM Element]";
}
if (this.domElementFormat === "inspect") {
let attributes = "";
for (let i = 0; i < element.attributes.length; i++) {
const attr = element.attributes[i];
attributes += ` ${attr.name}="${attr.value}"`;
}
let children = "";
if (element.children.length > 0) {
children = ` (${element.children.length} children)`;
}
return `<${element.tagName.toLowerCase()}${attributes}>${children}`;
}
// Default: summary
return `<${element.tagName.toLowerCase()}> with ${element.attributes.length} attributes and ${element.children.length} children`;
}
formatArgument(arg, depth = 0, seen = new WeakMap()) {
// Check for max depth to prevent call stack overflow
if (depth > this.maxDepth) {
return "[Max Depth Reached]";
}
if (arg === null) {
return "null";
}
if (arg === undefined) {
return "undefined";
}
if (typeof arg === "string") {
if (arg.length > this.maxStringLength) {
return `${arg.substring(0, this.maxStringLength)}... [truncated, ${arg.length} chars total]`;
}
return arg;
}
if (typeof arg === "number" ||
typeof arg === "boolean" ||
typeof arg === "symbol" ||
typeof arg === "bigint") {
return String(arg);
}
if (typeof arg === "function") {
return `[Function: ${arg.name || "anonymous"}]`;
}
if (typeof window !== "undefined" &&
window.Element &&
arg instanceof Element) {
return this.formatDOMElement(arg);
}
if (arg instanceof Error) {
return `${arg.name}: ${arg.message}\n${arg.stack || ""}`;
}
if (arg instanceof Date) {
return arg.toISOString();
}
if (arg instanceof RegExp) {
return arg.toString();
}
if (Array.isArray(arg)) {
if (this.enableCircularHandling && seen.has(arg)) {
return "[Circular Reference]";
}
if (this.enableCircularHandling) {
seen.set(arg, true);
}
const items = arg
.map((item) => this.formatArgument(item, depth + 1, seen))
.join(", ");
return `[${items}]`;
}
if (typeof arg === "object") {
if (this.enableCircularHandling && seen.has(arg)) {
return "[Circular Reference]";
}
if (this.enableCircularHandling) {
seen.set(arg, true);
}
try {
const entries = Object.entries(arg).map(([key, value]) => `${key}: ${this.formatArgument(value, depth + 1, seen)}`);
return `{${entries.join(", ")}}`;
}
catch (error) {
// Type-safe error handling
const errorMessage = error instanceof Error ? error.message : "unknown error";
return `[Object: failed to stringify - ${errorMessage}]`;
}
}
return String(arg);
}
_log(level, ...args) {
// Check if we should show logs in current environment
if (!this.env.isDevelopment && !this.showInProd) {
return;
}
const metadata = this.formatMetadata(level);
// Browser-specific styling
if (!this.env.isNode) {
const styles = {
log: "color: #0099cc",
debug: "color: #0099cc",
info: "color: #00cc00",
warn: "color: #cccc00",
error: "color: #cc0000",
};
// Special handling for objects in browser
// If it's a DOM element or a circular object, use our custom formatter
// Otherwise, pass the original object to console for better browser inspection
const formattedArgs = [];
const rawObjects = [];
args.forEach((arg) => {
if ((typeof window !== "undefined" &&
window.Element &&
arg instanceof Element) ||
(typeof arg === "object" && arg !== null)) {
formattedArgs.push(this.formatArgument(arg));
rawObjects.push(arg);
}
else {
formattedArgs.push(this.formatArgument(arg));
}
});
// Log with formatting
console[level](`%c${[...metadata, ...formattedArgs].join(" ")}`, styles[level]);
// Additionally log raw objects for better browser inspection if they exist
if (rawObjects.length > 0) {
console.groupCollapsed("Raw Objects");
rawObjects.forEach((obj) => console[level](obj));
console.groupEnd();
}
return;
}
// Node.js output with colors
const colors = {
log: "\x1b[36m",
debug: "\x1b[36m",
info: "\x1b[32m",
warn: "\x1b[33m",
error: "\x1b[31m",
reset: "\x1b[0m", // Reset
};
const formattedArgs = args.map((arg) => this.formatArgument(arg));
console[level](colors[level], ...metadata, ...formattedArgs, colors.reset);
}
log(...args) {
this._log("log", ...args);
}
debug(...args) {
this._log("debug", ...args);
}
info(...args) {
this._log("info", ...args);
}
warn(...args) {
this._log("warn", ...args);
}
error(...args) {
this._log("error", ...args);
}
// Group logging for better organization
group(label) {
if (!this.env.isDevelopment && !this.showInProd) {
return;
}
console.group(label);
}
groupEnd() {
if (!this.env.isDevelopment && !this.showInProd) {
return;
}
console.groupEnd();
}
// Utility method to time operations
time(label) {
if (!this.env.isDevelopment && !this.showInProd) {
return;
}
console.time(label);
}
timeEnd(label) {
if (!this.env.isDevelopment && !this.showInProd) {
return;
}
console.timeEnd(label);
}
}
exports.Logger = Logger;
// Create default instance
const logger = new Logger();
exports.logger = logger;