feat: add pino logger

This commit is contained in:
Anton Voylenko 2024-12-21 15:18:10 +02:00
commit 3015f13e0d
10 changed files with 195 additions and 47 deletions

122
package-lock.json generated
View file

@ -13,6 +13,7 @@
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.21.1", "express": "^4.21.1",
"express-rate-limit": "^7.4.1", "express-rate-limit": "^7.4.1",
"pino": "^9.5.0",
"qr-image": "^3.2.0", "qr-image": "^3.2.0",
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"swagger-ui-express": "^5.0.1", "swagger-ui-express": "^5.0.1",
@ -1697,6 +1698,15 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/available-typed-arrays": { "node_modules/available-typed-arrays": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@ -3605,6 +3615,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-redact": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
"integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/fast-safe-stringify": { "node_modules/fast-safe-stringify": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
@ -6122,6 +6141,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/on-exit-leak-free": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
"integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/on-finished": { "node_modules/on-finished": {
"version": "2.4.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
@ -6328,6 +6356,43 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/pino": {
"version": "9.5.0",
"resolved": "https://registry.npmjs.org/pino/-/pino-9.5.0.tgz",
"integrity": "sha512-xSEmD4pLnV54t0NOUN16yCl7RIB1c5UUOse5HSyEXtBp+FgFQyPeDutc+Q2ZO7/22vImV7VfEjH/1zV2QuqvYw==",
"license": "MIT",
"dependencies": {
"atomic-sleep": "^1.0.0",
"fast-redact": "^3.1.1",
"on-exit-leak-free": "^2.1.0",
"pino-abstract-transport": "^2.0.0",
"pino-std-serializers": "^7.0.0",
"process-warning": "^4.0.0",
"quick-format-unescaped": "^4.0.3",
"real-require": "^0.2.0",
"safe-stable-stringify": "^2.3.1",
"sonic-boom": "^4.0.1",
"thread-stream": "^3.0.0"
},
"bin": {
"pino": "bin.js"
}
},
"node_modules/pino-abstract-transport": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
"integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
"license": "MIT",
"dependencies": {
"split2": "^4.0.0"
}
},
"node_modules/pino-std-serializers": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz",
"integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==",
"license": "MIT"
},
"node_modules/pirates": { "node_modules/pirates": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
@ -6462,6 +6527,12 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
"node_modules/process-warning": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz",
"integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==",
"license": "MIT"
},
"node_modules/progress": { "node_modules/progress": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@ -6652,6 +6723,12 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/quick-format-unescaped": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
"license": "MIT"
},
"node_modules/range-parser": { "node_modules/range-parser": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -6730,6 +6807,15 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/real-require": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
"license": "MIT",
"engines": {
"node": ">= 12.13.0"
}
},
"node_modules/reflect.getprototypeof": { "node_modules/reflect.getprototypeof": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz",
@ -6963,6 +7049,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/safe-stable-stringify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/safer-buffer": { "node_modules/safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@ -7153,6 +7248,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/sonic-boom": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz",
"integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==",
"license": "MIT",
"dependencies": {
"atomic-sleep": "^1.0.0"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@ -7174,6 +7278,15 @@
"source-map": "^0.6.0" "source-map": "^0.6.0"
} }
}, },
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/sprintf-js": { "node_modules/sprintf-js": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -7538,6 +7651,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/thread-stream": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
"integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
"license": "MIT",
"dependencies": {
"real-require": "^0.2.0"
}
},
"node_modules/through": { "node_modules/through": {
"version": "2.3.8", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",

View file

@ -13,6 +13,7 @@
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.21.1", "express": "^4.21.1",
"express-rate-limit": "^7.4.1", "express-rate-limit": "^7.4.1",
"pino": "^9.5.0",
"qr-image": "^3.2.0", "qr-image": "^3.2.0",
"qrcode-terminal": "^0.12.0", "qrcode-terminal": "^0.12.0",
"swagger-ui-express": "^5.0.1", "swagger-ui-express": "^5.0.1",

View file

@ -1,5 +1,6 @@
const app = require('./src/app') const app = require('./src/app')
const { baseWebhookURL } = require('./src/config') const { baseWebhookURL } = require('./src/config')
const { logger } = require('./src/logger')
require('dotenv').config() require('dotenv').config()
// Start the server // Start the server
@ -7,12 +8,12 @@ const port = process.env.PORT || 3000
// Check if BASE_WEBHOOK_URL environment variable is available // Check if BASE_WEBHOOK_URL environment variable is available
if (!baseWebhookURL) { if (!baseWebhookURL) {
console.error('BASE_WEBHOOK_URL environment variable is not set. Exiting...') logger.error('BASE_WEBHOOK_URL environment variable is not set. Exiting...')
process.exit(1) // Terminate the application with an error code process.exit(1) // Terminate the application with an error code
} }
app.listen(port, () => { app.listen(port, () => {
console.log(`Server running on port ${port}`) logger.info(`Server running on port ${port}`)
}) })
// puppeteer uses subscriptions to SIGINT, SIGTERM, and SIGHUP to know when to close browser instances // puppeteer uses subscriptions to SIGINT, SIGTERM, and SIGHUP to know when to close browser instances

View file

@ -18,6 +18,7 @@ const recoverSessions = (process.env.RECOVER_SESSIONS || '').toLowerCase() === '
const chromeBin = process.env.CHROME_BIN || null const chromeBin = process.env.CHROME_BIN || null
const headless = process.env.HEADLESS ? (process.env.HEADLESS).toLowerCase() === 'true' : true const headless = process.env.HEADLESS ? (process.env.HEADLESS).toLowerCase() === 'true' : true
const releaseBrowserLock = process.env.RELEASE_BROWSER_LOCK ? (process.env.RELEASE_BROWSER_LOCK).toLowerCase() === 'true' : true const releaseBrowserLock = process.env.RELEASE_BROWSER_LOCK ? (process.env.RELEASE_BROWSER_LOCK).toLowerCase() === 'true' : true
const logLevel = process.env.LOGLEVEL || 'info'
module.exports = { module.exports = {
sessionFolderPath, sessionFolderPath,
@ -35,5 +36,6 @@ module.exports = {
recoverSessions, recoverSessions,
chromeBin, chromeBin,
headless, headless,
releaseBrowserLock releaseBrowserLock,
logLevel
} }

View file

@ -105,11 +105,10 @@ const sendMessage = async (req, res) => {
break break
} }
default: default:
return sendErrorResponse(res, 400, 'contentType invalid, must be string, MessageMedia, MessageMediaFromURL, Location, Buttons, List, Contact or Poll') return sendErrorResponse(res, 400, 'invalid contentType')
} }
res.json({ success: true, message: messageOut }) res.json({ success: true, message: messageOut })
} catch (error) { } catch (error) {
console.log(error)
sendErrorResponse(res, 500, error.message) sendErrorResponse(res, 500, error.message)
} }
} }

