diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 965f7f07..3bd871fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,3 +46,14 @@ repos: src/structures/.*| src/config.service.ts| )$ + + - repo: local + hooks: + - id: no-console-log + name: No console.log() calls + language: pygrep + entry: 'console\.log' + exclude: | + (?x)^( + .pre-commit-config.yaml + )$ diff --git a/package.json b/package.json index 2be05e81..3a6c063b 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,11 @@ "lodash": "^4.17.21", "mime-types": "^2.1.27", "mongodb": "^6.3.0", + "nestjs-pino": "^4.1.0", "passport": "^0.6.0", "passport-headerapikey": "^1.2.2", + "pino-http": "^10.2.0", + "pino-pretty": "^11.2.1", "puppeteer": "^21.0.0", "qrcode": "^1.5.1", "qrcode-terminal": "^0.12.0", diff --git a/src/core/SwaggerConfiguratorCore.ts b/src/core/SwaggerConfiguratorCore.ts index f7c633f2..1906cfd0 100644 --- a/src/core/SwaggerConfiguratorCore.ts +++ b/src/core/SwaggerConfiguratorCore.ts @@ -1,13 +1,18 @@ import { INestApplication } from '@nestjs/common'; import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger'; import { DECORATORS } from '@nestjs/swagger/dist/constants'; +import { Logger } from 'nestjs-pino'; import { WhatsappConfigService } from '../config.service'; import { VERSION } from '../version'; import { SwaggerConfigServiceCore } from './config/SwaggerConfigServiceCore'; export class SwaggerConfiguratorCore { - constructor(protected app: INestApplication) {} + protected logger: any; + + constructor(protected app: INestApplication) { + this.logger = app.get(Logger); + } get title() { return 'WAHA - WhatsApp HTTP API'; diff --git a/src/core/abc/session.abc.ts b/src/core/abc/session.abc.ts index 868d7cf6..b4ba999b 100644 --- a/src/core/abc/session.abc.ts +++ b/src/core/abc/session.abc.ts @@ -1,7 +1,8 @@ -import { ConsoleLogger } from '@nestjs/common'; import { GetChatsQuery } from '@waha/structures/chats.dto'; +import { LoggerBuilder } from '@waha/utils/logging'; import { EventEmitter } from 'events'; import * as fs from 'fs'; +import { Logger } from 'pino'; import { MessageId } from 'whatsapp-web.js'; import { @@ -82,7 +83,7 @@ export interface SessionParams { name: string; printQR: boolean; mediaManager: MediaManager; - log: ConsoleLogger; + loggerBuilder: LoggerBuilder; sessionStore: DataStore; proxyConfig?: ProxyConfig; sessionConfig?: SessionConfig; @@ -95,7 +96,8 @@ export abstract class WhatsappSession { public name: string; protected mediaManager: MediaManager; - protected log: ConsoleLogger; + public loggerBuilder: LoggerBuilder; + protected logger: Logger; protected sessionStore: DataStore; protected proxyConfig?: ProxyConfig; public sessionConfig?: SessionConfig; @@ -107,7 +109,7 @@ export abstract class WhatsappSession { public constructor({ name, printQR, - log, + loggerBuilder, sessionStore, proxyConfig, mediaManager, @@ -117,7 +119,8 @@ export abstract class WhatsappSession { this.events = new EventEmitter(); this.name = name; this.proxyConfig = proxyConfig; - this.log = log; + this.loggerBuilder = loggerBuilder; + this.logger = loggerBuilder.child({ name: 'WhatsappSession' }); this.sessionStore = sessionStore; this.mediaManager = mediaManager; this.sessionConfig = sessionConfig; @@ -170,7 +173,7 @@ export abstract class WhatsappSession { } protected isDebugEnabled() { - return this.log.isLevelEnabled('debug'); + return this.logger.isLevelEnabled('debug'); } /** Start the session */ @@ -488,12 +491,12 @@ export abstract class WhatsappSession { return; } if (!qr.raw) { - this.log.error( + this.logger.error( 'QR.raw is not available, can not print it in the console', ); return; } - this.log.log( + this.logger.info( "You can disable QR in console by setting 'WAHA_PRINT_QR=false' in your environment variables.", ); qrcode.generate(qr.raw, { small: true }); diff --git a/src/core/abc/webhooks.abc.ts b/src/core/abc/webhooks.abc.ts index 97526188..73605a21 100644 --- a/src/core/abc/webhooks.abc.ts +++ b/src/core/abc/webhooks.abc.ts @@ -1,15 +1,19 @@ -import { ConsoleLogger } from '@nestjs/common'; +import { LoggerBuilder } from '@waha/utils/logging'; +import { Logger } from 'pino'; import { WebhookConfig } from '../../structures/webhooks.config.dto'; import { WhatsappSession } from './session.abc'; export abstract class WebhookSender { protected url: string; + protected logger: Logger; + constructor( - protected log: ConsoleLogger, + loggerBuilder: LoggerBuilder, protected webhookConfig: WebhookConfig, ) { this.url = webhookConfig.url; + this.logger = loggerBuilder.child({ name: WebhookSender.name }); } abstract send(json); diff --git a/src/core/api/websocket.gateway.core.ts b/src/core/api/websocket.gateway.core.ts index ec70d9b2..74cc4b7c 100644 --- a/src/core/api/websocket.gateway.core.ts +++ b/src/core/api/websocket.gateway.core.ts @@ -1,4 +1,9 @@ -import { BeforeApplicationShutdown, ConsoleLogger } from '@nestjs/common'; +import { + BeforeApplicationShutdown, + ConsoleLogger, + Logger, + LoggerService, +} from '@nestjs/common'; import { sleep } from '@nestjs/terminus/dist/utils'; import { OnGatewayConnection, @@ -8,7 +13,6 @@ import { WebSocketServer, } from '@nestjs/websockets'; import { SessionManager } from '@waha/core/abc/manager.abc'; -import { getLogLevels } from '@waha/helpers'; import { WAHAEvents } from '@waha/structures/enums.dto'; import { HeartbeatJob } from '@waha/utils/HeartbeatJob'; import { WebSocket } from '@waha/utils/ws'; @@ -36,26 +40,25 @@ export class WebsocketGatewayCore private listeners: Map = new Map(); - private log: ConsoleLogger; + private readonly logger: LoggerService; private heartbeat: HeartbeatJob; constructor(private manager: SessionManager) { - const levels = getLogLevels(false); - this.log = new ConsoleLogger('WebsocketGateway', { logLevels: levels }); - this.heartbeat = new HeartbeatJob(this.log, this.HEARTBEAT_INTERVAL); + this.logger = new Logger('WebsocketGateway'); + this.heartbeat = new HeartbeatJob(this.logger, this.HEARTBEAT_INTERVAL); } handleConnection(socket: WebSocket, request: IncomingMessage, ...args): any { const random = Math.random().toString(36).substring(7); const id = `wsc_${random}`; socket.id = id; - this.log.debug(`New client connected: ${request.url}`); + this.logger.debug(`New client connected: ${request.url}`); const params = this.getParams(id, request, socket); if (!params) { return; } const { session, events } = params; - this.log.debug( + this.logger.debug( `Client connected to session: '${session}', events: ${events}, ${id}`, ); this.listeners.set(socket, { session, events }); @@ -65,7 +68,7 @@ export class WebsocketGatewayCore const query = url.parse(request.url, true).query; const session = (query.session as string) || '*'; if (session !== '*') { - this.log.warn( + this.logger.warn( `Only connecting to all sessions is allowed for now, use session=*, ${id}`, ); const error = @@ -79,7 +82,7 @@ export class WebsocketGatewayCore !lodash.isEqual(events, ['*']) && !lodash.isEqual(events, [WAHAEvents.SESSION_STATUS]) ) { - this.log.warn( + this.logger.warn( `Only \'session.status\' event is allowed for now, use events=session.status or events=*, ${id}`, ); const error = @@ -91,12 +94,12 @@ export class WebsocketGatewayCore } handleDisconnect(socket: WebSocket): any { - this.log.debug(`Client disconnected - ${socket.id}`); + this.logger.debug(`Client disconnected - ${socket.id}`); this.listeners.delete(socket); } async beforeApplicationShutdown(signal?: string) { - this.log.log('Shutting down websocket server'); + this.logger.log('Shutting down websocket server'); // Allow pending messages to be sent, it can be even 1ms, just to release the event loop await sleep(100); this.server.clients.forEach((options, client) => { @@ -104,20 +107,20 @@ export class WebsocketGatewayCore }); // Do not turn off heartbeat service here, // it's responsible for terminating the connection that is not alive - this.log.log('Websocket server is down'); + this.logger.log('Websocket server is down'); } afterInit(server: Server) { - this.log.debug('Websocket server initialized'); + this.logger.debug('Websocket server initialized'); this.manager.events.on( WAHAEvents.SESSION_STATUS, this.sendToAll.bind(this), ); - this.log.debug('Subscribed to manager events'); + this.logger.debug('Subscribed to manager events'); - this.log.debug('Starting heartbeat service...'); + this.logger.debug('Starting heartbeat service...'); this.heartbeat.start(server); - this.log.debug('Heartbeat service started'); + this.logger.debug('Heartbeat service started'); } sendToAll(data: any) { @@ -125,7 +128,7 @@ export class WebsocketGatewayCore if (options.events.length === 0) { return; } - this.log.debug('Sending data to client', data); + this.logger.debug('Sending data to client', data); client.send(JSON.stringify(data)); }); } diff --git a/src/core/app.module.core.ts b/src/core/app.module.core.ts index bc4a22d4..221c2529 100644 --- a/src/core/app.module.core.ts +++ b/src/core/app.module.core.ts @@ -6,7 +6,10 @@ import { ServeStaticModule } from '@nestjs/serve-static'; import { TerminusModule } from '@nestjs/terminus'; import { BufferJsonReplacerInterceptor } from '@waha/api/BufferJsonReplacerInterceptor'; import { WebsocketGatewayCore } from '@waha/core/api/websocket.gateway.core'; +import { getPinoLogLevel, getPinoTransport } from '@waha/utils/logging'; +import { LoggerModule } from 'nestjs-pino'; import { join } from 'path'; +import { Logger } from 'pino'; import { AuthController } from '../api/auth.controller'; import { ChatsController } from '../api/chats.controller'; @@ -32,6 +35,14 @@ import { WAHAHealthCheckServiceCore } from './health/WAHAHealthCheckServiceCore' import { SessionManagerCore } from './manager.core'; export const IMPORTS = [ + LoggerModule.forRoot({ + renameContext: 'name', + pinoHttp: { + quietReqLogger: true, + level: getPinoLogLevel(), + transport: getPinoTransport(), + }, + }), ConfigModule.forRoot({ isGlobal: true, }), @@ -105,11 +116,11 @@ const PROVIDERS = [ export class AppModuleCore { constructor(protected config: WhatsappConfigService) {} - static getHttpsOptions() { + static getHttpsOptions(logger: Logger) { return undefined; } - static appReady(app: INestApplication) { + static appReady(app: INestApplication, logger: Logger) { return; } } diff --git a/src/core/config/DashboardConfigServiceCore.ts b/src/core/config/DashboardConfigServiceCore.ts index a8232974..87928dd8 100644 --- a/src/core/config/DashboardConfigServiceCore.ts +++ b/src/core/config/DashboardConfigServiceCore.ts @@ -1,5 +1,6 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { InjectPinoLogger, PinoLogger } from 'nestjs-pino'; import { parseBool } from '../../helpers'; @@ -7,7 +8,11 @@ import { parseBool } from '../../helpers'; export class DashboardConfigServiceCore { public dashboardUri = '/dashboard'; - constructor(protected configService: ConfigService) {} + constructor( + protected configService: ConfigService, + @InjectPinoLogger('DashboardConfigService') + protected logger: PinoLogger, + ) {} get enabled(): boolean { const value = this.configService.get('WAHA_DASHBOARD_ENABLED', 'true'); diff --git a/src/core/config/EngineConfigService.ts b/src/core/config/EngineConfigService.ts index a518d8c5..382ea5fc 100644 --- a/src/core/config/EngineConfigService.ts +++ b/src/core/config/EngineConfigService.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { parseBool } from '../../helpers'; @@ -7,14 +7,18 @@ import { getEngineName } from '../../version'; @Injectable() export class EngineConfigService { - constructor(protected configService: ConfigService) {} + private logger: Logger; + + constructor(protected configService: ConfigService) { + this.logger = new Logger('EngineConfigService'); + } getDefaultEngineName(): WAHAEngine { const value = getEngineName(); if (value in WAHAEngine) { return WAHAEngine[value]; } - console.log( + this.logger.warn( `Unknown WhatsApp default engine WHATSAPP_DEFAULT_ENGINE=${value}. Using WEBJS`, ); return WAHAEngine.WEBJS; diff --git a/src/core/config/SwaggerConfigServiceCore.ts b/src/core/config/SwaggerConfigServiceCore.ts index 1a3791b1..eb12f4e9 100644 --- a/src/core/config/SwaggerConfigServiceCore.ts +++ b/src/core/config/SwaggerConfigServiceCore.ts @@ -1,11 +1,16 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { InjectPinoLogger, PinoLogger } from 'nestjs-pino'; import { parseBool } from '../../helpers'; @Injectable() export class SwaggerConfigServiceCore { - constructor(protected configService: ConfigService) {} + constructor( + protected configService: ConfigService, + @InjectPinoLogger('SwaggerConfigService') + protected logger: PinoLogger, + ) {} get advancedConfigEnabled(): boolean { const value = this.configService.get( diff --git a/src/core/engines/noweb/session.noweb.core.ts b/src/core/engines/noweb/session.noweb.core.ts index 94893185..3b666aa6 100644 --- a/src/core/engines/noweb/session.noweb.core.ts +++ b/src/core/engines/noweb/session.noweb.core.ts @@ -17,9 +17,10 @@ import makeWASocket, { WAMessageKey, } from '@adiwajshing/baileys'; import { isLidUser } from '@adiwajshing/baileys/lib/WABinary/jid-utils'; +import { Logger as BaileysLogger } from '@adiwajshing/baileys/node_modules/pino'; import { UnprocessableEntityException } from '@nestjs/common'; import { NowebInMemoryStore } from '@waha/core/engines/noweb/store/NowebInMemoryStore'; -import { flipObject, getLogLevels, parseBool, splitAt } from '@waha/helpers'; +import { flipObject, parseBool, splitAt } from '@waha/helpers'; import { PairingCodeResponse } from '@waha/structures/auth.dto'; import { GetChatsQuery } from '@waha/structures/chats.dto'; import { ContactQuery, ContactRequest } from '@waha/structures/contacts.dto'; @@ -29,12 +30,10 @@ import { WAMessageAckBody, } from '@waha/structures/webhooks.dto'; import * as Buffer from 'buffer'; -import { request } from 'express'; import { Agent } from 'https'; import * as lodash from 'lodash'; import { toNumber } from 'lodash'; import * as NodeCache from 'node-cache'; -import type { Logger } from 'pino'; import { ChatRequest, @@ -95,7 +94,6 @@ import { } from '../../exceptions'; import { toVcard } from '../../helpers'; import { createAgentProxy } from '../../helpers.proxy'; -import { buildLogger } from '../../manager.core'; import { QR } from '../../QR'; import { NowebAuthFactoryCore } from './NowebAuthFactoryCore'; import { INowebStore } from './store/INowebStore'; @@ -105,8 +103,6 @@ import { extractMediaContent } from './utils'; // eslint-disable-next-line @typescript-eslint/no-var-requires const QRCode = require('qrcode'); -// eslint-disable-next-line @typescript-eslint/no-var-requires -const pino = require('pino'); export const BaileysEvents = { CONNECTION_UPDATE: 'connection.update', @@ -136,7 +132,6 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { storageFactory = new NowebStorageFactoryCore(); private startTimeoutId: null | ReturnType = null; private autoRestartTimeoutId: null | ReturnType = null; - public logger: Logger; private msgRetryCounterCache: NodeCache; get listenConnectionEventsFromTheStart() { @@ -150,12 +145,6 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { public constructor(config) { super(config); this.qr = new QR(); - const levels = getLogLevels(this.sessionConfig?.debug); - const debug = levels.includes('debug'); - const logger = pino({ level: debug ? 'debug' : 'info' }); - this.logger = logger.child({ - session: this.name, - }); // external map to store retry counts of messages when decryption/encryption fails // keep this out of the socket itself, to prevent a message decryption/encryption loop across socket restarts this.msgRetryCounterCache = new NodeCache({ @@ -169,7 +158,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { this.buildClient(); } - getSocketConfig(agent, state) { + getSocketConfig(agent, state): any { const fullSyncEnabled = this.sessionConfig?.noweb?.store?.fullSync || false; const browser = fullSyncEnabled ? Browsers.ubuntu('Desktop') @@ -180,7 +169,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { auth: { creds: state.creds, /** caching makes the store faster to send/recv messages */ - keys: makeCacheableSignalKeyStore(state.keys, this.logger), + keys: makeCacheableSignalKeyStore( + state.keys, + this.logger as unknown as BaileysLogger, + ), }, printQRInTerminal: false, browser: browser, @@ -194,7 +186,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { }; } - async makeSocket() { + async makeSocket(): Promise { const { state, saveCreds } = await this.authFactory.buildAuth( this.sessionStore, this.name, @@ -214,31 +206,32 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { } async connectStore() { - this.log.debug(`Connecting store...`); + this.logger.debug(`Connecting store...`); if (!this.store) { - this.log.debug(`Making a new store...`); - const levels = getLogLevels(this.sessionConfig?.debug); - const log = buildLogger(`NowebStore - ${this.name}`, levels); + this.logger.debug(`Making a new store...`); const storeEnabled = this.sessionConfig?.noweb?.store?.enabled || false; if (storeEnabled) { - this.log.debug('Using NowebPersistentStore'); + this.logger.debug('Using NowebPersistentStore'); const storage = this.storageFactory.createStorage( this.sessionStore, this.name, ); - this.store = new NowebPersistentStore(log, storage); + this.store = new NowebPersistentStore( + this.loggerBuilder.child({ name: NowebPersistentStore.name }), + storage, + ); await this.store.init().catch((err) => { - this.log.error(`Failed to initialize storage or store: ${err}`); + this.logger.error(`Failed to initialize storage or store: ${err}`); this.status = WAHASessionStatus.FAILED; this.end(); throw err; }); } else { - this.log.debug('Using NowebInMemoryStore'); + this.logger.debug('Using NowebInMemoryStore'); this.store = new NowebInMemoryStore(); } } - this.log.debug(`Binding store to socket...`); + this.logger.debug(`Binding store to socket...`); this.store.bind(this.sock.ev, this.sock); } @@ -271,10 +264,10 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { // add random delay to avoid multiple restarts at the same time const shiftSeconds = Math.floor(Math.random() * 30); const delay = this.AUTO_RESTART_AFTER_SECONDS + shiftSeconds; - this.log.debug(`Auto-restart is enabled, after ${delay} seconds`); + this.logger.debug(`Auto-restart is enabled, after ${delay} seconds`); this.autoRestartTimeoutId = setTimeout(() => { this.autoRestartTimeoutId = null; - this.log.log('Auto-restarting the client connection...'); + this.logger.info('Auto-restarting the client connection...'); this.sock?.end(new Error('auto-restart')); }, delay * SECOND); } @@ -291,18 +284,18 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { protected listenEngineEventsInDebugMode() { this.sock.ev.process((events) => { - this.log.debug(`NOWEB events: ${JSON.stringify(events)}`); + this.logger.debug({ events: events }, `NOWEB events`); }); } private restartClient() { if (this.startTimeoutId) { - this.log.log( + this.logger.info( 'Request to restart is already in progress, ignoring this request', ); return; } - this.log.log( + this.logger.info( `Setting up client start in ${this.START_ATTEMPT_DELAY_SECONDS} seconds...`, ); this.startTimeoutId = setTimeout(() => { @@ -312,7 +305,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { } protected listenConnectionEvents() { - this.log.debug(`Start listening ${BaileysEvents.CONNECTION_UPDATE}...`); + this.logger.debug(`Start listening ${BaileysEvents.CONNECTION_UPDATE}...`); this.sock.ev.on('connection.update', async (update) => { const { connection, lastDisconnect, qr, isNewLogin } = update; if (isNewLogin) { @@ -330,12 +323,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { this.qr.save(''); // reconnect if not logged out if (shouldReconnect) { - this.log.log( + this.logger.info( `Connection closed due to '${lastDisconnect.error}', reconnecting...`, ); this.restartClient(); } else { - this.log.error( + this.logger.error( `Connection closed due to '${lastDisconnect.error}', do not reconnect the session.`, ); await this.end(); @@ -405,7 +398,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { const jidPresences = this.store?.presences?.[jid]; const participantPresence = jidPresences?.[participant]; if (participantPresence?.lastKnownPresence === 'composing') { - this.log.debug( + this.logger.debug( `Fixing presence for '${participant}' in '${jid} since it's typing`, ); const presence: PresenceData = { lastKnownPresence: 'available' }; @@ -462,7 +455,7 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { } if (this.status == WAHASessionStatus.STARTING) { - this.log.debug('Waiting for connection update...'); + this.logger.debug('Waiting for connection update...'); await this.sock.waitForConnectionUpdate((update) => !!update.qr); } @@ -471,12 +464,12 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { throw new UnprocessableEntityException(err); } - this.log.log(`Requesting pairing code for '${phoneNumber}'...`); + this.logger.info(`Requesting pairing code for '${phoneNumber}'...`); const code: string = await this.sock.requestPairingCode(phoneNumber); // show it as ABCD-ABCD const parts = splitAt(code, 4); const codeRepr = parts.join('-'); - this.log.log(`Your code: ${codeRepr}`); + this.logger.info(`Your code: ${codeRepr}`); return { code: codeRepr }; } @@ -994,16 +987,16 @@ export class WhatsappSessionNoWebCore extends WhatsappSession { try { message = await this.downloadMedia(message); } catch (e) { - this.log.error('Failed when tried to download media for a message'); - this.log.error(e, e.stack); + this.logger.error('Failed when tried to download media for a message'); + this.logger.error(e, e.stack); } } try { return await this.toWAMessage(message); } catch (error) { - this.log.error('Failed to process incoming message'); - this.log.error(error); + this.logger.error('Failed to process incoming message'); + this.logger.error(error); console.trace(error); return null; } diff --git a/src/core/engines/noweb/store/NowebPersistentStore.ts b/src/core/engines/noweb/store/NowebPersistentStore.ts index a1d7ba17..68a0b9f6 100644 --- a/src/core/engines/noweb/store/NowebPersistentStore.ts +++ b/src/core/engines/noweb/store/NowebPersistentStore.ts @@ -11,6 +11,7 @@ import { } from '@adiwajshing/baileys'; import { ConsoleLogger } from '@nestjs/common'; import { toNumber } from 'lodash'; +import { Logger } from 'pino'; import { toJID } from '../session.noweb.core'; import { IChatRepository } from './IChatRepository'; @@ -31,7 +32,7 @@ export class NowebPersistentStore implements INowebStore { private lock: any; constructor( - private log: ConsoleLogger, + private logger: Logger, public storage: INowebStorage, ) { this.socket = null; @@ -89,7 +90,7 @@ export class NowebPersistentStore implements INowebStore { async close(): Promise { await this.storage?.close().catch((error) => { - this.log.warn(`Failed to close storage: ${error}`); + this.logger.warn(`Failed to close storage: ${error}`); }); return; } @@ -97,7 +98,7 @@ export class NowebPersistentStore implements INowebStore { private async onMessagingHistorySet(history) { const { contacts, chats, messages, isLatest } = history; if (isLatest) { - this.log.debug( + this.logger.debug( 'history sync - clearing all entities, got latest history', ); await Promise.all([ @@ -110,7 +111,7 @@ export class NowebPersistentStore implements INowebStore { await Promise.all([ this.withLock('contacts', async () => { await this.onContactsUpsert(contacts); - this.log.log(`history sync - '${contacts.length}' synced contacts`); + this.logger.info(`history sync - '${contacts.length}' synced contacts`); }), this.withLock('chats', () => this.onChatUpsert(chats)), this.withLock('messages', () => this.syncMessagesHistory(messages)), @@ -120,7 +121,7 @@ export class NowebPersistentStore implements INowebStore { private async syncMessagesHistory(messages) { const realMessages = messages.filter(isRealMessage); await this.messagesRepo.upsert(realMessages); - this.log.log( + this.logger.info( `history sync - '${messages.length}' got messages, '${realMessages.length}' real messages`, ); } @@ -128,12 +129,12 @@ export class NowebPersistentStore implements INowebStore { private async onMessagesUpsert(update) { const { messages, type } = update; if (type !== 'notify' && type !== 'append') { - this.log.debug(`unexpected type for messages.upsert: '${type}'`); + this.logger.debug(`unexpected type for messages.upsert: '${type}'`); return; } const realMessages = messages.filter(isRealMessage); await this.messagesRepo.upsert(realMessages); - this.log.debug( + this.logger.debug( `messages.upsert - ${messages.length} got messages, ${realMessages.length} real messages`, ); } @@ -178,7 +179,7 @@ export class NowebPersistentStore implements INowebStore { chat.conversationTimestamp = toNumber(chat.conversationTimestamp); await this.chatRepo.save(chat); } - this.log.log(`history sync - '${chats.length}' synced chats`); + this.logger.info(`history sync - '${chats.length}' synced chats`); } private async onChatUpdate(updates: ChatUpdate[]) { @@ -219,7 +220,7 @@ export class NowebPersistentStore implements INowebStore { const contact = await this.contactRepo.getById(update.id); if (!contact) { - this.log.warn( + this.logger.warn( `got update for non-existent contact. update: '${JSON.stringify( update, )}'`, @@ -246,7 +247,7 @@ export class NowebPersistentStore implements INowebStore { for (const { key, reaction } of reactions) { const msg = await this.messagesRepo.getByJidById(key.remoteJid, key.id); if (!msg) { - this.log.warn( + this.logger.warn( `got reaction update for non-existent message. key: '${JSON.stringify( key, )}'`, @@ -262,7 +263,7 @@ export class NowebPersistentStore implements INowebStore { for (const { key, receipt } of updates) { const msg = await this.messagesRepo.getByJidById(key.remoteJid, key.id); if (!msg) { - this.log.warn( + this.logger.warn( `got receipt update for non-existent message. key: '${JSON.stringify( key, )}'`, diff --git a/src/core/engines/venom/session.venom.core.ts b/src/core/engines/venom/session.venom.core.ts index 9b4b088b..4b950806 100644 --- a/src/core/engines/venom/session.venom.core.ts +++ b/src/core/engines/venom/session.venom.core.ts @@ -69,7 +69,7 @@ export class WhatsappSessionVenomCore extends WhatsappSession { this.qr.save(base64Qrimg, urlCode); this.printQR(this.qr); this.status = WAHASessionStatus.SCAN_QR_CODE; - this.log.debug('Number of attempts to read the qrcode: ', attempts); + this.logger.debug('Number of attempts to read the qrcode: ', attempts); }; } @@ -79,7 +79,7 @@ export class WhatsappSessionVenomCore extends WhatsappSession { this.whatsapp = await this.buildClient(); } catch (error) { this.status = WAHASessionStatus.FAILED; - this.log.error(error); + this.logger.error(error); this.qr.save(''); return; } diff --git a/src/core/engines/webjs/session.webjs.core.ts b/src/core/engines/webjs/session.webjs.core.ts index 6ba4c842..4ce59e6d 100644 --- a/src/core/engines/webjs/session.webjs.core.ts +++ b/src/core/engines/webjs/session.webjs.core.ts @@ -116,17 +116,19 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { private restartClient() { if (!this.shouldRestart) { - this.log.debug("Shouldn't restart the client, ignoring restart request"); + this.logger.debug( + "Shouldn't restart the client, ignoring restart request", + ); return; } if (this.startTimeoutId) { const msg = 'Request to restart is already in progress, ignoring restart request'; - this.log.warn(msg); + this.logger.warn(msg); return; } - this.log.log( + this.logger.info( `Setting up client start in ${this.START_ATTEMPT_DELAY_SECONDS} seconds...`, ); this.startTimeoutId = setTimeout(() => { @@ -162,30 +164,30 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { // Listen for browser disconnected event this.whatsapp.pupBrowser.on('disconnected', () => { this.status = WAHASessionStatus.FAILED; - this.log.error('The browser has been disconnected'); + this.logger.error('The browser has been disconnected'); this.restartClient(); }); // Listen for page close event this.whatsapp.pupPage.on('close', () => { this.status = WAHASessionStatus.FAILED; - this.log.error('The WhatsApp Web page has been closed'); + this.logger.error('The WhatsApp Web page has been closed'); this.restartClient(); }); // Listen for page error event if (this.isDebugEnabled()) { - this.log.debug("Logging 'console' event for web page"); + this.logger.debug("Logging 'console' event for web page"); this.whatsapp.pupPage.on('console', (msg) => - this.log.debug(`WEBJS page log: ${msg.text()}`), + this.logger.debug(`WEBJS page log: ${msg.text()}`), ); this.whatsapp.pupPage.evaluate(() => - console.log(`url is ${location.href}`), + this.logger.info(`url is ${location.href}`), ); } }) .catch((error) => { this.status = WAHASessionStatus.FAILED; - this.log.error(error); + this.logger.error(error); this.restartClient(); return; }); @@ -214,10 +216,10 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { this.whatsapp?.removeAllListeners(); clearInterval(this.startTimeoutId); this.whatsapp?.destroy().catch((error) => { - this.log.debug('Failed to destroy the client', error); + this.logger.debug('Failed to destroy the client', error); }); } catch (error) { - this.log.error(error); + this.logger.error(error); } } @@ -240,14 +242,14 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { const event = Events[key]; this.whatsapp.on(event, (...data: any[]) => { const log = { event: event, data: data }; - this.log.debug(`WEBJS event: ${JSON.stringify(log)}`); + this.logger.debug({ event: log }, `WEBJS event`); }); } } protected listenConnectionEvents() { this.whatsapp.on(Events.QR_RECEIVED, async (qr) => { - this.log.debug('QR received'); + this.logger.debug('QR received'); // Convert to image and save const url = await QRCode.toDataURL(qr); this.qr.save(url, qr); @@ -258,25 +260,25 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { this.whatsapp.on(Events.READY, () => { this.status = WAHASessionStatus.WORKING; this.qr.save(''); - this.log.log(`Session '${this.name}' has been authenticated!`); + this.logger.info(`Session '${this.name}' has been authenticated!`); }); this.whatsapp.on(Events.AUTHENTICATED, () => { this.status = WAHASessionStatus.WORKING; this.qr.save(''); - this.log.log(`Session '${this.name}' has been authenticated!`); + this.logger.info(`Session '${this.name}' has been authenticated!`); }); this.whatsapp.on(Events.AUTHENTICATION_FAILURE, () => { this.status = WAHASessionStatus.FAILED; this.qr.save(''); - this.log.log(`Session '${this.name}' has been disconnected!`); + this.logger.info(`Session '${this.name}' has been disconnected!`); }); this.whatsapp.on(Events.DISCONNECTED, () => { this.status = WAHASessionStatus.FAILED; this.qr.save(''); - this.log.log(`Session '${this.name}' has been disconnected!`); + this.logger.info(`Session '${this.name}' has been disconnected!`); }); } @@ -705,8 +707,8 @@ export class WhatsappSessionWebJSCore extends WhatsappSession { try { message = await this.downloadMedia(message); } catch (e) { - this.log.error('Failed when tried to download media for a message'); - this.log.error(e, e.stack); + this.logger.error('Failed when tried to download media for a message'); + this.logger.error(e, e.stack); } } return await this.toWAMessage(message); diff --git a/src/core/manager.core.ts b/src/core/manager.core.ts index f05af33b..54ceae6d 100644 --- a/src/core/manager.core.ts +++ b/src/core/manager.core.ts @@ -1,16 +1,16 @@ import { ConsoleLogger, Injectable, - LoggerService, LogLevel, NotFoundException, UnprocessableEntityException, } from '@nestjs/common'; +import { getPinoLogLevel, LoggerBuilder } from '@waha/utils/logging'; import { promiseTimeout } from '@waha/utils/promiseTimeout'; import { EventEmitter } from 'events'; +import { PinoLogger } from 'nestjs-pino'; import { WhatsappConfigService } from '../config.service'; -import { getLogLevels } from '../helpers'; import { WAHAEngine, WAHAEvents, @@ -47,10 +47,6 @@ export class OnlyDefaultSessionIsAllowed extends UnprocessableEntityException { } } -export function buildLogger(name: string, levels: LogLevel[]) { - return new ConsoleLogger(name, { logLevels: levels }); -} - @Injectable() export class SessionManagerCore extends SessionManager { private session?: WhatsappSession; @@ -59,16 +55,15 @@ export class SessionManagerCore extends SessionManager { // @ts-ignore protected WebhookConductorClass = WebhookConductorCore; protected readonly EngineClass: typeof WhatsappSession; - private log: LoggerService; constructor( private config: WhatsappConfigService, private engineConfigService: EngineConfigService, + private log: PinoLogger, ) { super(); this.events = new EventEmitter(); - const levels = getLogLevels(false); - this.log = buildLogger('SessionManager', levels); + this.log.setContext(SessionManagerCore.name); this.session = undefined; const engineName = this.engineConfigService.getDefaultEngineName(); this.EngineClass = this.getEngine(engineName); @@ -122,20 +117,21 @@ export class SessionManagerCore extends SessionManager { } const name = request.name; - this.log.log(`'${name}' - starting session...`); - const levels = getLogLevels(request.config?.debug); - const log = buildLogger(`WhatsappSession - ${name}`, levels); + this.log.info(`'${name}' - starting session...`); const mediaManager = new CoreMediaManager( new MediaStorageCore(), this.config.mimetypes, ); - const webhookLog = buildLogger(`Webhook - ${name}`, levels); - const webhook = new this.WebhookConductorClass(webhookLog); + const logger = this.log.logger.child({ session: name }); + logger.level = getPinoLogLevel(request.config?.debug); + const loggerBuilder: LoggerBuilder = logger; + + const webhook = new this.WebhookConductorClass(loggerBuilder); const proxyConfig = this.getProxyConfig(request); const sessionConfig: SessionParams = { name, mediaManager, - log, + loggerBuilder, printQR: this.engineConfigService.shouldPrintQR, sessionStore: this.store, proxyConfig: proxyConfig, @@ -200,10 +196,10 @@ export class SessionManagerCore extends SessionManager { this.onlyDefault(request.name); const name = request.name; - this.log.log(`Stopping ${name} session...`); + this.log.info(`Stopping ${name} session...`); const session = this.getSession(name); await session.stop(); - this.log.log(`"${name}" has been stopped.`); + this.log.info(`"${name}" has been stopped.`); this.session = undefined; } @@ -212,7 +208,7 @@ export class SessionManagerCore extends SessionManager { this.onlyDefault(request.name); this.stop({ name: name, logout: false }) .then(() => { - this.log.log(`Session '${name}' has been stopped.`); + this.log.info(`Session '${name}' has been stopped.`); }) .catch((err) => { this.log.error( diff --git a/src/core/webhooks.core.ts b/src/core/webhooks.core.ts index eaa7e84a..5e5599e6 100644 --- a/src/core/webhooks.core.ts +++ b/src/core/webhooks.core.ts @@ -7,30 +7,36 @@ import { VERSION } from '../version'; import { WAHAInternalEvent, WhatsappSession } from './abc/session.abc'; import { WebhookConductor, WebhookSender } from './abc/webhooks.abc'; import request = require('request'); +import { LoggerBuilder } from '@waha/utils/logging'; +import { Logger } from 'pino'; export class WebhookSenderCore extends WebhookSender { send(json) { - this.log.log(`Sending POST to ${this.url}...`); - this.log.verbose(`POST DATA: ${JSON.stringify(json)}`); + this.logger.info({ url: this.url }, `Sending POST ...`); + this.logger.debug({ data: json }, `POST DATA`); request.post(this.url, { json: json }, (error, res, body) => { if (error) { - this.log.error(error); + this.logger.error(error); return; } - this.log.log(`POST request was sent with status code: ${res.statusCode}`); - this.log.debug(`Response: ${body}`); + this.logger.info( + `POST request was sent with status code: ${res.statusCode}`, + ); + this.logger.debug({ body: body }, `Response`); }); } } export class WebhookConductorCore implements WebhookConductor { - constructor(protected log: ConsoleLogger) { - this.log = log; + private logger: Logger; + + constructor(protected loggerBuilder: LoggerBuilder) { + this.logger = loggerBuilder.child({ name: WebhookConductor.name }); } protected buildSender(webhookConfig: WebhookConfig): WebhookSender { - return new WebhookSenderCore(this.log, webhookConfig); + return new WebhookSenderCore(this.loggerBuilder, webhookConfig); } private getSuitableEvents(events: WAHAEvents[] | string[]): WAHAEvents[] { @@ -47,7 +53,7 @@ export class WebhookConductorCore implements WebhookConductor { for (const event of events) { // @ts-ignore if (!allEvents.includes(event)) { - this.log.error(`Unknown event for webhook: '${event}'`); + this.logger.error(`Unknown event for webhook: '${event}'`); continue; } rightEvents.push(event); @@ -70,7 +76,7 @@ export class WebhookConductorCore implements WebhookConductor { } const url = webhook.url; - this.log.log(`Configuring webhooks for ${url}...`); + this.logger.info(`Configuring webhooks for ${url}...`); const events = this.getSuitableEvents(webhook.events); const sender = this.buildSender(webhook); for (const event of events) { @@ -93,14 +99,14 @@ export class WebhookConductorCore implements WebhookConductor { url, ); if (!found) { - this.log.error( + this.logger.error( `Engine does not support webhook event: '${event}' for url '${url}'`, ); } }); } } - this.log.log(`Webhooks were configured for ${url}.`); + this.logger.info(`Webhooks were configured for ${url}.`); } private configureSingleEvent( @@ -117,7 +123,7 @@ export class WebhookConductorCore implements WebhookConductor { if (!found) { return false; } - this.log.log(`Event '${event}' is enabled for url: ${url}`); + this.logger.info(`Event '${event}' is enabled for url: ${url}`); return true; } diff --git a/src/helpers.ts b/src/helpers.ts index 1a8c60e4..8b9136e2 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -1,5 +1,3 @@ -import { LogLevel } from '@nestjs/common'; - function parseBool(value: any): boolean { if (typeof value === 'boolean') { return value; @@ -37,11 +35,4 @@ function splitAt(str: string, index) { return [fst.join(''), snd.join('')]; } -function getLogLevels(debug: boolean): LogLevel[] { - const enableDebug = process.env.DEBUG != undefined || debug; - return enableDebug - ? ['log', 'error', 'warn', 'debug', 'verbose'] - : ['log', 'error', 'warn']; -} - -export { flipObject, getLogLevels, parseBool, splitAt }; +export { flipObject, parseBool, splitAt }; diff --git a/src/main.ts b/src/main.ts index 24245494..9f8d0636 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,27 +1,39 @@ import { NestFactory } from '@nestjs/core'; import { WsAdapter } from '@nestjs/platform-ws'; +import { + getNestJSLogLevels, + getPinoLogLevel, + getPinoTransport, +} from '@waha/utils/logging'; import { json, urlencoded } from 'express'; +import { Logger as NestJSPinoLogger } from 'nestjs-pino'; +import { Logger } from 'pino'; +import pino from 'pino'; import { AllExceptionsFilter } from './api/exception.filter'; import { WhatsappConfigService } from './config.service'; import { AppModuleCore } from './core/app.module.core'; import { SwaggerConfiguratorCore } from './core/SwaggerConfiguratorCore'; -import { getLogLevels } from './helpers'; import { WAHA_WEBHOOKS } from './structures/webhooks.dto'; import { getWAHAVersion, VERSION, WAHAVersion } from './version'; -console.log('NODE - Catching unhandled exceptions enabled'); +const logger: Logger = pino({ + level: getPinoLogLevel(), + transport: getPinoTransport(), +}).child({ name: 'Bootstrap' }); + +logger.info('NODE - Catching unhandled exceptions enabled'); process.on('unhandledRejection', (reason, promise) => { - console.error('Unhandled Rejection at:', promise, 'reason:', reason); + logger.error('Unhandled Rejection at:', promise, 'reason:', reason); // @ts-ignore - console.error(reason.stack); + logger.error(reason.stack); }); async function loadModules(): Promise< [typeof AppModuleCore, typeof SwaggerConfiguratorCore] > { const version = getWAHAVersion(); - console.log(`WAHA (WhatsApp HTTP API) - Running ${version} version...`); + logger.info(`WAHA (WhatsApp HTTP API) - Running ${version} version...`); if (version === WAHAVersion.CORE) { const { AppModuleCore } = await import('./core/app.module.core'); @@ -41,12 +53,13 @@ async function loadModules(): Promise< async function bootstrap() { const [AppModule, SwaggerModule] = await loadModules(); - const httpsOptions = AppModule.getHttpsOptions(); + const httpsOptions = AppModule.getHttpsOptions(logger); const app = await NestFactory.create(AppModule, { - logger: getLogLevels(false), + logger: getNestJSLogLevels(), httpsOptions: httpsOptions, + bufferLogs: true, }); - + app.useLogger(app.get(NestJSPinoLogger)); app.enableShutdownHooks(); app.useGlobalFilters(new AllExceptionsFilter()); app.enableCors(); @@ -63,11 +76,11 @@ async function bootstrap() { const swaggerConfigurator = new SwaggerModule(app); swaggerConfigurator.configure(WAHA_WEBHOOKS); - AppModule.appReady(app); + AppModule.appReady(app, logger); const config = app.get(WhatsappConfigService); await app.listen(config.port); - console.log(`WhatsApp HTTP API is running on: ${await app.getUrl()}`); - console.log(`Environment:`, VERSION); + logger.info(`WhatsApp HTTP API is running on: ${await app.getUrl()}`); + logger.info(VERSION, 'Environment'); } bootstrap(); diff --git a/src/utils/HeartbeatJob.ts b/src/utils/HeartbeatJob.ts index eb87f7a2..7b1dee0f 100644 --- a/src/utils/HeartbeatJob.ts +++ b/src/utils/HeartbeatJob.ts @@ -1,4 +1,4 @@ -import { ConsoleLogger } from '@nestjs/common'; +import { LoggerService } from '@nestjs/common'; import { WebSocket } from '@waha/utils/ws'; import { WebSocketServer } from 'ws'; @@ -6,7 +6,7 @@ export class HeartbeatJob { private interval: ReturnType; constructor( - private log: ConsoleLogger, + private logger: LoggerService, private intervalTime: number = 10_000, ) {} @@ -19,14 +19,14 @@ export class HeartbeatJob { this.interval = setInterval(() => { server.clients.forEach((client: WebSocket) => { if (client.isAlive === false) { - this.log.debug( + this.logger.debug( `Terminating client connection due to heartbeat timeout, ${client.id}`, ); return client.terminate(); } client.isAlive = false; - this.log.debug(`Sending heartbeat (ping) to ${client.id}`); + this.logger.debug(`Sending heartbeat (ping) to ${client.id}`); client.ping(); }); }, this.intervalTime); @@ -39,7 +39,7 @@ export class HeartbeatJob { private onPong(ws: WebSocket) { return (event: any) => { ws.isAlive = true; - this.log.debug(`Heartbeat (pong) received from ${ws.id}`); + this.logger.debug(`Heartbeat (pong) received from ${ws.id}`); }; } } diff --git a/src/utils/logging.ts b/src/utils/logging.ts new file mode 100644 index 00000000..66e350b0 --- /dev/null +++ b/src/utils/logging.ts @@ -0,0 +1,65 @@ +import { LogLevel } from '@nestjs/common'; +import { ChildLoggerOptions, Level, Logger } from 'pino'; + +export interface LoggerBuilder { + child(bindings: Record, options?: ChildLoggerOptions): Logger; +} + +function getNestJSLogLevels(): LogLevel[] { + const level = getPinoLogLevel(); + switch (level) { + case 'trace': + return ['error', 'warn', 'log', 'debug', 'verbose']; + case 'debug': + return ['error', 'warn', 'log', 'debug']; + case 'info': + return ['error', 'warn', 'log']; + case 'warn': + return ['error', 'warn']; + case 'error': + return ['error', 'warn']; + default: + return ['error', 'warn', 'log']; + } +} + +export function getPinoLogLevel(debug: boolean = false): Level { + const enableDebug = process.env.DEBUG != undefined || debug; + if (enableDebug) { + return 'debug'; + } + return getDefaultPinoLogLevel(); +} + +export function getDefaultPinoLogLevel(): Level { + const logLevel = (process.env.WAHA_LOG_LEVEL || 'info').toLowerCase(); + const levels = ['fatal', 'error', 'warn', 'info', 'debug', 'trace']; + if (!levels.includes(logLevel)) { + console.error( + `Unknown ${process.env.WAHA_LOG_LEVEL}' value for WAHA_LOG_LEVEL`, + ); + return 'info'; + } + return logLevel as Level; +} + +export function getPinoTransport() { + const logFormat = (process.env.WAHA_LOG_FORMAT || '').toUpperCase(); + if (!logFormat) { + return; + } + if (logFormat == 'JSON') { + return undefined; + } + if (logFormat == 'PRETTY') { + return { + target: 'pino-pretty', + options: { + singleLine: true, + colorize: true, + }, + }; + } +} + +export { getNestJSLogLevels }; diff --git a/yarn.lock b/yarn.lock index f5a161ca..25bab449 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4215,6 +4215,13 @@ __metadata: languageName: node linkType: hard +"colorette@npm:^2.0.7": + version: 2.0.20 + resolution: "colorette@npm:2.0.20" + checksum: 0c016fea2b91b733eb9f4bcdb580018f52c0bc0979443dad930e5037a968237ac53d9beb98e218d2e9235834f8eebce7f8e080422d6194e957454255bde71d3d + languageName: node + linkType: hard + "colorspace@npm:1.1.x": version: 1.1.4 resolution: "colorspace@npm:1.1.4" @@ -4641,6 +4648,13 @@ __metadata: languageName: node linkType: hard +"dateformat@npm:^4.6.3": + version: 4.6.3 + resolution: "dateformat@npm:4.6.3" + checksum: c3aa0617c0a5b30595122bc8d1bee6276a9221e4d392087b41cbbdf175d9662ae0e50d0d6dcdf45caeac5153c4b5b0844265f8cd2b2245451e3da19e39e3b65d + languageName: node + linkType: hard + "debug@npm:2.6.9, debug@npm:^2.6.9": version: 2.6.9 resolution: "debug@npm:2.6.9" @@ -5621,7 +5635,7 @@ __metadata: languageName: node linkType: hard -"events@npm:^3.2.0": +"events@npm:^3.2.0, events@npm:^3.3.0": version: 3.3.0 resolution: "events@npm:3.3.0" checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780 @@ -5802,6 +5816,13 @@ __metadata: languageName: node linkType: hard +"fast-copy@npm:^3.0.2": + version: 3.0.2 + resolution: "fast-copy@npm:3.0.2" + checksum: 47f584bcede08ab3198559d3e0e093a547d567715b86be2198da6e3366c3c73eed550d97b86f9fb90dae179982b89c15d68187def960f522cdce14bacdfc6184 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -5843,14 +5864,14 @@ __metadata: languageName: node linkType: hard -"fast-redact@npm:^3.0.0": +"fast-redact@npm:^3.0.0, fast-redact@npm:^3.1.1": version: 3.5.0 resolution: "fast-redact@npm:3.5.0" checksum: ef03f0d1849da074a520a531ad299bf346417b790a643931ab4e01cb72275c8d55b60dc8512fb1f1818647b696790edefaa96704228db9f012da935faa1940af languageName: node linkType: hard -"fast-safe-stringify@npm:2.1.1": +"fast-safe-stringify@npm:2.1.1, fast-safe-stringify@npm:^2.1.1": version: 2.1.1 resolution: "fast-safe-stringify@npm:2.1.1" checksum: a851cbddc451745662f8f00ddb622d6766f9bd97642dabfd9a405fb0d646d69fc0b9a1243cbf67f5f18a39f40f6fa821737651ff1bceeba06c9992ca2dc5bd3d @@ -6695,6 +6716,13 @@ __metadata: languageName: node linkType: hard +"help-me@npm:^5.0.0": + version: 5.0.0 + resolution: "help-me@npm:5.0.0" + checksum: 474436627b6c7d2f406a2768453895889eb2712c8ded4c47658d5c6dd46c2ff3f742be4e4e8dedd57b7f1ac6b28803896a2e026a32a977f507222c16f23ab2e1 + languageName: node + linkType: hard + "homedir-polyfill@npm:^1.0.1": version: 1.0.3 resolution: "homedir-polyfill@npm:1.0.3" @@ -7965,6 +7993,13 @@ __metadata: languageName: node linkType: hard +"joycon@npm:^3.1.1": + version: 3.1.1 + resolution: "joycon@npm:3.1.1" + checksum: 8003c9c3fc79c5c7602b1c7e9f7a2df2e9916f046b0dbad862aa589be78c15734d11beb9fe846f5e06138df22cb2ad29961b6a986ba81c4920ce2b15a7f11067 + languageName: node + linkType: hard + "js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -9151,6 +9186,16 @@ __metadata: languageName: node linkType: hard +"nestjs-pino@npm:^4.1.0": + version: 4.1.0 + resolution: "nestjs-pino@npm:4.1.0" + peerDependencies: + "@nestjs/common": ^8.0.0 || ^9.0.0 || ^10.0.0 + pino-http: ^6.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + checksum: 83e4bee5934447edfb60ec4eeed5fb5529f0e4a12a4498920ff7de6277152ceb0b37127637f447e60faaaf8c2ef9e3a2f9f6f51c1e936e50246e485d36972639 + languageName: node + linkType: hard + "netmask@npm:^2.0.2": version: 2.0.2 resolution: "netmask@npm:2.0.2" @@ -9419,6 +9464,13 @@ __metadata: languageName: node linkType: hard +"on-exit-leak-free@npm:^2.1.0": + version: 2.1.2 + resolution: "on-exit-leak-free@npm:2.1.2" + checksum: 6ce7acdc7b9ceb51cf029b5239cbf41937ee4c8dcd9d4e475e1777b41702564d46caa1150a744e00da0ac6d923ab83471646a39a4470f97481cf6e2d8d253c3f + languageName: node + linkType: hard + "on-finished@npm:2.4.1": version: 2.4.1 resolution: "on-finished@npm:2.4.1" @@ -9814,6 +9866,16 @@ __metadata: languageName: node linkType: hard +"pino-abstract-transport@npm:^1.0.0, pino-abstract-transport@npm:^1.2.0": + version: 1.2.0 + resolution: "pino-abstract-transport@npm:1.2.0" + dependencies: + readable-stream: ^4.0.0 + split2: ^4.0.0 + checksum: 3336c51fb91ced5ef8a4bfd70a96e41eb6deb905698e83350dc71eedffb34795db1286d2d992ce1da2f6cd330a68be3f7e2748775a6b8a2ee3416796070238d6 + languageName: node + linkType: hard + "pino-abstract-transport@npm:v0.5.0": version: 0.5.0 resolution: "pino-abstract-transport@npm:0.5.0" @@ -9824,6 +9886,42 @@ __metadata: languageName: node linkType: hard +"pino-http@npm:^10.2.0": + version: 10.2.0 + resolution: "pino-http@npm:10.2.0" + dependencies: + get-caller-file: ^2.0.5 + pino: ^9.0.0 + pino-std-serializers: ^7.0.0 + process-warning: ^3.0.0 + checksum: 1d7cc08409872f5dbb8405b92ce3252c82ff37edbb1988d65d2aab049df410203040f8bd236853103b27f88f48c9167e6a55fc45d88bf96952cbcb901359fb1f + languageName: node + linkType: hard + +"pino-pretty@npm:^11.2.1": + version: 11.2.1 + resolution: "pino-pretty@npm:11.2.1" + dependencies: + colorette: ^2.0.7 + dateformat: ^4.6.3 + fast-copy: ^3.0.2 + fast-safe-stringify: ^2.1.1 + help-me: ^5.0.0 + joycon: ^3.1.1 + minimist: ^1.2.6 + on-exit-leak-free: ^2.1.0 + pino-abstract-transport: ^1.0.0 + pump: ^3.0.0 + readable-stream: ^4.0.0 + secure-json-parse: ^2.4.0 + sonic-boom: ^4.0.1 + strip-json-comments: ^3.1.1 + bin: + pino-pretty: bin.js + checksum: ae9754484809d4485733a9badca339bad1f9e07d106549c4977e2557bf9183d775496866c786aee701d260abadf16f0bf201084943ebe16fa93b9ad80ba3a410 + languageName: node + linkType: hard + "pino-std-serializers@npm:^4.0.0": version: 4.0.0 resolution: "pino-std-serializers@npm:4.0.0" @@ -9831,6 +9929,13 @@ __metadata: languageName: node linkType: hard +"pino-std-serializers@npm:^7.0.0": + version: 7.0.0 + resolution: "pino-std-serializers@npm:7.0.0" + checksum: 08cd1d7b7adc4cfca39e42c2d5fd21bcf4513153734e7b8fa278b0e9e9f62df78c4c202886343fe882a462539c931cb8110b661775ad7f7217c96856795b5a86 + languageName: node + linkType: hard + "pino@npm:^7.0.0": version: 7.11.0 resolution: "pino@npm:7.11.0" @@ -9852,6 +9957,27 @@ __metadata: languageName: node linkType: hard +"pino@npm:^9.0.0": + version: 9.2.0 + resolution: "pino@npm:9.2.0" + dependencies: + atomic-sleep: ^1.0.0 + fast-redact: ^3.1.1 + on-exit-leak-free: ^2.1.0 + pino-abstract-transport: ^1.2.0 + pino-std-serializers: ^7.0.0 + process-warning: ^3.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 + checksum: d702a3f6ff031afc4b165fc3b514e35bd1864d691f27789225a165cd5d344acf08f21ceee4b7d55d522ede57ac9bcb661ce161d8e1510acbc1bfd32b0f56fe39 + languageName: node + linkType: hard + "pirates@npm:^4.0.4": version: 4.0.6 resolution: "pirates@npm:4.0.6" @@ -9992,6 +10118,20 @@ __metadata: languageName: node linkType: hard +"process-warning@npm:^3.0.0": + version: 3.0.0 + resolution: "process-warning@npm:3.0.0" + checksum: 1fc2eb4524041de3c18423334cc8b4e36bec5ad5472640ca1a936122c6e01da0864c1a4025858ef89aea93eabe7e77db93ccea225b10858617821cb6a8719efe + languageName: node + linkType: hard + +"process@npm:^0.11.10": + version: 0.11.10 + resolution: "process@npm:0.11.10" + checksum: bfcce49814f7d172a6e6a14d5fa3ac92cc3d0c3b9feb1279774708a719e19acd673995226351a082a9ae99978254e320ccda4240ddc474ba31a76c79491ca7c3 + languageName: node + linkType: hard + "progress@npm:2.0.3, progress@npm:^2.0.0": version: 2.0.3 resolution: "progress@npm:2.0.3" @@ -10523,6 +10663,19 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^4.0.0": + version: 4.5.2 + resolution: "readable-stream@npm:4.5.2" + dependencies: + abort-controller: ^3.0.0 + buffer: ^6.0.3 + events: ^3.3.0 + process: ^0.11.10 + string_decoder: ^1.3.0 + checksum: c4030ccff010b83e4f33289c535f7830190773e274b3fcb6e2541475070bdfd69c98001c3b0cb78763fc00c8b62f514d96c2b10a8bd35d5ce45203a25fa1d33a + languageName: node + linkType: hard + "readable-web-to-node-stream@npm:^3.0.0": version: 3.0.2 resolution: "readable-web-to-node-stream@npm:3.0.2" @@ -10557,6 +10710,13 @@ __metadata: languageName: node linkType: hard +"real-require@npm:^0.2.0": + version: 0.2.0 + resolution: "real-require@npm:0.2.0" + checksum: fa060f19f2f447adf678d1376928c76379dce5f72bd334da301685ca6cdcb7b11356813332cc243c88470796bc2e2b1e2917fc10df9143dd93c2ea608694971d + languageName: node + linkType: hard + "rechoir@npm:^0.6.2": version: 0.6.2 resolution: "rechoir@npm:0.6.2" @@ -10930,6 +11090,13 @@ __metadata: languageName: node linkType: hard +"secure-json-parse@npm:^2.4.0": + version: 2.7.0 + resolution: "secure-json-parse@npm:2.7.0" + checksum: d9d7d5a01fc6db6115744ba23cf9e67ecfe8c524d771537c062ee05ad5c11b64c730bc58c7f33f60bd6877f96b86f0ceb9ea29644e4040cb757f6912d4dd6737 + languageName: node + linkType: hard + "semver@npm:^6.0.0, semver@npm:^6.2.0, semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" @@ -11308,6 +11475,15 @@ __metadata: languageName: node linkType: hard +"sonic-boom@npm:^4.0.1": + version: 4.0.1 + resolution: "sonic-boom@npm:4.0.1" + dependencies: + atomic-sleep: ^1.0.0 + checksum: 451b0f09bc0a0abfa6bfed0e2d7d36a6fa245be8a444a7ef1e3c8abb006e9994cb7530b1da39c8aee9033598d1ce187e244a6194c92a81790a2e2633c60cd63d + languageName: node + linkType: hard + "source-map-support@npm:0.5.13": version: 0.5.13 resolution: "source-map-support@npm:0.5.13" @@ -11551,7 +11727,7 @@ __metadata: languageName: node linkType: hard -"string_decoder@npm:^1.1.1": +"string_decoder@npm:^1.1.1, string_decoder@npm:^1.3.0": version: 1.3.0 resolution: "string_decoder@npm:1.3.0" dependencies: @@ -11908,6 +12084,15 @@ __metadata: languageName: node linkType: hard +"thread-stream@npm:^3.0.0": + version: 3.1.0 + resolution: "thread-stream@npm:3.1.0" + dependencies: + real-require: ^0.2.0 + checksum: 3c5b494ce776f832dfd696792cc865f78c1e850db93e07979349bbc1a5845857cd447aea95808892906cc0178a2fd3233907329f3376e7fc9951e2833f5b7896 + languageName: node + linkType: hard + "through@npm:^2.3.6, through@npm:^2.3.8": version: 2.3.8 resolution: "through@npm:2.3.8" @@ -12722,8 +12907,11 @@ __metadata: lodash: ^4.17.21 mime-types: ^2.1.27 mongodb: ^6.3.0 + nestjs-pino: ^4.1.0 passport: ^0.6.0 passport-headerapikey: ^1.2.2 + pino-http: ^10.2.0 + pino-pretty: ^11.2.1 prettier: ^1.19.1 puppeteer: ^21.0.0 qrcode: ^1.5.1