UNPKG

@kangc/skywalking-backend-js

Version:

The NodeJS agent for Apache SkyWalking

289 lines 16.5 kB
"use strict"; /*! * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var SwPlugin_1 = require("../core/SwPlugin"); var ContextManager_1 = tslib_1.__importDefault(require("../trace/context/ContextManager")); var Component_1 = require("../trace/Component"); var Tag_1 = tslib_1.__importDefault(require("../Tag")); var Tracing_pb_1 = require("../proto/language-agent/Tracing_pb"); var AgentConfig_1 = tslib_1.__importDefault(require("../config/AgentConfig")); var MongoDBPlugin = /** @class */ (function () { function MongoDBPlugin() { this.module = 'mongodb'; this.versions = '*'; } MongoDBPlugin.prototype.hookCursorMaybe = function (span, cursor) { if (!(cursor instanceof this.Cursor)) return false; SwPlugin_1.wrapEmit(span, cursor, true, 'close'); return true; }; MongoDBPlugin.prototype.install = function (installer) { var plugin = this; this.Collection = installer.require('mongodb/lib/collection'); this.Cursor = installer.require('mongodb/lib/cursor'); this.Db = installer.require('mongodb/lib/db'); var wrapCallbackWithCursorMaybe = function (span, args, idx) { var callback = args.length > idx && typeof args[(idx = args.length - 1)] === 'function' ? args[idx] : null; if (!callback) return false; args[idx] = function () { // arguments = [error: any, result: any] span.mongodbInCall = false; // we do this because some operations may call callback immediately which would not create a new span for any operations in that callback (db.collection()) if (arguments[0]) span.error(arguments[0]); if (arguments[0] || !plugin.hookCursorMaybe(span, arguments[1])) span.stop(); return callback.apply(this, arguments); }; return true; }; var stringify = function (params) { if (params === undefined) return ''; else if (typeof params === 'function') return "" + params; var str = JSON.stringify(params); if (str.length > AgentConfig_1.default.mongoParametersMaxLength) str = str.slice(0, AgentConfig_1.default.mongoParametersMaxLength) + ' ...'; return str; }; var collInsertFunc = function (operation, span, args) { // args = [doc(s), options, callback] span.tag(Tag_1.default.dbStatement(this.s.namespace.collection + "." + operation + "()")); if (AgentConfig_1.default.mongoTraceParameters) span.tag(Tag_1.default.dbMongoParameters(stringify(args[0]))); return wrapCallbackWithCursorMaybe(span, args, 1); }; var collDeleteFunc = function (operation, span, args) { // args = [filter, options, callback] span.tag(Tag_1.default.dbStatement(this.s.namespace.collection + "." + operation + "(" + stringify(args[0]) + ")")); return wrapCallbackWithCursorMaybe(span, args, 1); }; var collUpdateFunc = function (operation, span, args) { // args = [filter, update, options, callback] span.tag(Tag_1.default.dbStatement(this.s.namespace.collection + "." + operation + "(" + stringify(args[0]) + ")")); if (AgentConfig_1.default.mongoTraceParameters) span.tag(Tag_1.default.dbMongoParameters(stringify(args[1]))); return wrapCallbackWithCursorMaybe(span, args, 2); }; var collFindOneFunc = function (operation, span, args) { // args = [query, options, callback] span.tag(Tag_1.default.dbStatement(this.s.namespace.collection + "." + operation + "(" + (typeof args[0] !== 'function' ? stringify(args[0]) : '') + ")")); return wrapCallbackWithCursorMaybe(span, args, 0); }; var collFindAndRemoveFunc = function (operation, span, args) { // args = [query, sort, options, callback] span.tag(Tag_1.default.dbStatement(this.s.namespace.collection + "." + operation + "(" + stringify(args[0]) + (typeof args[1] !== 'function' && args[1] !== undefined ? ', ' + stringify(args[1]) : '') + ")")); return wrapCallbackWithCursorMaybe(span, args, 1); }; var collFindAndModifyFunc = function (operation, span, args) { // args = [query, sort, doc, options, callback] var params = stringify(args[0]); if (typeof args[1] !== 'function' && args[1] !== undefined) { params += ', ' + stringify(args[1]); if (typeof args[2] !== 'function' && args[2] !== undefined) { if (AgentConfig_1.default.mongoTraceParameters) span.tag(Tag_1.default.dbMongoParameters(stringify(args[2]))); } } span.tag(Tag_1.default.dbStatement(this.s.namespace.collection + "." + operation + "(" + params + ")")); return wrapCallbackWithCursorMaybe(span, args, 1); }; var collMapReduceFunc = function (operation, span, args) { // args = [map, reduce, options, callback] span.tag(Tag_1.default.dbStatement(this.s.namespace.collection + "." + operation + "(" + stringify(args[0]) + ", " + stringify(args[1]) + ")")); return wrapCallbackWithCursorMaybe(span, args, 2); }; var collDropFunc = function (operation, span, args) { // args = [options, callback] span.tag(Tag_1.default.dbStatement(this.s.namespace.collection + "." + operation + "()")); return wrapCallbackWithCursorMaybe(span, args, 0); }; var dbAddUserFunc = function (operation, span, args) { // args = [username, password, options, callback] span.tag(Tag_1.default.dbStatement(operation + "(" + stringify(args[0]) + ")")); return wrapCallbackWithCursorMaybe(span, args, 2); }; var dbRemoveUserFunc = function (operation, span, args) { // args = [username, options, callback] span.tag(Tag_1.default.dbStatement(operation + "(" + stringify(args[0]) + ")")); return wrapCallbackWithCursorMaybe(span, args, 1); }; var dbRenameCollectionFunc = function (operation, span, args) { // args = [fromCollection, toCollection, options, callback] span.tag(Tag_1.default.dbStatement(operation + "(" + stringify(args[0]) + ", " + stringify(args[1]) + ")")); return wrapCallbackWithCursorMaybe(span, args, 2); }; var dbCollectionsFunc = function (operation, span, args) { // args = [options, callback] span.tag(Tag_1.default.dbStatement(operation + "()")); return wrapCallbackWithCursorMaybe(span, args, 0); }; var dbEvalFunc = function (operation, span, args) { // args = [code, parameters, options, callback] span.tag(Tag_1.default.dbStatement(operation + "(" + stringify(args[0]) + (typeof args[1] !== 'function' && args[1] !== undefined ? ', ' + stringify(args[1]) : '') + ")")); return wrapCallbackWithCursorMaybe(span, args, 1); }; this.interceptOperation(this.Collection, 'insert', collInsertFunc); this.interceptOperation(this.Collection, 'insertOne', collInsertFunc); this.interceptOperation(this.Collection, 'insertMany', collInsertFunc); this.interceptOperation(this.Collection, 'save', collInsertFunc); this.interceptOperation(this.Collection, 'deleteOne', collDeleteFunc); this.interceptOperation(this.Collection, 'deleteMany', collDeleteFunc); this.interceptOperation(this.Collection, 'remove', collDeleteFunc); this.interceptOperation(this.Collection, 'removeOne', collDeleteFunc); this.interceptOperation(this.Collection, 'removeMany', collDeleteFunc); this.interceptOperation(this.Collection, 'update', collUpdateFunc); this.interceptOperation(this.Collection, 'updateOne', collUpdateFunc); this.interceptOperation(this.Collection, 'updateMany', collUpdateFunc); this.interceptOperation(this.Collection, 'replaceOne', collUpdateFunc); this.interceptOperation(this.Collection, 'find', collFindOneFunc); // cursor this.interceptOperation(this.Collection, 'findOne', collFindOneFunc); this.interceptOperation(this.Collection, 'findOneAndDelete', collDeleteFunc); this.interceptOperation(this.Collection, 'findOneAndReplace', collUpdateFunc); this.interceptOperation(this.Collection, 'findOneAndUpdate', collUpdateFunc); this.interceptOperation(this.Collection, 'findAndRemove', collFindAndRemoveFunc); this.interceptOperation(this.Collection, 'findAndModify', collFindAndModifyFunc); this.interceptOperation(this.Collection, 'bulkWrite', collInsertFunc); this.interceptOperation(this.Collection, 'mapReduce', collMapReduceFunc); this.interceptOperation(this.Collection, 'aggregate', collDeleteFunc); // cursor this.interceptOperation(this.Collection, 'distinct', collFindAndRemoveFunc); this.interceptOperation(this.Collection, 'count', collFindOneFunc); this.interceptOperation(this.Collection, 'estimatedDocumentCount', collDropFunc); this.interceptOperation(this.Collection, 'countDocuments', collFindOneFunc); this.interceptOperation(this.Collection, 'createIndex', collDeleteFunc); this.interceptOperation(this.Collection, 'createIndexes', collDeleteFunc); this.interceptOperation(this.Collection, 'ensureIndex', collDeleteFunc); this.interceptOperation(this.Collection, 'dropIndex', collDeleteFunc); this.interceptOperation(this.Collection, 'dropIndexes', collDropFunc); this.interceptOperation(this.Collection, 'dropAllIndexes', collDropFunc); this.interceptOperation(this.Collection, 'reIndex', collDropFunc); this.interceptOperation(this.Collection, 'indexes', collDropFunc); this.interceptOperation(this.Collection, 'indexExists', collDeleteFunc); this.interceptOperation(this.Collection, 'indexInformation', collDropFunc); this.interceptOperation(this.Collection, 'listIndexes', collDropFunc); // cursor this.interceptOperation(this.Collection, 'stats', collDropFunc); this.interceptOperation(this.Collection, 'rename', collDeleteFunc); this.interceptOperation(this.Collection, 'drop', collDropFunc); this.interceptOperation(this.Collection, 'options', collDropFunc); this.interceptOperation(this.Collection, 'isCapped', collDropFunc); this.interceptOperation(this.Db, 'aggregate', dbAddUserFunc); // cursor this.interceptOperation(this.Db, 'addUser', dbAddUserFunc); this.interceptOperation(this.Db, 'removeUser', dbRemoveUserFunc); this.interceptOperation(this.Db, 'collection', dbRemoveUserFunc); this.interceptOperation(this.Db, 'createCollection', dbRemoveUserFunc); this.interceptOperation(this.Db, 'renameCollection', dbRenameCollectionFunc); this.interceptOperation(this.Db, 'dropCollection', dbRemoveUserFunc); this.interceptOperation(this.Db, 'collections', dbCollectionsFunc); this.interceptOperation(this.Db, 'listCollections', dbAddUserFunc); // cursor this.interceptOperation(this.Db, 'createIndex', dbRenameCollectionFunc); this.interceptOperation(this.Db, 'ensureIndex', dbRenameCollectionFunc); this.interceptOperation(this.Db, 'indexInformation', dbRemoveUserFunc); this.interceptOperation(this.Db, 'stats', dbCollectionsFunc); this.interceptOperation(this.Db, 'command', dbRemoveUserFunc); this.interceptOperation(this.Db, 'eval', dbEvalFunc); this.interceptOperation(this.Db, 'executeDbAdminCommand', dbRemoveUserFunc); this.interceptOperation(this.Db, 'dropDatabase', dbCollectionsFunc); // TODO collection? // group // parallelCollectionScan // geoHaystackSearch // NODO collection: // initializeUnorderedBulkOp // initializeOrderedBulkOp // watch // NODO db: // admin // profilingLevel // setProfilingLevel // unref // watch }; MongoDBPlugin.prototype.interceptOperation = function (Cls, operation, operationFunc) { var plugin = this; var _original = Cls.prototype[operation]; if (!_original) return; Cls.prototype[operation] = function () { var _a; var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var span = ContextManager_1.default.currentSpan; // XXX: mongodb calls back into itself at this level in several places, for this reason we just do a normal call // if this is detected instead of opening a new span. This should not affect secondary db calls being recorded // from a cursor since this span is kept async until the cursor is closed, at which point it is stoppped. if ((_a = span) === null || _a === void 0 ? void 0 : _a.mongodbInCall) // mongodb has called into itself internally return _original.apply(this, args); var host = '???'; try { var db = this instanceof plugin.Collection ? this.s.db : this; host = db.serverConfig.s.options.servers.map(function (s) { return s.host + ":" + s.port; }).join(','); // will this work for non-NativeTopology? } catch (_b) { /* nop */ } span = ContextManager_1.default.current.newExitSpan('MongoDB/' + operation, Component_1.Component.MONGODB); span.start(); try { if (span.component === Component_1.Component.UNKNOWN) // in case mongoose sitting on top span.component = Component_1.Component.MONGODB; span.layer = Tracing_pb_1.SpanLayer.DATABASE; span.peer = host; span.tag(Tag_1.default.dbType('MongoDB')); span.tag(Tag_1.default.dbInstance("" + this.s.namespace.db)); var hasCB = operationFunc.call(this, operation, span, args); span.mongodbInCall = true; var ret = _original.apply(this, args); span.mongodbInCall = false; if (!hasCB) { if (plugin.hookCursorMaybe(span, ret)) { // NOOP } else if (ret && typeof ret.then === 'function') { // generic Promise check ret = SwPlugin_1.wrapPromise(span, ret); } else { // no callback passed in and no Promise or Cursor returned, play it safe span.stop(); return ret; } } span.async(); return ret; } catch (e) { span.error(e); span.stop(); throw e; } }; }; return MongoDBPlugin; }()); // noinspection JSUnusedGlobalSymbols exports.default = new MongoDBPlugin(); //# sourceMappingURL=MongoDBPlugin.js.map