UNPKG

visilog

Version:

Stream browser console logs to files for LLM debugging. Zero-config setup with simple imports. No MCP required - just tell your LLM to read the log files.

2 lines โ€ข 13.4 kB
(()=>{"use strict";var e={3:e=>{e.exports=require("path")},86:e=>{e.exports=require("ws")},211:function(e,s,t){var n,o=this&&this.__createBinding||(Object.create?function(e,s,t,n){void 0===n&&(n=t);var o=Object.getOwnPropertyDescriptor(s,t);o&&!("get"in o?!s.__esModule:o.writable||o.configurable)||(o={enumerable:!0,get:function(){return s[t]}}),Object.defineProperty(e,n,o)}:function(e,s,t,n){void 0===n&&(n=t),e[n]=s[t]}),i=this&&this.__setModuleDefault||(Object.create?function(e,s){Object.defineProperty(e,"default",{enumerable:!0,value:s})}:function(e,s){e.default=s}),r=this&&this.__importStar||(n=function(e){return n=Object.getOwnPropertyNames||function(e){var s=[];for(var t in e)Object.prototype.hasOwnProperty.call(e,t)&&(s[s.length]=t);return s},n(e)},function(e){if(e&&e.__esModule)return e;var s={};if(null!=e)for(var t=n(e),r=0;r<t.length;r++)"default"!==t[r]&&o(s,e,t[r]);return i(s,e),s});Object.defineProperty(s,"__esModule",{value:!0}),s.WebSocketLoggerServer=void 0;const l=r(t(383)),a=r(t(3)),c=t(86);s.WebSocketLoggerServer=class{constructor(e={}){this.wss=null,this.sessions=new Map,this.cleanupTimer=null,this.sessionHandlers=[],this.logHandlers=[],this.errorHandlers=[],this.config={port:3001,host:"0.0.0.0",maxSessions:50,cleanupThreshold:75,cleanupAmount:25,logsDir:a.join(process.cwd(),"logs"),enableIndex:!0,enableSessionLogs:!0,cleanupInterval:18e5},this.config={...this.config,...e},this.indexFile=a.join(this.config.logsDir,"index.json")}async start(){if(this.wss)throw new Error("Server is already running");try{this.ensureDirectories(),this.wss=new c.WebSocketServer({port:this.config.port,host:this.config.host}),this.setupWebSocketHandlers(),this.startCleanupTimer(),console.log(`๐Ÿ”Œ WebSocket logging server started on ${this.config.host}:${this.config.port}`),console.log(`๐Ÿ“ Logs directory: ${this.config.logsDir}`),this.config.enableIndex&&this.initializeIndex()}catch(e){throw this.handleError(e,"Failed to start server"),e}}async stop(){if(this.wss)return console.log("๐Ÿ›‘ Shutting down logging server..."),this.cleanupTimer&&(clearInterval(this.cleanupTimer),this.cleanupTimer=null),this.sessions.forEach(((e,s)=>{this.handleSessionClose(s)})),new Promise(((e,s)=>{this.wss.close((t=>{t?(this.handleError(t,"Error stopping server"),s(t)):(this.wss=null,console.log("โœ… Logging server stopped"),e())}))}))}ensureDirectories(){if(l.existsSync(this.config.logsDir)||l.mkdirSync(this.config.logsDir,{recursive:!0}),this.config.enableSessionLogs){const e=a.join(this.config.logsDir,"sessions");l.existsSync(e)||l.mkdirSync(e,{recursive:!0})}}setupWebSocketHandlers(){this.wss&&(this.wss.on("connection",((e,s)=>{const t=this.generateSessionId(),n=this.config.enableSessionLogs?a.join(this.config.logsDir,"sessions",`session-${t}.log`):"",o={id:t,startTime:new Date,lastActivity:new Date,messageCount:0},i={...o,ws:e,logFile:n};this.sessions.set(t,i);const r=s.headers["user-agent"]||"unknown",l=s.socket.remoteAddress||"unknown",c=`[SESSION-START] ${t} - ${l} - ${r}`;this.config.enableIndex&&this.updateIndex(),this.config.enableSessionLogs&&n&&this.logToFile(n,c),console.log(`๐Ÿ“ฑ New session: ${t} (${this.sessions.size} active)`),this.sessionHandlers.forEach((e=>{try{e(o)}catch(e){this.handleError(e,"Session handler error")}})),this.sendMessage(e,{type:"session-init",sessionId:t}),e.on("message",(e=>{try{const s=JSON.parse(e.toString());this.handleClientMessage(t,s)}catch(e){this.handleError(e,"Failed to parse client message")}})),e.on("close",(()=>{this.handleSessionClose(t)})),e.on("error",(e=>{this.handleError(e,`WebSocket error for session ${t}`),this.handleSessionClose(t)})),this.checkAndCleanupOldSessions()})),this.wss.on("error",(e=>{this.handleError(e,"WebSocket server error")})))}handleClientMessage(e,s){const t=this.sessions.get(e);if(t)switch(s.type){case"log":s.log&&this.handleLogMessage(e,s.log);break;case"ping":this.sendMessage(t.ws,{type:"pong"});break;case"pong":t.lastActivity=new Date}}sendMessage(e,s){if(e.readyState===c.WebSocket.OPEN)try{e.send(JSON.stringify(s))}catch(e){this.handleError(e,"Failed to send message to client")}}generateSessionId(){return Math.random().toString(36).substring(2,10)}handleLogMessage(e,s){const t=this.sessions.get(e);if(!t)return;t.lastActivity=new Date,t.messageCount++;const n=(new Date).toISOString(),o=this.formatLogMessage(s,n);this.config.enableSessionLogs&&t.logFile&&this.logToFile(t.logFile,o),this.config.enableIndex&&this.updateIndex();const i=`[${e.slice(-6)}] ${s.namespace?`[${s.namespace}] `:""}${s.level.toUpperCase()}: ${s.message}`;switch(s.level){case"error":console.error(`๐Ÿ”ด ${i}`);break;case"warn":console.warn(`๐ŸŸก ${i}`);break;case"info":console.info(`โ„น๏ธ ${i}`);break;case"debug":console.debug(`๐Ÿ” ${i}`);break;default:console.log(`๐Ÿ“ ${i}`)}this.logHandlers.forEach((e=>{try{e(s)}catch(e){this.handleError(e,"Log handler error")}}))}formatLogMessage(e,s){const t={timestamp:s,level:e.level,message:e.message,sessionId:e.sessionId,url:e.url,...e.namespace&&{namespace:e.namespace},...e.data&&{data:e.data}};try{return JSON.stringify(t)}catch(t){const n={timestamp:s,level:e.level,message:e.message,sessionId:e.sessionId,url:e.url,error:"Failed to serialize log data",originalError:t instanceof Error?t.message:String(t)};return JSON.stringify(n)}}logToFile(e,s){try{const t=`${s}\n`;l.appendFileSync(e,t)}catch(s){this.handleError(s,`Failed to write to log file: ${e}`)}}initializeIndex(){if(!this.config.enableIndex)return;const e={lastUpdated:(new Date).toISOString(),totalSessions:0,activeSessions:0,sessions:[]};this.writeIndex(e)}writeIndex(e){try{l.writeFileSync(this.indexFile,JSON.stringify(e,null,2))}catch(e){this.handleError(e,`Failed to write index file: ${this.indexFile}`)}}readIndex(){try{if(l.existsSync(this.indexFile)){const e=l.readFileSync(this.indexFile,"utf8");return JSON.parse(e)}}catch(e){this.handleError(e,`Failed to read index file: ${this.indexFile}`)}return{lastUpdated:(new Date).toISOString(),totalSessions:0,activeSessions:0,sessions:[]}}updateIndex(){if(!this.config.enableIndex)return;const e=this.readIndex(),s=Array.from(this.sessions.values());e.activeSessions=s.length,e.lastUpdated=(new Date).toISOString(),s.forEach((s=>{const t=e.sessions.find((e=>e.id===s.id));if(t)t.messageCount=s.messageCount,t.status="active";else{const t={id:s.id,startTime:s.startTime.toISOString(),messageCount:s.messageCount,namespace:s.namespace,logFile:a.relative(this.config.logsDir,s.logFile),status:"active"};e.sessions.push(t),e.totalSessions=Math.max(e.totalSessions,e.sessions.length)}})),this.writeIndex(e)}updateSessionInIndex(e,s,t,n){if(!this.config.enableIndex)return;const o=this.readIndex(),i=o.sessions.find((s=>s.id===e));i&&(i.status=s,t&&(i.endTime=t.toISOString()),n&&(i.duration=n),o.activeSessions=Array.from(this.sessions.values()).length,o.lastUpdated=(new Date).toISOString(),this.writeIndex(o))}handleSessionClose(e){const s=this.sessions.get(e);if(!s)return;const t=Date.now()-s.startTime.getTime(),n=`[SESSION-END] ${e} - Duration: ${Math.round(t/1e3)}s - Messages: ${s.messageCount}`;this.config.enableIndex&&this.updateSessionInIndex(e,"completed",new Date,Math.round(t/1e3)),this.config.enableSessionLogs&&s.logFile&&this.logToFile(s.logFile,n),console.log(`๐Ÿ‘‹ Session ended: ${e} (${s.messageCount} messages, ${Math.round(t/1e3)}s)`),this.sessions.delete(e)}checkAndCleanupOldSessions(){if(!this.config.enableSessionLogs)return;const e=a.join(this.config.logsDir,"sessions");try{const s=l.readdirSync(e).filter((e=>e.startsWith("session-")&&e.endsWith(".log")));s.length>=this.config.cleanupThreshold&&(console.log(`๐Ÿงน Session cleanup triggered: ${s.length} files found (threshold: ${this.config.cleanupThreshold})`),this.cleanupOldSessionFiles(s))}catch(e){this.handleError(e,"Failed to check session files")}}cleanupOldSessionFiles(e){const s=a.join(this.config.logsDir,"sessions");try{const t=e.map((e=>{const t=a.join(s,e);return{file:e,path:t,mtime:l.statSync(t).mtime}})).sort(((e,s)=>e.mtime.getTime()-s.mtime.getTime())),n=t.slice(0,this.config.cleanupAmount),o=t.length-n.length;n.forEach((e=>{try{l.unlinkSync(e.path),console.log(`๐Ÿ—‘๏ธ Deleted old session log: ${e.file}`)}catch(s){this.handleError(s,`Failed to delete ${e.file}`)}})),console.log(`โœ… Cleanup complete: ${n.length} files deleted, ${o} remaining`)}catch(e){this.handleError(e,"Cleanup process failed")}}startCleanupTimer(){this.cleanupTimer=setInterval((()=>{console.log(`๐Ÿ” Periodic cleanup check: ${this.sessions.size} active sessions`),this.checkAndCleanupOldSessions()}),this.config.cleanupInterval)}handleError(e,s){console.error(`โŒ WebSocketLoggerServer Error${s?` (${s})`:""}:`,e),this.errorHandlers.forEach((t=>{try{const n=s?{operation:s,component:"WebSocketLoggerServer",timestamp:(new Date).toISOString()}:void 0;t(e,n)}catch(e){console.error("โŒ Error handler failed:",e)}}))}onSession(e){return this.sessionHandlers.push(e),()=>{const s=this.sessionHandlers.indexOf(e);s>-1&&this.sessionHandlers.splice(s,1)}}onLog(e){return this.logHandlers.push(e),()=>{const s=this.logHandlers.indexOf(e);s>-1&&this.logHandlers.splice(s,1)}}onError(e){return this.errorHandlers.push(e),()=>{const s=this.errorHandlers.indexOf(e);s>-1&&this.errorHandlers.splice(s,1)}}getStats(){return{activeSessions:this.sessions.size,logsDirectory:this.config.logsDir,indexFile:this.indexFile,maxSessions:this.config.maxSessions,cleanupThreshold:this.config.cleanupThreshold,config:this.config,isRunning:null!==this.wss}}getSessions(){return Array.from(this.sessions.values()).map((e=>({id:e.id,startTime:e.startTime,lastActivity:e.lastActivity,messageCount:e.messageCount,namespace:e.namespace})))}getSession(e){const s=this.sessions.get(e);return s?{id:s.id,startTime:s.startTime,lastActivity:s.lastActivity,messageCount:s.messageCount,namespace:s.namespace}:null}updateConfig(e){this.config={...this.config,...e},this.indexFile=a.join(this.config.logsDir,"index.json"),void 0!==e.cleanupInterval&&(this.cleanupTimer&&clearInterval(this.cleanupTimer),this.startCleanupTimer())}isRunning(){return null!==this.wss}setupGracefulShutdown(){const e=async()=>{console.log("\n๐Ÿ›‘ Received shutdown signal...");try{await this.stop(),process.exit(0)}catch(e){console.error("โŒ Error during shutdown:",e),process.exit(1)}};process.on("SIGINT",e),process.on("SIGTERM",e)}}},383:e=>{e.exports=require("fs")}},s={};function t(n){var o=s[n];if(void 0!==o)return o.exports;var i=s[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}var n={};(()=>{var e=n;Object.defineProperty(e,"__esModule",{value:!0}),e.createVitePlugin=o,e.createClientInitCode=function(e={}){return`\nimport { WebSocketLogger } from '@websocket-logger/client/websocket-logger';\n\nconst logger = new WebSocketLogger(${JSON.stringify(e)});\nlogger.enableConsoleOverride();\n\n// Make available globally for debugging\nif (typeof window !== 'undefined') {\n window.__websocketLogger = logger;\n}\n\nexport default logger;\n`},e.checkLoggerServer=async function(e=3001){try{const s=new WebSocket(`ws://localhost:${e}`);return new Promise((e=>{const t=setTimeout((()=>{s.close(),e(!1)}),1e3);s.onopen=()=>{clearTimeout(t),s.close(),e(!0)},s.onerror=()=>{clearTimeout(t),e(!1)}}))}catch{return!1}};const s=t(211);function o(e={}){let t=null,n=!0;const{startServer:o=!0,injectClient:i=!0,development:r=!0,server:l={},client:a={}}=e,c={port:3001,host:"0.0.0.0",logsDir:"logs",...l},g={enableWebSocket:!0,enableConsole:!0,minLevel:0,websocketUrl:`ws://localhost:${c.port}`,maxRetries:5,retryInterval:2e3,autoConnect:!0,...a};return{name:"websocket-logger",configResolved(e){n="serve"===e.command&&r},async configureServer(e){if(n&&o)try{t=new s.WebSocketLoggerServer(c),await t.start(),t.setupGracefulShutdown(),console.log(`๐Ÿ”Œ Vite WebSocket Logger Plugin: Server started on port ${c.port}`),e.httpServer?.on("close",(async()=>{t&&(await t.stop(),t=null)}))}catch(e){console.error("โŒ Failed to start WebSocket logger server:",e)}},transformIndexHtml:{enforce:"pre",transform(e,s){if(!n||!i)return e;const t=`\n<script type="module">\n import { WebSocketLogger } from '/@websocket-logger/client';\n \n // Initialize logger with configuration\n const logger = new WebSocketLogger(${JSON.stringify(g)});\n \n // Enable console override to capture all console.log calls\n logger.enableConsoleOverride();\n \n // Make logger available globally for debugging\n window.__websocketLogger = logger;\n \n // Log that the logger is ready\n console.log('๐Ÿ”Œ WebSocket Logger initialized and ready');\n<\/script>`;return e.replace("</head>",`${t}\n</head>`)}},resolveId:e=>"/@websocket-logger/client"===e?e:null,load:e=>"/@websocket-logger/client"===e?"\nimport { WebSocketLogger } from '@websocket-logger/client/websocket-logger';\nexport { WebSocketLogger };\nexport default WebSocketLogger;\n":null,handleHotUpdate(e){if(e.file.includes("websocket-logger"))return[]},buildStart(){n&&o&&!t&&console.log("๐Ÿ”Œ Vite WebSocket Logger Plugin: Preparing to start logging server...")},buildEnd(){t&&console.log("๐Ÿ”Œ Vite WebSocket Logger Plugin: Build completed with logging active")},closeBundle:{sequential:!0,async handler(){t&&(console.log("๐Ÿ”Œ Vite WebSocket Logger Plugin: Shutting down logger server..."),await t.stop(),t=null)}}}}e.default=o})(),module.exports["visilog-vite-plugin"]=n})(); //# sourceMappingURL=vite-plugin.js.map