Last active
August 20, 2025 15:13
-
-
Save mikecarroll/f6882e57bba8eda89c85b491b74e3d89 to your computer and use it in GitHub Desktop.
A lightweight Node.js script that automatically captures and logs all OpenAI API calls made by your application without requiring code changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // monitor.js - Enhanced debug version | |
| const fs = require('fs'); | |
| const https = require('https'); | |
| const http = require('http'); | |
| class CoolHandLlmMonitor { | |
| constructor(options = {}) { | |
| this.outputFile = options.outputFile || 'api_calls.jsonl'; | |
| this.callCounter = 0; | |
| this.interceptedCalls = 0; | |
| // Simple logging configuration - just one variable | |
| this.silent = options.silent !== false; // Default: false (verbose) | |
| if (!this.silent) { | |
| console.log('π Setting up CoolHandLlmMonitor...'); | |
| } | |
| this.setupMonitoring(); | |
| if (!this.silent) { | |
| console.log('β CoolHandLlmMonitor ready - will log to:', this.outputFile); | |
| } | |
| } | |
| log(...args) { | |
| if (!this.silent) { | |
| console.log(...args); | |
| } | |
| } | |
| setupMonitoring() { | |
| // Patch HTTPS | |
| this.patchHTTPS(); | |
| // Patch HTTP (some libraries might use HTTP with upgrade) | |
| this.patchHTTP(); | |
| // Patch fetch if available (Node 18+) | |
| this.patchFetch(); | |
| // Debug: Log when any request happens | |
| this.log('π‘ Monitoring all outbound requests...'); | |
| } | |
| patchHTTPS() { | |
| const originalRequest = https.request; | |
| const originalGet = https.get; | |
| const monitor = this; | |
| https.request = function(options, callback) { | |
| monitor.debugRequest('HTTPS REQUEST', options); | |
| // Check if this is an OpenAI call | |
| const isOpenAI = monitor.isOpenAICall(options); | |
| if (isOpenAI) { | |
| monitor.log('π― INTERCEPTING OpenAI HTTPS call'); | |
| return monitor.interceptRequest(originalRequest, options, callback, 'https'); | |
| } | |
| return originalRequest.call(this, options, callback); | |
| }; | |
| https.get = function(options, callback) { | |
| monitor.debugRequest('HTTPS GET', options); | |
| const isOpenAI = monitor.isOpenAICall(options); | |
| if (isOpenAI) { | |
| monitor.log('π― INTERCEPTING OpenAI HTTPS GET'); | |
| return monitor.interceptRequest(originalRequest, options, callback, 'https'); | |
| } | |
| return originalGet.call(this, options, callback); | |
| }; | |
| } | |
| patchHTTP() { | |
| const originalRequest = http.request; | |
| const originalGet = http.get; | |
| const monitor = this; | |
| http.request = function(options, callback) { | |
| monitor.debugRequest('HTTP REQUEST', options); | |
| if (isOpenAI) { | |
| monitor.log('π― INTERCEPTING OpenAI HTTP call'); | |
| return monitor.interceptRequest(originalRequest, options, callback, 'http'); | |
| } | |
| return originalRequest.call(this, options, callback); | |
| }; | |
| http.get = function(options, callback) { | |
| monitor.debugRequest('HTTP GET', options); | |
| const isOpenAI = monitor.isOpenAICall(options); | |
| if (isOpenAI) { | |
| monitor.log('π― INTERCEPTING OpenAI HTTP GET'); | |
| return monitor.interceptRequest(originalRequest, options, callback, 'http'); | |
| } | |
| return originalGet.call(this, options, callback); | |
| }; | |
| } | |
| patchFetch() { | |
| if (typeof globalThis.fetch === 'function') { | |
| const originalFetch = globalThis.fetch; | |
| const monitor = this; | |
| globalThis.fetch = async function(url, options = {}) { | |
| const urlStr = typeof url === 'string' ? url : url.toString(); | |
| monitor.debugRequest('FETCH', { url: urlStr, ...options }); | |
| if (urlStr.includes('openai.com')) { | |
| monitor.log('π― INTERCEPTING OpenAI FETCH call'); | |
| return monitor.interceptFetch(originalFetch, url, options); | |
| } | |
| return originalFetch.call(this, url, options); | |
| }; | |
| } | |
| } | |
| isOpenAICall(options) { | |
| if (typeof options === 'string') { | |
| return options.includes('openai.com'); | |
| } | |
| const hostname = options.hostname || options.host || ''; | |
| const href = options.href || ''; | |
| const path = options.path || ''; | |
| return hostname.includes('openai.com') || | |
| href.includes('openai.com') || | |
| path.includes('openai.com'); | |
| } | |
| debugRequest(type, options) { | |
| const hostname = options.hostname || options.host || options.url || 'unknown'; | |
| this.log(`π ${type} to: ${hostname}`); | |
| // Count all requests | |
| this.callCounter++; | |
| } | |
| interceptRequest(originalRequest, options, callback, protocol) { | |
| this.interceptedCalls++; | |
| const url = this.buildURL(options, protocol); | |
| const callData = { | |
| id: this.interceptedCalls, | |
| timestamp: new Date().toISOString(), | |
| method: options.method || 'GET', | |
| url: url, | |
| headers: this.sanitizeHeaders(options.headers || {}), | |
| request_body: null, | |
| response_body: null, | |
| response_headers: null, | |
| status_code: null, | |
| protocol: protocol | |
| }; | |
| this.log(`π Starting API call #${callData.id} to ${url}`); | |
| let requestBody = ''; | |
| const req = originalRequest.call(protocol === 'https' ? https : http, options, (res) => { | |
| monitor.log(`π₯ Response received for call #${callData.id}, status: ${res.statusCode}`); | |
| let responseBody = ''; | |
| res.on('data', (chunk) => { | |
| responseBody += chunk.toString(); | |
| }); | |
| res.on('end', () => { | |
| callData.response_body = monitor.parseJSON(responseBody); | |
| callData.response_headers = monitor.sanitizeHeaders(res.headers); | |
| callData.status_code = res.statusCode; | |
| monitor.logCall(callData); | |
| }); | |
| if (callback) callback(res); | |
| }); | |
| // Intercept request body | |
| const originalWrite = req.write.bind(req); | |
| const originalEnd = req.end.bind(req); | |
| req.write = function(chunk, encoding) { | |
| if (chunk) { | |
| requestBody += chunk.toString(); | |
| monitor.log(`π€ Request body chunk: ${chunk.toString().substring(0, 100)}...`); | |
| } | |
| return originalWrite(chunk, encoding); | |
| }; | |
| req.end = function(chunk, encoding) { | |
| if (chunk) { | |
| requestBody += chunk.toString(); | |
| } | |
| callData.request_body = monitor.parseJSON(requestBody); | |
| monitor.log(`π€ Request complete for call #${callData.id}`); | |
| return originalEnd(chunk, encoding); | |
| }; | |
| req.on('error', (err) => { | |
| monitor.log(`β Request error for call #${callData.id}:`, err.message); | |
| }); | |
| return req; | |
| } | |
| async interceptFetch(originalFetch, url, options) { | |
| this.interceptedCalls++; | |
| const callData = { | |
| id: this.interceptedCalls, | |
| timestamp: new Date().toISOString(), | |
| method: options.method || 'GET', | |
| url: url.toString(), | |
| headers: this.sanitizeHeaders(options.headers || {}), | |
| request_body: options.body ? this.parseJSON(options.body) : null, | |
| response_body: null, | |
| response_headers: null, | |
| status_code: null, | |
| protocol: 'fetch' | |
| }; | |
| this.log(`π Starting FETCH call #${callData.id} to ${url}`); | |
| try { | |
| const response = await originalFetch.call(globalThis, url, options); | |
| callData.status_code = response.status; | |
| callData.response_headers = Object.fromEntries(response.headers.entries()); | |
| // Clone response to read body without consuming it | |
| const responseClone = response.clone(); | |
| const responseText = await responseClone.text(); | |
| callData.response_body = this.parseJSON(responseText); | |
| this.logCall(callData); | |
| return response; | |
| } catch (error) { | |
| this.log(`β Fetch error for call #${callData.id}:`, error.message); | |
| throw error; | |
| } | |
| } | |
| buildURL(options, protocol) { | |
| if (options.href) return options.href; | |
| const hostname = options.hostname || options.host || 'unknown'; | |
| const path = options.path || '/'; | |
| const port = options.port ? `:${options.port}` : ''; | |
| return `${protocol}://${hostname}${port}${path}`; | |
| } | |
| sanitizeHeaders(headers) { | |
| const sanitized = { ...headers }; | |
| if (sanitized.authorization) { | |
| sanitized.authorization = sanitized.authorization.replace(/Bearer .+/, 'Bearer [REDACTED]'); | |
| } | |
| if (sanitized['openai-api-key']) { | |
| sanitized['openai-api-key'] = '[REDACTED]'; | |
| } | |
| if (sanitized['api-key']) { | |
| sanitized['api-key'] = '[REDACTED]'; | |
| } | |
| return sanitized; | |
| } | |
| parseJSON(str) { | |
| if (!str) return null; | |
| try { | |
| return JSON.parse(str); | |
| } catch { | |
| return str; | |
| } | |
| } | |
| logCall(callData) { | |
| // Always write to file | |
| const logEntry = JSON.stringify(callData, null, 2) + '\n' + '='.repeat(80) + '\n'; | |
| fs.appendFileSync(this.outputFile, logEntry); | |
| // Console output (only if not silent) | |
| if (!this.silent) { | |
| console.log(`\nπ LOGGED OpenAI API Call #${callData.id}`); | |
| console.log(`π Time: ${callData.timestamp}`); | |
| console.log(`π― ${callData.method} ${callData.url}`); | |
| console.log(`π Status: ${callData.status_code}`); | |
| console.log(`π§ Protocol: ${callData.protocol}`); | |
| if (callData.request_body?.model) { | |
| console.log(`π€ Model: ${callData.request_body.model}`); | |
| } | |
| if (callData.request_body?.messages) { | |
| console.log(`π¬ Messages: ${callData.request_body.messages.length}`); | |
| } | |
| if (callData.request_body?.temperature !== undefined) { | |
| console.log(`π‘οΈ Temperature: ${callData.request_body.temperature}`); | |
| } | |
| console.log(`π Logged to: ${this.outputFile}`); | |
| console.log('β'.repeat(60)); | |
| } | |
| } | |
| getStats() { | |
| return { | |
| totalRequests: this.callCounter, | |
| interceptedCalls: this.interceptedCalls | |
| }; | |
| } | |
| } | |
| // Auto-start monitoring with default options | |
| const monitor = new CoolHandLlmMonitor(); | |
| // Export both the instance and the constructor | |
| module.exports = monitor; | |
| module.exports.CoolHandLlmMonitor = CoolHandLlmMonitor; | |
| // Add a test function | |
| monitor.test = function() { | |
| this.log('π§ͺ CoolHandLlmMonitor test - stats:', this.getStats()); | |
| return 'CoolHandLlmMonitor is active'; | |
| }; | |
| if (!monitor.silent) { | |
| console.log('β¨ CoolHandLlmMonitor loaded! Test with: require("./monitor").test()'); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment