You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2021/03/23 01:21:07 UTC

[skywalking-nodejs] branch master updated: Instrumented MongoDB DB operations (#40)

This is an automated email from the ASF dual-hosted git repository.

kezhenxu94 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-nodejs.git


The following commit(s) were added to refs/heads/master by this push:
     new 2b80d35  Instrumented MongoDB DB operations (#40)
2b80d35 is described below

commit 2b80d35459d13c649e6e37f37d20f2b3ec17160e
Author: Tomasz Pytel <to...@gmail.com>
AuthorDate: Mon Mar 22 22:21:01 2021 -0300

    Instrumented MongoDB DB operations (#40)
---
 src/plugins/MongoDBPlugin.ts             | 207 +++++++++++++++++++------------
 tests/plugins/mongodb/expected.data.yaml |  17 ++-
 2 files changed, 147 insertions(+), 77 deletions(-)

diff --git a/src/plugins/MongoDBPlugin.ts b/src/plugins/MongoDBPlugin.ts
index a4355ce..b9ed87e 100644
--- a/src/plugins/MongoDBPlugin.ts
+++ b/src/plugins/MongoDBPlugin.ts
@@ -20,7 +20,6 @@
 import SwPlugin, {wrapPromise} from '../core/SwPlugin';
 import ContextManager from '../trace/context/ContextManager';
 import { Component } from '../trace/Component';
-import ExitSpan from '../trace/span/ExitSpan';
 import Tag from '../Tag';
 import { SpanLayer } from '../proto/language-agent/Tracing_pb';
 import PluginInstaller from '../core/PluginInstaller';
@@ -30,7 +29,9 @@ class MongoDBPlugin implements SwPlugin {
   readonly module = 'mongodb';
   readonly versions = '*';
 
+  Collection: any;
   Cursor: any;
+  Db: any;
 
   // Experimental method to determine proper end time of cursor DB operation, we stop the span when the cursor is closed.
   // Problematic because other exit spans may be created during processing, for this reason we do not .resync() this
@@ -52,9 +53,10 @@ class MongoDBPlugin implements SwPlugin {
   }
 
   install(installer: PluginInstaller): void {
-    const plugin     = this;
-    const Collection = installer.require('mongodb/lib/collection');
-    this.Cursor      = installer.require('mongodb/lib/cursor');
+    const plugin    = this;
+    this.Collection = installer.require('mongodb/lib/collection');
+    this.Cursor     = installer.require('mongodb/lib/cursor');
+    this.Db         = installer.require('mongodb/lib/db');
 
     const wrapCallbackWithCursorMaybe = (span: any, args: any[], idx: number): boolean => {
       const callback = args.length > idx && typeof args[idx = args.length - 1] === 'function' ? args[idx] : null;
@@ -87,7 +89,7 @@ class MongoDBPlugin implements SwPlugin {
       return str;
     }
 
-    const insertFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [doc(s), options, callback]
+    const collInsertFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [doc(s), options, callback]
       span.tag(Tag.dbStatement(`${this.s.namespace.collection}.${operation}()`));
 
       if (agentConfig.mongoTraceParameters)
@@ -96,13 +98,13 @@ class MongoDBPlugin implements SwPlugin {
       return wrapCallbackWithCursorMaybe(span, args, 1);
     };
 
-    const deleteFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [filter, options, callback]
+    const collDeleteFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [filter, options, callback]
       span.tag(Tag.dbStatement(`${this.s.namespace.collection}.${operation}(${stringify(args[0])})`));
 
       return wrapCallbackWithCursorMaybe(span, args, 1);
     };
 
-    const updateFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [filter, update, options, callback]
+    const collUpdateFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [filter, update, options, callback]
       span.tag(Tag.dbStatement(`${this.s.namespace.collection}.${operation}(${stringify(args[0])})`));
 
       if (agentConfig.mongoTraceParameters)
@@ -111,19 +113,19 @@ class MongoDBPlugin implements SwPlugin {
       return wrapCallbackWithCursorMaybe(span, args, 2);
     };
 
-    const findOneFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [query, options, callback]
+    const collFindOneFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [query, options, callback]
       span.tag(Tag.dbStatement(`${this.s.namespace.collection}.${operation}(${typeof args[0] !== 'function' ? stringify(args[0]) : ''})`));
 
       return wrapCallbackWithCursorMaybe(span, args, 0);
     };
 
-    const findAndRemoveFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [query, sort, options, callback]
+    const collFindAndRemoveFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [query, sort, options, callback]
       span.tag(Tag.dbStatement(`${this.s.namespace.collection}.${operation}(${stringify(args[0])}${typeof args[1] !== 'function' && args[1] !== undefined ? ', ' + stringify(args[1]) : ''})`));
 
       return wrapCallbackWithCursorMaybe(span, args, 1);
     };
 
-    const findAndModifyFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [query, sort, doc, options, callback]
+    const collFindAndModifyFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [query, sort, doc, options, callback]
       let params = stringify(args[0]);
 
       if (typeof args[1] !== 'function' && args[1] !== undefined) {
@@ -140,91 +142,145 @@ class MongoDBPlugin implements SwPlugin {
       return wrapCallbackWithCursorMaybe(span, args, 1);
     };
 
-    const mapReduceFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [map, reduce, options, callback]
+    const collMapReduceFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [map, reduce, options, callback]
       span.tag(Tag.dbStatement(`${this.s.namespace.collection}.${operation}(${args[0]}, ${args[1]})`));
 
       return wrapCallbackWithCursorMaybe(span, args, 2);
     };
 
-    const dropFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [options, callback]
+    const collDropFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [options, callback]
       span.tag(Tag.dbStatement(`${this.s.namespace.collection}.${operation}()`));
 
       return wrapCallbackWithCursorMaybe(span, args, 0);
     };
 
-    this.interceptOperation(Collection, 'insert', insertFunc);
-    this.interceptOperation(Collection, 'insertOne', insertFunc);
-    this.interceptOperation(Collection, 'insertMany', insertFunc);
-    this.interceptOperation(Collection, 'save', insertFunc);
-    this.interceptOperation(Collection, 'deleteOne', deleteFunc);
-    this.interceptOperation(Collection, 'deleteMany', deleteFunc);
-    this.interceptOperation(Collection, 'remove', deleteFunc);
-    this.interceptOperation(Collection, 'removeOne', deleteFunc);
-    this.interceptOperation(Collection, 'removeMany', deleteFunc);
-    this.interceptOperation(Collection, 'update', updateFunc);
-    this.interceptOperation(Collection, 'updateOne', updateFunc);
-    this.interceptOperation(Collection, 'updateMany', updateFunc);
-    this.interceptOperation(Collection, 'replaceOne', updateFunc);
-    this.interceptOperation(Collection, 'find', findOneFunc);  // cursor
-    this.interceptOperation(Collection, 'findOne', findOneFunc);
-    this.interceptOperation(Collection, 'findOneAndDelete', deleteFunc);
-    this.interceptOperation(Collection, 'findOneAndReplace', updateFunc);
-    this.interceptOperation(Collection, 'findOneAndUpdate', updateFunc);
-    this.interceptOperation(Collection, 'findAndRemove', findAndRemoveFunc);
-    this.interceptOperation(Collection, 'findAndModify', findAndModifyFunc);
-
-    this.interceptOperation(Collection, 'bulkWrite', insertFunc);
-    this.interceptOperation(Collection, 'mapReduce', mapReduceFunc);
-    this.interceptOperation(Collection, 'aggregate', deleteFunc);  // cursor
-    this.interceptOperation(Collection, 'distinct', findAndRemoveFunc);
-    this.interceptOperation(Collection, 'count', findOneFunc);
-    this.interceptOperation(Collection, 'estimatedDocumentCount', dropFunc);
-    this.interceptOperation(Collection, 'countDocuments', findOneFunc);
-
-    this.interceptOperation(Collection, 'createIndex', deleteFunc);
-    this.interceptOperation(Collection, 'createIndexes', deleteFunc);
-    this.interceptOperation(Collection, 'ensureIndex', deleteFunc);
-    this.interceptOperation(Collection, 'dropIndex', deleteFunc);
-    this.interceptOperation(Collection, 'dropIndexes', dropFunc);
-    this.interceptOperation(Collection, 'dropAllIndexes', dropFunc);
-    this.interceptOperation(Collection, 'reIndex', dropFunc);
-
-    this.interceptOperation(Collection, 'indexes', dropFunc);
-    this.interceptOperation(Collection, 'indexExists', deleteFunc);
-    this.interceptOperation(Collection, 'indexInformation', dropFunc);
-    this.interceptOperation(Collection, 'listIndexes', dropFunc);  // cursor
-    this.interceptOperation(Collection, 'stats', dropFunc);
-
-    this.interceptOperation(Collection, 'rename', deleteFunc);
-    this.interceptOperation(Collection, 'drop', dropFunc);
-    this.interceptOperation(Collection, 'options', dropFunc);
-    this.interceptOperation(Collection, 'isCapped', dropFunc);
-
-    // TODO
-
-    //   DB functions
-
-    // TODO?
+    const dbAddUserFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [username, password, options, callback]
+      span.tag(Tag.dbStatement(`${operation}(${stringify(args[0])})`));
 
-    //   group
+      return wrapCallbackWithCursorMaybe(span, args, 2);
+    };
+
+    const dbRemoveUserFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [username, options, callback]
+      span.tag(Tag.dbStatement(`${operation}(${stringify(args[0])})`));
+
+      return wrapCallbackWithCursorMaybe(span, args, 1);
+    };
+
+    const dbRenameCollectionFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [fromCollection, toCollection, options, callback]
+      span.tag(Tag.dbStatement(`${operation}(${stringify(args[0])}, ${stringify(args[1])})`));
+
+      return wrapCallbackWithCursorMaybe(span, args, 2);
+    };
+
+    const dbCollectionsFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [options, callback]
+      span.tag(Tag.dbStatement(`${operation}()`));
 
