1
- import type { Writable } from 'stream' ;
2
- import { inspect } from 'util' ;
1
+ import { inspect , promisify } from 'util' ;
3
2
4
3
import { type Document , EJSON , type EJSONOptions , type ObjectId } from './bson' ;
5
4
import type { CommandStartedEvent } from './cmap/command_monitoring_events' ;
@@ -58,7 +57,7 @@ import type {
58
57
ServerSelectionSucceededEvent ,
59
58
WaitingForSuitableServerEvent
60
59
} from './sdam/server_selection_events' ;
61
- import { HostAddress , parseUnsignedInteger } from './utils' ;
60
+ import { HostAddress , isPromiseLike , parseUnsignedInteger } from './utils' ;
62
61
63
62
/** @internal */
64
63
export const SeverityLevel = Object . freeze ( {
@@ -192,16 +191,19 @@ export interface MongoLoggerOptions {
192
191
/** Max length of embedded EJSON docs. Setting to 0 disables truncation. Defaults to 1000. */
193
192
maxDocumentLength : number ;
194
193
/** Destination for log messages. */
195
- logDestination : Writable | MongoDBLogWritable ;
194
+ logDestination : MongoDBLogWritable ;
195
+ /** For internal check to see if error should stop logging. */
196
+ logDestinationIsStdErr : boolean ;
196
197
}
197
198
198
199
/**
199
200
* Parses a string as one of SeverityLevel
201
+ * @internal
200
202
*
201
203
* @param s - the value to be parsed
202
204
* @returns one of SeverityLevel if value can be parsed as such, otherwise null
203
205
*/
204
- function parseSeverityFromString ( s ?: string ) : SeverityLevel | null {
206
+ export function parseSeverityFromString ( s ?: string ) : SeverityLevel | null {
205
207
const validSeverities : string [ ] = Object . values ( SeverityLevel ) ;
206
208
const lowerSeverity = s ?. toLowerCase ( ) ;
207
209
@@ -217,10 +219,10 @@ export function createStdioLogger(stream: {
217
219
write : NodeJS . WriteStream [ 'write' ] ;
218
220
} ) : MongoDBLogWritable {
219
221
return {
220
- write : ( log : Log ) : unknown => {
221
- stream . write ( inspect ( log , { compact : true , breakLength : Infinity } ) , 'utf-8' ) ;
222
+ write : promisify ( ( log : Log , cb : ( error ?: Error ) => void ) : unknown => {
223
+ stream . write ( inspect ( log , { compact : true , breakLength : Infinity } ) , 'utf-8' , cb ) ;
222
224
return ;
223
- }
225
+ } )
224
226
} ;
225
227
}
226
228
@@ -237,26 +239,26 @@ export function createStdioLogger(stream: {
237
239
function resolveLogPath (
238
240
{ MONGODB_LOG_PATH } : MongoLoggerEnvOptions ,
239
241
{ mongodbLogPath } : MongoLoggerMongoClientOptions
240
- ) : MongoDBLogWritable {
242
+ ) : { mongodbLogPath : MongoDBLogWritable ; mongodbLogPathIsStdErr : boolean } {
241
243
if ( typeof mongodbLogPath === 'string' && / ^ s t d e r r $ / i. test ( mongodbLogPath ) ) {
242
- return createStdioLogger ( process . stderr ) ;
244
+ return { mongodbLogPath : createStdioLogger ( process . stderr ) , mongodbLogPathIsStdErr : true } ;
243
245
}
244
246
if ( typeof mongodbLogPath === 'string' && / ^ s t d o u t $ / i. test ( mongodbLogPath ) ) {
245
- return createStdioLogger ( process . stdout ) ;
247
+ return { mongodbLogPath : createStdioLogger ( process . stdout ) , mongodbLogPathIsStdErr : false } ;
246
248
}
247
249
248
250
if ( typeof mongodbLogPath === 'object' && typeof mongodbLogPath ?. write === 'function' ) {
249
- return mongodbLogPath ;
251
+ return { mongodbLogPath : mongodbLogPath , mongodbLogPathIsStdErr : false } ;
250
252
}
251
253
252
254
if ( MONGODB_LOG_PATH && / ^ s t d e r r $ / i. test ( MONGODB_LOG_PATH ) ) {
253
- return createStdioLogger ( process . stderr ) ;
255
+ return { mongodbLogPath : createStdioLogger ( process . stderr ) , mongodbLogPathIsStdErr : true } ;
254
256
}
255
257
if ( MONGODB_LOG_PATH && / ^ s t d o u t $ / i. test ( MONGODB_LOG_PATH ) ) {
256
- return createStdioLogger ( process . stdout ) ;
258
+ return { mongodbLogPath : createStdioLogger ( process . stdout ) , mongodbLogPathIsStdErr : false } ;
257
259
}
258
260
259
- return createStdioLogger ( process . stderr ) ;
261
+ return { mongodbLogPath : createStdioLogger ( process . stderr ) , mongodbLogPathIsStdErr : true } ;
260
262
}
261
263
262
264
function resolveSeverityConfiguration (
@@ -281,7 +283,7 @@ export interface Log extends Record<string, any> {
281
283
282
284
/** @internal */
283
285
export interface MongoDBLogWritable {
284
- write ( log : Log ) : void ;
286
+ write ( log : Log ) : PromiseLike < unknown > | unknown ;
285
287
}
286
288
287
289
function compareSeverity ( s0 : SeverityLevel , s1 : SeverityLevel ) : 1 | 0 | - 1 {
@@ -415,10 +417,10 @@ export function stringifyWithMaxLen(
415
417
) : string {
416
418
let strToTruncate = '' ;
417
419
418
- if ( typeof value === 'function' ) {
419
- strToTruncate = value . toString ( ) ;
420
- } else {
421
- strToTruncate = EJSON . stringify ( value , options ) ;
420
+ try {
421
+ strToTruncate = typeof value !== 'function' ? EJSON . stringify ( value , options ) : value . name ;
422
+ } catch ( e ) {
423
+ strToTruncate = `Extended JSON serialization failed with: ${ e . message } ` ;
422
424
}
423
425
424
426
return maxDocumentLength !== 0 && strToTruncate . length > maxDocumentLength
@@ -455,15 +457,15 @@ function attachCommandFields(
455
457
) {
456
458
log . commandName = commandEvent . commandName ;
457
459
log . requestId = commandEvent . requestId ;
458
- log . driverConnectionId = commandEvent ? .connectionId ;
460
+ log . driverConnectionId = commandEvent . connectionId ;
459
461
const { host, port } = HostAddress . fromString ( commandEvent . address ) . toHostPort ( ) ;
460
462
log . serverHost = host ;
461
463
log . serverPort = port ;
462
464
if ( commandEvent ?. serviceId ) {
463
465
log . serviceId = commandEvent . serviceId . toHexString ( ) ;
464
466
}
465
467
log . databaseName = commandEvent . databaseName ;
466
- log . serverConnectionId = commandEvent ? .serverConnectionId ;
468
+ log . serverConnectionId = commandEvent . serverConnectionId ;
467
469
468
470
return log ;
469
471
}
@@ -497,7 +499,8 @@ function attachServerHeartbeatFields(
497
499
return log ;
498
500
}
499
501
500
- function defaultLogTransform (
502
+ /** @internal */
503
+ export function defaultLogTransform (
501
504
logObject : LoggableEvent | Record < string , any > ,
502
505
maxDocumentLength : number = DEFAULT_MAX_DOCUMENT_LENGTH
503
506
) : Omit < Log , 's' | 't' | 'c' > {
@@ -509,7 +512,7 @@ function defaultLogTransform(
509
512
return log ;
510
513
case SERVER_SELECTION_FAILED :
511
514
log = attachServerSelectionFields ( log , logObject , maxDocumentLength ) ;
512
- log . failure = logObject . failure . message ;
515
+ log . failure = logObject . failure ? .message ;
513
516
return log ;
514
517
case SERVER_SELECTION_SUCCEEDED :
515
518
log = attachServerSelectionFields ( log , logObject , maxDocumentLength ) ;
@@ -536,7 +539,7 @@ function defaultLogTransform(
536
539
log = attachCommandFields ( log , logObject ) ;
537
540
log . message = 'Command failed' ;
538
541
log . durationMS = logObject . duration ;
539
- log . failure = logObject . failure . message ?? '(redacted)' ;
542
+ log . failure = logObject . failure ? .message ?? '(redacted)' ;
540
543
return log ;
541
544
case CONNECTION_POOL_CREATED :
542
545
log = attachConnectionFields ( log , logObject ) ;
@@ -562,7 +565,7 @@ function defaultLogTransform(
562
565
log = attachConnectionFields ( log , logObject ) ;
563
566
log . message = 'Connection pool cleared' ;
564
567
if ( logObject . serviceId ?. _bsontype === 'ObjectId' ) {
565
- log . serviceId = logObject . serviceId . toHexString ( ) ;
568
+ log . serviceId = logObject . serviceId ? .toHexString ( ) ;
566
569
}
567
570
return log ;
568
571
case CONNECTION_POOL_CLOSED :
@@ -666,7 +669,7 @@ function defaultLogTransform(
666
669
log = attachServerHeartbeatFields ( log , logObject ) ;
667
670
log . message = 'Server heartbeat failed' ;
668
671
log . durationMS = logObject . duration ;
669
- log . failure = logObject . failure . message ;
672
+ log . failure = logObject . failure ? .message ;
670
673
return log ;
671
674
case TOPOLOGY_OPENING :
672
675
log = attachSDAMFields ( log , logObject ) ;
@@ -700,7 +703,9 @@ function defaultLogTransform(
700
703
export class MongoLogger {
701
704
componentSeverities : Record < MongoLoggableComponent , SeverityLevel > ;
702
705
maxDocumentLength : number ;
703
- logDestination : MongoDBLogWritable | Writable ;
706
+ logDestination : MongoDBLogWritable ;
707
+ logDestinationIsStdErr : boolean ;
708
+ pendingLog : PromiseLike < unknown > | unknown = null ;
704
709
705
710
/**
706
711
* This method should be used when logging errors that do not have a public driver API for
@@ -732,12 +737,44 @@ export class MongoLogger {
732
737
this . componentSeverities = options . componentSeverities ;
733
738
this . maxDocumentLength = options . maxDocumentLength ;
734
739
this . logDestination = options . logDestination ;
740
+ this . logDestinationIsStdErr = options . logDestinationIsStdErr ;
735
741
}
736
742
737
743
willLog ( severity : SeverityLevel , component : MongoLoggableComponent ) : boolean {
738
744
return compareSeverity ( severity , this . componentSeverities [ component ] ) <= 0 ;
739
745
}
740
746
747
+ turnOffSeverities ( ) {
748
+ for ( const key of Object . values ( MongoLoggableComponent ) ) {
749
+ this . componentSeverities [ key as MongoLoggableComponent ] = SeverityLevel . OFF ;
750
+ }
751
+ }
752
+
753
+ private logWriteFailureHandler ( error : Error ) {
754
+ if ( this . logDestinationIsStdErr ) {
755
+ this . turnOffSeverities ( ) ;
756
+ this . clearPendingLog ( ) ;
757
+ return ;
758
+ }
759
+ this . logDestination = createStdioLogger ( process . stderr ) ;
760
+ this . logDestinationIsStdErr = true ;
761
+ this . clearPendingLog ( ) ;
762
+ this . error ( MongoLoggableComponent . CLIENT , {
763
+ toLog : function ( ) {
764
+ return {
765
+ message : 'User input for mongodbLogPath is now invalid. Logging is halted.' ,
766
+ error : error . message
767
+ } ;
768
+ }
769
+ } ) ;
770
+ this . turnOffSeverities ( ) ;
771
+ this . clearPendingLog ( ) ;
772
+ }
773
+
774
+ private clearPendingLog ( ) {
775
+ this . pendingLog = null ;
776
+ }
777
+
741
778
private log (
742
779
severity : SeverityLevel ,
743
780
component : MongoLoggableComponent ,
@@ -755,7 +792,25 @@ export class MongoLogger {
755
792
logMessage = { ...logMessage , ...defaultLogTransform ( message , this . maxDocumentLength ) } ;
756
793
}
757
794
}
758
- this . logDestination . write ( logMessage ) ;
795
+
796
+ if ( isPromiseLike ( this . pendingLog ) ) {
797
+ this . pendingLog = this . pendingLog
798
+ . then ( ( ) => this . logDestination . write ( logMessage ) )
799
+ . then ( this . clearPendingLog . bind ( this ) , this . logWriteFailureHandler . bind ( this ) ) ;
800
+ return ;
801
+ }
802
+
803
+ try {
804
+ const logResult = this . logDestination . write ( logMessage ) ;
805
+ if ( isPromiseLike ( logResult ) ) {
806
+ this . pendingLog = logResult . then (
807
+ this . clearPendingLog . bind ( this ) ,
808
+ this . logWriteFailureHandler . bind ( this )
809
+ ) ;
810
+ }
811
+ } catch ( error ) {
812
+ this . logWriteFailureHandler ( error ) ;
813
+ }
759
814
}
760
815
761
816
/**
@@ -776,10 +831,12 @@ export class MongoLogger {
776
831
clientOptions : MongoLoggerMongoClientOptions
777
832
) : MongoLoggerOptions {
778
833
// client options take precedence over env options
834
+ const resolvedLogPath = resolveLogPath ( envOptions , clientOptions ) ;
779
835
const combinedOptions = {
780
836
...envOptions ,
781
837
...clientOptions ,
782
- mongodbLogPath : resolveLogPath ( envOptions , clientOptions )
838
+ mongodbLogPath : resolvedLogPath . mongodbLogPath ,
839
+ mongodbLogPathIsStdErr : resolvedLogPath . mongodbLogPathIsStdErr
783
840
} ;
784
841
const defaultSeverity = resolveSeverityConfiguration (
785
842
combinedOptions . mongodbLogComponentSeverities ?. default ,
@@ -820,7 +877,8 @@ export class MongoLogger {
820
877
combinedOptions . mongodbLogMaxDocumentLength ??
821
878
parseUnsignedInteger ( combinedOptions . MONGODB_LOG_MAX_DOCUMENT_LENGTH ) ??
822
879
1000 ,
823
- logDestination : combinedOptions . mongodbLogPath
880
+ logDestination : combinedOptions . mongodbLogPath ,
881
+ logDestinationIsStdErr : combinedOptions . mongodbLogPathIsStdErr
824
882
} ;
825
883
}
826
884
}
0 commit comments