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

View file

@ -18,6 +18,7 @@ const recoverSessions = (process.env.RECOVER_SESSIONS || '').toLowerCase() === '
const chromeBin = process.env.CHROME_BIN || null
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 logLevel = process.env.LOGLEVEL || 'info'
module.exports = {
sessionFolderPath,
@ -35,5 +36,6 @@ module.exports = {
recoverSessions,
chromeBin,
headless,
releaseBrowserLock
releaseBrowserLock,
logLevel
}

View file

@ -105,11 +105,10 @@ const sendMessage = async (req, res) => {
break
}
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 })
} catch (error) {
console.log(error)
sendErrorResponse(res, 500, error.message)
}
}

View file

@ -2,6 +2,7 @@ const fsp = require('fs').promises
const qrcode = require('qrcode-terminal')
const { sessionFolderPath } = require('../config')
const { sendErrorResponse } = require('../utils')
const { logger } = require('../logger')
/**
* 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)
}
}

View file

@ -1,6 +1,7 @@
const qr = require('qr-image')
const { setupSession, deleteSession, reloadSession, validateSession, flushSessions, sessions } = require('../sessions')
const { sendErrorResponse, waitForNestedObject } = require('../utils')
const { logger } = require('../logger')
/**
* Starts a session for the given session ID.
@ -16,8 +17,8 @@ const { sendErrorResponse, waitForNestedObject } = require('../utils')
const startSession = async (req, res) => {
// #swagger.summary = 'Start new session'
// #swagger.description = 'Starts a session for the given session ID.'
const sessionId = req.params.sessionId
try {
const sessionId = req.params.sessionId
const setupSessionReturn = await setupSession(sessionId)
if (!setupSessionReturn.success) {
/* #swagger.responses[422] = {
@ -45,7 +46,7 @@ const startSession = async (req, res) => {
await waitForNestedObject(setupSessionReturn.client, 'pupPage')
res.json({ success: true, message: setupSessionReturn.message })
} catch (error) {
console.log('startSession ERROR', error)
logger.error({ sessionId, err: error }, 'Failed to start session')
sendErrorResponse(res, 500, error.message)
}
}
@ -64,8 +65,8 @@ const startSession = async (req, res) => {
const statusSession = async (req, res) => {
// #swagger.summary = 'Get session status'
// #swagger.description = 'Status of the session with the given session ID.'
const sessionId = req.params.sessionId
try {
const sessionId = req.params.sessionId
const sessionData = await validateSession(sessionId)
/* #swagger.responses[200] = {
description: "Status of the session.",
@ -78,7 +79,7 @@ const statusSession = async (req, res) => {
*/
res.json(sessionData)
} catch (error) {
console.log('statusSession ERROR', error)
logger.error({ sessionId, err: error }, 'Failed to get session status')
sendErrorResponse(res, 500, error.message)
}
}
@ -97,8 +98,8 @@ const statusSession = async (req, res) => {
const sessionQrCode = async (req, res) => {
// #swagger.summary = 'Get session QR code'
// #swagger.description = 'QR code of the session with the given session ID.'
const sessionId = req.params.sessionId
try {
const sessionId = req.params.sessionId
const session = sessions.get(sessionId)
if (!session) {
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' })
} catch (error) {
console.log('sessionQrCode ERROR', error)
logger.error({ sessionId, err: error }, 'Failed to get session qr code')
sendErrorResponse(res, 500, error.message)
}
}
@ -127,8 +128,8 @@ const sessionQrCode = async (req, res) => {
const sessionQrCodeImage = async (req, res) => {
// #swagger.summary = 'Get session QR code as image'
// #swagger.description = 'QR code as image of the session with the given session ID.'
const sessionId = req.params.sessionId
try {
const sessionId = req.params.sessionId
const session = sessions.get(sessionId)
if (!session) {
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' })
} catch (error) {
console.log('sessionQrCodeImage ERROR', error)
logger.error({ sessionId, err: error }, 'Failed to get session qr code image')
sendErrorResponse(res, 500, error.message)
}
}
@ -168,8 +169,8 @@ const sessionQrCodeImage = async (req, res) => {
const restartSession = async (req, res) => {
// #swagger.summary = 'Restart session'
// #swagger.description = 'Restarts the session with the given session ID.'
const sessionId = req.params.sessionId
try {
const sessionId = req.params.sessionId
const validation = await validateSession(sessionId)
if (validation.message === 'session_not_found') {
return res.json(validation)
@ -186,7 +187,7 @@ const restartSession = async (req, res) => {
*/
res.json({ success: true, message: 'Restarted successfully' })
} catch (error) {
console.log('restartSession ERROR', error)
logger.error({ sessionId, err: error }, 'Failed to restart session')
sendErrorResponse(res, 500, error.message)
}
}
@ -205,8 +206,8 @@ const restartSession = async (req, res) => {
const terminateSession = async (req, res) => {
// #swagger.summary = 'Terminate session'
// #swagger.description = 'Terminates the session with the given session ID.'
const sessionId = req.params.sessionId
try {
const sessionId = req.params.sessionId
const validation = await validateSession(sessionId)
if (validation.message === 'session_not_found') {
return res.json(validation)
@ -223,7 +224,7 @@ const terminateSession = async (req, res) => {
*/
res.json({ success: true, message: 'Logged out successfully' })
} catch (error) {
console.log('terminateSession ERROR', error)
logger.error({ sessionId, err: error }, 'Failed to terminate session')
sendErrorResponse(res, 500, error.message)
}
}
@ -254,7 +255,7 @@ const terminateInactiveSessions = async (req, res) => {
*/
res.json({ success: true, message: 'Flush completed successfully' })
} catch (error) {
console.log('terminateInactiveSessions ERROR', error)
logger.error(error, 'Failed to terminate inactive sessions')
sendErrorResponse(res, 500, error.message)
}
}
@ -285,7 +286,7 @@ const terminateAllSessions = async (req, res) => {
*/
res.json({ success: true, message: 'Flush completed successfully' })
} catch (error) {
console.log('terminateAllSessions ERROR', error)
logger.error(error, 'Failed to terminate all sessions')
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 sessions = new Map()
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
const validateSession = async (sessionId) => {
@ -53,7 +54,7 @@ const validateSession = async (sessionId) => {
returnData.message = 'session_connected'
return returnData
} catch (error) {
console.log(error)
logger.error({ sessionId, err: error }, 'Failed to validate session')
return { success: false, state: null, message: error.message }
}
}
@ -72,14 +73,13 @@ const restoreSessions = () => {
const match = file.match(/^session-(.+)$/)
if (match) {
const sessionId = match[1]
console.log('existing session detected', sessionId)
logger.warn({ sessionId }, 'existing session detected')
await setupSession(sessionId)
}
}
})
} catch (error) {
console.log(error)
console.error('Failed to restore sessions:', error)
logger.error(error, 'Failed to restore sessions')
}
}
@ -131,7 +131,7 @@ const setupSession = async (sessionId) => {
const singletonLockPath = path.resolve(path.join(sessionFolderPath, `session-${sessionId}`, 'SingletonLock'))
const singletonLockExists = await fs.promises.lstat(singletonLockPath).then(() => true).catch(() => false)
if (singletonLockExists) {
console.log('Browser lock file exists, removing ', sessionId)
logger.warn({ sessionId }, 'Browser lock file exists, removing')
await fs.promises.unlink(singletonLockPath)
}
}
@ -139,7 +139,7 @@ const setupSession = async (sessionId) => {
try {
await client.initialize()
} catch (error) {
console.log('Initialize error:', error.message)
logger.error({ sessionId, err: error }, 'Initialize error')
throw error
}
@ -166,12 +166,12 @@ const initializeEvents = (client, sessionId) => {
}
client.pupPage.once('close', function () {
// emitted when the page closes
console.log(`Browser page closed for ${sessionId}. Restoring`)
logger.warn({ sessionId }, 'Browser page closed. Restoring')
restartSession(sessionId)
})
client.pupPage.once('error', function () {
// 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)
})
}).catch(e => { })
@ -270,14 +270,13 @@ const initializeEvents = (client, sessionId) => {
checkIfEventisEnabled('media').then(_ => {
message.downloadMedia().then(messageMedia => {
triggerWebhook(sessionWebhook, sessionId, 'media', { messageMedia, message })
}).catch(e => {
console.log('Download media error:', e.message)
}).catch(error => {
logger.error({ sessionId, err: error }, 'Failed to download media')
})
})
}
if (setMessagesAsSeen) {
const chat = await message.getChat()
chat.sendSeen()
sendMessageSeenStatus(message)
}
})
})
@ -287,8 +286,7 @@ const initializeEvents = (client, sessionId) => {
client.on('message_ack', async (message, ack) => {
triggerWebhook(sessionWebhook, sessionId, 'message_ack', { message, ack })
if (setMessagesAsSeen) {
const chat = await message.getChat()
chat.sendSeen()
sendMessageSeenStatus(message)
}
})
})
@ -298,8 +296,7 @@ const initializeEvents = (client, sessionId) => {
client.on('message_create', async (message) => {
triggerWebhook(sessionWebhook, sessionId, 'message_create', { message })
if (setMessagesAsSeen) {
const chat = await message.getChat()
chat.sendSeen()
sendMessageSeenStatus(message)
}
})
})
@ -407,7 +404,7 @@ const deleteSessionFolder = async (sessionId) => {
}
await fs.promises.rm(resolvedTargetDirPath, { recursive: true, force: true })
} catch (error) {
console.log('Folder deletion error', error)
logger.error({ sessionId, err: error }, 'Folder deletion error')
throw error
}
}
@ -437,7 +434,7 @@ const reloadSession = async (sessionId) => {
sessions.delete(sessionId)
await setupSession(sessionId)
} catch (error) {
console.log(error)
logger.error({ sessionId, err: error }, 'Failed to reload session')
throw error
}
}
@ -452,11 +449,11 @@ const deleteSession = async (sessionId, validation) => {
client.pupPage?.removeAllListeners('error')
if (validation.success) {
// Client Connected, request logout
console.log(`Logging out session ${sessionId}`)
logger.info({ sessionId }, 'Logging out session')
await client.logout()
} else if (validation.message === 'session_not_connected') {
// Client not Connected, request destroy
console.log(`Destroying session ${sessionId}`)
logger.info({ sessionId }, 'Destroying session')
await client.destroy()
}
// 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)
sessions.delete(sessionId)
} catch (error) {
console.log(error)
logger.error({ sessionId, err: error }, 'Failed to delete session')
throw error
}
}
@ -491,7 +488,7 @@ const flushSessions = async (deleteOnlyInactive) => {
}
}
} catch (error) {
console.log(error)
logger.error(error, 'Failed to flush sessions')
throw error
}
}

View file

@ -1,10 +1,12 @@
const axios = require('axios')
const { globalApiKey, disabledCallbacks } = require('./config')
const { logger } = require('./logger')
// Trigger webhook endpoint
const triggerWebhook = (webhookURL, sessionId, dataType, data) => {
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
@ -23,7 +25,7 @@ const waitForNestedObject = (rootObj, nestedPath, maxWaitTime = 10000, interval
resolve()
} else if (Date.now() - start > maxWaitTime) {
// 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'))
} else {
// Nested object not yet created, continue waiting
@ -35,12 +37,22 @@ const waitForNestedObject = (rootObj, nestedPath, maxWaitTime = 10000, interval
}
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 = {
triggerWebhook,
sendErrorResponse,
waitForNestedObject,
checkIfEventisEnabled
checkIfEventisEnabled,
sendMessageSeenStatus
}