-    // NODO:
+      return wrapCallbackWithCursorMaybe(span, args, 0);
+    };
 
+    const dbEvalFunc = function(this: any, operation: string, span: any, args: any[]): boolean {  // args = [code, parameters, options, callback]
+      span.tag(Tag.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
+
+    // NODO collection:
     //   initializeUnorderedBulkOp
     //   initializeOrderedBulkOp
-    //   parallelCollectionScan
     //   geoHaystackSearch
     //   watch
+
+    // NODO db:
+    //   admin
+    //   profilingLevel
+    //   setProfilingLevel
+    //   unref
+    //   watch
   }
 
-  interceptOperation(Collection: any, operation: string, operationFunc: any): void {
+  interceptOperation(Cls: any, operation: string, operationFunc: any): void {
     const plugin    = this;
-    const _original = Collection.prototype[operation];
+    const _original = Cls.prototype[operation];
 
     if (!_original)
         return;
 
-    Collection.prototype[operation] = function(...args: any[]) {
+    Cls.prototype[operation] = function(...args: any[]) {
       const spans = ContextManager.spans;
       let   span = spans[spans.length - 1];
 
@@ -235,13 +291,12 @@ class MongoDBPlugin implements SwPlugin {
       if (span?.component === Component.MONGODB)  // mongodb has called into itself internally, span instanceof ExitSpan assumed
         return _original.apply(this, args);
 
-      let host: string;
+      let host = '???';
 
       try {
-        host = this.s.db.serverConfig.s.options.servers.map((s: any) => `${s.host}:${s.port}`).join(',');  // will this work for non-NativeTopology?
-      } catch {
-        host = '???';
-      }
+        const db = this instanceof plugin.Collection ? this.s.db : this;
+        host = db.serverConfig.s.options.servers.map((s: any) => `${s.host}:${s.port}`).join(',');  // will this work for non-NativeTopology?
+      } catch { /* nop */ }
 
       span = ContextManager.current.newExitSpan('MongoDB/' + operation, host, Component.MONGODB);
 
diff --git a/tests/plugins/mongodb/expected.data.yaml b/tests/plugins/mongodb/expected.data.yaml
index cb5165f..442a27f 100644
--- a/tests/plugins/mongodb/expected.data.yaml
+++ b/tests/plugins/mongodb/expected.data.yaml
@@ -21,7 +21,7 @@ segmentItems:
     segments:
       - segmentId: not null
         spans:
-          - operationName: MongoDB/findOne
+          - operationName: MongoDB/collection
             operationId: 0
             parentSpanId: 0
             spanId: 1
@@ -35,6 +35,21 @@ segmentItems:
             tags:
               - { key: db.type, value: MongoDB }
               - { key: db.instance, value: admin }
+              - { key: db.statement, value: "collection(\"docs\")" }
+          - operationName: MongoDB/findOne
+            operationId: 0
+            parentSpanId: 0
+            spanId: 2
+            spanLayer: Database
+            startTime: gt 0
+            endTime: gt 0
+            componentId: 9
+            spanType: Exit
+            peer: mongo:27017
+            skipAnalysis: false
+            tags:
+              - { key: db.type, value: MongoDB }
+              - { key: db.instance, value: admin }
               - { key: db.statement, value: docs.findOne() }
           - operationName: /mongo
             operationId: 0