diff --git a/lib/otel/segment-synthesis.js b/lib/otel/segment-synthesis.js index bebc9ba287..62f852fc55 100644 --- a/lib/otel/segment-synthesis.js +++ b/lib/otel/segment-synthesis.js @@ -6,7 +6,7 @@ 'use strict' const { RulesEngine } = require('./rules') const defaultLogger = require('../logger').child({ component: 'segment-synthesizer' }) -const { DatabaseSegment, HttpExternalSegment, ServerSegment } = require('./segments') +const { createDbSegment, createHttpExternalSegment, createServerSegment } = require('./segments') class SegmentSynthesizer { constructor(agent, { logger = defaultLogger } = {}) { @@ -28,11 +28,11 @@ class SegmentSynthesizer { switch (rule.type) { case 'external': - return new HttpExternalSegment(this.agent, otelSpan) + return createHttpExternalSegment(this.agent, otelSpan) case 'db': - return new DatabaseSegment(this.agent, otelSpan) + return createDbSegment(this.agent, otelSpan) case 'server': - return new ServerSegment(this.agent, otelSpan) + return createServerSegment(this.agent, otelSpan) default: this.logger.debug('Found type: %s, no synthesis rule currently built', rule.type) } diff --git a/lib/otel/segments/database.js b/lib/otel/segments/database.js index 4c37f86830..42bf0d8861 100644 --- a/lib/otel/segments/database.js +++ b/lib/otel/segments/database.js @@ -17,54 +17,52 @@ const parseSql = require('../../db/query-parsers/sql') // TODO: This probably has some holes // I did analysis and tried to apply the best logic // to extract table/operation -module.exports = class DatabaseSegment { - constructor(agent, otelSpan) { - const context = agent.tracer.getContext() - const name = this.setName(otelSpan) - const segment = agent.tracer.createSegment({ - name, - parent: context.segment, - transaction: context.transaction - }) - return { segment, transaction: context.transaction } - } - - parseStatement(otelSpan, system) { - let table = otelSpan.attributes[SEMATTRS_DB_SQL_TABLE] - let operation = otelSpan.attributes[SEMATTRS_DB_OPERATION] - const statement = otelSpan.attributes[SEMATTRS_DB_STATEMENT] - if (statement && !(table || operation)) { - const parsed = parseSql({ sql: statement }) - if (parsed.operation && !operation) { - operation = parsed.operation - } +module.exports = function createDbSegment(agent, otelSpan) { + const context = agent.tracer.getContext() + const name = setName(otelSpan) + const segment = agent.tracer.createSegment({ + name, + parent: context.segment, + transaction: context.transaction + }) + return { segment, transaction: context.transaction } +} - if (parsed.collection && !table) { - table = parsed.collection - } - } - if (system === DbSystemValues.MONGODB) { - table = otelSpan.attributes[SEMATTRS_DB_MONGODB_COLLECTION] +function parseStatement(otelSpan, system) { + let table = otelSpan.attributes[SEMATTRS_DB_SQL_TABLE] + let operation = otelSpan.attributes[SEMATTRS_DB_OPERATION] + const statement = otelSpan.attributes[SEMATTRS_DB_STATEMENT] + if (statement && !(table || operation)) { + const parsed = parseSql({ sql: statement }) + if (parsed.operation && !operation) { + operation = parsed.operation } - if (system === DbSystemValues.REDIS && statement) { - ;[operation] = statement.split(' ') + if (parsed.collection && !table) { + table = parsed.collection } + } + if (system === DbSystemValues.MONGODB) { + table = otelSpan.attributes[SEMATTRS_DB_MONGODB_COLLECTION] + } - table = table || 'Unknown' - operation = operation || 'Unknown' - - return { operation, table } + if (system === DbSystemValues.REDIS && statement) { + ;[operation] = statement.split(' ') } - setName(otelSpan) { - const system = otelSpan.attributes[SEMATTRS_DB_SYSTEM] - const { operation, table } = this.parseStatement(otelSpan, system) - let name = `Datastore/statement/${system}/${table}/${operation}` - // All segment name shapes are same except redis/memcached - if (system === DbSystemValues.REDIS || system === DbSystemValues.MEMCACHED) { - name = `Datastore/operation/${system}/${operation}` - } - return name + table = table || 'Unknown' + operation = operation || 'Unknown' + + return { operation, table } +} + +function setName(otelSpan) { + const system = otelSpan.attributes[SEMATTRS_DB_SYSTEM] + const { operation, table } = parseStatement(otelSpan, system) + let name = `Datastore/statement/${system}/${table}/${operation}` + // All segment name shapes are same except redis/memcached + if (system === DbSystemValues.REDIS || system === DbSystemValues.MEMCACHED) { + name = `Datastore/operation/${system}/${operation}` } + return name } diff --git a/lib/otel/segments/http-external.js b/lib/otel/segments/http-external.js index 581650aae4..226c97c6ff 100644 --- a/lib/otel/segments/http-external.js +++ b/lib/otel/segments/http-external.js @@ -7,16 +7,14 @@ const NAMES = require('../../metrics/names') const { SEMATTRS_HTTP_HOST } = require('@opentelemetry/semantic-conventions') -module.exports = class HttpExternalSegment { - constructor(agent, otelSpan) { - const context = agent.tracer.getContext() - const host = otelSpan.attributes[SEMATTRS_HTTP_HOST] || 'Unknown' - const name = NAMES.EXTERNAL.PREFIX + host - const segment = agent.tracer.createSegment({ - name, - parent: context.segment, - transaction: context.transaction - }) - return { segment, transaction: context.transaction } - } +module.exports = function createHttpExternalSegment(agent, otelSpan) { + const context = agent.tracer.getContext() + const host = otelSpan.attributes[SEMATTRS_HTTP_HOST] || 'Unknown' + const name = NAMES.EXTERNAL.PREFIX + host + const segment = agent.tracer.createSegment({ + name, + parent: context.segment, + transaction: context.transaction + }) + return { segment, transaction: context.transaction } } diff --git a/lib/otel/segments/index.js b/lib/otel/segments/index.js index 019bbf2829..b7d258c596 100644 --- a/lib/otel/segments/index.js +++ b/lib/otel/segments/index.js @@ -4,12 +4,12 @@ */ 'use strict' -const HttpExternalSegment = require('./http-external') -const DatabaseSegment = require('./database') -const ServerSegment = require('./server') +const createHttpExternalSegment = require('./http-external') +const createDbSegment = require('./database') +const createServerSegment = require('./server') module.exports = { - DatabaseSegment, - HttpExternalSegment, - ServerSegment + createDbSegment, + createHttpExternalSegment, + createServerSegment } diff --git a/lib/otel/segments/server.js b/lib/otel/segments/server.js index 6046eff487..94181efb52 100644 --- a/lib/otel/segments/server.js +++ b/lib/otel/segments/server.js @@ -19,70 +19,67 @@ const Transaction = require('../../transaction') const urltils = require('../../util/urltils') const url = require('url') -module.exports = class ServerSegment { - constructor(agent, otelSpan) { - this.agent = agent - this.transaction = new Transaction(agent) - this.transaction.type = 'web' - this.otelSpan = otelSpan - const rpcSystem = otelSpan.attributes[SEMATTRS_RPC_SYSTEM] - const httpMethod = otelSpan.attributes[SEMATTRS_HTTP_METHOD] - if (rpcSystem) { - this.segment = this.rpcSegment(rpcSystem) - } else if (httpMethod) { - this.segment = this.httpSegment(httpMethod) - } else { - this.segment = this.genericHttpSegment() - } - this.transaction.baseSegment = this.segment - return { segment: this.segment, transaction: this.transaction } +module.exports = function createServerSegment(agent, otelSpan) { + const transaction = new Transaction(agent) + transaction.type = 'web' + const rpcSystem = otelSpan.attributes[SEMATTRS_RPC_SYSTEM] + const httpMethod = otelSpan.attributes[SEMATTRS_HTTP_METHOD] + let segment + if (rpcSystem) { + segment = rpcSegment({ agent, otelSpan, transaction, rpcSystem }) + } else if (httpMethod) { + segment = httpSegment({ agent, otelSpan, transaction, httpMethod }) + } else { + segment = genericHttpSegment({ agent, transaction }) } + transaction.baseSegment = segment + return { segment, transaction } +} - rpcSegment(rpcSystem) { - const rpcService = this.otelSpan.attributes[SEMATTRS_RPC_SERVICE] || 'Unknown' - const rpcMethod = this.otelSpan.attributes[SEMATTRS_RPC_METHOD] || 'Unknown' - const name = `WebTransaction/WebFrameworkUri/${rpcSystem}/${rpcService}.${rpcMethod}` - this.transaction.name = name - this.transaction.trace.attributes.addAttribute(DESTINATION, 'request.method', rpcMethod) - this.transaction.trace.attributes.addAttribute(DESTINATION, 'request.uri', name) - this.transaction.url = name - const segment = this.agent.tracer.createSegment({ - name, - parent: this.transaction.trace.root, - transaction: this.transaction - }) - segment.addAttribute('component', rpcSystem) - return segment - } +function rpcSegment({ agent, otelSpan, transaction, rpcSystem }) { + const rpcService = otelSpan.attributes[SEMATTRS_RPC_SERVICE] || 'Unknown' + const rpcMethod = otelSpan.attributes[SEMATTRS_RPC_METHOD] || 'Unknown' + const name = `WebTransaction/WebFrameworkUri/${rpcSystem}/${rpcService}.${rpcMethod}` + transaction.name = name + transaction.trace.attributes.addAttribute(DESTINATION, 'request.method', rpcMethod) + transaction.trace.attributes.addAttribute(DESTINATION, 'request.uri', name) + transaction.url = name + const segment = agent.tracer.createSegment({ + name, + parent: transaction.trace.root, + transaction + }) + segment.addAttribute('component', rpcSystem) + return segment +} - // most instrumentation will hit this case - // I find that if the request is in a web framework, the web framework instrumentation - // sets `http.route` and when the span closes it pulls that attribute in - // we'll most likely need to wire up some naming reconciliation - // to handle this use case. - httpSegment(httpMethod) { - const httpRoute = this.otelSpan.attributes[SEMATTRS_HTTP_ROUTE] || 'Unknown' - const httpUrl = this.otelSpan.attributes[SEMATTRS_HTTP_URL] || '/Unknown' - const requestUrl = url.parse(httpUrl, true) - const name = `WebTransaction/Nodejs/${httpMethod}/${httpRoute}` - this.transaction.name = name - this.transaction.url = urltils.obfuscatePath(this.agent.config, requestUrl.pathname) - this.transaction.trace.attributes.addAttribute(DESTINATION, 'request.uri', this.transaction.url) - this.transaction.trace.attributes.addAttribute(DESTINATION, 'request.method', httpMethod) - return this.agent.tracer.createSegment({ - name, - parent: this.transaction.trace.root, - transaction: this.transaction - }) - } +// most instrumentation will hit this case +// I find that if the request is in a web framework, the web framework instrumentation +// sets `http.route` and when the span closes it pulls that attribute in +// we'll most likely need to wire up some naming reconciliation +// to handle this use case. +function httpSegment({ agent, otelSpan, transaction, httpMethod }) { + const httpRoute = otelSpan.attributes[SEMATTRS_HTTP_ROUTE] || 'Unknown' + const httpUrl = otelSpan.attributes[SEMATTRS_HTTP_URL] || '/Unknown' + const requestUrl = url.parse(httpUrl, true) + const name = `WebTransaction/Nodejs/${httpMethod}/${httpRoute}` + transaction.name = name + transaction.url = urltils.obfuscatePath(agent.config, requestUrl.pathname) + transaction.trace.attributes.addAttribute(DESTINATION, 'request.uri', transaction.url) + transaction.trace.attributes.addAttribute(DESTINATION, 'request.method', httpMethod) + return agent.tracer.createSegment({ + name, + parent: transaction.trace.root, + transaction + }) +} - genericHttpSegment() { - const name = 'WebTransaction/NormalizedUri/*' - this.transaction.name = name - return this.agent.tracer.createSegment({ - name, - parent: this.transaction.trace.root, - transaction: this.transaction - }) - } +function genericHttpSegment({ agent, transaction }) { + const name = 'WebTransaction/NormalizedUri/*' + transaction.name = name + return agent.tracer.createSegment({ + name, + parent: transaction.trace.root, + transaction: transaction + }) }