Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
/*
 * (c) Copyright IBM Corp. 2021
 * (c) Copyright Instana Inc. and contributors 2016
 */

'use strict';

/**
 * @typedef {import('../../clsHooked/context').InstanaCLSContext} InstanaCLSContext
 * @typedef {import('@supertenant/superbrain/lib/binding.cjs').JSOpenSpanResult} JSOpenSpanResult
 * @typedef {import('@supertenant/superbrain/types/binding.cjs').SpanLabels} SpanLabels
 * @typedef {Error & { ok?: number, code?: number, codeName?: string, message?: string }} MongodbError
 * @typedef {{
 *  Connection?: {
 *    prototype: any
 *  }
 * }} CmapConnection
 */

const shimmer = require('shimmer');

const requireHook = require('../../../util/requireHook');
const tracingUtil = require('../../tracingUtil');
const constants = require('../../constants');
const cls = require('../../cls');

const { superbrain } = require('@supertenant/superbrain');
const superconsts = require('@supertenant/superconsts');
const { getOrCreateTask } = require('../../taskManager');
const { getWaitPollInterval, getMongodbRejectActionResult, getDelayDuration } = require('../../actions');

const { dedupReportError } = require('../../../logger');

// Create a mapping from code to codeName to mimic mongodb errors.
const DefaultErrorConstants = {
  11601: 'Interrupted',
  262: 'ExceededTimeLimit'
};

let ErrorConstants = {};
Object.assign(ErrorConstants, DefaultErrorConstants);

let isActive = true;

const commands = [
  //
  'aggregate',
  'count',
  'delete',
  'distinct',
  'find',
  'findAndModify',
  'findandmodify',
  'getMore',
  'getmore',
  'insert',
  'update'
];

exports.spanName = 'mongo';
exports.batchable = true;

exports.init = function init() {
  // unified topology layer
  requireHook.onFileLoad(/\/mongodb\/lib\/cmap\/connection.js/, instrumentCmapConnection);
  // mongodb >= 3.3.x, legacy topology layer
  requireHook.onFileLoad(/\/mongodb\/lib\/core\/connection\/pool.js/, instrumentLegacyTopologyPool);
  // mongodb < 3.3.x, legacy topology layer
  requireHook.onFileLoad(/\/mongodb-core\/lib\/connection\/pool.js/, instrumentLegacyTopologyPool);
};

/**
 * @param {CmapConnection} connection
 */
function instrumentCmapConnection(connection) {
  superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumenting mongodb cmap connection');
  if (connection.Connection && connection.Connection.prototype) {
    // collection.findOne, collection.find et al.
    shimmer.wrap(connection.Connection.prototype, 'query', shimCmapQuery);
    // collection.count et al.
    shimmer.wrap(connection.Connection.prototype, 'command', shimCmapCommand);

    [
      'insert', // collection.insertOne et al.
      'update', // collection.replaceOne et al.
      'remove' // collection.delete et al.
    ].forEach(fnName => {
      if (connection.Connection.prototype[fnName]) {
        shimmer.wrap(connection.Connection.prototype, fnName, shimCmapMethod);
      } else {
        superbrain.log(superconsts.BrainLogLevel.Debug, `instrumentCmapConnection: no ${fnName} found`);
      }
    });

    shimmer.wrap(connection.Connection.prototype, 'getMore', shimCmapGetMore);
  } else {
    superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentCmapConnection: no Connection.prototype found');
  }
}

function shimCmapQuery(original) {
  return function tmp() {
    if (cls.skipExitTracing({ isActive })) {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'shimCmapQuery: skipping exit tracing');
      return original.apply(this, arguments);
    }

    const originalArgs = new Array(arguments.length);
    for (let i = 0; i < arguments.length; i++) {
      originalArgs[i] = arguments[i];
    }

    return instrumentedCmapQuery(this, original, originalArgs);
  };
}

function shimCmapCommand(original) {
  return function () {
    if (cls.skipExitTracing({ isActive })) {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'shimCmapCommand: skipping exit tracing');
      return original.apply(this, arguments);
    }

    const command = arguments[1] && commands.find(c => arguments[1][c]);

    if (!command) {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'shimCmapCommand: no command found');
      return original.apply(this, arguments);
    }

    const originalArgs = new Array(arguments.length);
    for (let i = 0; i < arguments.length; i++) {
      originalArgs[i] = arguments[i];
    }

    return instrumentedCmapMethod(this, original, originalArgs, command);
  };
}

