@amit_mandal/smart-logger-devtools 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,348 @@
1
+ var __typeError = (msg) => {
2
+ throw TypeError(msg);
3
+ };
4
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
6
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
9
+
10
+ // src/core/eventEmitter.ts
11
+ var CustomEventEmitter = class {
12
+ constructor() {
13
+ this.listeners = {};
14
+ }
15
+ on(event, listener) {
16
+ if (!this.listeners[event]) {
17
+ this.listeners[event] = [];
18
+ }
19
+ this.listeners[event].push(listener);
20
+ return () => this.removeListener(event, listener);
21
+ }
22
+ emit(event, ...args) {
23
+ if (this.listeners[event]) {
24
+ this.listeners[event].forEach((listener) => {
25
+ listener(...args);
26
+ });
27
+ }
28
+ }
29
+ removeListener(event, listener) {
30
+ if (this.listeners[event]) {
31
+ this.listeners[event] = this.listeners[event].filter(
32
+ (l) => l !== listener
33
+ );
34
+ }
35
+ }
36
+ };
37
+ var eventEmitter = new CustomEventEmitter();
38
+
39
+ // src/core/logger.ts
40
+ var LOG_EVENT = "log";
41
+ var _config, _logHistory, _originalConsoleMethods, _isInitialized, _SmartLogger_static, interceptLog_fn;
42
+ var _SmartLogger = class _SmartLogger {
43
+ static getConfig() {
44
+ return { ...__privateGet(_SmartLogger, _config) };
45
+ }
46
+ static init(config) {
47
+ if (__privateGet(_SmartLogger, _isInitialized)) {
48
+ console.warn("SmartLogger is already initialized. Call destroy() first to re-initialize.");
49
+ return;
50
+ }
51
+ Object.assign(__privateGet(_SmartLogger, _config), config);
52
+ ["log", "warn", "error", "info", "debug"].forEach((level) => {
53
+ __privateGet(_SmartLogger, _originalConsoleMethods)[level] = console[level];
54
+ console[level] = (...args) => {
55
+ var _a;
56
+ return __privateMethod(_a = _SmartLogger, _SmartLogger_static, interceptLog_fn).call(_a, level, args);
57
+ };
58
+ });
59
+ __privateSet(_SmartLogger, _isInitialized, true);
60
+ }
61
+ static getHistory() {
62
+ const maxHistory = __privateGet(_SmartLogger, _config).maxHistory || 100;
63
+ return __privateGet(_SmartLogger, _logHistory).slice(Math.max(__privateGet(_SmartLogger, _logHistory).length - maxHistory, 0));
64
+ }
65
+ static clearHistory() {
66
+ __privateSet(_SmartLogger, _logHistory, []);
67
+ eventEmitter.emit("clearHistory");
68
+ }
69
+ static destroy() {
70
+ if (!__privateGet(_SmartLogger, _isInitialized)) {
71
+ console.warn("SmartLogger is not initialized. No need to destroy.");
72
+ return;
73
+ }
74
+ ["log", "warn", "error", "info", "debug"].forEach((level) => {
75
+ if (__privateGet(_SmartLogger, _originalConsoleMethods)[level]) {
76
+ console[level] = __privateGet(_SmartLogger, _originalConsoleMethods)[level];
77
+ }
78
+ });
79
+ __privateSet(_SmartLogger, _logHistory, []);
80
+ __privateSet(_SmartLogger, _originalConsoleMethods, {});
81
+ __privateSet(_SmartLogger, _isInitialized, false);
82
+ eventEmitter.emit("destroy");
83
+ }
84
+ static subscribeToLogs(callback) {
85
+ return eventEmitter.on(LOG_EVENT, callback);
86
+ }
87
+ };
88
+ _config = new WeakMap();
89
+ _logHistory = new WeakMap();
90
+ _originalConsoleMethods = new WeakMap();
91
+ _isInitialized = new WeakMap();
92
+ _SmartLogger_static = new WeakSet();
93
+ interceptLog_fn = function(level, args) {
94
+ if (!__privateGet(_SmartLogger, _config).enabled || !__privateGet(_SmartLogger, _config).level?.includes(level)) {
95
+ return;
96
+ }
97
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
98
+ let message = "";
99
+ let componentName = "";
100
+ let logArgs = [...args];
101
+ if (__privateGet(_SmartLogger, _config).showComponentName && typeof args[0] === "string" && args[0].startsWith("[") && args[0].endsWith("]")) {
102
+ componentName = args[0];
103
+ logArgs = args.slice(1);
104
+ }
105
+ if (typeof logArgs[0] === "string") {
106
+ message = logArgs[0];
107
+ } else if (logArgs.length > 0) {
108
+ message = String(logArgs[0]);
109
+ }
110
+ const formattedArgs = [];
111
+ if (__privateGet(_SmartLogger, _config).showTimestamp) {
112
+ formattedArgs.push(`%c${timestamp}`, "color: gray;");
113
+ }
114
+ if (__privateGet(_SmartLogger, _config).showComponentName && componentName) {
115
+ formattedArgs.push(`%c${componentName}`, "color: blue;");
116
+ }
117
+ formattedArgs.push(...logArgs);
118
+ if (__privateGet(_SmartLogger, _originalConsoleMethods)[level]) {
119
+ __privateGet(_SmartLogger, _originalConsoleMethods)[level].apply(console, formattedArgs);
120
+ }
121
+ const logEntry = {
122
+ id: Math.random().toString(36).substring(2, 9),
123
+ // Simple unique ID
124
+ timestamp,
125
+ level,
126
+ message,
127
+ args: logArgs
128
+ };
129
+ __privateGet(_SmartLogger, _logHistory).push(logEntry);
130
+ const maxHistory = __privateGet(_SmartLogger, _config).maxHistory || 100;
131
+ if (__privateGet(_SmartLogger, _logHistory).length > maxHistory) {
132
+ __privateSet(_SmartLogger, _logHistory, __privateGet(_SmartLogger, _logHistory).slice(__privateGet(_SmartLogger, _logHistory).length - maxHistory));
133
+ }
134
+ eventEmitter.emit(LOG_EVENT, logEntry);
135
+ };
136
+ __privateAdd(_SmartLogger, _SmartLogger_static);
137
+ __privateAdd(_SmartLogger, _config, {
138
+ enabled: typeof process !== "undefined" && process.env.NODE_ENV !== "production",
139
+ showTimestamp: true,
140
+ showComponentName: false,
141
+ maxHistory: 100,
142
+ level: ["log", "warn", "error", "info", "debug"]
143
+ });
144
+ __privateAdd(_SmartLogger, _logHistory, []);
145
+ __privateAdd(_SmartLogger, _originalConsoleMethods, {});
146
+ __privateAdd(_SmartLogger, _isInitialized, false);
147
+ var SmartLogger = _SmartLogger;
148
+ var logger_default = SmartLogger;
149
+
150
+ // src/SmartLoggerDevTools.tsx
151
+ import { useState as useState4 } from "react";
152
+
153
+ // src/ui/FloatingButton.tsx
154
+ import { useState } from "react";
155
+
156
+ // #style-inject:#style-inject
157
+ function styleInject(css, { insertAt } = {}) {
158
+ if (!css || typeof document === "undefined") return;
159
+ const head = document.head || document.getElementsByTagName("head")[0];
160
+ const style = document.createElement("style");
161
+ style.type = "text/css";
162
+ if (insertAt === "top") {
163
+ if (head.firstChild) {
164
+ head.insertBefore(style, head.firstChild);
165
+ } else {
166
+ head.appendChild(style);
167
+ }
168
+ } else {
169
+ head.appendChild(style);
170
+ }
171
+ if (style.styleSheet) {
172
+ style.styleSheet.cssText = css;
173
+ } else {
174
+ style.appendChild(document.createTextNode(css));
175
+ }
176
+ }
177
+
178
+ // src/styles/index.css
179
+ styleInject('.smart-logger-floating-button {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 60px;\n height: 60px;\n border-radius: 50%;\n background:\n linear-gradient(\n 135deg,\n #667eea 0%,\n #764ba2 100%);\n border: none;\n color: white;\n font-size: 24px;\n cursor: pointer;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.3s ease;\n z-index: 9998;\n font-weight: bold;\n}\n.smart-logger-floating-button:hover,\n.smart-logger-floating-button.hovered {\n transform: scale(1.1);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2);\n}\n.smart-logger-floating-button:active {\n transform: scale(0.95);\n}\n.smart-logger-button-icon {\n position: absolute;\n font-size: 28px;\n}\n.smart-logger-button-count {\n position: absolute;\n bottom: 0;\n right: 0;\n background: #ff4757;\n color: white;\n border-radius: 50%;\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: bold;\n border: 2px solid white;\n}\n.smart-logger-modal-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 9999;\n animation: fadeIn 0.3s ease;\n}\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n.smart-logger-modal {\n background: white;\n border-radius: 12px;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);\n width: 90%;\n max-width: 900px;\n height: 80vh;\n max-height: 600px;\n display: flex;\n flex-direction: column;\n animation: slideUp 0.3s ease;\n font-family:\n -apple-system,\n BlinkMacSystemFont,\n "Segoe UI",\n Roboto,\n "Helvetica Neue",\n Arial,\n sans-serif;\n}\n@keyframes slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n.smart-logger-modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 20px;\n border-bottom: 1px solid #e0e0e0;\n background:\n linear-gradient(\n 135deg,\n #667eea 0%,\n #764ba2 100%);\n color: white;\n border-radius: 12px 12px 0 0;\n}\n.smart-logger-modal-header h2 {\n margin: 0;\n font-size: 20px;\n font-weight: 600;\n}\n.smart-logger-close-button {\n background: transparent;\n border: none;\n color: white;\n font-size: 24px;\n cursor: pointer;\n padding: 0;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: transform 0.2s ease;\n}\n.smart-logger-close-button:hover {\n transform: scale(1.2);\n}\n.smart-logger-modal-controls {\n display: flex;\n gap: 10px;\n padding: 15px 20px;\n border-bottom: 1px solid #e0e0e0;\n background: #f9f9f9;\n flex-wrap: wrap;\n}\n.smart-logger-search-input,\n.smart-logger-filter-select {\n padding: 8px 12px;\n border: 1px solid #d0d0d0;\n border-radius: 6px;\n font-size: 14px;\n font-family: inherit;\n transition: border-color 0.2s ease;\n}\n.smart-logger-search-input {\n flex: 1;\n min-width: 200px;\n}\n.smart-logger-search-input:focus,\n.smart-logger-filter-select:focus {\n outline: none;\n border-color: #667eea;\n box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);\n}\n.smart-logger-filter-select {\n min-width: 120px;\n}\n.smart-logger-clear-button {\n padding: 8px 16px;\n background: #ff4757;\n color: white;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n font-size: 14px;\n font-weight: 500;\n transition: background 0.2s ease;\n}\n.smart-logger-clear-button:hover {\n background: #ff3838;\n}\n.smart-logger-modal-content {\n flex: 1;\n overflow-y: auto;\n padding: 0;\n}\n.smart-logger-log-list {\n list-style: none;\n margin: 0;\n padding: 0;\n}\n.smart-logger-log-item {\n padding: 12px 20px;\n border-bottom: 1px solid #f0f0f0;\n transition: background-color 0.2s ease;\n}\n.smart-logger-log-item:hover {\n background-color: #f9f9f9;\n}\n.smart-logger-log-item.error {\n background-color: #fff5f5;\n}\n.smart-logger-log-item.warn {\n background-color: #fffbf0;\n}\n.smart-logger-log-item.info {\n background-color: #f0f7ff;\n}\n.smart-logger-log-header {\n display: flex;\n gap: 10px;\n align-items: center;\n margin-bottom: 6px;\n}\n.smart-logger-log-level {\n font-weight: 600;\n font-size: 12px;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n.smart-logger-log-timestamp {\n font-size: 12px;\n color: #999;\n font-family:\n "Monaco",\n "Courier New",\n monospace;\n}\n.smart-logger-log-message {\n font-size: 14px;\n color: #333;\n word-break: break-word;\n font-family:\n "Monaco",\n "Courier New",\n monospace;\n}\n.smart-logger-log-details {\n margin-top: 8px;\n font-size: 12px;\n}\n.smart-logger-log-details summary {\n cursor: pointer;\n color: #667eea;\n font-weight: 500;\n padding: 4px 0;\n user-select: none;\n}\n.smart-logger-log-details summary:hover {\n text-decoration: underline;\n}\n.smart-logger-log-args {\n background: #f5f5f5;\n padding: 10px;\n border-radius: 4px;\n overflow-x: auto;\n font-size: 12px;\n margin: 8px 0 0 0;\n border: 1px solid #e0e0e0;\n max-height: 200px;\n overflow-y: auto;\n}\n.smart-logger-empty-state {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n color: #999;\n font-size: 16px;\n}\n.smart-logger-modal-footer {\n padding: 12px 20px;\n border-top: 1px solid #e0e0e0;\n background: #f9f9f9;\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-radius: 0 0 12px 12px;\n}\n.smart-logger-log-count {\n font-size: 12px;\n color: #666;\n font-weight: 500;\n}\n@media (max-width: 768px) {\n .smart-logger-modal {\n width: 95%;\n max-height: 90vh;\n }\n .smart-logger-modal-controls {\n flex-direction: column;\n }\n .smart-logger-search-input,\n .smart-logger-filter-select,\n .smart-logger-clear-button {\n width: 100%;\n }\n .smart-logger-floating-button {\n bottom: 10px;\n right: 10px;\n width: 50px;\n height: 50px;\n }\n}\n.text-red-500 {\n color: #ff4757;\n}\n.text-yellow-500 {\n color: #ffa502;\n}\n.text-blue-500 {\n color: #0984e3;\n}\n.text-purple-500 {\n color: #a29bfe;\n}\n.text-gray-700 {\n color: #2d3436;\n}\n');
180
+
181
+ // src/ui/FloatingButton.tsx
182
+ import { jsx, jsxs } from "react/jsx-runtime";
183
+ var FloatingButton = ({ onClick, logCount }) => {
184
+ const [isHovered, setIsHovered] = useState(false);
185
+ return /* @__PURE__ */ jsxs(
186
+ "button",
187
+ {
188
+ onClick,
189
+ onMouseEnter: () => setIsHovered(true),
190
+ onMouseLeave: () => setIsHovered(false),
191
+ className: `smart-logger-floating-button ${isHovered ? "hovered" : ""}`,
192
+ title: "Open SmartLogger DevTools",
193
+ children: [
194
+ /* @__PURE__ */ jsx("span", { className: "smart-logger-button-icon", children: "\u{1F4CB}" }),
195
+ /* @__PURE__ */ jsx("span", { className: "smart-logger-button-count", children: logCount })
196
+ ]
197
+ }
198
+ );
199
+ };
200
+
201
+ // src/ui/LogModal.tsx
202
+ import { useState as useState2 } from "react";
203
+
204
+ // src/utils/formatters.ts
205
+ var getLogLevelColor = (level) => {
206
+ switch (level) {
207
+ case "error":
208
+ return "text-red-500";
209
+ case "warn":
210
+ return "text-yellow-500";
211
+ case "info":
212
+ return "text-blue-500";
213
+ case "debug":
214
+ return "text-purple-500";
215
+ case "log":
216
+ default:
217
+ return "text-gray-700";
218
+ }
219
+ };
220
+
221
+ // src/ui/LogModal.tsx
222
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
223
+ var LogModal = ({ isOpen, onClose, logs, onClearLogs }) => {
224
+ const [selectedLevel, setSelectedLevel] = useState2("all");
225
+ const [searchQuery, setSearchQuery] = useState2("");
226
+ const filteredLogs = logs.filter((log) => {
227
+ const levelMatch = selectedLevel === "all" || log.level === selectedLevel;
228
+ const searchMatch = log.message.toLowerCase().includes(searchQuery.toLowerCase());
229
+ return levelMatch && searchMatch;
230
+ });
231
+ if (!isOpen) return null;
232
+ return /* @__PURE__ */ jsx2("div", { className: "smart-logger-modal-overlay", onClick: onClose, children: /* @__PURE__ */ jsxs2("div", { className: "smart-logger-modal", onClick: (e) => e.stopPropagation(), children: [
233
+ /* @__PURE__ */ jsxs2("div", { className: "smart-logger-modal-header", children: [
234
+ /* @__PURE__ */ jsx2("h2", { children: "SmartLogger DevTools" }),
235
+ /* @__PURE__ */ jsx2("button", { className: "smart-logger-close-button", onClick: onClose, children: "\u2715" })
236
+ ] }),
237
+ /* @__PURE__ */ jsxs2("div", { className: "smart-logger-modal-controls", children: [
238
+ /* @__PURE__ */ jsx2(
239
+ "input",
240
+ {
241
+ type: "text",
242
+ placeholder: "Search logs...",
243
+ value: searchQuery,
244
+ onChange: (e) => setSearchQuery(e.target.value),
245
+ className: "smart-logger-search-input"
246
+ }
247
+ ),
248
+ /* @__PURE__ */ jsxs2(
249
+ "select",
250
+ {
251
+ value: selectedLevel,
252
+ onChange: (e) => setSelectedLevel(e.target.value),
253
+ className: "smart-logger-filter-select",
254
+ children: [
255
+ /* @__PURE__ */ jsx2("option", { value: "all", children: "All Levels" }),
256
+ /* @__PURE__ */ jsx2("option", { value: "log", children: "Log" }),
257
+ /* @__PURE__ */ jsx2("option", { value: "info", children: "Info" }),
258
+ /* @__PURE__ */ jsx2("option", { value: "warn", children: "Warn" }),
259
+ /* @__PURE__ */ jsx2("option", { value: "error", children: "Error" }),
260
+ /* @__PURE__ */ jsx2("option", { value: "debug", children: "Debug" })
261
+ ]
262
+ }
263
+ ),
264
+ /* @__PURE__ */ jsx2("button", { className: "smart-logger-clear-button", onClick: onClearLogs, children: "Clear Logs" })
265
+ ] }),
266
+ /* @__PURE__ */ jsx2("div", { className: "smart-logger-modal-content", children: filteredLogs.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "smart-logger-empty-state", children: /* @__PURE__ */ jsx2("p", { children: "No logs to display" }) }) : /* @__PURE__ */ jsx2("ul", { className: "smart-logger-log-list", children: filteredLogs.map((log) => /* @__PURE__ */ jsxs2("li", { className: `smart-logger-log-item ${log.level}`, children: [
267
+ /* @__PURE__ */ jsxs2("div", { className: "smart-logger-log-header", children: [
268
+ /* @__PURE__ */ jsxs2("span", { className: `smart-logger-log-level ${getLogLevelColor(log.level)}`, children: [
269
+ "[",
270
+ log.level.toUpperCase(),
271
+ "]"
272
+ ] }),
273
+ /* @__PURE__ */ jsx2("span", { className: "smart-logger-log-timestamp", children: log.timestamp })
274
+ ] }),
275
+ /* @__PURE__ */ jsx2("div", { className: "smart-logger-log-message", children: log.message }),
276
+ log.args && log.args.length > 0 && /* @__PURE__ */ jsxs2("details", { className: "smart-logger-log-details", children: [
277
+ /* @__PURE__ */ jsx2("summary", { children: "View Arguments" }),
278
+ /* @__PURE__ */ jsx2("pre", { className: "smart-logger-log-args", children: JSON.stringify(log.args, null, 2) })
279
+ ] })
280
+ ] }, log.id)) }) }),
281
+ /* @__PURE__ */ jsx2("div", { className: "smart-logger-modal-footer", children: /* @__PURE__ */ jsxs2("span", { className: "smart-logger-log-count", children: [
282
+ "Total: ",
283
+ filteredLogs.length,
284
+ " logs"
285
+ ] }) })
286
+ ] }) });
287
+ };
288
+
289
+ // src/hooks/useLogHistory.ts
290
+ import { useState as useState3, useEffect } from "react";
291
+ var useLogHistory = () => {
292
+ const [history, setHistory] = useState3(logger_default.getHistory());
293
+ useEffect(() => {
294
+ const unsubscribeLog = eventEmitter.on("log", (newLog) => {
295
+ setHistory((prevHistory) => {
296
+ const newHistory = [...prevHistory, newLog];
297
+ const maxHistory = logger_default.getConfig().maxHistory || 100;
298
+ return newHistory.slice(Math.max(newHistory.length - maxHistory, 0));
299
+ });
300
+ });
301
+ const unsubscribeClear = eventEmitter.on("clearHistory", () => {
302
+ setHistory([]);
303
+ });
304
+ const unsubscribeDestroy = eventEmitter.on("destroy", () => {
305
+ setHistory([]);
306
+ });
307
+ setHistory(logger_default.getHistory());
308
+ return () => {
309
+ unsubscribeLog();
310
+ unsubscribeClear();
311
+ unsubscribeDestroy();
312
+ };
313
+ }, []);
314
+ return history;
315
+ };
316
+
317
+ // src/SmartLoggerDevTools.tsx
318
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
319
+ var SmartLoggerDevTools = () => {
320
+ const [isModalOpen, setIsModalOpen] = useState4(false);
321
+ const logs = useLogHistory();
322
+ const handleClearLogs = () => {
323
+ logger_default.clearHistory();
324
+ };
325
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
326
+ /* @__PURE__ */ jsx3(
327
+ FloatingButton,
328
+ {
329
+ onClick: () => setIsModalOpen(true),
330
+ logCount: logs.length
331
+ }
332
+ ),
333
+ /* @__PURE__ */ jsx3(
334
+ LogModal,
335
+ {
336
+ isOpen: isModalOpen,
337
+ onClose: () => setIsModalOpen(false),
338
+ logs,
339
+ onClearLogs: handleClearLogs
340
+ }
341
+ )
342
+ ] });
343
+ };
344
+ export {
345
+ logger_default as SmartLogger,
346
+ SmartLoggerDevTools,
347
+ useLogHistory
348
+ };
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@amit_mandal/smart-logger-devtools",
3
+ "version": "1.0.0",
4
+ "description": "A reusable console logger with history management and production suppression.",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean --injectStyle",
17
+ "dev": "tsup src/index.ts --format cjs,esm --watch --dts --injectStyle",
18
+ "test": "vitest run"
19
+ },
20
+ "keywords": [
21
+ "logger",
22
+ "console",
23
+ "intercept",
24
+ "history",
25
+ "typescript"
26
+ ],
27
+ "author": "Amit Chand Mandal",
28
+ "license": "MIT",
29
+ "devDependencies": {
30
+ "@types/node": "^25.5.0",
31
+ "@types/react": "^19.2.14",
32
+ "@types/react-dom": "^19.2.3",
33
+ "react": "^19.2.4",
34
+ "react-dom": "^19.2.4",
35
+ "tsup": "^8.5.1",
36
+ "typescript": "^5.9.3",
37
+ "vitest": "^1.6.1"
38
+ },
39
+ "peerDependencies": {
40
+ "react": "^18.0.0",
41
+ "react-dom": "^18.0.0"
42
+ },
43
+ "dependencies": {
44
+ "react": "^18.0.0"
45
+ }
46
+ }
@@ -0,0 +1,49 @@
1
+ import React, { useState } from "react";
2
+ import { FloatingButton } from "./ui/FloatingButton";
3
+ import { LogModal } from "./ui/LogModal";
4
+ import { useLogHistory } from "./hooks/useLogHistory";
5
+ import SmartLogger from "./core/logger";
6
+
7
+ /**
8
+ * SmartLoggerDevTools is the main component that combines the floating button and log modal.
9
+ * It should be placed at the root level of your React application.
10
+ *
11
+ * Usage:
12
+ * ```tsx
13
+ * import { SmartLoggerDevTools } from 'smart-logger-devtools';
14
+ *
15
+ * function App() {
16
+ * return (
17
+ * <>
18
+ * <YourApp />
19
+ * <SmartLoggerDevTools />
20
+ * </>
21
+ * );
22
+ * }
23
+ * ```
24
+ */
25
+ export const SmartLoggerDevTools: React.FC = () => {
26
+ const [isModalOpen, setIsModalOpen] = useState(false);
27
+ const logs = useLogHistory();
28
+
29
+ const handleClearLogs = () => {
30
+ SmartLogger.clearHistory();
31
+ };
32
+
33
+ return (
34
+ <>
35
+ <FloatingButton
36
+ onClick={() => setIsModalOpen(true)}
37
+ logCount={logs.length}
38
+ />
39
+ <LogModal
40
+ isOpen={isModalOpen}
41
+ onClose={() => setIsModalOpen(false)}
42
+ logs={logs}
43
+ onClearLogs={handleClearLogs}
44
+ />
45
+ </>
46
+ );
47
+ };
48
+
49
+ export default SmartLoggerDevTools;
@@ -0,0 +1,30 @@
1
+ import type { EventEmitter } from "../types";
2
+ class CustomEventEmitter implements EventEmitter {
3
+ private listeners: { [event: string]: Function[] } = {};
4
+
5
+ on(event: string, listener: Function): () => void {
6
+ if (!this.listeners[event]) {
7
+ this.listeners[event] = [];
8
+ }
9
+ this.listeners[event].push(listener);
10
+ return () => this.removeListener(event, listener);
11
+ }
12
+
13
+ emit(event: string, ...args: any[]): void {
14
+ if (this.listeners[event]) {
15
+ this.listeners[event].forEach((listener) => {
16
+ listener(...args);
17
+ });
18
+ }
19
+ }
20
+
21
+ private removeListener(event: string, listener: Function): void {
22
+ if (this.listeners[event]) {
23
+ this.listeners[event] = this.listeners[event].filter(
24
+ (l) => l !== listener
25
+ );
26
+ }
27
+ }
28
+ }
29
+
30
+ export const eventEmitter = new CustomEventEmitter();
@@ -0,0 +1,114 @@
1
+ import type { SmartLoggerConfig, LogLevel, LogEntry } from "../types";
2
+ import { eventEmitter } from "./eventEmitter";
3
+ const LOG_EVENT = "log";
4
+ class SmartLogger {
5
+ static #config: SmartLoggerConfig = {
6
+ enabled: typeof process !== "undefined" && process.env.NODE_ENV !== "production",
7
+ showTimestamp: true,
8
+ showComponentName: false,
9
+ maxHistory: 100,
10
+ level: ["log", "warn", "error", "info", "debug"],
11
+ };
12
+
13
+ static #logHistory: LogEntry[] = [];
14
+
15
+ static getConfig(): SmartLoggerConfig {
16
+ return { ...SmartLogger.#config };
17
+ }
18
+ static #originalConsoleMethods: Record<LogLevel, Console["log"]> = {} as Record<LogLevel, Console["log"]>;
19
+ static #isInitialized = false;
20
+ static init(config?: SmartLoggerConfig): void {
21
+ if (SmartLogger.#isInitialized) {
22
+ console.warn("SmartLogger is already initialized. Call destroy() first to re-initialize.");
23
+ return;
24
+ }
25
+
26
+ Object.assign(SmartLogger.#config, config);
27
+ (["log", "warn", "error", "info", "debug"] as LogLevel[]).forEach((level) => {
28
+ SmartLogger.#originalConsoleMethods[level] = console[level];
29
+ console[level] = (...args: any[]) => SmartLogger.#interceptLog(level, args);
30
+ });
31
+
32
+ SmartLogger.#isInitialized = true;
33
+ }
34
+ static #interceptLog(level: LogLevel, args: any[]): void {
35
+ if (!SmartLogger.#config.enabled || !SmartLogger.#config.level?.includes(level)) {
36
+ return;
37
+ }
38
+
39
+ const timestamp = new Date().toISOString();
40
+ let message = "";
41
+ let componentName = "";
42
+ let logArgs = [...args];
43
+ if (SmartLogger.#config.showComponentName && typeof args[0] === "string" && args[0].startsWith("[") && args[0].endsWith("]")) {
44
+ componentName = args[0];
45
+ logArgs = args.slice(1);
46
+ }
47
+
48
+ if (typeof logArgs[0] === "string") {
49
+ message = logArgs[0];
50
+ } else if (logArgs.length > 0) {
51
+ message = String(logArgs[0]);
52
+ }
53
+
54
+ const formattedArgs: any[] = [];
55
+ if (SmartLogger.#config.showTimestamp) {
56
+ formattedArgs.push(`%c${timestamp}`, "color: gray;");
57
+ }
58
+ if (SmartLogger.#config.showComponentName && componentName) {
59
+ formattedArgs.push(`%c${componentName}`, "color: blue;");
60
+ }
61
+ formattedArgs.push(...logArgs);
62
+ if (SmartLogger.#originalConsoleMethods[level]) {
63
+ SmartLogger.#originalConsoleMethods[level].apply(console, formattedArgs);
64
+ }
65
+
66
+ const logEntry: LogEntry = {
67
+ id: Math.random().toString(36).substring(2, 9), // Simple unique ID
68
+ timestamp,
69
+ level,
70
+ message,
71
+ args: logArgs,
72
+ };
73
+
74
+ SmartLogger.#logHistory.push(logEntry);
75
+ const maxHistory = SmartLogger.#config.maxHistory || 100;
76
+ if (SmartLogger.#logHistory.length > maxHistory) {
77
+ SmartLogger.#logHistory = SmartLogger.#logHistory.slice(SmartLogger.#logHistory.length - maxHistory);
78
+ }
79
+ eventEmitter.emit(LOG_EVENT, logEntry);
80
+ }
81
+
82
+ static getHistory(): LogEntry[] {
83
+ const maxHistory = SmartLogger.#config.maxHistory || 100;
84
+ return SmartLogger.#logHistory.slice(Math.max(SmartLogger.#logHistory.length - maxHistory, 0));
85
+ }
86
+ static clearHistory(): void {
87
+ SmartLogger.#logHistory = [];
88
+ eventEmitter.emit("clearHistory");
89
+ }
90
+
91
+ static destroy(): void {
92
+ if (!SmartLogger.#isInitialized) {
93
+ console.warn("SmartLogger is not initialized. No need to destroy.");
94
+ return;
95
+ }
96
+
97
+ (["log", "warn", "error", "info", "debug"] as LogLevel[]).forEach((level) => {
98
+ if (SmartLogger.#originalConsoleMethods[level]) {
99
+ console[level] = SmartLogger.#originalConsoleMethods[level];
100
+ }
101
+ });
102
+
103
+ SmartLogger.#logHistory = [];
104
+ SmartLogger.#originalConsoleMethods = {} as Record<LogLevel, Console["log"]>;
105
+ SmartLogger.#isInitialized = false;
106
+ eventEmitter.emit("destroy");
107
+ }
108
+
109
+ static subscribeToLogs(callback: (log: LogEntry) => void): () => void {
110
+ return eventEmitter.on(LOG_EVENT, callback);
111
+ }
112
+ }
113
+
114
+ export default SmartLogger;
@@ -0,0 +1 @@
1
+ declare module '*.css';
@@ -0,0 +1,37 @@
1
+ import { useState, useEffect } from "react";
2
+ import SmartLogger from "../core/logger";
3
+ import type { LogEntry } from "../types";
4
+ import { eventEmitter } from "../core/eventEmitter";
5
+
6
+ export const useLogHistory = () => {
7
+ const [history, setHistory] = useState<LogEntry[]>(SmartLogger.getHistory());
8
+
9
+ useEffect(() => {
10
+ const unsubscribeLog = eventEmitter.on("log", (newLog: LogEntry) => {
11
+ setHistory((prevHistory) => {
12
+ const newHistory = [...prevHistory, newLog];
13
+ const maxHistory = SmartLogger.getConfig().maxHistory || 100;
14
+ return newHistory.slice(Math.max(newHistory.length - maxHistory, 0));
15
+ });
16
+ });
17
+
18
+ const unsubscribeClear = eventEmitter.on("clearHistory", () => {
19
+ setHistory([]);
20
+ });
21
+
22
+ const unsubscribeDestroy = eventEmitter.on("destroy", () => {
23
+ setHistory([]);
24
+ });
25
+
26
+ // Initial sync in case logs occurred before hook mounted
27
+ setHistory(SmartLogger.getHistory());
28
+
29
+ return () => {
30
+ unsubscribeLog();
31
+ unsubscribeClear();
32
+ unsubscribeDestroy();
33
+ };
34
+ }, []);
35
+
36
+ return history;
37
+ };
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { default as SmartLogger } from './core/logger';
2
+ export { SmartLoggerDevTools } from './SmartLoggerDevTools';
3
+ export * from './types';
4
+ export { useLogHistory } from './hooks/useLogHistory';
5
+
6
+ import './styles/index.css';