w-funnel/src/shared/utils/logger.ts
gofnnp ace03937db session
add session
2025-10-04 20:56:37 +04:00

400 lines
10 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* eslint-disable @typescript-eslint/no-explicit-any */
"use client";
export enum LogType {
API = "API",
SOCKET = "SOCKET",
ERROR = "ERROR",
INFO = "INFO",
}
export enum LogDirection {
REQUEST = "REQUEST",
RESPONSE = "RESPONSE",
INCOMING = "INCOMING",
OUTGOING = "OUTGOING",
}
interface LogEntry {
type: LogType;
direction?: LogDirection;
event: string;
data?: unknown;
url?: string;
method?: string;
status?: number;
timestamp: Date;
duration?: number;
}
class DevLogger {
private enabled = false;
private enabledTypes = new Set<LogType>(Object.values(LogType));
private envEnabled = false;
private serverLoggingEnabled = false;
constructor() {
// Check ENV variables first
if (typeof window !== "undefined") {
this.envEnabled = process.env.NEXT_PUBLIC_DEV_LOGGER_ENABLED !== "false";
} else {
// Server side - check server env
this.serverLoggingEnabled =
process.env.DEV_LOGGER_SERVER_ENABLED === "true";
this.envEnabled = process.env.DEV_LOGGER_ENABLED !== "false";
}
// Check localStorage for logging preferences (client-side only)
if (typeof window !== "undefined") {
const stored = localStorage.getItem("dev-logger-enabled");
this.enabled = stored ? JSON.parse(stored) : this.envEnabled;
const storedTypes = localStorage.getItem("dev-logger-types");
if (storedTypes) {
this.enabledTypes = new Set(JSON.parse(storedTypes));
}
} else {
this.enabled = this.envEnabled;
}
}
private shouldLog(type: LogType): boolean {
// Check ENV first, then user preferences
return this.envEnabled && this.enabled && this.enabledTypes.has(type);
}
private shouldLogServer(type: LogType): boolean {
// Server logging requires explicit ENV enable
return (
this.serverLoggingEnabled &&
this.envEnabled &&
this.enabled &&
this.enabledTypes.has(type)
);
}
private getLogStyle(
type: LogType,
direction?: LogDirection
): { emoji: string; color: string; bgColor?: string } {
const styles: Record<LogType, any> = {
[LogType.API]: {
[LogDirection.REQUEST]: {
emoji: "🚀",
color: "#3b82f6",
bgColor: "#eff6ff",
},
[LogDirection.RESPONSE]: {
emoji: "📨",
color: "#10b981",
bgColor: "#f0fdf4",
},
},
[LogType.SOCKET]: {
[LogDirection.OUTGOING]: { emoji: "🟢", color: "#16a34a" },
[LogDirection.INCOMING]: { emoji: "🔵", color: "#2563eb" },
},
[LogType.ERROR]: { emoji: "❌", color: "#ef4444" },
[LogType.INFO]: { emoji: "", color: "#6366f1" },
};
const typeStyles = styles[type];
if (
direction &&
typeof typeStyles === "object" &&
direction in typeStyles
) {
return typeStyles[direction];
}
return typeof typeStyles === "object"
? { emoji: "📝", color: "#6b7280" }
: typeStyles;
}
private formatTime(date: Date): string {
return date.toLocaleTimeString("en-US", {
hour12: false,
hour: "2-digit",
minute: "2-digit",
second: "2-digit",
fractionalSecondDigits: 3,
});
}
log(entry: Omit<LogEntry, "timestamp">) {
if (!this.shouldLog(entry.type)) return;
const timestamp = new Date();
const { emoji, color, bgColor } = this.getLogStyle(
entry.type,
entry.direction
);
const timeStr = this.formatTime(timestamp);
const baseStyle = `color: ${color}; font-weight: bold;`;
const groupStyle = bgColor
? `${baseStyle} background: ${bgColor}; padding: 2px 6px; border-radius: 3px;`
: baseStyle;
// Create compact collapsible group
const groupTitle = `${emoji} ${entry.type}${
entry.direction ? ` ${entry.direction}` : ""
}: ${entry.event}`;
// Always use groupCollapsed for cleaner output
console.groupCollapsed(`%c${groupTitle} [${timeStr}]`, groupStyle);
// Compact one-line summary with key info
const summaryParts = [];
if (entry.method) summaryParts.push(`${entry.method}`);
if (entry.status) {
const statusColor =
entry.status >= 200 && entry.status < 300 ? "✅" : "❌";
summaryParts.push(`${statusColor} ${entry.status}`);
}
if (entry.duration !== undefined)
summaryParts.push(`⏱️ ${entry.duration}ms`);
if (summaryParts.length > 0) {
console.log(
`%c${summaryParts.join(" • ")}`,
"color: #6b7280; font-size: 11px;"
);
}
if (entry.data !== undefined) {
// Show preview for objects/arrays, full value for primitives
if (typeof entry.data === "object" && entry.data !== null) {
const preview = Array.isArray(entry.data)
? `Array[${entry.data.length}]`
: `Object{${Object.keys(entry.data).slice(0, 3).join(", ")}${
Object.keys(entry.data).length > 3 ? "..." : ""
}}`;
console.log(`%c📦 Data:`, "color: #6b7280; font-size: 11px;", preview);
console.log(entry.data);
} else {
console.log(
`%c📦 Data:`,
"color: #6b7280; font-size: 11px;",
entry.data
);
}
}
console.groupEnd();
}
// API logging methods
apiRequest(url: string, method: string, data?: unknown) {
this.log({
type: LogType.API,
direction: LogDirection.REQUEST,
event: `${method.toUpperCase()} ${url.split("?")[0]}`,
url,
method,
data,
});
}
apiResponse(
url: string,
method: string,
status: number,
data?: unknown,
duration?: number
) {
this.log({
type: LogType.API,
direction: LogDirection.RESPONSE,
event: `${method.toUpperCase()} ${url.split("?")[0]}`,
url,
method,
status,
data,
duration,
});
}
// Socket logging methods
socketOutgoing(event: string, data?: unknown) {
this.log({
type: LogType.SOCKET,
direction: LogDirection.OUTGOING,
event,
data,
});
}
socketIncoming(event: string, data?: unknown) {
this.log({
type: LogType.SOCKET,
direction: LogDirection.INCOMING,
event,
data,
});
}
// Connection state logging
socketConnected() {
console.log(
`%c✅ SOCKET CONNECTED`,
"color: #10b981; font-weight: bold; background: #f0fdf4; padding: 2px 6px; border-radius: 3px;"
);
}
socketDisconnected(reason?: string) {
console.log(
`%c❌ SOCKET DISCONNECTED`,
"color: #ef4444; font-weight: bold; background: #fef2f2; padding: 2px 6px; border-radius: 3px;",
reason || ""
);
}
socketError(error?: unknown) {
console.log(
`%c⚠ SOCKET ERROR`,
"color: #f59e0b; font-weight: bold; background: #fffbeb; padding: 2px 6px; border-radius: 3px;",
error || ""
);
}
// Server-side logging methods
serverApiRequest(url: string, method: string, body?: unknown) {
if (!this.shouldLogServer(LogType.API)) return;
console.group(`\n🚀 [SERVER] API REQUEST: ${method} ${url}`);
if (body !== undefined) {
console.log("📦 Request Body:", JSON.stringify(body, null, 2));
}
console.groupEnd();
}
serverApiResponse(
url: string,
method: string,
status: number,
data?: unknown,
duration?: number
) {
if (!this.shouldLogServer(LogType.API)) return;
const emoji = status >= 200 && status < 300 ? "✅" : "❌";
console.group(
`\n${emoji} [SERVER] API ${
status >= 200 && status < 300 ? "SUCCESS" : "ERROR"
}: ${method} ${url}`
);
console.log(`📊 Status: ${status}`);
if (duration !== undefined) {
console.log(`⏱️ Duration: ${duration}ms`);
}
if (data !== undefined) {
// Limit response data display to avoid overwhelming logs
const responsePreview =
typeof data === "object" && data !== null
? Array.isArray(data)
? `Array[${data.length}]`
: `Object{${Object.keys(data).slice(0, 5).join(", ")}${
Object.keys(data).length > 5 ? "..." : ""
}}`
: data;
console.log("📦 Response Preview:", responsePreview);
// Full response data (collapsed)
console.groupCollapsed("📄 Full Response Data:");
console.log(data);
console.groupEnd();
}
console.groupEnd();
}
// Control methods
enable() {
this.enabled = true;
if (typeof window !== "undefined") {
localStorage.setItem("dev-logger-enabled", "true");
}
console.log(
"%c📝 Dev Logger ENABLED",
"color: #10b981; font-weight: bold;"
);
}
disable() {
this.enabled = false;
if (typeof window !== "undefined") {
localStorage.setItem("dev-logger-enabled", "false");
}
console.log(
"%c📝 Dev Logger DISABLED",
"color: #ef4444; font-weight: bold;"
);
}
enableType(type: LogType) {
this.enabledTypes.add(type);
this.saveEnabledTypes();
console.log(
`%c📝 ${type} logging ENABLED`,
"color: #10b981; font-weight: bold;"
);
}
disableType(type: LogType) {
this.enabledTypes.delete(type);
this.saveEnabledTypes();
console.log(
`%c📝 ${type} logging DISABLED`,
"color: #ef4444; font-weight: bold;"
);
}
private saveEnabledTypes() {
if (typeof window !== "undefined") {
localStorage.setItem(
"dev-logger-types",
JSON.stringify(Array.from(this.enabledTypes))
);
}
}
// Helper method to show current settings
status() {
console.group(
"%c🔧 Dev Logger Status",
"color: #6366f1; font-weight: bold;"
);
console.log("Enabled:", this.enabled);
console.log("Active Types:", Array.from(this.enabledTypes));
console.groupEnd();
}
}
// Create singleton instance
export const devLogger = new DevLogger();
// Make it available globally for easy console access
if (typeof window !== "undefined") {
(window as any).devLogger = devLogger;
}
// Export convenience methods for quick filtering
export const filterAPI = () => {
console.clear();
devLogger.disableType(LogType.SOCKET);
devLogger.disableType(LogType.ERROR);
devLogger.disableType(LogType.INFO);
devLogger.enableType(LogType.API);
};
export const filterSocket = () => {
console.clear();
devLogger.disableType(LogType.API);
devLogger.disableType(LogType.ERROR);
devLogger.disableType(LogType.INFO);
devLogger.enableType(LogType.SOCKET);
};
export const showAll = () => {
console.clear();
Object.values(LogType).forEach((type) => devLogger.enableType(type));
};