11import promClient from 'prom-client' ;
22import { MongoClient , MongoClientOptions } from 'mongodb' ;
3+ import { Effect , sgr } from '../utils/ansi' ;
34
45/**
56 * MongoDB command duration histogram
@@ -113,12 +114,155 @@ export function withMongoMetrics(options: MongoClientOptions = {}): MongoClientO
113114 } ;
114115}
115116
117+ /**
118+ * Format filter/update parameters for logging
119+ * @param params - Parameters to format
120+ * @returns Formatted string
121+ */
122+ function formatParams ( params : any ) : string {
123+ if ( ! params || Object . keys ( params ) . length === 0 ) {
124+ return '' ;
125+ }
126+
127+ try {
128+ return JSON . stringify ( params ) ;
129+ } catch ( e ) {
130+ return String ( params ) ;
131+ }
132+ }
133+
134+ /**
135+ * Colorize duration based on performance thresholds
136+ * @param duration - Duration in milliseconds
137+ * @returns Colorized duration string
138+ */
139+ function colorizeDuration ( duration : number ) : string {
140+ let color : Effect ;
141+
142+ if ( duration < 50 ) {
143+ color = Effect . ForegroundGreen ;
144+ } else if ( duration < 100 ) {
145+ color = Effect . ForegroundYellow ;
146+ } else {
147+ color = Effect . ForegroundRed ;
148+ }
149+
150+ return sgr ( `${ duration } ms` , color ) ;
151+ }
152+
153+ /**
154+ * Interface for storing command information with timestamp
155+ */
156+ interface StoredCommandInfo {
157+ formattedCommand : string ;
158+ timestamp : number ;
159+ }
160+
161+ /**
162+ * Map to store formatted command information by requestId
163+ */
164+ const commandInfoMap = new Map < number , StoredCommandInfo > ( ) ;
165+
166+ /**
167+ * Timeout for cleaning up stale command info (30 seconds)
168+ */
169+ const COMMAND_INFO_TIMEOUT_MS = 30000 ;
170+
171+ /**
172+ * Cleanup stale command info to prevent memory leaks
173+ * Removes entries older than COMMAND_INFO_TIMEOUT_MS
174+ */
175+ function cleanupStaleCommandInfo ( ) : void {
176+ const now = Date . now ( ) ;
177+ const keysToDelete : number [ ] = [ ] ;
178+
179+ for ( const [ requestId , info ] of commandInfoMap . entries ( ) ) {
180+ if ( now - info . timestamp > COMMAND_INFO_TIMEOUT_MS ) {
181+ keysToDelete . push ( requestId ) ;
182+ }
183+ }
184+
185+ if ( keysToDelete . length > 0 ) {
186+ console . warn ( `Cleaning up ${ keysToDelete . length } stale MongoDB command info entries (possible memory leak)` ) ;
187+ for ( const key of keysToDelete ) {
188+ commandInfoMap . delete ( key ) ;
189+ }
190+ }
191+ }
192+
193+ /**
194+ * Periodic cleanup interval
195+ */
196+ setInterval ( cleanupStaleCommandInfo , COMMAND_INFO_TIMEOUT_MS ) ;
197+
198+ /**
199+ * Store MongoDB command details for later logging
200+ * @param event - MongoDB command event
201+ */
202+ function storeCommandInfo ( event : any ) : void {
203+ const collectionRaw = extractCollectionFromCommand ( event . command , event . commandName ) ;
204+ const collection = sgr ( normalizeCollectionName ( collectionRaw ) , Effect . ForegroundGreen ) ;
205+ const db = event . databaseName || 'unknown db' ;
206+ const commandName = sgr ( event . commandName , Effect . ForegroundRed ) ;
207+ const filter = event . command . filter ;
208+ const update = event . command . update ;
209+ const pipeline = event . command . pipeline ;
210+ const projection = event . command . projection ;
211+ const params = filter || update || pipeline ;
212+ const paramsStr = formatParams ( params ) ;
213+ const projectionStr = projection ? ` projection: ${ formatParams ( projection ) } ` : '' ;
214+
215+ const formattedCommand = `[${ event . requestId } ] ${ db } .${ collection } .${ commandName } (${ paramsStr } )${ projectionStr } ` ;
216+
217+ commandInfoMap . set ( event . requestId , {
218+ formattedCommand,
219+ timestamp : Date . now ( ) ,
220+ } ) ;
221+ }
222+
223+ /**
224+ * Log MongoDB command success to console
225+ * Format: [requestId] db.collection.command(params) ✓ duration
226+ * @param event - MongoDB command event
227+ */
228+ function logCommandSucceeded ( event : any ) : void {
229+ const info = commandInfoMap . get ( event . requestId ) ;
230+ const durationStr = colorizeDuration ( event . duration ) ;
231+
232+ if ( info ) {
233+ console . log ( `${ info . formattedCommand } ✓ ${ durationStr } ` ) ;
234+ commandInfoMap . delete ( event . requestId ) ;
235+ } else {
236+ console . log ( `[${ event . requestId } ] ${ event . commandName } ✓ ${ durationStr } ` ) ;
237+ }
238+ }
239+
240+ /**
241+ * Log MongoDB command failure to console
242+ * Format: [requestId] db.collection.command(params) ✗ error duration
243+ * @param event - MongoDB command event
244+ */
245+ function logCommandFailed ( event : any ) : void {
246+ const errorMsg = event . failure ?. message || event . failure ?. errmsg || 'Unknown error' ;
247+ const info = commandInfoMap . get ( event . requestId ) ;
248+ const durationStr = colorizeDuration ( event . duration ) ;
249+
250+ if ( info ) {
251+ console . error ( `${ info . formattedCommand } ✗ ${ errorMsg } ${ durationStr } ` ) ;
252+ commandInfoMap . delete ( event . requestId ) ;
253+ } else {
254+ console . error ( `[${ event . requestId } ] ${ event . commandName } ✗ ${ errorMsg } ${ durationStr } ` ) ;
255+ }
256+ }
257+
116258/**
117259 * Setup MongoDB metrics monitoring on a MongoClient
118260 * @param client - MongoDB client to monitor
119261 */
120262export function setupMongoMetrics ( client : MongoClient ) : void {
121263 client . on ( 'commandStarted' , ( event ) => {
264+ storeCommandInfo ( event ) ;
265+
122266 // Store start time and metadata for this command
123267 const metadataKey = `${ event . requestId } ` ;
124268
@@ -139,6 +283,8 @@ export function setupMongoMetrics(client: MongoClient): void {
139283 } ) ;
140284
141285 client . on ( 'commandSucceeded' , ( event ) => {
286+ logCommandSucceeded ( event ) ;
287+
142288 const metadataKey = `${ event . requestId } ` ;
143289 // eslint-disable-next-line @typescript-eslint/no-explicit-any
144290 const metadata = ( client as any ) [ metadataKey ] ;
@@ -157,6 +303,8 @@ export function setupMongoMetrics(client: MongoClient): void {
157303 } ) ;
158304
159305 client . on ( 'commandFailed' , ( event ) => {
306+ logCommandFailed ( event ) ;
307+
160308 const metadataKey = `${ event . requestId } ` ;
161309 // eslint-disable-next-line @typescript-eslint/no-explicit-any
162310 const metadata = ( client as any ) [ metadataKey ] ;
0 commit comments