function shimCmapMethod(original) {
  return function () {
    if (cls.skipExitTracing({ isActive })) {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'shimCmapMethod: skipping exit tracing');
      return original.apply(this, arguments);
    }

    const originalArgs = new Array(arguments.length);
    for (let i = 0; i < arguments.length; i++) {
      originalArgs[i] = arguments[i];
    }

    return instrumentedCmapMethod(this, original, originalArgs, original.name);
  };
}

function shimCmapGetMore(original) {
  return function () {
    if (cls.skipExitTracing({ isActive })) {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'shimCmapGetMore: skipping exit tracing');
      return original.apply(this, arguments);
    }

    const originalArgs = new Array(arguments.length);
    for (let i = 0; i < arguments.length; i++) {
      originalArgs[i] = arguments[i];
    }

    return instrumentedCmapGetMore(this, original, originalArgs);
  };
}

/**
 * @param {InstanaCLSContext} ctx
 */
function instrumentedCmapQuery(ctx, originalQuery, originalArgs) {
  const { originalCallback, callbackIndex } = findCallback(originalArgs);
  if (callbackIndex < 0) {
    superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapQuery: no callback found');
    return originalQuery.apply(ctx, originalArgs);
  }

  return cls.ns.runAndReturn(() => {
    const span = cls.startSpan(exports.spanName, constants.EXIT);

    const namespace = originalArgs[0];
    const splitNamespace = dbAndCollectionFromNamespace(namespace);
    const cmd = originalArgs[1];

    /** @type{string?} */
    let command;
    if (cmd) {
      command = findCommand(cmd);
    }

    let { service, hostnameAndPort } = getServiceHostnameAndPortFromCtx(ctx);
    let spanData = {
      command,
      service,
      namespace
    };
    span.data.peer = hostnameAndPort;
    span.data.mongo = spanData;

    const jsonAndFilter = readJsonOrFilter(cmd, span);
    const json = jsonAndFilter.stringJson;
    const filter = jsonAndFilter.filter;

    /** @type {JSOpenSpanResult} */
    let openSpanResult = null;
    const stTaskId = getOrCreateTask();
    if (stTaskId !== 0) {
      /** @type{import('@supertenant/superbrain/types/binding.cjs').SpanLabels} */
      const stSpanData = {};
      stSpanData[superconsts.Label.SupertenantResourceType] = superconsts.ResourceType.Mongodb;
      stSpanData[superconsts.Label.IntegrationModuleResourceId] = hostnameAndPort.hostname;
      stSpanData[superconsts.Label.DbDatabase] = splitNamespace.db;
      stSpanData[superconsts.Label.DbMongodbCollection] = splitNamespace.collection;
      stSpanData[superconsts.Label.DbHost] = hostnameAndPort.hostname;
      stSpanData[superconsts.Label.DbPort] = hostnameAndPort.port.toString();
      stSpanData[superconsts.Label.DbCommand] = command;
      stSpanData[superconsts.Label.DbMongodbCommandJson] = json;
      stSpanData[superconsts.Label.DbMongodbFilter] = filter;
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapQuery: openSpan');
      openSpanResult = superbrain.openSpan(stTaskId, superconsts.SpanType.ClientRequest, stSpanData);
    } else {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapQuery: no stTaskId');
    }

    const stCallback = createWrappedCallback(span, openSpanResult, originalCallback);
    originalArgs[callbackIndex] = stCallback;

    if (openSpanResult) {
      callbackHandleAction(openSpanResult, ctx, originalQuery, originalArgs, stCallback);
    } else {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapQuery: no openSpanResult');
      return originalQuery.apply(ctx, originalArgs);
    }
  });
}

/**
 *
 * @param {InstanaCLSContext} ctx
 * @param {string} command
 */