View file

@ -2,6 +2,7 @@ const fsp = require('fs').promises
const qrcode = require('qrcode-terminal') const qrcode = require('qrcode-terminal')
const { sessionFolderPath } = require('../config') const { sessionFolderPath } = require('../config')
const { sendErrorResponse } = require('../utils') const { sendErrorResponse } = require('../utils')
const { logger } = require('../logger')
/** /**
* Responds to request with 'pong' * Responds to request with 'pong'
@ -77,7 +78,7 @@ const localCallbackExample = async (req, res) => {
} }
} }
*/ */
console.log(error) logger.error(error, 'Failed to handle local callback')
sendErrorResponse(res, 500, error.message) sendErrorResponse(res, 500, error.message)
} }
} }

View file

@ -1,6 +1,7 @@
const qr = require('qr-image') const qr = require('qr-image')
const { setupSession, deleteSession, reloadSession, validateSession, flushSessions, sessions } = require('../sessions') const { setupSession, deleteSession, reloadSession, validateSession, flushSessions, sessions } = require('../sessions')
const { sendErrorResponse, waitForNestedObject } = require('../utils') const { sendErrorResponse, waitForNestedObject } = require('../utils')
const { logger } = require('../logger')
/** /**
* Starts a session for the given session ID. * Starts a session for the given session ID.
@ -16,8 +17,8 @@ const { sendErrorResponse, waitForNestedObject } = require('../utils')
const startSession = async (req, res) => { const startSession = async (req, res) => {
// #swagger.summary = 'Start new session' // #swagger.summary = 'Start new session'
// #swagger.description = 'Starts a session for the given session ID.' // #swagger.description = 'Starts a session for the given session ID.'
try {
const sessionId = req.params.sessionId const sessionId = req.params.sessionId
try {
const setupSessionReturn = await setupSession(sessionId) const setupSessionReturn = await setupSession(sessionId)
if (!setupSessionReturn.success) { if (!setupSessionReturn.success) {
/* #swagger.responses[422] = { /* #swagger.responses[422] = {
@ -45,7 +46,7 @@ const startSession = async (req, res) => {
await waitForNestedObject(setupSessionReturn.client, 'pupPage') await waitForNestedObject(setupSessionReturn.client, 'pupPage')
res.json({ success: true, message: setupSessionReturn.message }) res.json({ success: true, message: setupSessionReturn.message })
} catch (error) { } catch (error) {
console.log('startSession ERROR', error) logger.error({ sessionId, err: error }, 'Failed to start session')
sendErrorResponse(res, 500, error.message) sendErrorResponse(res, 500, error.message)
} }
} }
@ -64,8 +65,8 @@ const startSession = async (req, res) => {
const statusSession = async (req, res) => { const statusSession = async (req, res) => {
// #swagger.summary = 'Get session status' // #swagger.summary = 'Get session status'
// #swagger.description = 'Status of the session with the given session ID.' // #swagger.description = 'Status of the session with the given session ID.'
try {
const sessionId = req.params.sessionId const sessionId = req.params.sessionId
try {
const sessionData = await validateSession(sessionId) const sessionData = await validateSession(sessionId)
/* #swagger.responses[200] = { /* #swagger.responses[200] = {
description: "Status of the session.", description: "Status of the session.",
@ -78,7 +79,7 @@ const statusSession = async (req, res) => {
*/ */
res.json(sessionData) res.json(sessionData)
} catch (error) { } catch (error) {
console.log('statusSession ERROR', error) logger.error({ sessionId, err: error }, 'Failed to get session status')
sendErrorResponse(res, 500, error.message) sendErrorResponse(res, 500, error.message)
} }
} }
@ -97,8 +98,8 @@ const statusSession = async (req, res) => {
const sessionQrCode = async (req, res) => { const sessionQrCode = async (req, res) => {
// #swagger.summary = 'Get session QR code' // #swagger.summary = 'Get session QR code'
// #swagger.description = 'QR code of the session with the given session ID.' // #swagger.description = 'QR code of the session with the given session ID.'
try {
const sessionId = req.params.sessionId const sessionId = req.params.sessionId
try {
const session = sessions.get(sessionId) const session = sessions.get(sessionId)
if (!session) { if (!session) {
return res.json({ success: false, message: 'session_not_found' }) return res.json({ success: false, message: 'session_not_found' })
@ -108,7 +109,7 @@ const sessionQrCode = async (req, res) => {
} }
return res.json({ success: false, message: 'qr code not ready or already scanned' }) return res.json({ success: false, message: 'qr code not ready or already scanned' })
} catch (error) { } catch (error) {
console.log('sessionQrCode ERROR', error) logger.error({ sessionId, err: error }, 'Failed to get session qr code')
sendErrorResponse(res, 500, error.message) sendErrorResponse(res, 500, error.message)
} }
} }
@ -127,8 +128,8 @@ const sessionQrCode = async (req, res) => {
const sessionQrCodeImage = async (req, res) => { const sessionQrCodeImage = async (req, res) => {
// #swagger.summary = 'Get session QR code as image' // #swagger.summary = 'Get session QR code as image'
// #swagger.description = 'QR code as image of the session with the given session ID.' // #swagger.description = 'QR code as image of the session with the given session ID.'
try {
const sessionId = req.params.sessionId const sessionId = req.params.sessionId
try {
const session = sessions.get(sessionId) const session = sessions.get(sessionId)
if (!session) { if (!session) {
return res.json({ success: false, message: 'session_not_found' }) return res.json({ success: false, message: 'session_not_found' })
@ -149,7 +150,7 @@ const sessionQrCodeImage = async (req, res) => {
} }
return res.json({ success: false, message: 'qr code not ready or already scanned' }) return res.json({ success: false, message: 'qr code not ready or already scanned' })
} catch (error) { } catch (error) {
console.log('sessionQrCodeImage ERROR', error) logger.error({ sessionId, err: error }, 'Failed to get session qr code image')
sendErrorResponse(res, 500, error.message) sendErrorResponse(res, 500, error.message)
} }
} }
@ -168,8 +169,8 @@ const sessionQrCodeImage = async (req, res) => {
const restartSession = async (req, res) => { const restartSession = async (req, res) => {
// #swagger.summary = 'Restart session' // #swagger.summary = 'Restart session'
// #swagger.description = 'Restarts the session with the given session ID.' // #swagger.description = 'Restarts the session with the given session ID.'
try {
const sessionId = req.params.sessionId const sessionId = req.params.sessionId
try {
const validation = await validateSession(sessionId) const validation = await validateSession(sessionId)
if (validation.message === 'session_not_found') { if (validation.message === 'session_not_found') {
return res.json(validation) return res.json(validation)
@ -186,7 +187,7 @@ const restartSession = async (req, res) => {
*/ */
res.json({ success: true, message: 'Restarted successfully' }) res.json({ success: true, message: 'Restarted successfully' })
} catch (error) { } catch (error) {
console.log('restartSession ERROR', error) logger.error({ sessionId, err: error }, 'Failed to restart session')
sendErrorResponse(res, 500, error.message) sendErrorResponse(res, 500, error.message)
} }
} }
@ -205,8 +206,8 @@ const restartSession = async (req, res) => {
const terminateSession = async (req, res) => { const terminateSession = async (req, res) => {
// #swagger.summary = 'Terminate session' // #swagger.summary = 'Terminate session'
// #swagger.description = 'Terminates the session with the given session ID.' // #swagger.description = 'Terminates the session with the given session ID.'
try {
const sessionId = req.params.sessionId const sessionId = req.params.sessionId
try {
const validation = await validateSession(sessionId) const validation = await validateSession(sessionId)
if (validation.message === 'session_not_found') { if (validation.message === 'session_not_found') {
return res.json(validation) return res.json(validation)
@ -223,7 +224,7 @@ const terminateSession = async (req, res) => {
*/ */
res.json({ success: true, message: 'Logged out successfully' }) res.json({ success: true, message: 'Logged out successfully' })
} catch (error) { } catch (error) {
console.log('terminateSession ERROR', error) logger.error({ sessionId, err: error }, 'Failed to terminate session')
sendErrorResponse(res, 500, error.message) sendErrorResponse(res, 500, error.message)
} }
} }
@ -254,7 +255,7 @@ const terminateInactiveSessions = async (req, res) => {
*/ */
res.json({ success: true, message: 'Flush completed successfully' }) res.json({ success: true, message: 'Flush completed successfully' })
} catch (error) { } catch (error) {
console.log('terminateInactiveSessions ERROR', error) logger.error(error, 'Failed to terminate inactive sessions')
sendErrorResponse(res, 500, error.message) sendErrorResponse(res, 500, error.message)
} }
} }
@ -285,7 +286,7 @@ const terminateAllSessions = async (req, res) => {
*/ */
res.json({ success: true, message: 'Flush completed successfully' }) res.json({ success: true, message: 'Flush completed successfully' })
} catch (error) { } catch (error) {
console.log('terminateAllSessions ERROR', error) logger.error(error, 'Failed to terminate all sessions')
sendErrorResponse(res, 500, error.message) sendErrorResponse(res, 500, error.message)
} }
} }

12
src/logger.js Normal file
View file

@ -0,0 +1,12 @@
const { logLevel } = require('./config')
const pino = require('pino')
const logger = pino({
level: logLevel
}, pino.destination(1, { sync: false }))
logger.on('level-change', (lvl, val, prevLvl, prevVal) => {
logger.info('%s (%d) was changed to %s (%d)', prevLvl, prevVal, lvl, val)
})
module.exports = { logger }

View file

@ -3,7 +3,8 @@ const fs = require('fs')
const path = require('path') const path = require('path')
const sessions = new Map() const sessions = new Map()
const { baseWebhookURL, sessionFolderPath, maxAttachmentSize, setMessagesAsSeen, webVersion, webVersionCacheType, recoverSessions, chromeBin, headless, releaseBrowserLock } = require('./config') const { baseWebhookURL, sessionFolderPath, maxAttachmentSize, setMessagesAsSeen, webVersion, webVersionCacheType, recoverSessions, chromeBin, headless, releaseBrowserLock } = require('./config')
const { triggerWebhook, waitForNestedObject, checkIfEventisEnabled } = require('./utils') const { triggerWebhook, waitForNestedObject, checkIfEventisEnabled, sendMessageSeenStatus } = require('./utils')
const { logger } = require('./logger')
// Function to validate if the session is ready // Function to validate if the session is ready
const validateSession = async (sessionId) => { const validateSession = async (sessionId) => {
@ -53,7 +54,7 @@ const validateSession = async (sessionId) => {
returnData.message = 'session_connected' returnData.message = 'session_connected'
return returnData return returnData
} catch (error) { } catch (error) {
console.log(error) logger.error({ sessionId, err: error }, 'Failed to validate session')
return { success: false, state: null, message: error.message } return { success: false, state: null, message: error.message }
} }
} }
@ -72,14 +73,13 @@ const restoreSessions = () => {
const match = file.match(/^session-(.+)$/) const match = file.match(/^session-(.+)$/)
if (match) { if (match) {
const sessionId = match[1] const sessionId = match[1]
console.log('existing session detected', sessionId) logger.warn({ sessionId }, 'existing session detected')
await setupSession(sessionId) await setupSession(sessionId)
} }
} }
}) })
} catch (error) { } catch (error) {
console.log(error) logger.error(error, 'Failed to restore sessions')
console.error('Failed to restore sessions:', error)
} }
} }
@ -131,7 +131,7 @@ const setupSession = async (sessionId) => {
const singletonLockPath = path.resolve(path.join(sessionFolderPath, `session-${sessionId}`, 'SingletonLock')) const singletonLockPath = path.resolve(path.join(sessionFolderPath, `session-${sessionId}`, 'SingletonLock'))
const singletonLockExists = await fs.promises.lstat(singletonLockPath).then(() => true).catch(() => false) const singletonLockExists = await fs.promises.lstat(singletonLockPath).then(() => true).catch(() => false)
if (singletonLockExists) { if (singletonLockExists) {
console.log('Browser lock file exists, removing ', sessionId) logger.warn({ sessionId }, 'Browser lock file exists, removing')
await fs.promises.unlink(singletonLockPath) await fs.promises.unlink(singletonLockPath)
} }
} }
@ -139,7 +139,7 @@ const setupSession = async (sessionId) => {
try { try {
await client.initialize() await client.initialize()
} catch (error) { } catch (error) {
console.log('Initialize error:', error.message) logger.error({ sessionId, err: error }, 'Initialize error')
throw error throw error
} }
@ -166,12 +166,12 @@ const initializeEvents = (client, sessionId) => {
} }
client.pupPage.once('close', function () { client.pupPage.once('close', function () {
// emitted when the page closes // emitted when the page closes
console.log(`Browser page closed for ${sessionId}. Restoring`) logger.warn({ sessionId }, 'Browser page closed. Restoring')
restartSession(sessionId) restartSession(sessionId)
}) })
client.pupPage.once('error', function () { client.pupPage.once('error', function () {
// emitted when the page crashes // emitted when the page crashes
console.log(`Error occurred on browser page for ${sessionId}. Restoring`) logger.warn({ sessionId }, 'Error occurred on browser page. Restoring')
restartSession(sessionId) restartSession(sessionId)
}) })
}).catch(e => { }) }).catch(e => { })
@ -270,14 +270,13 @@ const initializeEvents = (client, sessionId) => {
checkIfEventisEnabled('media').then(_ => { checkIfEventisEnabled('media').then(_ => {
message.downloadMedia().then(messageMedia => { message.downloadMedia().then(messageMedia => {
triggerWebhook(sessionWebhook, sessionId, 'media', { messageMedia, message }) triggerWebhook(sessionWebhook, sessionId, 'media', { messageMedia, message })
}).catch(e => { }).catch(error => {
console.log('Download media error:', e.message) logger.error({ sessionId, err: error }, 'Failed to download media')
}) })
}) })
} }
if (setMessagesAsSeen) { if (setMessagesAsSeen) {
const chat = await message.getChat() sendMessageSeenStatus(message)
chat.sendSeen()
} }
}) })
}) })
@ -287,8 +286,7 @@ const initializeEvents = (client, sessionId) => {
client.on('message_ack', async (message, ack) => { client.on('message_ack', async (message, ack) => {
triggerWebhook(sessionWebhook, sessionId, 'message_ack', { message, ack }) triggerWebhook(sessionWebhook, sessionId, 'message_ack', { message, ack })
if (setMessagesAsSeen) { if (setMessagesAsSeen) {
const chat = await message.getChat() sendMessageSeenStatus(message)
chat.sendSeen()
} }
}) })
}) })
@ -298,8 +296,7 @@ const initializeEvents = (client, sessionId) => {
client.on('message_create', async (message) => { client.on('message_create', async (message) => {
triggerWebhook(sessionWebhook, sessionId, 'message_create', { message }) triggerWebhook(sessionWebhook, sessionId, 'message_create', { message })
if (setMessagesAsSeen) { if (setMessagesAsSeen) {
const chat = await message.getChat() sendMessageSeenStatus(message)
chat.sendSeen()
} }
}) })
}) })
@ -407,7 +404,7 @@ const deleteSessionFolder = async (sessionId) => {
} }
await fs.promises.rm(resolvedTargetDirPath, { recursive: true, force: true }) await fs.promises.rm(resolvedTargetDirPath, { recursive: true, force: true })
} catch (error) { } catch (error) {
console.log('Folder deletion error', error) logger.error({ sessionId, err: error }, 'Folder deletion error')
throw error throw error
} }
} }
@ -437,7 +434,7 @@ const reloadSession = async (sessionId) => {
sessions.delete(sessionId) sessions.delete(sessionId)
await setupSession(sessionId) await setupSession(sessionId)
} catch (error) { } catch (error) {
console.log(error) logger.error({ sessionId, err: error }, 'Failed to reload session')
throw error throw error
} }
} }
@ -452,11 +449,11 @@ const deleteSession = async (sessionId, validation) => {
client.pupPage?.removeAllListeners('error') client.pupPage?.removeAllListeners('error')
if (validation.success) { if (validation.success) {
// Client Connected, request logout // Client Connected, request logout
console.log(`Logging out session ${sessionId}`) logger.info({ sessionId }, 'Logging out session')
await client.logout() await client.logout()
} else if (validation.message === 'session_not_connected') { } else if (validation.message === 'session_not_connected') {
// Client not Connected, request destroy // Client not Connected, request destroy
console.log(`Destroying session ${sessionId}`) logger.info({ sessionId }, 'Destroying session')
await client.destroy() await client.destroy()
} }
// Wait 10 secs for client.pupBrowser to be disconnected before deleting the folder // Wait 10 secs for client.pupBrowser to be disconnected before deleting the folder
@ -468,7 +465,7 @@ const deleteSession = async (sessionId, validation) => {
await deleteSessionFolder(sessionId) await deleteSessionFolder(sessionId)
sessions.delete(sessionId) sessions.delete(sessionId)
} catch (error) { } catch (error) {
console.log(error) logger.error({ sessionId, err: error }, 'Failed to delete session')
throw error throw error
} }
} }
@ -491,7 +488,7 @@ const flushSessions = async (deleteOnlyInactive) => {
} }
} }
} catch (error) { } catch (error) {
console.log(error) logger.error(error, 'Failed to flush sessions')
throw error throw error
} }
} }

