mongo.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. var {
  2. _optionalChain
  3. } = require('@sentry/utils');
  4. Object.defineProperty(exports, '__esModule', { value: true });
  5. const utils = require('@sentry/utils');
  6. const debugBuild = require('../../common/debug-build.js');
  7. const nodeUtils = require('./utils/node-utils.js');
  8. // This allows us to use the same array for both defaults options and the type itself.
  9. // (note `as const` at the end to make it a union of string literal types (i.e. "a" | "b" | ... )
  10. // and not just a string[])
  11. const OPERATIONS = [
  12. 'aggregate', // aggregate(pipeline, options, callback)
  13. 'bulkWrite', // bulkWrite(operations, options, callback)
  14. 'countDocuments', // countDocuments(query, options, callback)
  15. 'createIndex', // createIndex(fieldOrSpec, options, callback)
  16. 'createIndexes', // createIndexes(indexSpecs, options, callback)
  17. 'deleteMany', // deleteMany(filter, options, callback)
  18. 'deleteOne', // deleteOne(filter, options, callback)
  19. 'distinct', // distinct(key, query, options, callback)
  20. 'drop', // drop(options, callback)
  21. 'dropIndex', // dropIndex(indexName, options, callback)
  22. 'dropIndexes', // dropIndexes(options, callback)
  23. 'estimatedDocumentCount', // estimatedDocumentCount(options, callback)
  24. 'find', // find(query, options, callback)
  25. 'findOne', // findOne(query, options, callback)
  26. 'findOneAndDelete', // findOneAndDelete(filter, options, callback)
  27. 'findOneAndReplace', // findOneAndReplace(filter, replacement, options, callback)
  28. 'findOneAndUpdate', // findOneAndUpdate(filter, update, options, callback)
  29. 'indexes', // indexes(options, callback)
  30. 'indexExists', // indexExists(indexes, options, callback)
  31. 'indexInformation', // indexInformation(options, callback)
  32. 'initializeOrderedBulkOp', // initializeOrderedBulkOp(options, callback)
  33. 'insertMany', // insertMany(docs, options, callback)
  34. 'insertOne', // insertOne(doc, options, callback)
  35. 'isCapped', // isCapped(options, callback)
  36. 'mapReduce', // mapReduce(map, reduce, options, callback)
  37. 'options', // options(options, callback)
  38. 'parallelCollectionScan', // parallelCollectionScan(options, callback)
  39. 'rename', // rename(newName, options, callback)
  40. 'replaceOne', // replaceOne(filter, doc, options, callback)
  41. 'stats', // stats(options, callback)
  42. 'updateMany', // updateMany(filter, update, options, callback)
  43. 'updateOne', // updateOne(filter, update, options, callback)
  44. ] ;
  45. // All of the operations above take `options` and `callback` as their final parameters, but some of them
  46. // take additional parameters as well. For those operations, this is a map of
  47. // { <operation name>: [<names of additional parameters>] }, as a way to know what to call the operation's
  48. // positional arguments when we add them to the span's `data` object later
  49. const OPERATION_SIGNATURES
  50. = {
  51. // aggregate intentionally not included because `pipeline` arguments are too complex to serialize well
  52. // see https://github.com/getsentry/sentry-javascript/pull/3102
  53. bulkWrite: ['operations'],
  54. countDocuments: ['query'],
  55. createIndex: ['fieldOrSpec'],
  56. createIndexes: ['indexSpecs'],
  57. deleteMany: ['filter'],
  58. deleteOne: ['filter'],
  59. distinct: ['key', 'query'],
  60. dropIndex: ['indexName'],
  61. find: ['query'],
  62. findOne: ['query'],
  63. findOneAndDelete: ['filter'],
  64. findOneAndReplace: ['filter', 'replacement'],
  65. findOneAndUpdate: ['filter', 'update'],
  66. indexExists: ['indexes'],
  67. insertMany: ['docs'],
  68. insertOne: ['doc'],
  69. mapReduce: ['map', 'reduce'],
  70. rename: ['newName'],
  71. replaceOne: ['filter', 'doc'],
  72. updateMany: ['filter', 'update'],
  73. updateOne: ['filter', 'update'],
  74. };
  75. function isCursor(maybeCursor) {
  76. return maybeCursor && typeof maybeCursor === 'object' && maybeCursor.once && typeof maybeCursor.once === 'function';
  77. }
  78. /** Tracing integration for mongo package */
  79. class Mongo {
  80. /**
  81. * @inheritDoc
  82. */
  83. static __initStatic() {this.id = 'Mongo';}
  84. /**
  85. * @inheritDoc
  86. */
  87. /**
  88. * @inheritDoc
  89. */
  90. constructor(options = {}) {
  91. this.name = Mongo.id;
  92. this._operations = Array.isArray(options.operations) ? options.operations : (OPERATIONS );
  93. this._describeOperations = 'describeOperations' in options ? options.describeOperations : true;
  94. this._useMongoose = !!options.useMongoose;
  95. }
  96. /** @inheritdoc */
  97. loadDependency() {
  98. const moduleName = this._useMongoose ? 'mongoose' : 'mongodb';
  99. return (this._module = this._module || utils.loadModule(moduleName));
  100. }
  101. /**
  102. * @inheritDoc
  103. */
  104. setupOnce(_, getCurrentHub) {
  105. if (nodeUtils.shouldDisableAutoInstrumentation(getCurrentHub)) {
  106. debugBuild.DEBUG_BUILD && utils.logger.log('Mongo Integration is skipped because of instrumenter configuration.');
  107. return;
  108. }
  109. const pkg = this.loadDependency();
  110. if (!pkg) {
  111. const moduleName = this._useMongoose ? 'mongoose' : 'mongodb';
  112. debugBuild.DEBUG_BUILD && utils.logger.error(`Mongo Integration was unable to require \`${moduleName}\` package.`);
  113. return;
  114. }
  115. this._instrumentOperations(pkg.Collection, this._operations, getCurrentHub);
  116. }
  117. /**
  118. * Patches original collection methods
  119. */
  120. _instrumentOperations(collection, operations, getCurrentHub) {
  121. operations.forEach((operation) => this._patchOperation(collection, operation, getCurrentHub));
  122. }
  123. /**
  124. * Patches original collection to utilize our tracing functionality
  125. */
  126. _patchOperation(collection, operation, getCurrentHub) {
  127. if (!(operation in collection.prototype)) return;
  128. const getSpanContext = this._getSpanContextFromOperationArguments.bind(this);
  129. utils.fill(collection.prototype, operation, function (orig) {
  130. return function ( ...args) {
  131. const lastArg = args[args.length - 1];
  132. // eslint-disable-next-line deprecation/deprecation
  133. const hub = getCurrentHub();
  134. // eslint-disable-next-line deprecation/deprecation
  135. const scope = hub.getScope();
  136. // eslint-disable-next-line deprecation/deprecation
  137. const client = hub.getClient();
  138. // eslint-disable-next-line deprecation/deprecation
  139. const parentSpan = scope.getSpan();
  140. const sendDefaultPii = _optionalChain([client, 'optionalAccess', _2 => _2.getOptions, 'call', _3 => _3(), 'access', _4 => _4.sendDefaultPii]);
  141. // Check if the operation was passed a callback. (mapReduce requires a different check, as
  142. // its (non-callback) arguments can also be functions.)
  143. if (typeof lastArg !== 'function' || (operation === 'mapReduce' && args.length === 2)) {
  144. // eslint-disable-next-line deprecation/deprecation
  145. const span = _optionalChain([parentSpan, 'optionalAccess', _5 => _5.startChild, 'call', _6 => _6(getSpanContext(this, operation, args, sendDefaultPii))]);
  146. const maybePromiseOrCursor = orig.call(this, ...args);
  147. if (utils.isThenable(maybePromiseOrCursor)) {
  148. return maybePromiseOrCursor.then((res) => {
  149. _optionalChain([span, 'optionalAccess', _7 => _7.end, 'call', _8 => _8()]);
  150. return res;
  151. });
  152. }
  153. // If the operation returns a Cursor
  154. // we need to attach a listener to it to finish the span when the cursor is closed.
  155. else if (isCursor(maybePromiseOrCursor)) {
  156. const cursor = maybePromiseOrCursor ;
  157. try {
  158. cursor.once('close', () => {
  159. _optionalChain([span, 'optionalAccess', _9 => _9.end, 'call', _10 => _10()]);
  160. });
  161. } catch (e) {
  162. // If the cursor is already closed, `once` will throw an error. In that case, we can
  163. // finish the span immediately.
  164. _optionalChain([span, 'optionalAccess', _11 => _11.end, 'call', _12 => _12()]);
  165. }
  166. return cursor;
  167. } else {
  168. _optionalChain([span, 'optionalAccess', _13 => _13.end, 'call', _14 => _14()]);
  169. return maybePromiseOrCursor;
  170. }
  171. }
  172. // eslint-disable-next-line deprecation/deprecation
  173. const span = _optionalChain([parentSpan, 'optionalAccess', _15 => _15.startChild, 'call', _16 => _16(getSpanContext(this, operation, args.slice(0, -1)))]);
  174. return orig.call(this, ...args.slice(0, -1), function (err, result) {
  175. _optionalChain([span, 'optionalAccess', _17 => _17.end, 'call', _18 => _18()]);
  176. lastArg(err, result);
  177. });
  178. };
  179. });
  180. }
  181. /**
  182. * Form a SpanContext based on the user input to a given operation.
  183. */
  184. _getSpanContextFromOperationArguments(
  185. collection,
  186. operation,
  187. args,
  188. sendDefaultPii = false,
  189. ) {
  190. const data = {
  191. 'db.system': 'mongodb',
  192. 'db.name': collection.dbName,
  193. 'db.operation': operation,
  194. 'db.mongodb.collection': collection.collectionName,
  195. };
  196. const spanContext = {
  197. op: 'db',
  198. // TODO v8: Use `${collection.collectionName}.${operation}`
  199. origin: 'auto.db.mongo',
  200. description: operation,
  201. data,
  202. };
  203. // If the operation takes no arguments besides `options` and `callback`, or if argument
  204. // collection is disabled for this operation, just return early.
  205. const signature = OPERATION_SIGNATURES[operation];
  206. const shouldDescribe = Array.isArray(this._describeOperations)
  207. ? this._describeOperations.includes(operation)
  208. : this._describeOperations;
  209. if (!signature || !shouldDescribe || !sendDefaultPii) {
  210. return spanContext;
  211. }
  212. try {
  213. // Special case for `mapReduce`, as the only one accepting functions as arguments.
  214. if (operation === 'mapReduce') {
  215. const [map, reduce] = args ;
  216. data[signature[0]] = typeof map === 'string' ? map : map.name || '<anonymous>';
  217. data[signature[1]] = typeof reduce === 'string' ? reduce : reduce.name || '<anonymous>';
  218. } else {
  219. for (let i = 0; i < signature.length; i++) {
  220. data[`db.mongodb.${signature[i]}`] = JSON.stringify(args[i]);
  221. }
  222. }
  223. } catch (_oO) {
  224. // no-empty
  225. }
  226. return spanContext;
  227. }
  228. }Mongo.__initStatic();
  229. exports.Mongo = Mongo;
  230. //# sourceMappingURL=mongo.js.map