function instrumentedCmapMethod(ctx, originalMethod, originalArgs, command) {
  const { originalCallback, callbackIndex } = findCallback(originalArgs);
  if (callbackIndex < 0) {
    superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapMethod: no callback found');
    return originalMethod.apply(ctx, originalArgs);
  }

  return cls.ns.runAndReturn(() => {
    const span = cls.startSpan(exports.spanName, constants.EXIT);

    const namespace = originalArgs[0];
    const splitNamespace = dbAndCollectionFromNamespace(namespace);

    let { service, hostnameAndPort } = getServiceHostnameAndPortFromCtx(ctx);
    let spanData = {
      command,
      service,
      namespace
    };

    span.data.peer = hostnameAndPort;
    span.data.mongo = spanData;

    let json;
    let filter;
    // TODO: remove the not-insert check?
    if (command && command.indexOf('insert') < 0) {
      // we do not capture the document for insert commands
      const jsonAndFilter = readJsonOrFilter(originalArgs[1], span);
      json = jsonAndFilter.stringJson;
      filter = jsonAndFilter.filter;
    }

    /** @type {JSOpenSpanResult} */
    let openSpanResult = null;
    const stTaskId = getOrCreateTask();
    if (stTaskId !== 0) {
      /** @type{SpanLabels} */
      const stSpanData = {};
      stSpanData[superconsts.Label.SupertenantResourceType] = superconsts.ResourceType.Mongodb;
      stSpanData[superconsts.Label.IntegrationModuleResourceId] = hostnameAndPort.hostname;
      stSpanData[superconsts.Label.DbDatabase] = splitNamespace.db;
      stSpanData[superconsts.Label.DbMongodbCollection] = splitNamespace.collection;
      stSpanData[superconsts.Label.DbHost] = hostnameAndPort.hostname;
      stSpanData[superconsts.Label.DbPort] = hostnameAndPort.port.toString();
      stSpanData[superconsts.Label.DbCommand] = command;
      stSpanData[superconsts.Label.DbMongodbCommandJson] = json;
      stSpanData[superconsts.Label.DbMongodbFilter] = filter;
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapMethod: openSpan');
      openSpanResult = superbrain.openSpan(stTaskId, superconsts.SpanType.ClientRequest, stSpanData);
    } else {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapMethod: no stTaskId');
    }

    const stCallback = createWrappedCallback(span, openSpanResult, originalCallback);
    originalArgs[callbackIndex] = stCallback;

    if (openSpanResult) {
      callbackHandleAction(openSpanResult, ctx, originalMethod, originalArgs, stCallback);
    } else {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapMethod: no openSpanResult');
      return originalMethod.apply(ctx, originalArgs);
    }
  });
}

function instrumentedCmapGetMore(ctx, originalMethod, originalArgs) {
  const { originalCallback, callbackIndex } = findCallback(originalArgs);
  if (callbackIndex < 0) {
    superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapGetMore: no callback found');
    return originalMethod.apply(ctx, originalArgs);
  }

  return cls.ns.runAndReturn(() => {
    const span = cls.startSpan(exports.spanName, constants.EXIT);

    const namespace = originalArgs[0];
    const splitNamespace = dbAndCollectionFromNamespace(namespace);

    let { service, hostnameAndPort } = getServiceHostnameAndPortFromCtx(ctx);
    let spanData = {
      command: 'getMore',
      service,
      namespace
    };

    span.data.peer = hostnameAndPort;
    span.data.mongo = spanData;

    /** @type {JSOpenSpanResult} */
    let openSpanResult = null;
    const stTaskId = getOrCreateTask();
    if (stTaskId !== 0) {
      /** @type{SpanLabels} */
      const stSpanData = {};
      stSpanData[superconsts.Label.SupertenantResourceType] = superconsts.ResourceType.Mongodb;
      stSpanData[superconsts.Label.IntegrationModuleResourceId] = hostnameAndPort.hostname;
      stSpanData[superconsts.Label.DbDatabase] = splitNamespace.db;
      stSpanData[superconsts.Label.DbMongodbCollection] = splitNamespace.collection;
      stSpanData[superconsts.Label.DbHost] = hostnameAndPort.hostname;
      stSpanData[superconsts.Label.DbPort] = hostnameAndPort.port;
      stSpanData[superconsts.Label.DbCommand] = 'getMore';
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapGetMore: openSpan');
      openSpanResult = superbrain.openSpan(stTaskId, superconsts.SpanType.ClientRequest, stSpanData);
    } else {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapGetMore: no stTaskId');
    }

    const stCallback = createWrappedCallback(span, openSpanResult, originalCallback);
    originalArgs[callbackIndex] = stCallback;

    if (openSpanResult) {
      callbackHandleAction(openSpanResult, ctx, originalMethod, originalArgs, stCallback);
    } else {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedCmapGetMore: no openSpanResult');
      return originalMethod.apply(ctx, originalArgs);
    }
  });
}

function instrumentLegacyTopologyPool(Pool) {
  superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumenting mongodb legacy topology pool');
  shimmer.wrap(Pool.prototype, 'write', shimLegacyWrite);
}

