From 39ee1fe5375d1da7dc498726ea64a19d9c8f6189 Mon Sep 17 00:00:00 2001 From: null Date: Wed, 13 May 2026 18:31:52 -0500 Subject: [PATCH] feat: structured logging with timestamps, request logging, and submission details (v0.4.6) --- server/index.js | 58 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/server/index.js b/server/index.js index abe1dec..591b18b 100644 --- a/server/index.js +++ b/server/index.js @@ -19,9 +19,32 @@ if (!existsSync(dbDir)) { try { chmodSync(dbDir, 0o755) } catch (e) {} } +// --- Logger --- +const LOG_LEVELS = { error: 0, warn: 1, info: 2, debug: 3 } +const currentLevel = LOG_LEVELS[process.env.LOG_LEVEL?.toLowerCase()] ?? LOG_LEVELS.info + +const log = { + info: (...args) => { if (currentLevel >= LOG_LEVELS.info) console.log(`[${new Date().toISOString()}] INFO `, ...args) }, + warn: (...args) => { if (currentLevel >= LOG_LEVELS.warn) console.warn(`[${new Date().toISOString()}] WARN `, ...args) }, + error: (...args) => { if (currentLevel >= LOG_LEVELS.error) console.error(`[${new Date().toISOString()}] ERROR`, ...args) }, + debug: (...args) => { if (currentLevel >= LOG_LEVELS.debug) console.debug(`[${new Date().toISOString()}] DEBUG`, ...args) }, +} + // Middleware app.use(express.json({ limit: '1mb' })) app.use(express.urlencoded({ extended: true, limit: '1mb' })) + +// Request logging middleware +app.use((req, res, next) => { + const start = Date.now() + res.on('finish', () => { + const ms = Date.now() - start + const level = res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'info' + log[level](`${req.method} ${req.originalUrl} ${res.statusCode} ${ms}ms`) + }) + next() +}) + app.use(express.static(path.join(__dirname, '../dist'))) // --- Database --- @@ -139,14 +162,14 @@ async function getZohoAccessToken() { if (data.access_token) { zohoAccessToken = data.access_token zohoTokenExpiry = Date.now() + (data.expires_in || 3600) * 1000 - console.log('[Zoho] Access token acquired, expires in', data.expires_in || 3600, 'seconds') + log.info('[Zoho] Access token acquired, expires in', data.expires_in || 3600, 'seconds') return zohoAccessToken } else { - console.error('[Zoho] Token exchange failed:', JSON.stringify(data)) + log.error('[Zoho] Token exchange failed:', JSON.stringify(data)) return null } } catch (err) { - console.error('[Zoho] Token acquisition error:', err.message) + log.error('[Zoho] Token acquisition error:', err.message) return null } } @@ -157,7 +180,7 @@ async function forwardToZoho(leadData) { try { const accessToken = await getZohoAccessToken() if (!accessToken) { - console.error('[Zoho] No access token available, skipping lead forwarding') + log.warn('[Zoho] No access token available, skipping lead forwarding') return } @@ -187,13 +210,13 @@ async function forwardToZoho(leadData) { if (response.ok) { const result = await response.json() - console.log('[Zoho] Lead forwarded successfully:', result.data?.[0]?.details?.id || 'no id returned') + log.info('[Zoho] Lead forwarded successfully:', result.data?.[0]?.details?.id || 'no id returned') } else { const text = await response.text() - console.error(`[Zoho] Lead forwarding failed (${response.status}):`, text) + log.error(`[Zoho] Lead forwarding failed (${response.status}):`, text) } } catch (err) { - console.error('[Zoho] Forwarding error:', err.message) + log.error('[Zoho] Forwarding error:', err.message) } } @@ -238,7 +261,7 @@ app.post('/api/leads', (req, res) => { VALUES (?, ?, ?, ?, ?, ?, ?) `) - stmt.run( + const result = stmt.run( sanitized.company, sanitized.name, sanitized.email, @@ -248,12 +271,14 @@ app.post('/api/leads', (req, res) => { sanitized.service_interest || null ) + log.info(`Lead submitted: ${sanitized.email} from ${sanitized.company} (id: ${result.lastInsertRowid})`) + // Fire-and-forget Zoho forwarding (best-effort, non-blocking) forwardToZoho(sanitized) res.json({ success: true, message: 'Thanks! We\'ll be in touch shortly.' }) } catch (err) { - console.error('Error submitting lead:', err) + log.error('Error submitting lead:', err) res.status(500).json({ error: 'Failed to submit lead' }) } }) @@ -291,7 +316,7 @@ app.post('/api/support', (req, res) => { VALUES (?, ?, ?, ?, ?, ?) `) - stmt.run( + const result = stmt.run( sanitized.name, sanitized.company, sanitized.email, @@ -300,9 +325,11 @@ app.post('/api/support', (req, res) => { sanitized.priority || 'medium' ) + log.info(`Support request submitted: ${sanitized.email} from ${sanitized.company} priority=${sanitized.priority || 'medium'} (id: ${result.lastInsertRowid})`) + res.json({ success: true, message: 'Thanks! We\'ll get back to you soon.' }) } catch (err) { - console.error('Error submitting support request:', err) + log.error('Error submitting support request:', err) res.status(500).json({ error: 'Failed to submit support request' }) } }) @@ -311,6 +338,11 @@ app.post('/api/support', (req, res) => { const PORT = process.env.SERVER_PORT || 3001 app.listen(PORT, () => { - console.log(`Server running on http://localhost:${PORT}`) - console.log(`Health check: http://localhost:${PORT}/api/health`) + log.info(`Server running on http://localhost:${PORT}`) + log.info(`Health check: http://localhost:${PORT}/api/health`) + if (ZOHO_ENABLED) { + log.info(`Zoho CRM forwarding: ENABLED (domain: ${ZOHO_API_DOMAIN})`) + } else { + log.info('Zoho CRM forwarding: DISABLED (set ZOHO_ENABLED=true to enable)') + } })