blob: 234abd572b3ee37db9dcb8d62d199699890e4e78 [file] [log] [blame]
/*!
*
* 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.
*
*/
import SwPlugin, { wrapCallback, wrapPromise } from '../core/SwPlugin';
import ContextManager from '../trace/context/ContextManager';
import { Component } from '../trace/Component';
import Tag from '../Tag';
import { SpanLayer } from '../proto/language-agent/Tracing_pb';
import PluginInstaller from '../core/PluginInstaller';
class MongoosePlugin implements SwPlugin {
readonly module = 'mongoose';
readonly versions = '*';
mongodbEnabled?: boolean;
install(installer: PluginInstaller): void {
const { Model } = installer.require?.('mongoose') ?? require('mongoose');
this.interceptOperation(Model, 'aggregate');
this.interceptOperation(Model, 'bulkWrite');
this.interceptOperation(Model, 'cleanIndexes');
this.interceptOperation(Model, 'count');
this.interceptOperation(Model, 'countDocuments');
this.interceptOperation(Model, 'create');
this.interceptOperation(Model, 'createCollection');
this.interceptOperation(Model, 'createIndexes');
this.interceptOperation(Model, 'deleteMany');
this.interceptOperation(Model, 'deleteOne');
this.interceptOperation(Model, 'distinct');
this.interceptOperation(Model, 'ensureIndexes');
this.interceptOperation(Model, 'estimatedDocumentCount');
this.interceptOperation(Model, 'exists');
this.interceptOperation(Model, 'find');
this.interceptOperation(Model, 'findById');
this.interceptOperation(Model, 'findByIdAndDelete');
this.interceptOperation(Model, 'findByIdAndRemove');
this.interceptOperation(Model, 'findByIdAndUpdate');
this.interceptOperation(Model, 'findOne');
this.interceptOperation(Model, 'findOneAndDelete');
this.interceptOperation(Model, 'findOneAndRemove');
this.interceptOperation(Model, 'findOneAndReplace');
this.interceptOperation(Model, 'findOneAndUpdate');
this.interceptOperation(Model, 'geoSearch');
this.interceptOperation(Model, 'insertMany');
this.interceptOperation(Model, 'listIndexes');
this.interceptOperation(Model, 'mapReduce');
this.interceptOperation(Model, 'populate');
this.interceptOperation(Model, 'remove');
this.interceptOperation(Model, 'replaceOne');
this.interceptOperation(Model, 'syncIndexes');
this.interceptOperation(Model, 'update');
this.interceptOperation(Model, 'updateMany');
this.interceptOperation(Model, 'updateOne');
this.interceptOperation(Model, 'validate');
this.interceptOperation(Model.prototype, 'delete');
this.interceptOperation(Model.prototype, 'deleteOne');
this.interceptOperation(Model.prototype, 'remove');
this.interceptOperation(Model.prototype, 'save');
// TODO:
// discriminator?
// startSession?
// where?
// NODO:
// hydrate
}
interceptOperation(Container: any, operation: string): void {
const _original = Container[operation];
if (!_original) return;
Container[operation] = function () {
let span = ContextManager.currentSpan;
if ((span as any)?.mongooseInCall)
// mongoose has called into itself internally
return _original.apply(this, arguments);
const host = `${this.db.host}:${this.db.port}`;
span = ContextManager.current.newExitSpan('Mongoose/' + operation, Component.MONGOOSE, Component.MONGODB);
span.start();
try {
span.component = Component.MONGOOSE;
span.layer = SpanLayer.DATABASE; // mongodb may not actually be called so we set these here in case
span.peer = host;
span.tag(Tag.dbType('MongoDB'));
span.tag(Tag.dbInstance(this.db.name));
const hasCB = typeof arguments[arguments.length - 1] === 'function';
if (hasCB) {
const wrappedCallback = wrapCallback(span, arguments[arguments.length - 1], 0);
arguments[arguments.length - 1] = function () {
// in case of immediate synchronous callback from mongoose
(span as any).mongooseInCall = false;
wrappedCallback.apply(this, arguments as any);
};
}
(span as any).mongooseInCall = true; // if mongoose calls into itself while executing this operation then ignore it
let ret = _original.apply(this, arguments);
(span as any).mongooseInCall = false;
if (!hasCB) {
if (ret && typeof ret.then === 'function') {
// generic Promise check
ret = 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 (err) {
span.error(err);
span.stop();
throw err;
}
};
}
}
// noinspection JSUnusedGlobalSymbols
export default new MongoosePlugin();