function shimLegacyWrite(original) {
  return function () {
    if (cls.skipExitTracing({ isActive })) {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'shimLegacyWrite: skipping exit tracing');
      return original.apply(this, arguments);
    }

    const originalArgs = new Array(arguments.length);
    for (let i = 0; i < arguments.length; i++) {
      originalArgs[i] = arguments[i];
    }

    return instrumentedLegacyWrite(this, original, originalArgs);
  };
}

function instrumentedLegacyWrite(ctx, originalWrite, originalArgs) {
  // pool.js#write throws a sync error if there is no callback, so we can safely assume there is one. If there was no
  // callback, we wouldn't be able to finish the span, so we won't start one.
  const { originalCallback, callbackIndex } = findCallback(originalArgs);
  if (callbackIndex < 0) {
    superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedLegacyWrite: no callback found');
    return originalWrite.apply(ctx, originalArgs);
  }

  return cls.ns.runAndReturn(() => {
    const span = cls.startSpan(exports.spanName, constants.EXIT);

    let hostname;
    let port;
    let service;
    let command;
    let database;
    let collection;
    let namespace;

    const message = originalArgs[0];
    if (message && typeof message === 'object') {
      if (
        message.options &&
        message.options.session &&
        message.options.session.topology &&
        message.options.session.topology.s &&
        message.options.session.topology.s
      ) {
        hostname = message.options.session.topology.s.host;
        port = message.options.session.topology.s.port;
      }

      if ((!hostname || !port) && ctx.options) {
        // fallback for older versions of mongodb package
        if (!hostname) {
          hostname = ctx.options.host;
        }
        if (!port) {
          port = ctx.options.port;
        }
      }

      let cmdObj = message.command;
      if (!cmdObj) {
        // fallback for older mongodb versions
        cmdObj = message.query;
      }
      if (cmdObj) {
        if (cmdObj.collection) {
          // only getMore commands have the collection attribute
          collection = cmdObj.collection;
        }
        if (!collection) {
          collection = findCollection(cmdObj);
        }
        command = findCommand(cmdObj);
        database = cmdObj.$db;
      }

      if (!database && typeof message.ns === 'string') {
        // fallback for older mongodb versions
        database = message.ns.split('.')[0];
      }
    }

    if (database && collection) {
      namespace = `${database}.${collection}`;
    } else if (database) {
      namespace = `${database}.?`;
    } else if (collection) {
      namespace = `?.${collection}`;
    }

    if (hostname || port) {
      span.data.peer = {
        hostname,
        port
      };
    }

    if (hostname && port) {
      service = `${hostname}:${port}`;
    } else if (hostname) {
      service = `${hostname}:27017`;
    } else if (port) {
      service = '?:27017';
    }

    let spanData = {
      command,
      service,
      namespace
    };

    span.data.mongo = spanData;

    let json;
    let filter;
    const jsonAndFilter = readJsonOrFilterFromMessage(message, span);
    if (jsonAndFilter) {
      json = jsonAndFilter.stringJson;
      filter = jsonAndFilter.filter;
    }

    /** @type {JSOpenSpanResult} */
    let openSpanResult = null;
    const stTaskId = getOrCreateTask();
    if (stTaskId !== 0) {
      /** @type{SpanLabels} */
      const stSpanData = {};
      stSpanData[superconsts.Label.SupertenantResourceType] = superconsts.ResourceType.Mongodb;
      stSpanData[superconsts.Label.IntegrationModuleResourceId] = hostname;
      stSpanData[superconsts.Label.DbDatabase] = database;
      stSpanData[superconsts.Label.DbMongodbCollection] = collection;
      stSpanData[superconsts.Label.DbHost] = hostname;
      stSpanData[superconsts.Label.DbPort] = port;
      stSpanData[superconsts.Label.DbCommand] = command;
      stSpanData[superconsts.Label.DbMongodbCommandJson] = json;
      stSpanData[superconsts.Label.DbMongodbFilter] = filter;
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedLegacyWrite: openSpan');
      openSpanResult = superbrain.openSpan(stTaskId, superconsts.SpanType.ClientRequest, stSpanData);
    } else {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedLegacyWrite: no stTaskId');
    }

    const stCallback = createWrappedCallback(span, openSpanResult, originalCallback);
    originalArgs[callbackIndex] = stCallback;

    if (openSpanResult) {
      callbackHandleAction(openSpanResult, ctx, originalWrite, originalArgs, stCallback);
    } else {
      superbrain.log(superconsts.BrainLogLevel.Debug, 'instrumentedLegacyWrite: no openSpanResult');
      return originalWrite.apply(ctx, originalArgs);
    }
  });
}