View file

@ -1,10 +1,12 @@
const axios = require('axios') const axios = require('axios')
const { globalApiKey, disabledCallbacks } = require('./config') const { globalApiKey, disabledCallbacks } = require('./config')
const { logger } = require('./logger')
// Trigger webhook endpoint // Trigger webhook endpoint
const triggerWebhook = (webhookURL, sessionId, dataType, data) => { const triggerWebhook = (webhookURL, sessionId, dataType, data) => {
axios.post(webhookURL, { dataType, data, sessionId }, { headers: { 'x-api-key': globalApiKey } }) axios.post(webhookURL, { dataType, data, sessionId }, { headers: { 'x-api-key': globalApiKey } })
.catch(error => console.error('Failed to send new message webhook:', sessionId, dataType, error.message, data || '')) .then(() => logger.debug({ sessionId, dataType, data: data || '' }, `New webhook message sent to ${webhookURL}`))
.catch(error => logger.error({ sessionId, dataType, err: error, data: data || '' }, `Failed to send new webhook message to ${webhookURL}`))
} }
// Function to send a response with error status and message // Function to send a response with error status and message
@ -23,7 +25,7 @@ const waitForNestedObject = (rootObj, nestedPath, maxWaitTime = 10000, interval
resolve() resolve()
} else if (Date.now() - start > maxWaitTime) { } else if (Date.now() - start > maxWaitTime) {
// Maximum wait time exceeded, reject the promise // Maximum wait time exceeded, reject the promise
console.log('Timed out waiting for nested object') logger.error('Timed out waiting for nested object')
reject(new Error('Timeout waiting for nested object')) reject(new Error('Timeout waiting for nested object'))
} else { } else {
// Nested object not yet created, continue waiting // Nested object not yet created, continue waiting
@ -35,12 +37,22 @@ const waitForNestedObject = (rootObj, nestedPath, maxWaitTime = 10000, interval
} }
const checkIfEventisEnabled = (event) => { const checkIfEventisEnabled = (event) => {
return new Promise((resolve, reject) => { if (!disabledCallbacks.includes(event)) { resolve() } }) return new Promise((resolve) => { if (!disabledCallbacks.includes(event)) { resolve() } })
}
const sendMessageSeenStatus = async (message) => {
try {
const chat = await message.getChat()
await chat.sendSeen()
} catch (error) {
logger.error(error, 'Failed to send seen status')
}
} }
module.exports = { module.exports = {
triggerWebhook, triggerWebhook,
sendErrorResponse, sendErrorResponse,
waitForNestedObject, waitForNestedObject,
checkIfEventisEnabled checkIfEventisEnabled,
sendMessageSeenStatus
} }