mongo_logger.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.MongoLogger = exports.MongoLoggableComponent = exports.SEVERITY_LEVEL_MAP = exports.DEFAULT_MAX_DOCUMENT_LENGTH = exports.SeverityLevel = void 0;
  4. exports.parseSeverityFromString = parseSeverityFromString;
  5. exports.createStdioLogger = createStdioLogger;
  6. exports.stringifyWithMaxLen = stringifyWithMaxLen;
  7. exports.defaultLogTransform = defaultLogTransform;
  8. const util_1 = require("util");
  9. const bson_1 = require("./bson");
  10. const constants_1 = require("./constants");
  11. const utils_1 = require("./utils");
  12. /**
  13. * @public
  14. * Severity levels align with unix syslog.
  15. * Most typical driver functions will log to debug.
  16. */
  17. exports.SeverityLevel = Object.freeze({
  18. EMERGENCY: 'emergency',
  19. ALERT: 'alert',
  20. CRITICAL: 'critical',
  21. ERROR: 'error',
  22. WARNING: 'warn',
  23. NOTICE: 'notice',
  24. INFORMATIONAL: 'info',
  25. DEBUG: 'debug',
  26. TRACE: 'trace',
  27. OFF: 'off'
  28. });
  29. /** @internal */
  30. exports.DEFAULT_MAX_DOCUMENT_LENGTH = 1000;
  31. /** @internal */
  32. class SeverityLevelMap extends Map {
  33. constructor(entries) {
  34. const newEntries = [];
  35. for (const [level, value] of entries) {
  36. newEntries.push([value, level]);
  37. }
  38. newEntries.push(...entries);
  39. super(newEntries);
  40. }
  41. getNumericSeverityLevel(severity) {
  42. return this.get(severity);
  43. }
  44. getSeverityLevelName(level) {
  45. return this.get(level);
  46. }
  47. }
  48. /** @internal */
  49. exports.SEVERITY_LEVEL_MAP = new SeverityLevelMap([
  50. [exports.SeverityLevel.OFF, -Infinity],
  51. [exports.SeverityLevel.EMERGENCY, 0],
  52. [exports.SeverityLevel.ALERT, 1],
  53. [exports.SeverityLevel.CRITICAL, 2],
  54. [exports.SeverityLevel.ERROR, 3],
  55. [exports.SeverityLevel.WARNING, 4],
  56. [exports.SeverityLevel.NOTICE, 5],
  57. [exports.SeverityLevel.INFORMATIONAL, 6],
  58. [exports.SeverityLevel.DEBUG, 7],
  59. [exports.SeverityLevel.TRACE, 8]
  60. ]);
  61. /** @public */
  62. exports.MongoLoggableComponent = Object.freeze({
  63. COMMAND: 'command',
  64. TOPOLOGY: 'topology',
  65. SERVER_SELECTION: 'serverSelection',
  66. CONNECTION: 'connection',
  67. CLIENT: 'client'
  68. });
  69. /**
  70. * Parses a string as one of SeverityLevel
  71. * @internal
  72. *
  73. * @param s - the value to be parsed
  74. * @returns one of SeverityLevel if value can be parsed as such, otherwise null
  75. */
  76. function parseSeverityFromString(s) {
  77. const validSeverities = Object.values(exports.SeverityLevel);
  78. const lowerSeverity = s?.toLowerCase();
  79. if (lowerSeverity != null && validSeverities.includes(lowerSeverity)) {
  80. return lowerSeverity;
  81. }
  82. return null;
  83. }
  84. /** @internal */
  85. function createStdioLogger(stream) {
  86. return {
  87. write: (0, util_1.promisify)((log, cb) => {
  88. const logLine = (0, util_1.inspect)(log, { compact: true, breakLength: Infinity });
  89. stream.write(`${logLine}\n`, 'utf-8', cb);
  90. return;
  91. })
  92. };
  93. }
  94. /**
  95. * resolves the MONGODB_LOG_PATH and mongodbLogPath options from the environment and the
  96. * mongo client options respectively. The mongodbLogPath can be either 'stdout', 'stderr', a NodeJS
  97. * Writable or an object which has a `write` method with the signature:
  98. * ```ts
  99. * write(log: Log): void
  100. * ```
  101. *
  102. * @returns the MongoDBLogWritable object to write logs to
  103. */
  104. function resolveLogPath({ MONGODB_LOG_PATH }, { mongodbLogPath }) {
  105. if (typeof mongodbLogPath === 'string' && /^stderr$/i.test(mongodbLogPath)) {
  106. return { mongodbLogPath: createStdioLogger(process.stderr), mongodbLogPathIsStdErr: true };
  107. }
  108. if (typeof mongodbLogPath === 'string' && /^stdout$/i.test(mongodbLogPath)) {
  109. return { mongodbLogPath: createStdioLogger(process.stdout), mongodbLogPathIsStdErr: false };
  110. }
  111. if (typeof mongodbLogPath === 'object' && typeof mongodbLogPath?.write === 'function') {
  112. return { mongodbLogPath: mongodbLogPath, mongodbLogPathIsStdErr: false };
  113. }
  114. if (MONGODB_LOG_PATH && /^stderr$/i.test(MONGODB_LOG_PATH)) {
  115. return { mongodbLogPath: createStdioLogger(process.stderr), mongodbLogPathIsStdErr: true };
  116. }
  117. if (MONGODB_LOG_PATH && /^stdout$/i.test(MONGODB_LOG_PATH)) {
  118. return { mongodbLogPath: createStdioLogger(process.stdout), mongodbLogPathIsStdErr: false };
  119. }
  120. return { mongodbLogPath: createStdioLogger(process.stderr), mongodbLogPathIsStdErr: true };
  121. }
  122. function resolveSeverityConfiguration(clientOption, environmentOption, defaultSeverity) {
  123. return (parseSeverityFromString(clientOption) ??
  124. parseSeverityFromString(environmentOption) ??
  125. defaultSeverity);
  126. }
  127. function compareSeverity(s0, s1) {
  128. const s0Num = exports.SEVERITY_LEVEL_MAP.getNumericSeverityLevel(s0);
  129. const s1Num = exports.SEVERITY_LEVEL_MAP.getNumericSeverityLevel(s1);
  130. return s0Num < s1Num ? -1 : s0Num > s1Num ? 1 : 0;
  131. }
  132. /** @internal */
  133. function stringifyWithMaxLen(value, maxDocumentLength, options = {}) {
  134. let strToTruncate = '';
  135. let currentLength = 0;
  136. const maxDocumentLengthEnsurer = function maxDocumentLengthEnsurer(key, value) {
  137. if (currentLength >= maxDocumentLength) {
  138. return undefined;
  139. }
  140. // Account for root document
  141. if (key === '') {
  142. // Account for starting brace
  143. currentLength += 1;
  144. return value;
  145. }
  146. // +4 accounts for 2 quotation marks, colon and comma after value
  147. // Note that this potentially undercounts since it does not account for escape sequences which
  148. // will have an additional backslash added to them once passed through JSON.stringify.
  149. currentLength += key.length + 4;
  150. if (value == null)
  151. return value;
  152. switch (typeof value) {
  153. case 'string':
  154. // +2 accounts for quotes
  155. // Note that this potentially undercounts similarly to the key length calculation
  156. currentLength += value.length + 2;
  157. break;
  158. case 'number':
  159. case 'bigint':
  160. currentLength += String(value).length;
  161. break;
  162. case 'boolean':
  163. currentLength += value ? 4 : 5;
  164. break;
  165. case 'object':
  166. if ((0, utils_1.isUint8Array)(value)) {
  167. // '{"$binary":{"base64":"<base64 string>","subType":"XX"}}'
  168. // This is an estimate based on the fact that the base64 is approximately 1.33x the length of
  169. // the actual binary sequence https://en.wikipedia.org/wiki/Base64
  170. currentLength += (22 + value.byteLength + value.byteLength * 0.33 + 18) | 0;
  171. }
  172. else if ('_bsontype' in value) {
  173. const v = value;
  174. switch (v._bsontype) {
  175. case 'Int32':
  176. currentLength += String(v.value).length;
  177. break;
  178. case 'Double':
  179. // Account for representing integers as <value>.0
  180. currentLength +=
  181. (v.value | 0) === v.value ? String(v.value).length + 2 : String(v.value).length;
  182. break;
  183. case 'Long':
  184. currentLength += v.toString().length;
  185. break;
  186. case 'ObjectId':
  187. // '{"$oid":"XXXXXXXXXXXXXXXXXXXXXXXX"}'
  188. currentLength += 35;
  189. break;
  190. case 'MaxKey':
  191. case 'MinKey':
  192. // '{"$maxKey":1}' or '{"$minKey":1}'
  193. currentLength += 13;
  194. break;
  195. case 'Binary':
  196. // '{"$binary":{"base64":"<base64 string>","subType":"XX"}}'
  197. // This is an estimate based on the fact that the base64 is approximately 1.33x the length of
  198. // the actual binary sequence https://en.wikipedia.org/wiki/Base64
  199. currentLength += (22 + value.position + value.position * 0.33 + 18) | 0;
  200. break;
  201. case 'Timestamp':
  202. // '{"$timestamp":{"t":<t>,"i":<i>}}'
  203. currentLength += 19 + String(v.t).length + 5 + String(v.i).length + 2;
  204. break;
  205. case 'Code':
  206. // '{"$code":"<code>"}' or '{"$code":"<code>","$scope":<scope>}'
  207. if (v.scope == null) {
  208. currentLength += v.code.length + 10 + 2;
  209. }
  210. else {
  211. // Ignoring actual scope object, so this undercounts by a significant amount
  212. currentLength += v.code.length + 10 + 11;
  213. }
  214. break;
  215. case 'BSONRegExp':
  216. // '{"$regularExpression":{"pattern":"<pattern>","options":"<options>"}}'
  217. currentLength += 34 + v.pattern.length + 13 + v.options.length + 3;
  218. break;
  219. }
  220. }
  221. }
  222. return value;
  223. };
  224. if (typeof value === 'string') {
  225. strToTruncate = value;
  226. }
  227. else if (typeof value === 'function') {
  228. strToTruncate = value.name;
  229. }
  230. else {
  231. try {
  232. if (maxDocumentLength !== 0) {
  233. strToTruncate = bson_1.EJSON.stringify(value, maxDocumentLengthEnsurer, 0, options);
  234. }
  235. else {
  236. strToTruncate = bson_1.EJSON.stringify(value, options);
  237. }
  238. }
  239. catch (e) {
  240. strToTruncate = `Extended JSON serialization failed with: ${e.message}`;
  241. }
  242. }
  243. // handle truncation that occurs in the middle of multi-byte codepoints
  244. if (maxDocumentLength !== 0 &&
  245. strToTruncate.length > maxDocumentLength &&
  246. strToTruncate.charCodeAt(maxDocumentLength - 1) !==
  247. strToTruncate.codePointAt(maxDocumentLength - 1)) {
  248. maxDocumentLength--;
  249. if (maxDocumentLength === 0) {
  250. return '';
  251. }
  252. }
  253. return maxDocumentLength !== 0 && strToTruncate.length > maxDocumentLength
  254. ? `${strToTruncate.slice(0, maxDocumentLength)}...`
  255. : strToTruncate;
  256. }
  257. function isLogConvertible(obj) {
  258. const objAsLogConvertible = obj;
  259. // eslint-disable-next-line no-restricted-syntax
  260. return objAsLogConvertible.toLog !== undefined && typeof objAsLogConvertible.toLog === 'function';
  261. }
  262. function attachServerSelectionFields(log, serverSelectionEvent, maxDocumentLength = exports.DEFAULT_MAX_DOCUMENT_LENGTH) {
  263. const { selector, operation, topologyDescription, message } = serverSelectionEvent;
  264. log.selector = stringifyWithMaxLen(selector, maxDocumentLength);
  265. log.operation = operation;
  266. log.topologyDescription = stringifyWithMaxLen(topologyDescription, maxDocumentLength);
  267. log.message = message;
  268. return log;
  269. }
  270. function attachCommandFields(log, commandEvent) {
  271. log.commandName = commandEvent.commandName;
  272. log.requestId = commandEvent.requestId;
  273. log.driverConnectionId = commandEvent.connectionId;
  274. const { host, port } = utils_1.HostAddress.fromString(commandEvent.address).toHostPort();
  275. log.serverHost = host;
  276. log.serverPort = port;
  277. if (commandEvent?.serviceId) {
  278. log.serviceId = commandEvent.serviceId.toHexString();
  279. }
  280. log.databaseName = commandEvent.databaseName;
  281. log.serverConnectionId = commandEvent.serverConnectionId;
  282. return log;
  283. }
  284. function attachConnectionFields(log, event) {
  285. const { host, port } = utils_1.HostAddress.fromString(event.address).toHostPort();
  286. log.serverHost = host;
  287. log.serverPort = port;
  288. return log;
  289. }
  290. function attachSDAMFields(log, sdamEvent) {
  291. log.topologyId = sdamEvent.topologyId;
  292. return log;
  293. }
  294. function attachServerHeartbeatFields(log, serverHeartbeatEvent) {
  295. const { awaited, connectionId } = serverHeartbeatEvent;
  296. log.awaited = awaited;
  297. log.driverConnectionId = serverHeartbeatEvent.connectionId;
  298. const { host, port } = utils_1.HostAddress.fromString(connectionId).toHostPort();
  299. log.serverHost = host;
  300. log.serverPort = port;
  301. return log;
  302. }
  303. /** @internal */
  304. function defaultLogTransform(logObject, maxDocumentLength = exports.DEFAULT_MAX_DOCUMENT_LENGTH) {
  305. let log = Object.create(null);
  306. switch (logObject.name) {
  307. case constants_1.SERVER_SELECTION_STARTED:
  308. log = attachServerSelectionFields(log, logObject, maxDocumentLength);
  309. return log;
  310. case constants_1.SERVER_SELECTION_FAILED:
  311. log = attachServerSelectionFields(log, logObject, maxDocumentLength);
  312. log.failure = logObject.failure?.message;
  313. return log;
  314. case constants_1.SERVER_SELECTION_SUCCEEDED:
  315. log = attachServerSelectionFields(log, logObject, maxDocumentLength);
  316. log.serverHost = logObject.serverHost;
  317. log.serverPort = logObject.serverPort;
  318. return log;
  319. case constants_1.WAITING_FOR_SUITABLE_SERVER:
  320. log = attachServerSelectionFields(log, logObject, maxDocumentLength);
  321. log.remainingTimeMS = logObject.remainingTimeMS;
  322. return log;
  323. case constants_1.COMMAND_STARTED:
  324. log = attachCommandFields(log, logObject);
  325. log.message = 'Command started';
  326. log.command = stringifyWithMaxLen(logObject.command, maxDocumentLength, { relaxed: true });
  327. log.databaseName = logObject.databaseName;
  328. return log;
  329. case constants_1.COMMAND_SUCCEEDED:
  330. log = attachCommandFields(log, logObject);
  331. log.message = 'Command succeeded';
  332. log.durationMS = logObject.duration;
  333. log.reply = stringifyWithMaxLen(logObject.reply, maxDocumentLength, { relaxed: true });
  334. return log;
  335. case constants_1.COMMAND_FAILED:
  336. log = attachCommandFields(log, logObject);
  337. log.message = 'Command failed';
  338. log.durationMS = logObject.duration;
  339. log.failure = logObject.failure?.message ?? '(redacted)';
  340. return log;
  341. case constants_1.CONNECTION_POOL_CREATED:
  342. log = attachConnectionFields(log, logObject);
  343. log.message = 'Connection pool created';
  344. if (logObject.options) {
  345. const { maxIdleTimeMS, minPoolSize, maxPoolSize, maxConnecting, waitQueueTimeoutMS } = logObject.options;
  346. log = {
  347. ...log,
  348. maxIdleTimeMS,
  349. minPoolSize,
  350. maxPoolSize,
  351. maxConnecting,
  352. waitQueueTimeoutMS
  353. };
  354. }
  355. return log;
  356. case constants_1.CONNECTION_POOL_READY:
  357. log = attachConnectionFields(log, logObject);
  358. log.message = 'Connection pool ready';
  359. return log;
  360. case constants_1.CONNECTION_POOL_CLEARED:
  361. log = attachConnectionFields(log, logObject);
  362. log.message = 'Connection pool cleared';
  363. if (logObject.serviceId?._bsontype === 'ObjectId') {
  364. log.serviceId = logObject.serviceId?.toHexString();
  365. }
  366. return log;
  367. case constants_1.CONNECTION_POOL_CLOSED:
  368. log = attachConnectionFields(log, logObject);
  369. log.message = 'Connection pool closed';
  370. return log;
  371. case constants_1.CONNECTION_CREATED:
  372. log = attachConnectionFields(log, logObject);
  373. log.message = 'Connection created';
  374. log.driverConnectionId = logObject.connectionId;
  375. return log;
  376. case constants_1.CONNECTION_READY:
  377. log = attachConnectionFields(log, logObject);
  378. log.message = 'Connection ready';
  379. log.driverConnectionId = logObject.connectionId;
  380. log.durationMS = logObject.durationMS;
  381. return log;
  382. case constants_1.CONNECTION_CLOSED:
  383. log = attachConnectionFields(log, logObject);
  384. log.message = 'Connection closed';
  385. log.driverConnectionId = logObject.connectionId;
  386. switch (logObject.reason) {
  387. case 'stale':
  388. log.reason = 'Connection became stale because the pool was cleared';
  389. break;
  390. case 'idle':
  391. log.reason =
  392. 'Connection has been available but unused for longer than the configured max idle time';
  393. break;
  394. case 'error':
  395. log.reason = 'An error occurred while using the connection';
  396. if (logObject.error) {
  397. log.error = logObject.error;
  398. }
  399. break;
  400. case 'poolClosed':
  401. log.reason = 'Connection pool was closed';
  402. break;
  403. default:
  404. log.reason = `Unknown close reason: ${logObject.reason}`;
  405. }
  406. return log;
  407. case constants_1.CONNECTION_CHECK_OUT_STARTED:
  408. log = attachConnectionFields(log, logObject);
  409. log.message = 'Connection checkout started';
  410. return log;
  411. case constants_1.CONNECTION_CHECK_OUT_FAILED:
  412. log = attachConnectionFields(log, logObject);
  413. log.message = 'Connection checkout failed';
  414. switch (logObject.reason) {
  415. case 'poolClosed':
  416. log.reason = 'Connection pool was closed';
  417. break;
  418. case 'timeout':
  419. log.reason = 'Wait queue timeout elapsed without a connection becoming available';
  420. break;
  421. case 'connectionError':
  422. log.reason = 'An error occurred while trying to establish a new connection';
  423. if (logObject.error) {
  424. log.error = logObject.error;
  425. }
  426. break;
  427. default:
  428. log.reason = `Unknown close reason: ${logObject.reason}`;
  429. }
  430. log.durationMS = logObject.durationMS;
  431. return log;
  432. case constants_1.CONNECTION_CHECKED_OUT:
  433. log = attachConnectionFields(log, logObject);
  434. log.message = 'Connection checked out';
  435. log.driverConnectionId = logObject.connectionId;
  436. log.durationMS = logObject.durationMS;
  437. return log;
  438. case constants_1.CONNECTION_CHECKED_IN:
  439. log = attachConnectionFields(log, logObject);
  440. log.message = 'Connection checked in';
  441. log.driverConnectionId = logObject.connectionId;
  442. return log;
  443. case constants_1.SERVER_OPENING:
  444. log = attachSDAMFields(log, logObject);
  445. log = attachConnectionFields(log, logObject);
  446. log.message = 'Starting server monitoring';
  447. return log;
  448. case constants_1.SERVER_CLOSED:
  449. log = attachSDAMFields(log, logObject);
  450. log = attachConnectionFields(log, logObject);
  451. log.message = 'Stopped server monitoring';
  452. return log;
  453. case constants_1.SERVER_HEARTBEAT_STARTED:
  454. log = attachSDAMFields(log, logObject);
  455. log = attachServerHeartbeatFields(log, logObject);
  456. log.message = 'Server heartbeat started';
  457. return log;
  458. case constants_1.SERVER_HEARTBEAT_SUCCEEDED:
  459. log = attachSDAMFields(log, logObject);
  460. log = attachServerHeartbeatFields(log, logObject);
  461. log.message = 'Server heartbeat succeeded';
  462. log.durationMS = logObject.duration;
  463. log.serverConnectionId = logObject.serverConnectionId;
  464. log.reply = stringifyWithMaxLen(logObject.reply, maxDocumentLength, { relaxed: true });
  465. return log;
  466. case constants_1.SERVER_HEARTBEAT_FAILED:
  467. log = attachSDAMFields(log, logObject);
  468. log = attachServerHeartbeatFields(log, logObject);
  469. log.message = 'Server heartbeat failed';
  470. log.durationMS = logObject.duration;
  471. log.failure = logObject.failure?.message;
  472. return log;
  473. case constants_1.TOPOLOGY_OPENING:
  474. log = attachSDAMFields(log, logObject);
  475. log.message = 'Starting topology monitoring';
  476. return log;
  477. case constants_1.TOPOLOGY_CLOSED:
  478. log = attachSDAMFields(log, logObject);
  479. log.message = 'Stopped topology monitoring';
  480. return log;
  481. case constants_1.TOPOLOGY_DESCRIPTION_CHANGED:
  482. log = attachSDAMFields(log, logObject);
  483. log.message = 'Topology description changed';
  484. log.previousDescription = log.reply = stringifyWithMaxLen(logObject.previousDescription, maxDocumentLength);
  485. log.newDescription = log.reply = stringifyWithMaxLen(logObject.newDescription, maxDocumentLength);
  486. return log;
  487. default:
  488. for (const [key, value] of Object.entries(logObject)) {
  489. if (value != null)
  490. log[key] = value;
  491. }
  492. }
  493. return log;
  494. }
  495. /** @internal */
  496. class MongoLogger {
  497. constructor(options) {
  498. this.pendingLog = null;
  499. /**
  500. * This method should be used when logging errors that do not have a public driver API for
  501. * reporting errors.
  502. */
  503. this.error = this.log.bind(this, 'error');
  504. /**
  505. * This method should be used to log situations where undesirable application behaviour might
  506. * occur. For example, failing to end sessions on `MongoClient.close`.
  507. */
  508. this.warn = this.log.bind(this, 'warn');
  509. /**
  510. * This method should be used to report high-level information about normal driver behaviour.
  511. * For example, the creation of a `MongoClient`.
  512. */
  513. this.info = this.log.bind(this, 'info');
  514. /**
  515. * This method should be used to report information that would be helpful when debugging an
  516. * application. For example, a command starting, succeeding or failing.
  517. */
  518. this.debug = this.log.bind(this, 'debug');
  519. /**
  520. * This method should be used to report fine-grained details related to logic flow. For example,
  521. * entering and exiting a function body.
  522. */
  523. this.trace = this.log.bind(this, 'trace');
  524. this.componentSeverities = options.componentSeverities;
  525. this.maxDocumentLength = options.maxDocumentLength;
  526. this.logDestination = options.logDestination;
  527. this.logDestinationIsStdErr = options.logDestinationIsStdErr;
  528. this.severities = this.createLoggingSeverities();
  529. }
  530. createLoggingSeverities() {
  531. const severities = Object();
  532. for (const component of Object.values(exports.MongoLoggableComponent)) {
  533. severities[component] = {};
  534. for (const severityLevel of Object.values(exports.SeverityLevel)) {
  535. severities[component][severityLevel] =
  536. compareSeverity(severityLevel, this.componentSeverities[component]) <= 0;
  537. }
  538. }
  539. return severities;
  540. }
  541. turnOffSeverities() {
  542. for (const component of Object.values(exports.MongoLoggableComponent)) {
  543. this.componentSeverities[component] = exports.SeverityLevel.OFF;
  544. for (const severityLevel of Object.values(exports.SeverityLevel)) {
  545. this.severities[component][severityLevel] = false;
  546. }
  547. }
  548. }
  549. logWriteFailureHandler(error) {
  550. if (this.logDestinationIsStdErr) {
  551. this.turnOffSeverities();
  552. this.clearPendingLog();
  553. return;
  554. }
  555. this.logDestination = createStdioLogger(process.stderr);
  556. this.logDestinationIsStdErr = true;
  557. this.clearPendingLog();
  558. this.error(exports.MongoLoggableComponent.CLIENT, {
  559. toLog: function () {
  560. return {
  561. message: 'User input for mongodbLogPath is now invalid. Logging is halted.',
  562. error: error.message
  563. };
  564. }
  565. });
  566. this.turnOffSeverities();
  567. this.clearPendingLog();
  568. }
  569. clearPendingLog() {
  570. this.pendingLog = null;
  571. }
  572. willLog(component, severity) {
  573. if (severity === exports.SeverityLevel.OFF)
  574. return false;
  575. return this.severities[component][severity];
  576. }
  577. log(severity, component, message) {
  578. if (!this.willLog(component, severity))
  579. return;
  580. let logMessage = { t: new Date(), c: component, s: severity };
  581. if (typeof message === 'string') {
  582. logMessage.message = message;
  583. }
  584. else if (typeof message === 'object') {
  585. if (isLogConvertible(message)) {
  586. logMessage = { ...logMessage, ...message.toLog() };
  587. }
  588. else {
  589. logMessage = { ...logMessage, ...defaultLogTransform(message, this.maxDocumentLength) };
  590. }
  591. }
  592. if ((0, utils_1.isPromiseLike)(this.pendingLog)) {
  593. this.pendingLog = this.pendingLog
  594. .then(() => this.logDestination.write(logMessage))
  595. .then(this.clearPendingLog.bind(this), this.logWriteFailureHandler.bind(this));
  596. return;
  597. }
  598. try {
  599. const logResult = this.logDestination.write(logMessage);
  600. if ((0, utils_1.isPromiseLike)(logResult)) {
  601. this.pendingLog = logResult.then(this.clearPendingLog.bind(this), this.logWriteFailureHandler.bind(this));
  602. }
  603. }
  604. catch (error) {
  605. this.logWriteFailureHandler(error);
  606. }
  607. }
  608. /**
  609. * Merges options set through environment variables and the MongoClient, preferring environment
  610. * variables when both are set, and substituting defaults for values not set. Options set in
  611. * constructor take precedence over both environment variables and MongoClient options.
  612. *
  613. * @remarks
  614. * When parsing component severity levels, invalid values are treated as unset and replaced with
  615. * the default severity.
  616. *
  617. * @param envOptions - options set for the logger from the environment
  618. * @param clientOptions - options set for the logger in the MongoClient options
  619. * @returns a MongoLoggerOptions object to be used when instantiating a new MongoLogger
  620. */
  621. static resolveOptions(envOptions, clientOptions) {
  622. // client options take precedence over env options
  623. const resolvedLogPath = resolveLogPath(envOptions, clientOptions);
  624. const combinedOptions = {
  625. ...envOptions,
  626. ...clientOptions,
  627. mongodbLogPath: resolvedLogPath.mongodbLogPath,
  628. mongodbLogPathIsStdErr: resolvedLogPath.mongodbLogPathIsStdErr
  629. };
  630. const defaultSeverity = resolveSeverityConfiguration(combinedOptions.mongodbLogComponentSeverities?.default, combinedOptions.MONGODB_LOG_ALL, exports.SeverityLevel.OFF);
  631. return {
  632. componentSeverities: {
  633. command: resolveSeverityConfiguration(combinedOptions.mongodbLogComponentSeverities?.command, combinedOptions.MONGODB_LOG_COMMAND, defaultSeverity),
  634. topology: resolveSeverityConfiguration(combinedOptions.mongodbLogComponentSeverities?.topology, combinedOptions.MONGODB_LOG_TOPOLOGY, defaultSeverity),
  635. serverSelection: resolveSeverityConfiguration(combinedOptions.mongodbLogComponentSeverities?.serverSelection, combinedOptions.MONGODB_LOG_SERVER_SELECTION, defaultSeverity),
  636. connection: resolveSeverityConfiguration(combinedOptions.mongodbLogComponentSeverities?.connection, combinedOptions.MONGODB_LOG_CONNECTION, defaultSeverity),
  637. client: resolveSeverityConfiguration(combinedOptions.mongodbLogComponentSeverities?.client, combinedOptions.MONGODB_LOG_CLIENT, defaultSeverity),
  638. default: defaultSeverity
  639. },
  640. maxDocumentLength: combinedOptions.mongodbLogMaxDocumentLength ??
  641. (0, utils_1.parseUnsignedInteger)(combinedOptions.MONGODB_LOG_MAX_DOCUMENT_LENGTH) ??
  642. 1000,
  643. logDestination: combinedOptions.mongodbLogPath,
  644. logDestinationIsStdErr: combinedOptions.mongodbLogPathIsStdErr
  645. };
  646. }
  647. }
  648. exports.MongoLogger = MongoLogger;
  649. //# sourceMappingURL=mongo_logger.js.map