/**
 * @param {JSOpenSpanResult} openSpanResult
 */
 function callbackHandleAction(openSpanResult, ctx, originalFunction, originalArgs, resultCallback, firstCall = true) {
  let actionRef;
  if (firstCall) {
    actionRef = superbrain.getAction(openSpanResult.action);
  } else {
    actionRef = superbrain.pollSpanAction(openSpanResult.openSpanResult);
  }
  if (actionRef.Action === superconsts.Action.Execute) {
    if (!openSpanResult.canceled) {
      openSpanResult.executed = true;
      originalFunction.apply(ctx, originalArgs);
    }
    return;
  }
  if (actionRef.Action === superconsts.Action.Wait) {
    let pollInterval = getWaitPollInterval(actionRef);
    setTimeout(cls.ns.bind(function () {
      callbackHandleAction(openSpanResult, ctx, originalFunction, originalArgs, resultCallback, false);
    }), pollInterval).unref();
    return;
  }
  if (actionRef.Action === superconsts.Action.Delay) {
    const delayDuration = getDelayDuration(actionRef);
    return new Promise((resolve) => setTimeout(resolve, delayDuration).unref()).then(cls.ns.bind(() => {
      return function () {
        if (!openSpanResult.canceled) {
          openSpanResult.executed = true;
          originalFunction.apply(ctx, originalArgs);
        }
      }();
    }));
  }
  if (actionRef.Action === superconsts.Action.Reject) {
    resultCallback(rejectToError(getMongodbRejectActionResult(actionRef)));
  } else {
    dedupReportError('httpServer:UNKNOWN_ACTION', 'received unknown action ID', actionRef);
    originalFunction.apply(ctx, originalArgs);
  }
}

/**
 *
 * @param {import('../../actions').MongodbRejectActionResult} rejectActionResult
 * @returns {MongodbError}
 */
 function rejectToError(rejectActionResult) {
  /** @type{MongodbError} */
  let err = new Error(rejectActionResult.error_message);
  err.ok = 0;
  err.code = rejectActionResult.code;
  err.codeName = ErrorConstants[rejectActionResult.code];
  err.message = rejectActionResult.error_message;
  return err;
}

function findCallback(originalArgs) {
  let originalCallback;
  let callbackIndex = -1;
  for (let i = 1; i < originalArgs.length; i++) {
    if (typeof originalArgs[i] === 'function') {
      originalCallback = originalArgs[i];
      callbackIndex = i;
      break;
    }
  }
  return {
    originalCallback,
    callbackIndex
  };
}

function findCollection(cmdObj) {
  for (let j = 0; j < commands.length; j++) {
    if (cmdObj[commands[j]] && typeof cmdObj[commands[j]] === 'string') {
      // most commands (except for getMore) add the collection as the value for the command-specific key
      return cmdObj[commands[j]];
    }
  }
}

function findCommand(cmdObj) {
  for (let j = 0; j < commands.length; j++) {
    if (cmdObj[commands[j]]) {
      return commands[j];
    }
  }
}

/**
 * @param {InstanaCLSContext} ctx
 */
function getServiceHostnameAndPortFromCtx(ctx) {
  let address = '';
  let hostname = '';
  let port = '';
  if (ctx.host) {
    address = ctx.port ? `${ctx.host}:${ctx.port}` : ctx.host;
    hostname = ctx.host;
    port = ctx.port || '';
  } else if (ctx.address) {
    const hostnameAndPort = splitIntoHostAndPort(ctx.address);
    return { address: ctx.address, hostnameAndPort };
  }
  return { address, hostnameAndPort: { hostname, port } };
}

