UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

186 lines (151 loc) 4.98 kB
'use strict' const { isTrue } = require('../../dd-trace/src/util') const DatabasePlugin = require('../../dd-trace/src/plugins/database') const coalesce = require('koalas') const { getEnvironmentVariable } = require('../../dd-trace/src/config-helper') class MongodbCorePlugin extends DatabasePlugin { static id = 'mongodb-core' static component = 'mongodb' // avoid using db.name for peer.service since it includes the collection name // should be removed if one day this will be fixed static peerServicePrecursors = [] configure (config) { super.configure(config) const heartbeatFromEnv = getEnvironmentVariable('DD_TRACE_MONGODB_HEARTBEAT_ENABLED') this.config.heartbeatEnabled = coalesce( config.heartbeatEnabled, heartbeatFromEnv && isTrue(heartbeatFromEnv), true ) } bindStart (ctx) { const { ns, ops, options = {}, name } = ctx // heartbeat commands can be disabled if this.config.heartbeatEnabled is false if (!this.config.heartbeatEnabled && isHeartbeat(ops, this.config)) { return } const query = getQuery(ops) const resource = truncate(getResource(this, ns, query, name)) const service = this.serviceName({ pluginConfig: this.config }) const span = this.startSpan(this.operationName(), { service, resource, type: 'mongodb', kind: 'client', meta: { // this is not technically correct since it includes the collection but we changing will break customer stuff 'db.name': ns, 'mongodb.query': query, 'out.host': options.host, 'out.port': options.port } }, ctx) const comment = this.injectDbmComment(span, ops.comment, service) if (comment) { ops.comment = comment } return ctx.currentStore } getPeerService (tags) { const ns = tags['db.name'] if (ns && tags['peer.service'] === undefined) { // the mongo ns is either dbName either dbName.collection. So we keep the first part tags['peer.service'] = ns.split('.', 1)[0] } return super.getPeerService(tags) } injectDbmComment (span, comment, serviceName) { const dbmTraceComment = this.createDbmComment(span, serviceName) if (!dbmTraceComment) { return comment } if (comment) { // if the command already has a comment, append the dbm trace comment if (typeof comment === 'string') { comment += `,${dbmTraceComment}` } else if (Array.isArray(comment)) { comment.push(dbmTraceComment) } // do nothing if the comment is not a string or an array } else { comment = dbmTraceComment } return comment } } function sanitizeBigInt (data) { return JSON.stringify(data, (_key, value) => typeof value === 'bigint' ? value.toString() : value) } function getQuery (cmd) { if (!cmd || typeof cmd !== 'object' || Array.isArray(cmd)) return if (cmd.query) return sanitizeBigInt(limitDepth(cmd.query)) if (cmd.filter) return sanitizeBigInt(limitDepth(cmd.filter)) if (cmd.pipeline) return sanitizeBigInt(limitDepth(cmd.pipeline)) } function getResource (plugin, ns, query, operationName) { const parts = [operationName, ns] if (plugin.config.queryInResourceName && query) { parts.push(query) } return parts.join(' ') } function truncate (input) { return input.slice(0, Math.min(input.length, 10_000)) } function shouldSimplify (input) { return !isObject(input) || typeof input.toJSON === 'function' } function shouldHide (input) { return Buffer.isBuffer(input) || typeof input === 'function' || isBinary(input) } function limitDepth (input) { if (isBSON(input)) { input = input.toJSON() } if (shouldHide(input)) return '?' if (shouldSimplify(input)) return input const output = {} const queue = [{ input, output, depth: 0 }] while (queue.length) { const { input, output, depth } = queue.pop() const nextDepth = depth + 1 for (const key in input) { if (typeof input[key] === 'function') continue let child = input[key] if (isBSON(child)) { child = typeof child.toJSON === 'function' ? child.toJSON() : '?' } if (depth >= 10 || shouldHide(child)) { output[key] = '?' } else if (shouldSimplify(child)) { output[key] = child } else { queue.push({ input: child, output: output[key] = {}, depth: nextDepth }) } } } return output } function isObject (val) { return val !== null && typeof val === 'object' && !Array.isArray(val) } function isBSON (val) { return val && val._bsontype && !isBinary(val) } function isBinary (val) { return val && val._bsontype === 'Binary' } function isHeartbeat (ops, config) { // Check if it's a heartbeat command hello: 1 or helloOk: 1 return ops && typeof ops === 'object' && (ops.hello === 1 || ops.helloOk === true) } module.exports = MongodbCorePlugin