function splitIntoHostAndPort(address) {
  let hostname = '';
  let port = '';
  if (typeof address === 'string') {
    // address can be IPv6, which may contain a lot of colons.
    // Unfortunately, there's no good way to tell if we're using IPv4/DNS or IPv6.
    // when ports are also involved, so we use the following heuristic:
    // 1. If we have [ then it should be [ipv6]:port format.
    // 2. If we have more than one colon then it's ipv6 w/o port.
    // 3. IPv4/DNS:
    // 3.1. If we have a colon then it's ipv4/DNS w/ port.
    // 3.2. IPv4/DNS w/o port.
    hostname = address;
    if (address[0] === '[') {
      // case 1: IPv6 []:port
      const closingBracketIdx = address.lastIndexOf(']');
      if (closingBracketIdx >= 0) {
        const lastColon = address.lastIndexOf(':');
        if (lastColon > closingBracketIdx) {
          port = address.substring(lastColon + 1);
        }
        hostname = address.substring(1, closingBracketIdx);
      } else {
        // this is a bug in the mongodb driver (address: "[xxxx"), so we just copy
        // address as the hostname (already set).
      }
    } else {
      const idx = address.indexOf(':');
      if (idx >= 0) {
        const idx2 = address.indexOf(':', idx + 1);
        if (idx2 < 0) {
          // case 3.1: IPv4/DNS + port
          hostname = address.substring(0, idx);
          port = address.substring(idx + 1);
        } // else case 2: IPv6 w/o port (hostname is already set to address)
      } // else case 3.2: IPv4/DNS w/o port (hostname is already set to address)
    }
  } else {
    dedupReportError('mongodb:splitIntoHostAndPort', 'address is not a string', address);
  }
  return { hostname, port };
}

function readJsonOrFilterFromMessage(message, span) {
  if (!message) {
    return;
  }
  let cmdObj = message.command;
  if (!cmdObj) {
    cmdObj = message.query;
  }
  if (!cmdObj) {
    return;
  }
  return readJsonOrFilter(cmdObj, span);
}

function readJsonOrFilter(cmdObj, span) {
  let json;
  if (Array.isArray(cmdObj) && cmdObj.length >= 1) {
    json = cmdObj;
  } else if (Array.isArray(cmdObj.updates) && cmdObj.updates.length >= 1) {
    json = cmdObj.updates;
  } else if (Array.isArray(cmdObj.deletes) && cmdObj.deletes.length >= 1) {
    json = cmdObj.deletes;
  } else if (Array.isArray(cmdObj.pipeline) && cmdObj.pipeline.length >= 1) {
    json = cmdObj.pipeline;
  }

  // The back end will process exactly one of json, query, or filter, so it does not matter too much which one we
  // provide.
  let stringJson = '';
  if (json) {
    stringJson = stringifyWhenNecessary(json);
    span.data.mongo.json = stringJson;
  } else if (cmdObj.filter || cmdObj.query) {
    span.data.mongo.filter = stringifyWhenNecessary(cmdObj.filter || cmdObj.query);
  }
  let filter = '';
  if (cmdObj.filter || cmdObj.query) {
    filter = stringifyWhenNecessary(cmdObj.filter || cmdObj.query);
  }
  return { stringJson, filter };
}

function stringifyWhenNecessary(obj) {
  if (obj == null) {
    return undefined;
  } else if (typeof obj === 'string') {
    return tracingUtil.shortenDatabaseStatement(obj);
  }
  return tracingUtil.shortenDatabaseStatement(JSON.stringify(obj));
}

function createWrappedCallback(span, openSpanResult, originalCallback) {
  return cls.ns.bind(function (error) {
    /** @type{SpanLabels} */
    const stSpanData = {};
    if (error) {
      span.ec = 1;
      span.data.mongo.error = tracingUtil.getErrorDetails(error);
      stSpanData[superconsts.Label.SupertenantError] = 'true';
    }

    span.d = Date.now() - span.ts;
    span.transmit();

    if (openSpanResult != null) {
      if (!openSpanResult.executed) {
        // #860q6qe8z mark the request as canceled.
        // TODO: Is this needed without promises?
        openSpanResult.canceled = true;
        stSpanData[superconsts.Label.SupertenantCanceled] = 'true';
      }
      superbrain.closeSpan(openSpanResult.spanId, stSpanData);
    }

    return originalCallback.apply(this, arguments);
  });
}

function dbAndCollectionFromNamespace(namespace) {
  let db;
  let collection;
  if (namespace != null) {
    if (typeof namespace === 'string') {
      const idx = namespace.indexOf('.');
      if (idx >= 0) {
        db = namespace.substring(0, idx);
        collection = namespace.substring(idx + 1);
      } else {
        db = namespace;
      }
    } else {
      db = namespace.db;
      collection = namespace.collection;
    }
  }
  return {
    db,
    collection
  };
}

exports.activate = function activate() {
  isActive = true;
};

exports.deactivate = function deactivate() {
  isActive = false;
};