From ce7eb3932c89c34221c86bb9e6a666b806efb900 Mon Sep 17 00:00:00 2001 From: Bob Evans Date: Thu, 31 Oct 2024 08:44:03 -0400 Subject: [PATCH] feat: Removed transaction from segment. Introduced a new enterSegment and enterTransaction to make context propagation more clear (#2646) --- api.js | 45 ++- lib/agent.js | 45 ++- lib/attributes.js | 4 +- lib/collector/remote-method.js | 2 +- .../async-local-context-manager.js | 10 +- lib/context-manager/context.js | 29 ++ lib/context-manager/create-context-manager.js | 29 -- lib/db/query-sample.js | 6 +- lib/db/query-trace-aggregator.js | 8 +- lib/db/slow-query.js | 3 +- lib/environment.js | 2 +- lib/grpc/connection.js | 2 +- lib/instrumentation/@nestjs/core.js | 3 +- lib/instrumentation/aws-sdk/v3/bedrock.js | 48 ++- lib/instrumentation/core/globals.js | 4 +- lib/instrumentation/core/http-outbound.js | 64 ++-- lib/instrumentation/core/http.js | 24 +- lib/instrumentation/core/net.js | 19 +- lib/instrumentation/grpc-js/grpc.js | 14 +- lib/instrumentation/kafkajs/consumer.js | 7 +- lib/instrumentation/koa/instrumentation.js | 6 +- lib/instrumentation/langchain/runnable.js | 37 ++- lib/instrumentation/langchain/tools.js | 7 +- lib/instrumentation/langchain/vectorstore.js | 35 ++- lib/instrumentation/nextjs/next-server.js | 6 +- lib/instrumentation/openai.js | 38 ++- lib/instrumentation/restify.js | 6 +- lib/instrumentation/superagent.js | 6 +- lib/instrumentation/undici.js | 62 ++-- lib/instrumentation/when/contextualizer.js | 27 +- lib/instrumentation/when/index.js | 50 +-- .../aws-bedrock/chat-completion-summary.js | 1 + lib/llm-events/aws-bedrock/embedding.js | 1 + lib/llm-events/aws-bedrock/event.js | 9 +- lib/llm-events/langchain/event.js | 9 +- .../openai/chat-completion-message.js | 13 +- .../openai/chat-completion-summary.js | 4 +- lib/llm-events/openai/embedding.js | 4 +- lib/llm-events/openai/event.js | 4 +- lib/metrics/recorders/custom.js | 3 +- lib/metrics/recorders/database-operation.js | 6 +- lib/metrics/recorders/database.js | 3 +- lib/metrics/recorders/generic.js | 3 +- lib/metrics/recorders/http.js | 5 +- lib/metrics/recorders/http_external.js | 3 +- lib/metrics/recorders/message-transaction.js | 5 +- lib/metrics/recorders/middleware.js | 3 +- lib/metrics/recorders/other.js | 5 +- lib/serverless/aws-lambda.js | 3 +- lib/shim/datastore-shim.js | 5 +- lib/shim/message-shim/subscribe-consume.js | 7 +- lib/shim/promise-shim.js | 49 ++- lib/shim/shim.js | 289 ++++++++++++------ lib/shim/transaction-shim.js | 52 ++-- lib/shim/webframework-shim/middleware.js | 4 +- lib/spans/base-span-streamer.js | 2 +- lib/spans/span-event-aggregator.js | 17 +- lib/spans/span-event.js | 29 +- lib/spans/streaming-span-event-aggregator.js | 13 +- lib/spans/streaming-span-event.js | 5 +- lib/transaction/index.js | 41 +-- lib/transaction/trace/index.js | 31 +- lib/transaction/trace/segment.js | 99 +++--- lib/transaction/tracer/index.js | 214 +++++++------ lib/util/cat.js | 12 +- lib/util/copy.js | 2 +- lib/util/trace-stacks.js | 25 ++ test/benchmark/events/span-event.bench.js | 42 +-- test/benchmark/shim/record.bench.js | 4 +- test/benchmark/trace/segment.bench.js | 21 +- test/benchmark/tracer/bindFunction.bench.js | 36 +-- test/benchmark/tracer/segments.bench.js | 2 +- test/benchmark/tracer/transaction.bench.js | 2 +- test/benchmark/tracer/utilities.bench.js | 2 +- test/benchmark/tracer/wrapping.bench.js | 16 +- .../recordMiddleware.bench.js | 4 +- .../agent/serverless-harvest.tap.js | 8 +- .../native-promises/native-promises.tap.js | 52 ++-- test/integration/core/net.tap.js | 6 +- .../trace-context-cross-agent.tap.js | 2 +- .../newrelic-response-handling.tap.js | 8 +- .../integration/transaction/total-time.tap.js | 62 ++-- test/integration/transaction/tracer.tap.js | 191 ++++++------ .../transaction/wide-segment-tree.js | 6 +- test/lib/agent_helper.js | 3 +- test/unit/agent/agent.test.js | 22 +- test/unit/api/api-set-controller-name.test.js | 2 +- .../unit/api/api-set-transaction-name.test.js | 2 +- .../async-local-context-manager.test.js | 78 ++--- .../create-context-manager.test.js | 21 -- test/unit/db/query-sample.test.js | 48 ++- test/unit/db/query-trace-aggregator.test.js | 8 +- test/unit/db/trace.test.js | 24 +- test/unit/distributed_tracing/dt-cats.test.js | 3 +- .../distributed_tracing/tracecontext.test.js | 8 + test/unit/instrumentation/http/http.test.js | 6 +- .../instrumentation/http/outbound.test.js | 18 +- test/unit/instrumentation/undici.test.js | 55 +++- .../chat-completion-message.test.js | 10 +- .../chat-completion-summary.test.js | 6 +- .../llm-events/aws-bedrock/embedding.test.js | 4 +- .../unit/llm-events/aws-bedrock/event.test.js | 8 +- .../langchain/chat-completion-message.test.js | 10 +- .../langchain/chat-completion-summary.test.js | 8 +- test/unit/llm-events/langchain/event.test.js | 10 +- test/unit/llm-events/langchain/tool.test.js | 32 +- .../langchain/vector-search-result.test.js | 29 +- .../langchain/vector-search.test.js | 6 +- .../openai/chat-completion-message.test.js | 16 +- .../openai/chat-completion-summary.test.js | 3 + test/unit/llm-events/openai/embedding.test.js | 9 +- .../database-metrics-recorder.test.js | 20 +- .../distributed-trace.test.js | 2 +- test/unit/metrics-recorder/generic.test.js | 14 +- .../metrics-recorder/http-external.test.js | 22 +- test/unit/metrics-recorder/http.test.js | 6 +- .../metrics-recorder/queue-time-http.test.js | 6 +- test/unit/shim/datastore-shim.test.js | 34 +-- test/unit/shim/message-shim.test.js | 52 ++-- test/unit/shim/shim.test.js | 255 ++++++++-------- test/unit/shim/transaction-shim.test.js | 58 ++-- test/unit/shim/webframework-shim.test.js | 25 -- test/unit/shimmer.test.js | 12 +- test/unit/spans/span-event-aggregator.test.js | 12 +- test/unit/spans/span-event.test.js | 16 +- test/unit/spans/streaming-span-event.test.js | 16 +- test/unit/transaction.test.js | 26 +- test/unit/transaction/trace/index.test.js | 247 +++++++++++---- test/unit/transaction/trace/segment.test.js | 289 +++++++++++------- test/unit/transaction/tracer.test.js | 91 +++++- test/versioned-external/external-repos.js | 2 +- .../versioned/amqplib-esm/issue-2663.test.mjs | 2 +- test/versioned/amqplib/amqp-utils.js | 8 +- test/versioned/amqplib/callback.test.js | 26 +- test/versioned/amqplib/promises.test.js | 28 +- test/versioned/cassandra-driver/query.test.js | 1 + test/versioned/koa/koa.tap.js | 5 +- test/versioned/pg-esm/pg.common.mjs | 46 ++- test/versioned/pg/pg.common.js | 44 ++- .../versioned/when/legacy-promise-segments.js | 51 +++- 140 files changed, 2308 insertions(+), 1649 deletions(-) create mode 100644 lib/context-manager/context.js delete mode 100644 lib/context-manager/create-context-manager.js create mode 100644 lib/util/trace-stacks.js delete mode 100644 test/unit/context-manager/create-context-manager.test.js diff --git a/api.js b/api.js index cde8f4f663..a9723f7698 100644 --- a/api.js +++ b/api.js @@ -961,19 +961,21 @@ API.prototype.startWebTransaction = function startWebTransaction(url, handle) { const shim = this.shim const tracer = this.agent.tracer - const parent = tracer.getTransaction() + const parentTx = tracer.getTransaction() assignCLMSymbol(shim, handle) return tracer.transactionNestProxy('web', function startWebSegment() { - const tx = tracer.getTransaction() + const context = tracer.getContext() + const tx = context?.transaction + const parent = context?.segment if (!tx) { return handle.apply(this, arguments) } - if (tx === parent) { + if (tx === parentTx) { logger.debug('not creating nested transaction %s using transaction %s', url, tx.id) - return tracer.addSegment(url, null, null, true, handle) + return tracer.addSegment(url, null, parent, true, handle) } logger.debug( @@ -985,10 +987,16 @@ API.prototype.startWebTransaction = function startWebTransaction(url, handle) { tx.nameState.setName(NAMES.CUSTOM, null, NAMES.ACTION_DELIMITER, url) tx.url = url tx.applyUserNamingRules(tx.url) - tx.baseSegment = tracer.createSegment(url, recordWeb) + tx.baseSegment = tracer.createSegment({ + name: url, + recorder: recordWeb, + transaction: tx, + parent + }) + const newContext = context.enterSegment({ transaction: tx, segment: tx.baseSegment }) tx.baseSegment.start() - const boundHandle = tracer.bindFunction(handle, tx.baseSegment) + const boundHandle = tracer.bindFunction(handle, newContext) maybeAddCLMAttributes(handle, tx.baseSegment) let returnResult = boundHandle.call(this) if (returnResult && shim.isPromise(returnResult)) { @@ -1061,19 +1069,21 @@ function startBackgroundTransaction(name, group, handle) { const tracer = this.agent.tracer const shim = this.shim const txName = group + '/' + name - const parent = tracer.getTransaction() + const parentTx = tracer.getTransaction() assignCLMSymbol(shim, handle) return tracer.transactionNestProxy('bg', function startBackgroundSegment() { - const tx = tracer.getTransaction() + const context = tracer.getContext() + const tx = context?.transaction + const parent = context?.segment if (!tx) { return handle.apply(this, arguments) } - if (tx === parent) { + if (tx === parentTx) { logger.debug('not creating nested transaction %s using transaction %s', txName, tx.id) - return tracer.addSegment(txName, null, null, true, handle) + return tracer.addSegment(txName, null, parent, true, handle) } logger.debug( @@ -1085,11 +1095,17 @@ function startBackgroundTransaction(name, group, handle) { ) tx._partialName = txName - tx.baseSegment = tracer.createSegment(name, recordBackground) + tx.baseSegment = tracer.createSegment({ + name, + recorder: recordBackground, + transaction: tx, + parent + }) + const newContext = context.enterSegment({ transaction: tx, segment: tx.baseSegment }) tx.baseSegment.partialName = group tx.baseSegment.start() - const boundHandle = tracer.bindFunction(handle, tx.baseSegment) + const boundHandle = tracer.bindFunction(handle, newContext) maybeAddCLMAttributes(handle, tx.baseSegment) let returnResult = boundHandle.call(this) if (returnResult && shim.isPromise(returnResult)) { @@ -1524,12 +1540,13 @@ API.prototype.getTraceMetadata = function getTraceMetadata() { const metadata = {} const segment = this.agent.tracer.getSegment() - if (!segment) { + const transaction = this.agent.tracer.getTransaction() + if (!(segment || transaction)) { logger.debug('No transaction found when calling API#getTraceMetadata') } else if (!this.agent.config.distributed_tracing.enabled) { logger.debug('Distributed tracing disabled when calling API#getTraceMetadata') } else { - metadata.traceId = segment.transaction.traceId + metadata.traceId = transaction.traceId const spanId = segment.getSpanId() if (spanId) { diff --git a/lib/agent.js b/lib/agent.js index 599ce34260..28f47d956b 100644 --- a/lib/agent.js +++ b/lib/agent.js @@ -29,7 +29,6 @@ const TxSegmentNormalizer = require('./metrics/normalizer/tx_segment') const uninstrumented = require('./uninstrumented') const util = require('util') const createSpanEventAggregator = require('./spans/create-span-event-aggregator') -const createContextManager = require('./context-manager/create-context-manager') const { maybeAddDatabaseAttributes, maybeAddExternalAttributes, @@ -257,9 +256,8 @@ function Agent(config) { this.errors = new ErrorCollector(config, errorTraceAggregator, errorEventAggregator, this.metrics) - this._contextManager = createContextManager(this.config) // Transaction tracing. - this.tracer = new Tracer(this, this._contextManager) + this.tracer = new Tracer(this) this.traces = new TransactionTraceAggregator( { periodMs: DEFAULT_HARVEST_INTERVAL_MS, @@ -293,13 +291,7 @@ function Agent(config) { // Set up all the configuration events the agent needs to listen for. this._listenForConfigChanges() - // Entity tracking metrics. - this.totalActiveSegments = 0 - this.segmentsCreatedInHarvest = 0 - this.segmentsClearedInHarvest = 0 - // Used by shutdown code as well as entity tracking stats - this.activeTransactions = 0 - + this.initCounters() this.llm = {} // Finally, add listeners for the agent's own events. @@ -598,11 +590,33 @@ Agent.prototype._generateEntityStatsAndClear = function _generateHarvestMetrics( ) } - // Reset the counters. + this.resetCounters() +} + +Agent.prototype.initCounters = function initCounters() { + // Entity tracking metrics. + this.totalActiveSegments = 0 this.segmentsCreatedInHarvest = 0 this.segmentsClearedInHarvest = 0 + // Used by shutdown code as well as entity tracking stats + this.activeTransactions = 0 } +Agent.prototype.incrementCounters = function incrementCounters() { + ++this.totalActiveSegments + ++this.segmentsCreatedInHarvest +} + +Agent.prototype.decrementCounters = function decrementCounters(transaction) { + --this.activeTransactions + this.totalActiveSegments -= transaction.numSegments + this.segmentsClearedInHarvest += transaction.numSegments +} + +Agent.prototype.resetCounters = function resetCounters() { + this.segmentsCreatedInHarvest = 0 + this.segmentsClearedInHarvest = 0 +} /** * Public interface for passing configuration data from the collector * on to the configuration, in an effort to keep them at least somewhat @@ -753,9 +767,7 @@ Agent.prototype._transactionFinished = function _transactionFinished(transaction logger.debug('Ignoring %s (%s).', transaction.name, transaction.id) } - --this.activeTransactions - this.totalActiveSegments -= transaction.numSegments - this.segmentsClearedInHarvest += transaction.numSegments + this.decrementCounters(transaction) } Agent.prototype.setLambdaArn = function setLambdaArn(arn) { @@ -834,6 +846,7 @@ Agent.prototype._listenForConfigChanges = function _listenForConfigChanges() { */ Agent.prototype.getLinkingMetadata = function getLinkingMetadata() { const segment = this.tracer.getSegment() + const transaction = this.tracer.getTransaction() const config = this.config const linkingMetadata = { @@ -842,8 +855,8 @@ Agent.prototype.getLinkingMetadata = function getLinkingMetadata() { 'hostname': config.getHostnameSafe() } - if (config.distributed_tracing.enabled && segment) { - linkingMetadata['trace.id'] = segment.transaction.traceId + if (config.distributed_tracing.enabled && segment && transaction) { + linkingMetadata['trace.id'] = transaction.traceId const spanId = segment.getSpanId() if (spanId) { linkingMetadata['span.id'] = spanId diff --git a/lib/attributes.js b/lib/attributes.js index 5b6ec8205d..04aa964e6c 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -22,7 +22,7 @@ class Attributes { * @param {string} scope * The scope of the attributes this will collect. Must be `transaction` or * `segment`. - * @param {number} [limit=Infinity] + * @param {number} [limit] * The maximum number of attributes to retrieve for each destination. */ constructor(scope, limit = Infinity) { @@ -104,7 +104,7 @@ class Attributes { * @param {DESTINATIONS} destinations - The default destinations for this key. * @param {string} key - The attribute name. * @param {string} value - The attribute value. - * @param {boolean} [truncateExempt=false] - Flag marking value exempt from truncation + * @param {boolean} [truncateExempt] - Flag marking value exempt from truncation */ addAttribute(destinations, key, value, truncateExempt = false) { if (this.attributeCount + 1 > this.limit) { diff --git a/lib/collector/remote-method.js b/lib/collector/remote-method.js index 897b366939..dc0a2e53f6 100644 --- a/lib/collector/remote-method.js +++ b/lib/collector/remote-method.js @@ -92,7 +92,7 @@ RemoteMethod.prototype._reportDataUsage = function reportDataUsage(sent, receive * you're doing it wrong. * * @param {object} payload Serializable payload. - * @param {object} [nrHeaders=null] NR request headers from connect response. + * @param {object} [nrHeaders] NR request headers from connect response. * @param {Function} callback What to do next. Gets passed any error. */ RemoteMethod.prototype.invoke = function invoke(payload, nrHeaders, callback) { diff --git a/lib/context-manager/async-local-context-manager.js b/lib/context-manager/async-local-context-manager.js index 9ad596b550..e3cafa2e10 100644 --- a/lib/context-manager/async-local-context-manager.js +++ b/lib/context-manager/async-local-context-manager.js @@ -6,6 +6,7 @@ 'use strict' const { AsyncLocalStorage } = require('async_hooks') +const Context = require('./context') /** * Class for managing state in the agent. @@ -17,12 +18,7 @@ const { AsyncLocalStorage } = require('async_hooks') * @class */ class AsyncLocalContextManager { - /** - * @param {object} config New Relic config instance - */ - constructor(config) { - this._config = config - + constructor() { this._asyncLocalStorage = new AsyncLocalStorage() } @@ -32,7 +28,7 @@ class AsyncLocalContextManager { * @returns {object} The current active context. */ getContext() { - return this._asyncLocalStorage.getStore() || null + return this._asyncLocalStorage.getStore() || new Context() } /** diff --git a/lib/context-manager/context.js b/lib/context-manager/context.js new file mode 100644 index 0000000000..eef668a758 --- /dev/null +++ b/lib/context-manager/context.js @@ -0,0 +1,29 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +module.exports = class Context { + constructor(transaction, segment) { + this._transaction = transaction + this._segment = segment + } + + get segment() { + return this._segment + } + + get transaction() { + return this._transaction + } + + enterSegment({ segment, transaction = this.transaction }) { + return new this.constructor(transaction, segment) + } + + enterTransaction(transaction) { + return new this.constructor(transaction, transaction.trace.root) + } +} diff --git a/lib/context-manager/create-context-manager.js b/lib/context-manager/create-context-manager.js deleted file mode 100644 index 336d79c1af..0000000000 --- a/lib/context-manager/create-context-manager.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2021 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const logger = require('../logger') - -/** - * Factory function to create context manager implementations used by the - * ContextManager class. - * - * @param {object} config New Relic config instance. - * @returns {*} The appropriate underlying context manager implementation based on - * the current configuration. - */ -function createContextManager(config) { - return createAsyncLocalContextManager(config) -} - -function createAsyncLocalContextManager(config) { - logger.info('Using AsyncLocalContextManager') - - const AsyncLocalContextManager = require('./async-local-context-manager') - return new AsyncLocalContextManager(config) -} - -module.exports = createContextManager diff --git a/lib/db/query-sample.js b/lib/db/query-sample.js index 46051d38e4..985f258286 100644 --- a/lib/db/query-sample.js +++ b/lib/db/query-sample.js @@ -34,7 +34,7 @@ QuerySample.prototype.merge = function merge(sample) { } QuerySample.prototype.prepareJSON = function prepareJSON(done) { - const transaction = this.trace.segment.transaction + const transaction = this.trace.transaction const sample = this const trace = sample.trace @@ -56,7 +56,7 @@ QuerySample.prototype.prepareJSON = function prepareJSON(done) { } QuerySample.prototype.prepareJSONSync = function prepareJSONSync() { - const transaction = this.trace.segment.transaction + const transaction = this.trace.transaction const sample = this const trace = sample.trace @@ -99,7 +99,7 @@ QuerySample.prototype.getParams = function getParams() { } if (this.tracer.config.distributed_tracing.enabled) { - this.trace.segment.transaction.addDistributedTraceIntrinsics(params) + this.trace.transaction.addDistributedTraceIntrinsics(params) } return params diff --git a/lib/db/query-trace-aggregator.js b/lib/db/query-trace-aggregator.js index bb3e2700a3..2f4b6047da 100644 --- a/lib/db/query-trace-aggregator.js +++ b/lib/db/query-trace-aggregator.js @@ -48,7 +48,7 @@ class QueryTraceAggregator extends Aggregator { } } - add(segment, type, query, trace) { + add({ segment, transaction, type, query, trace }) { const ttConfig = this.config.transaction_tracer // If DT is enabled and the segment is part of a sampled transaction @@ -57,12 +57,12 @@ class QueryTraceAggregator extends Aggregator { let slowQuery switch (ttConfig.record_sql) { case 'raw': - slowQuery = new SlowQuery(segment, type, query, trace) + slowQuery = new SlowQuery({ segment, transaction, type, query, trace }) logger.trace('recording raw sql') segment.addAttribute('sql', slowQuery.query, true) break case 'obfuscated': - slowQuery = new SlowQuery(segment, type, query, trace) + slowQuery = new SlowQuery({ transaction, segment, type, query, trace }) logger.trace('recording obfuscated sql') segment.addAttribute('sql_obfuscated', slowQuery.obfuscated, true) break @@ -78,7 +78,7 @@ class QueryTraceAggregator extends Aggregator { return } - slowQuery = slowQuery || new SlowQuery(segment, type, query, trace) + slowQuery = slowQuery || new SlowQuery({ segment, transaction, type, query, trace }) segment.addAttribute('backtrace', slowQuery.trace) diff --git a/lib/db/slow-query.js b/lib/db/slow-query.js index c80bcf881a..2dcc38a237 100644 --- a/lib/db/slow-query.js +++ b/lib/db/slow-query.js @@ -10,7 +10,7 @@ const crypto = require('crypto') const path = require('path') const NR_ROOT = path.resolve(__dirname, '..') -function SlowQuery(segment, type, query, trace) { +function SlowQuery({ segment, transaction, type, query, trace }) { this.obfuscated = obfuscate(query, type) this.normalized = this.obfuscated.replace(/\?\s*,\s*|\s*/g, '') this.id = normalizedHash(this.normalized) @@ -18,6 +18,7 @@ function SlowQuery(segment, type, query, trace) { this.query = query this.metric = segment.name this.trace = formatTrace(trace) + this.transaction = transaction this.duration = segment.getDurationInMillis() } diff --git a/lib/environment.js b/lib/environment.js index 694e679b45..c8c2becc5c 100644 --- a/lib/environment.js +++ b/lib/environment.js @@ -67,7 +67,7 @@ function clearSetting(name) { * the provided root. * * @param {string} root - Path to start listing packages from. - * @param {Array} [packages=[]] - Array to append found packages to. + * @param {Array} [packages] - Array to append found packages to. */ async function listPackages(root, packages = []) { _log('Listing packages in %s', root) diff --git a/lib/grpc/connection.js b/lib/grpc/connection.js index 29579a78d5..49929b4340 100644 --- a/lib/grpc/connection.js +++ b/lib/grpc/connection.js @@ -43,7 +43,7 @@ class GrpcConnection extends EventEmitter { * * @param {object} infiniteTracingConfig config item config.infinite_tracing * @param {MetricAggregator} metrics metric aggregator, for supportability metrics - * @param {number} [reconnectDelayMs=15000] number of milliseconds to wait before reconnecting + * @param {number} [reconnectDelayMs] number of milliseconds to wait before reconnecting * for error states that require a reconnect delay. */ constructor(infiniteTracingConfig, metrics, reconnectDelayMs = DEFAULT_RECONNECT_DELAY_MS) { diff --git a/lib/instrumentation/@nestjs/core.js b/lib/instrumentation/@nestjs/core.js index 93cbce1c7e..0628da11cc 100644 --- a/lib/instrumentation/@nestjs/core.js +++ b/lib/instrumentation/@nestjs/core.js @@ -20,8 +20,7 @@ module.exports = function initialize(agent, core, moduleName, shim) { shim.wrap(core.BaseExceptionFilter.prototype, 'handleUnknownError', (shim, original) => { return function wrappedHandleUnknownError(exception) { - const segment = shim.getActiveSegment() - const transaction = segment?.transaction + const transaction = shim.tracer.getTransaction() if (transaction) { shim.agent.errors.add(transaction, exception) logger.trace(exception, 'Captured error handled by Nest.js exception filter.') diff --git a/lib/instrumentation/aws-sdk/v3/bedrock.js b/lib/instrumentation/aws-sdk/v3/bedrock.js index 7fe1b4d277..54d5ef4616 100644 --- a/lib/instrumentation/aws-sdk/v3/bedrock.js +++ b/lib/instrumentation/aws-sdk/v3/bedrock.js @@ -70,10 +70,11 @@ function recordEvent({ agent, type, msg }) { * @param {object} params input params * @param {Agent} params.agent NR agent instance * @param {TraceSegment} params.segment active segment + * @param {Transaction} params.transaction active transaction */ -function addLlmMeta({ agent, segment }) { +function addLlmMeta({ agent, segment, transaction }) { agent.metrics.getOrCreateMetric(TRACKING_METRIC).incrementCallCount() - segment.transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true) + transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true) // end segment to get a consistent segment duration // for both the LLM events and the segment segment.end() @@ -90,13 +91,15 @@ function addLlmMeta({ agent, segment }) { * @param {object} params.segment active segment * @param {BedrockCommand} params.bedrockCommand parsed input * @param {Error|null} params.err error from request if exists - * @param params.bedrockResponse - * @param params.shim + * @param {BedrockResponse} params.bedrockResponse parsed response + * @param {Shim} params.shim shim instace + * @param {Transaction} params.transaction active transaction */ function recordChatCompletionMessages({ agent, shim, segment, + transaction, bedrockCommand, bedrockResponse, err @@ -110,6 +113,7 @@ function recordChatCompletionMessages({ agent, bedrockResponse, bedrockCommand, + transaction, segment, isError: err !== null }) @@ -119,6 +123,7 @@ function recordChatCompletionMessages({ segment, bedrockCommand, bedrockResponse, + transaction, index: 0, completionId: summary.id }) @@ -128,6 +133,7 @@ function recordChatCompletionMessages({ const chatCompletionMessage = new LlmChatCompletionMessage({ agent, segment, + transaction, bedrockCommand, bedrockResponse, isResponse: true, @@ -142,7 +148,7 @@ function recordChatCompletionMessages({ if (err) { const llmError = new LlmError({ bedrockResponse, err, summary }) - agent.errors.add(segment.transaction, err, llmError) + agent.errors.add(transaction, err, llmError) } } @@ -156,9 +162,18 @@ function recordChatCompletionMessages({ * @param {object} params.segment active segment * @param {BedrockCommand} params.bedrockCommand parsed input * @param {Error|null} params.err error from request if exists - * @param params.bedrockResponse + * @param {BedrockResponse} params.bedrockResponse parsed response + * @param {Transaction} params.transaction active transaction */ -function recordEmbeddingMessage({ agent, shim, segment, bedrockCommand, bedrockResponse, err }) { +function recordEmbeddingMessage({ + agent, + shim, + segment, + transaction, + bedrockCommand, + bedrockResponse, + err +}) { if (shouldSkipInstrumentation(agent.config) === true) { shim.logger.debug('skipping sending of ai data') return @@ -167,6 +182,7 @@ function recordEmbeddingMessage({ agent, shim, segment, bedrockCommand, bedrockR const embedding = new LlmEmbedding({ agent, segment, + transaction, bedrockCommand, bedrockResponse, isError: err !== null @@ -175,7 +191,7 @@ function recordEmbeddingMessage({ agent, shim, segment, bedrockCommand, bedrockR recordEvent({ agent, type: 'LlmEmbedding', msg: embedding }) if (err) { const llmError = new LlmError({ bedrockResponse, err, embedding }) - agent.errors.add(segment.transaction, err, llmError) + agent.errors.add(transaction, err, llmError) } } @@ -204,9 +220,8 @@ function createBedrockResponse({ bedrockCommand, response, err }) { * Registers the specification for instrumentation bedrock calls * * @param {object} params { config, commandName } aws config and command name - * @param {Shim} _shim instance of shim - * @param params.commandName - * @param shim + * @param {string} params.commandName name of command + * @param {Shim} shim instance of shim * @param {function} _original original middleware function * @param {string} _name function name * @param {array} args argument passed to middleware @@ -222,12 +237,13 @@ function getBedrockSpec({ commandName }, shim, _original, _name, args) { return new RecorderSpec({ promise: true, name: `Llm/${modelType}/Bedrock/${commandName}`, - after: ({ shim, error: err, result: response, segment }) => { + after: ({ shim, error: err, result: response, segment, transaction }) => { const passThroughParams = { shim, err, response, segment, + transaction, bedrockCommand, modelType } @@ -250,22 +266,23 @@ function getBedrockSpec({ commandName }, shim, _original, _name, args) { 'ai_monitoring.streaming.enabled is set to `false`, stream will not be instrumented.' ) agent.metrics.getOrCreateMetric(AI.STREAMING_DISABLED).incrementCallCount() - addLlmMeta({ agent, segment }) + addLlmMeta({ agent, segment, transaction }) } } }) } -function handleResponse({ shim, err, response, segment, bedrockCommand, modelType }) { +function handleResponse({ shim, err, response, segment, transaction, bedrockCommand, modelType }) { const { agent } = shim const bedrockResponse = createBedrockResponse({ bedrockCommand, response, err }) - addLlmMeta({ agent, segment }) + addLlmMeta({ agent, segment, transaction }) if (modelType === 'completion') { recordChatCompletionMessages({ agent, shim, segment, + transaction, bedrockCommand, bedrockResponse, err @@ -275,6 +292,7 @@ function handleResponse({ shim, err, response, segment, bedrockCommand, modelTyp agent, shim, segment, + transaction, bedrockCommand, bedrockResponse, err diff --git a/lib/instrumentation/core/globals.js b/lib/instrumentation/core/globals.js index 2cd4a2dfba..d97fe67a18 100644 --- a/lib/instrumentation/core/globals.js +++ b/lib/instrumentation/core/globals.js @@ -43,9 +43,7 @@ function initialize(agent, nodule, name, shim) { !process.domain && process.listenerCount('unhandledRejection') === 0 ) { - // If there are no unhandledRejection handlers report the error. - const segment = promise[symbols.context] && promise[symbols.context].getSegment() - const tx = segment && segment.transaction + const tx = promise[symbols.context] && promise[symbols.context].getTransaction() shim.logger.trace('Captured unhandled rejection for transaction %s', tx && tx.id) agent.errors.add(tx, error) } diff --git a/lib/instrumentation/core/http-outbound.js b/lib/instrumentation/core/http-outbound.js index fb3d2c4918..5e06f552c9 100644 --- a/lib/instrumentation/core/http-outbound.js +++ b/lib/instrumentation/core/http-outbound.js @@ -16,7 +16,6 @@ const symbols = require('../../symbols') const http = require('http') const synthetics = require('../../synthetics') const { URL } = require('node:url') - const NAMES = require('../../metrics/names') const DEFAULT_HOST = 'localhost' const DEFAULT_HTTP_PORT = 80 @@ -169,10 +168,14 @@ module.exports = function instrumentOutbound(agent, opts, makeRequest) { * @param {string} params.hostname domain of outbound request * @param {string} params.port port of outbound request * @param {TraceSegment} segment outbound http segment + * @param {Transaction} transaction active tx * @returns {http.IncomingMessage} request actual http outbound request */ -function instrumentRequest({ agent, opts, makeRequest, host, port, hostname }, segment) { - const transaction = segment.transaction +function instrumentRequest( + { agent, opts, makeRequest, host, port, hostname }, + segment, + transaction +) { const outboundHeaders = Object.create(null) opts.headers = opts.headers || {} @@ -181,7 +184,15 @@ function instrumentRequest({ agent, opts, makeRequest, host, port, hostname }, s maybeAddDtCatHeaders(agent, transaction, outboundHeaders, opts?.headers) opts.headers = assignOutgoingHeaders(opts.headers, outboundHeaders) - const request = applySegment({ opts, makeRequest, hostname, host, port, segment }) + const request = applySegment({ + opts, + makeRequest, + hostname, + host, + port, + segment, + config: agent.config + }) instrumentRequestEmit(agent, host, segment, request) @@ -249,13 +260,14 @@ function assignOutgoingHeaders(currentHeaders, outboundHeaders) { * @param {string} params.port port of outbound request * @param {string} params.hostname host + port of outbound request * @param {TraceSegment} params.segment outbound http segment + * @param {object} params.config agent config * @returns {http.IncomingMessage} request actual http outbound request */ -function applySegment({ opts, makeRequest, host, port, hostname, segment }) { +function applySegment({ opts, makeRequest, host, port, hostname, segment, config }) { segment.start() const request = makeRequest(opts) const parsed = urltils.scrubAndParseParameters(request.path) - parsed.path = urltils.obfuscatePath(segment.transaction.agent.config, parsed.path) + parsed.path = urltils.obfuscatePath(config, parsed.path) const proto = parsed.protocol || opts.protocol || 'http:' segment.name += parsed.path segment.captureExternalAttributes({ @@ -284,13 +296,16 @@ function applySegment({ opts, makeRequest, host, port, hostname, segment }) { */ function instrumentRequestEmit(agent, host, segment, request) { shimmer.wrapMethod(request, 'request.emit', 'emit', function wrapEmit(emit) { - const boundEmit = agent.tracer.bindFunction(emit, segment) + const context = agent.tracer.getContext() + const newContext = context.enterSegment({ segment }) + const boundEmit = agent.tracer.bindFunction(emit, newContext) return function wrappedRequestEmit(evnt, arg) { + const transaction = agent.tracer.getTransaction() if (evnt === 'error') { segment.end() - handleError(segment, request, arg) + handleError({ transaction, req: request, error: arg }) } else if (evnt === 'response') { - handleResponse(segment, host, arg) + handleResponse({ agent, segment, transaction, host, res: arg }) } return boundEmit.apply(this, arguments) @@ -304,47 +319,50 @@ function instrumentRequestEmit(agent, host, segment, request) { * Notices the given error if there is no listener for the `error` event on the * request object. * - * @param {object} segment TraceSegment instance - * @param {object} req http.ClientRequest - * @param {Error} error If provided, unhandled error that occurred during request + * @param {object} params to function + * @param {Transaction} params.transaction active transaction + * @param {object} params.req http.ClientRequest + * @param {Error} params.error If provided, unhandled error that occurred during request * @returns {boolean} True if the error will be collected by New Relic. */ -function handleError(segment, req, error) { +function handleError({ transaction, req, error }) { if (req.listenerCount('error') > 0) { logger.trace(error, 'Not capturing outbound error because user has already handled it.') return false } logger.trace(error, 'Captured outbound error on behalf of the user.') - const tx = segment.transaction - tx.agent.errors.add(tx, error) + transaction.agent.errors.add(transaction, error) return true } /** * Ties the response object to the request segment. * - * @param {object} segment TraceSegment instance - * @param {string} hostname host of the HTTP request - * @param host - * @param {object} res http.ServerResponse + * @param {object} params to function + * @param {Agent} params.agent agent instance + * @param {TraceSegment} params.segment active segment + * @param {Transaction} params.transaction active transaction + * @param {string} params.host hostname of the HTTP request + * @param {object} params.res http.ServerResponse */ -function handleResponse(segment, host, res) { +function handleResponse({ agent, segment, transaction, host, res }) { // Add response attributes for spans segment.addSpanAttribute('http.statusCode', res.statusCode) segment.addSpanAttribute('http.statusText', res.statusMessage) // If CAT is enabled, grab those headers! - const agent = segment.transaction.agent if (agent.config.cross_application_tracer.enabled && !agent.config.distributed_tracing.enabled) { const { appData } = cat.extractCatHeaders(res.headers) const decodedAppData = cat.parseAppData(agent.config, appData) - cat.assignCatToSegment(decodedAppData, segment, host) + cat.assignCatToSegment({ appData: decodedAppData, segment, host, transaction }) } // Again a custom emit wrapper because we want to watch for the `end` event. shimmer.wrapMethod(res, 'response', 'emit', function wrapEmit(emit) { - const boundEmit = agent.tracer.bindFunction(emit, segment) + const context = agent.tracer.getContext() + const newContext = context.enterSegment({ segment }) + const boundEmit = agent.tracer.bindFunction(emit, newContext) return function wrappedResponseEmit(evnt) { if (evnt === 'end') { segment.end() diff --git a/lib/instrumentation/core/http.js b/lib/instrumentation/core/http.js index 549f87714c..156ae9078b 100644 --- a/lib/instrumentation/core/http.js +++ b/lib/instrumentation/core/http.js @@ -18,7 +18,6 @@ const synthetics = require('../../synthetics') const NAMES = require('../../metrics/names') const DESTS = require('../../config/attribute-filter').DESTINATIONS - const symbols = require('../../symbols') // For incoming requests this instrumentation functions by wrapping @@ -34,6 +33,7 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) { const tracer = agent.tracer const transport = isHTTPS ? 'HTTPS' : 'HTTP' return tracer.transactionProxy(function wrappedHandler(evnt, request, response) { + const context = tracer.getContext() const transaction = tracer.getTransaction() if (!transaction) { return emit.apply(this, arguments) @@ -51,7 +51,12 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) { // Create the transaction segment using the request URL for now. Once a // better name can be determined this segment will be renamed to that. - const segment = tracer.createSegment(request.url, recordWeb) + const segment = tracer.createSegment({ + name: request.url, + recorder: recordWeb, + parent: context.segment, + transaction + }) segment.start() if (request.method != null) { @@ -107,7 +112,8 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) { response.once('finish', instrumentedFinish.bind(response, segment, transaction)) response.once('close', instrumentedFinish.bind(response, segment, transaction)) - return tracer.bindFunction(emit, segment).apply(this, arguments) + const newContext = context.enterSegment({ segment }) + return tracer.bindFunction(emit, newContext).apply(this, arguments) }) } @@ -490,18 +496,24 @@ module.exports = function initialize(agent, http, moduleName) { 'createConnection', function wrapCreateConnection(original) { return function wrappedCreateConnection() { + const context = agent.tracer.getContext() if (!agent.getTransaction()) { return original.apply(this, arguments) } - const segment = agent.tracer.createSegment('http.Agent#createConnection') + const segment = agent.tracer.createSegment({ + name: 'http.Agent#createConnection', + parent: context.segment, + transaction: context.transaction + }) const args = agent.tracer.slice(arguments) + const newContext = context.enterSegment({ segment }) if (typeof args[1] === 'function') { - args[1] = agent.tracer.bindFunction(args[1], segment, true) + args[1] = agent.tracer.bindFunction(args[1], newContext, true) } - return agent.tracer.bindFunction(original, segment, true).apply(this, args) + return agent.tracer.bindFunction(original, newContext, true).apply(this, args) } } ) diff --git a/lib/instrumentation/core/net.js b/lib/instrumentation/core/net.js index c7df7090ce..736eebcad4 100644 --- a/lib/instrumentation/core/net.js +++ b/lib/instrumentation/core/net.js @@ -14,7 +14,7 @@ module.exports = function initialize(agent, net, moduleName, shim) { return fn.apply(this, arguments) } - const child = shim.createSegment('net.' + name, null, segment) + const child = shim.createSegment({ name: 'net.' + name, parent: segment }) const sock = shim.applySegment(fn, child, true, this, arguments) wrapSocket(sock, child) return sock @@ -43,6 +43,7 @@ module.exports = function initialize(agent, net, moduleName, shim) { function wrapListen2(shim, fn) { return function wrappedListen2() { + const context = shim.tracer.getContext() const segment = shim.getActiveSegment() const emit = this.emit @@ -59,13 +60,20 @@ module.exports = function initialize(agent, net, moduleName, shim) { return emit.apply(this, arguments) } - const child = shim.createSegment('net.Server.onconnection', segment) + const child = shim.createSegment({ name: 'net.Server.onconnection', parent: segment }) + const newContext = context.enterSegment({ segment: child }) if (socket._handle.onread) { - shim.bindSegment(socket._handle, 'onread', child) + shim.bindContext({ nodule: socket._handle, property: 'onread', context: newContext }) } - return shim.applySegment(emit, child, true, this, arguments) + return shim.applyContext({ + func: emit, + context: newContext, + full: true, + boundThis: this, + args: arguments + }) } } } @@ -75,11 +83,12 @@ module.exports = function initialize(agent, net, moduleName, shim) { if (!agent.getTransaction()) { return fn.apply(this, arguments) } + const context = agent.tracer.getContext() const socket = this const args = normalizeConnectArgs(arguments) - const segment = shim.createSegment('net.Socket.connect') + const segment = shim.createSegment({ name: 'net.Socket.connect', parent: context.segment }) if (args[1]) { args[1] = shim.bindSegment(args[1], segment) diff --git a/lib/instrumentation/grpc-js/grpc.js b/lib/instrumentation/grpc-js/grpc.js index 438facefec..b223e5c69e 100644 --- a/lib/instrumentation/grpc-js/grpc.js +++ b/lib/instrumentation/grpc-js/grpc.js @@ -48,6 +48,7 @@ function wrapStart(shim, original) { return original.apply(this, arguments) } + const transaction = shim.tracer.getTransaction() const channel = this.channel const authorityName = (channel.target && channel.target.path) || channel.getDefaultAuthority // in 1.8.0 this changed from methodName to method @@ -56,7 +57,8 @@ function wrapStart(shim, original) { const segment = shim.createSegment({ name: `External/${authorityName}${method}`, opaque: true, - recorder: recordExternal(authorityName, 'gRPC') + recorder: recordExternal(authorityName, 'gRPC'), + parent: activeSegment }) return shim.applySegment(callStart, segment, true, this, arguments) @@ -64,8 +66,6 @@ function wrapStart(shim, original) { function callStart() { const args = shim.argsToArray.apply(shim, arguments) - const transaction = segment.transaction - const originalMetadata = args[0] const nrMetadata = originalMetadata.clone() @@ -93,7 +93,7 @@ function wrapStart(shim, original) { const config = agent.config if (shouldTrackError(code, config)) { - shim.agent.errors.add(segment.transaction, details) + shim.agent.errors.add(transaction, details) } segment.addAttribute('component', 'gRPC') @@ -171,12 +171,12 @@ function wrapRegister(shim, original) { } function createTransaction() { - const parent = shim.getActiveSegment() - const transaction = parent.transaction + const context = shim.tracer.getContext() + const transaction = context.transaction // Create the transaction segment using the request URL for now. Once a // better name can be determined this segment will be renamed to that. - const segment = shim.createSegment(name, recordHttp) + const segment = shim.createSegment(name, recordHttp, context.segment) segment.start() transaction.type = 'web' diff --git a/lib/instrumentation/kafkajs/consumer.js b/lib/instrumentation/kafkajs/consumer.js index c8a71ada44..327910582d 100644 --- a/lib/instrumentation/kafkajs/consumer.js +++ b/lib/instrumentation/kafkajs/consumer.js @@ -100,14 +100,14 @@ function handler({ consumer }) { * * @param {MessageShim} shim instance of shim * @param {Array} args arguments passed to the `eachMessage` function of the `consumer.run` + * @param {Transaction} tx active transaction * @returns {MessageSpec} spec for message handling */ - return function messageHandler(shim, args) { + return function messageHandler(shim, args, tx) { recordMethodMetric({ agent: shim.agent, name: 'eachMessage' }) const [data] = args const { topic } = data - const segment = shim.getActiveSegment() recordLinkingMetrics({ agent: shim.agent, @@ -116,8 +116,7 @@ function handler({ consumer }) { producer: false }) - if (segment?.transaction) { - const tx = segment.transaction + if (tx) { const byteLength = data?.message.value?.byteLength const metricPrefix = `Message/Kafka/Topic/Named/${topic}/Received` // This will always be 1 diff --git a/lib/instrumentation/koa/instrumentation.js b/lib/instrumentation/koa/instrumentation.js index 7514529017..602f02895c 100644 --- a/lib/instrumentation/koa/instrumentation.js +++ b/lib/instrumentation/koa/instrumentation.js @@ -117,14 +117,12 @@ function wrapMatchedRoute(shim, context) { set: (val) => { // match should never be undefined given _matchedRoute was set if (val) { - const currentSegment = shim.getActiveSegment() + const transaction = shim.tracer.getTransaction() // Segment/Transaction may be null, see: // - https://github.com/newrelic/node-newrelic-koa/issues/32 // - https://github.com/newrelic/node-newrelic-koa/issues/33 - if (currentSegment) { - const transaction = currentSegment.transaction - + if (transaction) { if (context[symbols.koaMatchedRoute]) { transaction.nameState.popPath() } diff --git a/lib/instrumentation/langchain/runnable.js b/lib/instrumentation/langchain/runnable.js index b6d76f4ba6..52882ad866 100644 --- a/lib/instrumentation/langchain/runnable.js +++ b/lib/instrumentation/langchain/runnable.js @@ -62,8 +62,9 @@ function instrumentInvokeChain({ langchain, shim }) { return new RecorderSpec({ name: `${LANGCHAIN.CHAIN}/${fnName}`, promise: true, - after({ error: err, result: output, segment }) { + after({ error: err, result: output, segment, transaction }) { recordChatCompletionEvents({ + transaction, segment, messages: [output], events: [request, output], @@ -97,14 +98,15 @@ function instrumentStream({ langchain, shim }) { return new RecorderSpec({ name: `${LANGCHAIN.CHAIN}/${fnName}`, promise: true, - after({ error: err, result: output, segment }) { + after({ error: err, result: output, segment, transaction }) { // Input error occurred which means a stream was not created. // Skip instrumenting streaming and create Llm Events from // the data we have if (output?.next) { - wrapNextHandler({ shim, output, segment, request, metadata, tags }) + wrapNextHandler({ shim, output, segment, request, metadata, tags, transaction }) } else { recordChatCompletionEvents({ + transaction, segment, messages: [], events: [request], @@ -131,8 +133,9 @@ function instrumentStream({ langchain, shim }) { * @param {string} params.request the prompt message * @param {object} params.metadata metadata for the call * @param {Array} params.tags tags for the call + * @param {Transaction} params.transaction active transaction */ -function wrapNextHandler({ shim, output, segment, request, metadata, tags }) { +function wrapNextHandler({ shim, output, segment, transaction, request, metadata, tags }) { shim.wrap(output, 'next', function wrapIterator(shim, orig) { let content = '' return async function wrappedIterator() { @@ -141,6 +144,7 @@ function wrapNextHandler({ shim, output, segment, request, metadata, tags }) { // only create Llm events when stream iteration is done if (result?.done) { recordChatCompletionEvents({ + transaction, segment, messages: [content], events: [request, content], @@ -154,6 +158,7 @@ function wrapNextHandler({ shim, output, segment, request, metadata, tags }) { return result } catch (error) { recordChatCompletionEvents({ + transaction, segment, messages: [content], events: [request, content], @@ -183,8 +188,18 @@ function wrapNextHandler({ shim, output, segment, request, metadata, tags }) { * @param {Array} params.tags tags for the call * @param {Error} params.err error object from call * @param {Shim} params.shim shim instance + * @param {Transaction} params.transaction active transaction */ -function recordChatCompletionEvents({ segment, messages, events, metadata, tags, err, shim }) { +function recordChatCompletionEvents({ + segment, + transaction, + messages, + events, + metadata, + tags, + err, + shim +}) { const { pkgVersion, agent } = shim segment.end() @@ -202,6 +217,7 @@ function recordChatCompletionEvents({ segment, messages, events, metadata, tags, metadata, tags, segment, + transaction, error: err != null, runId: segment[langchainRunId] }) @@ -221,12 +237,13 @@ function recordChatCompletionEvents({ segment, messages, events, metadata, tags, completionSummary, agent, segment, - shim + shim, + transaction }) if (err) { agent.errors.add( - segment.transaction, + transaction, err, new LlmErrorMessage({ response: {}, @@ -236,7 +253,7 @@ function recordChatCompletionEvents({ segment, messages, events, metadata, tags, ) } - segment.transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true) + transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true) } /** @@ -248,8 +265,9 @@ function recordChatCompletionEvents({ segment, messages, events, metadata, tags, * @param {Agent} params.agent instance of agent * @param {TraceSegment} params.segment active segment * @param {Shim} params.shim shim instance + * @param {Transaction} params.transaction active transaction */ -function recordCompletions({ events, completionSummary, agent, segment, shim }) { +function recordCompletions({ events, completionSummary, agent, segment, shim, transaction }) { for (let i = 0; i < events.length; i += 1) { let msg = events[i] if (msg?.content) { @@ -270,6 +288,7 @@ function recordCompletions({ events, completionSummary, agent, segment, shim }) content: msgString, completionId: completionSummary.id, segment, + transaction, runId: segment[langchainRunId], // We call the final output in a LangChain "chain" the "response": isResponse: i === events.length - 1 diff --git a/lib/instrumentation/langchain/tools.js b/lib/instrumentation/langchain/tools.js index 17b0178998..2554e70103 100644 --- a/lib/instrumentation/langchain/tools.js +++ b/lib/instrumentation/langchain/tools.js @@ -31,7 +31,7 @@ module.exports = function initialize(shim, tools) { return new RecorderSpec({ name: `${LANGCHAIN.TOOL}/${name}`, promise: true, - after({ error: err, result: output, segment }) { + after({ error: err, result: output, segment, transaction }) { const metadata = mergeMetadata(instanceMeta, paramsMeta) const tags = mergeTags(instanceTags, paramsTags) segment.end() @@ -50,6 +50,7 @@ module.exports = function initialize(shim, tools) { name, runId: segment[langchainRunId], metadata, + transaction, tags, input: request?.input, output, @@ -60,7 +61,7 @@ module.exports = function initialize(shim, tools) { if (err) { agent.errors.add( - segment.transaction, + transaction, err, new LlmErrorMessage({ response: {}, @@ -70,7 +71,7 @@ module.exports = function initialize(shim, tools) { ) } - segment.transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true) + transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true) } }) }) diff --git a/lib/instrumentation/langchain/vectorstore.js b/lib/instrumentation/langchain/vectorstore.js index 61d27174b0..3d7ae673d5 100644 --- a/lib/instrumentation/langchain/vectorstore.js +++ b/lib/instrumentation/langchain/vectorstore.js @@ -27,11 +27,23 @@ const LlmErrorMessage = require('../../llm-events/error-message') * @param {TraceSegment} params.segment active segment from vector search * @param {string} params.pkgVersion langchain version * @param {Error} params.err if it exists + * @param {Transaction} params.transaction active transaction */ -function recordVectorSearch({ request, k, output, agent, shim, segment, pkgVersion, err }) { +function recordVectorSearch({ + request, + k, + output, + agent, + shim, + segment, + transaction, + pkgVersion, + err +}) { const vectorSearch = new LangChainVectorSearch({ agent, segment, + transaction, query: request, k, documents: output, @@ -47,7 +59,8 @@ function recordVectorSearch({ request, k, output, agent, shim, segment, pkgVersi metadata: document.metadata, pageContent: document.pageContent, sequence, - search_id: vectorSearch.id + search_id: vectorSearch.id, + transaction }) recordEvent({ @@ -61,7 +74,7 @@ function recordVectorSearch({ request, k, output, agent, shim, segment, pkgVersi if (err) { agent.errors.add( - segment.transaction, + transaction, err, new LlmErrorMessage({ response: output, @@ -91,7 +104,7 @@ module.exports = function initialize(shim, vectorstores) { return new RecorderSpec({ name: `${LANGCHAIN.VECTORSTORE}/${fnName}`, promise: true, - after({ error: err, result: output, segment }) { + after({ error: err, result: output, segment, transaction }) { if (!output) { // If we get an error, it is possible that `output = null`. // In that case, we define it to be an empty array. @@ -107,9 +120,19 @@ module.exports = function initialize(shim, vectorstores) { return } - recordVectorSearch({ request, k, output, agent, shim, segment, pkgVersion, err }) + recordVectorSearch({ + request, + k, + output, + agent, + shim, + segment, + transaction, + pkgVersion, + err + }) - segment.transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true) + transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true) } }) } diff --git a/lib/instrumentation/nextjs/next-server.js b/lib/instrumentation/nextjs/next-server.js index c2528be5d6..37272e6a35 100644 --- a/lib/instrumentation/nextjs/next-server.js +++ b/lib/instrumentation/nextjs/next-server.js @@ -122,10 +122,8 @@ module.exports = function initialize(shim, nextServer) { } function assignParameters(shim, parameters) { - const activeSegment = shim.getActiveSegment() - if (activeSegment) { - const transaction = activeSegment.transaction - + const transaction = shim.tracer.getTransaction() + if (transaction) { const prefixedParameters = shim.prefixRouteParameters(parameters) // We have to add params because this framework doesn't diff --git a/lib/instrumentation/openai.js b/lib/instrumentation/openai.js index 36e4487057..275497bba5 100644 --- a/lib/instrumentation/openai.js +++ b/lib/instrumentation/openai.js @@ -89,11 +89,11 @@ function recordEvent({ agent, type, msg }) { * * @param {object} params input params * @param {Agent} params.agent NR agent instance - * @param {TraceSegment} params.segment active segment + * @param {Transaction} params.transaction active transaction */ -function addLlmMeta({ agent, segment }) { +function addLlmMeta({ agent, transaction }) { agent.metrics.getOrCreateMetric(TRACKING_METRIC).incrementCallCount() - segment.transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true) + transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true) } /** @@ -110,8 +110,17 @@ function addLlmMeta({ agent, segment }) { * @param {object} params.request chat completion params * @param {object} params.response chat completion response * @param {boolean} [params.err] err if it exists + * @param {Transaction} params.transaction active transaction */ -function recordChatCompletionMessages({ agent, shim, segment, request, response, err }) { +function recordChatCompletionMessages({ + agent, + shim, + segment, + request, + response, + err, + transaction +}) { if (shouldSkipInstrumentation(agent.config, shim) === true) { shim.logger.debug('skipping sending of ai data') return @@ -130,6 +139,7 @@ function recordChatCompletionMessages({ agent, shim, segment, request, response, const completionSummary = new LlmChatCompletionSummary({ agent, segment, + transaction, request, response, withError: err != null @@ -141,6 +151,7 @@ function recordChatCompletionMessages({ agent, shim, segment, request, response, const completionMsg = new LlmChatCompletionMessage({ agent, segment, + transaction, request, response, index, @@ -155,7 +166,7 @@ function recordChatCompletionMessages({ agent, shim, segment, request, response, if (err) { const llmError = new LlmErrorMessage({ cause: err, summary: completionSummary, response }) - agent.errors.add(segment.transaction, err, llmError) + agent.errors.add(transaction, err, llmError) } delete response.headers @@ -170,7 +181,7 @@ function recordChatCompletionMessages({ agent, shim, segment, request, response, * messages * */ -function instrumentStream({ agent, shim, request, response, segment }) { +function instrumentStream({ agent, shim, request, response, segment, transaction }) { if (!agent.config.ai_monitoring.streaming.enabled) { shim.logger.warn( '`ai_monitoring.streaming.enabled` is set to `false`, stream will not be instrumented.' @@ -209,6 +220,7 @@ function instrumentStream({ agent, shim, request, response, segment }) { agent: shim.agent, shim, segment, + transaction, request, response: chunk, err @@ -276,21 +288,22 @@ module.exports = function initialize(agent, openai, moduleName, shim) { return new RecorderSpec({ name: OPENAI.COMPLETION, promise: true, - after({ error: err, result: response, segment }) { + after({ error: err, result: response, segment, transaction }) { if (request.stream) { - instrumentStream({ agent, shim, request, response, segment }) + instrumentStream({ agent, shim, request, response, segment, transaction }) } else { recordChatCompletionMessages({ agent, shim, segment, + transaction, request, response, err }) } - addLlmMeta({ agent, segment }) + addLlmMeta({ agent, transaction }) } }) } @@ -308,8 +321,8 @@ module.exports = function initialize(agent, openai, moduleName, shim) { return new RecorderSpec({ name: OPENAI.EMBEDDING, promise: true, - after({ error: err, result: response, segment }) { - addLlmMeta({ agent, segment }) + after({ error: err, result: response, segment, transaction }) { + addLlmMeta({ agent, transaction }) if (!response) { // If we get an error, it is possible that `response = null`. @@ -333,6 +346,7 @@ module.exports = function initialize(agent, openai, moduleName, shim) { const embedding = new LlmEmbedding({ agent, segment, + transaction, request, response, withError: err != null @@ -342,7 +356,7 @@ module.exports = function initialize(agent, openai, moduleName, shim) { if (err) { const llmError = new LlmErrorMessage({ cause: err, embedding, response }) - shim.agent.errors.add(segment.transaction, err, llmError) + shim.agent.errors.add(transaction, err, llmError) } // cleanup keys on response before returning to user code diff --git a/lib/instrumentation/restify.js b/lib/instrumentation/restify.js index 85960bf157..463cc733de 100644 --- a/lib/instrumentation/restify.js +++ b/lib/instrumentation/restify.js @@ -24,13 +24,13 @@ module.exports = function initialize(_agent, restify, _moduleName, shim) { shim.wrap(http.ServerResponse.prototype, methods, function wrapMethod(shim, fn) { return function wrappedMethod() { - const segment = shim.getActiveSegment() + const transaction = shim.tracer.getTransaction() - if (segment) { + if (transaction) { // Freezing the name state prevents transaction names from potentially being // manipulated by asynchronous response methods executed as part of res.send() // but before `next()` is called. - segment.transaction.nameState.freeze() + transaction.nameState.freeze() } return fn.apply(this, arguments) diff --git a/lib/instrumentation/superagent.js b/lib/instrumentation/superagent.js index 185925ee55..94aaa64cc9 100644 --- a/lib/instrumentation/superagent.js +++ b/lib/instrumentation/superagent.js @@ -48,8 +48,8 @@ function wrapHttpReq(shim, fn, name, req) { function wrapCallback(shim, callback) { return function wrappedCallback() { - const segment = shim.getSegment(this) - if (segment && segment.transaction.isActive()) { + const segment = shim.getActiveSegment(this) + if (segment) { shim.bindCallbackSegment(null, this, '_callback', segment) } return callback.apply(this, arguments) @@ -58,7 +58,7 @@ function wrapCallback(shim, callback) { function wrapThen(shim, then) { return function wrappedThen(resolve, reject) { - const segment = shim.getSegment(this) || shim.getActiveSegment() + const segment = shim.getActiveSegment(this) if (!segment) { return then.apply(this, arguments) } diff --git a/lib/instrumentation/undici.js b/lib/instrumentation/undici.js index cda18b0942..a72e719394 100644 --- a/lib/instrumentation/undici.js +++ b/lib/instrumentation/undici.js @@ -48,8 +48,8 @@ module.exports.unsubscribe = function unsubscribe() { } /** - * Retrieves the current segment in transaction(parent in our context) from executionAsyncResource - * or from `shim.getSegment()` then adds to the executionAsyncResource for future + * Retrieves the current segment(parent in our context) and transaction from executionAsyncResource + * or from context manager then adds to the executionAsyncResource for future * undici requests within same async context. * * It was found that when running concurrent undici requests @@ -61,12 +61,12 @@ module.exports.unsubscribe = function unsubscribe() { * and the request to the transaction is using a keep alive there is a chance the * executionAsyncResource may be incorrect because of shared connections. To revert to a more * naive tracking of parent set `config.feature_flag.undici_async_tracking: false` and - * it will just call `shim.getSegment()` + * it will just get the segment and transaction from the context manager. * * @param {Shim} shim instance of shim * @returns {TraceSegment} parent segment */ -function getParentSegment(shim) { +function getParentContext(shim) { const { config } = shim.agent if (config.feature_flag.undici_async_tracking) { const resource = executionAsyncResource() @@ -74,10 +74,13 @@ function getParentSegment(shim) { if (!resource[symbols.parentSegment]) { const parent = shim.getSegment() resource[symbols.parentSegment] = parent + const tx = shim.tracer.getTransaction() + resource[symbols.transaction] = tx } - return resource[symbols.parentSegment] + return { segment: resource[symbols.parentSegment], transaction: resource[symbols.transaction] } } - return shim.getSegment() + + return shim.tracer.getContext() } /** @@ -112,25 +115,25 @@ function addDTHeaders({ transaction, config, request }) { * @param {object} params object to fn * @param {Shim} params.shim instance of shim * @param {object} params.request undici request object - * @param {object} params.parentSegment current active, about to be parent of external segment + * @param {TraceSegment} params.segment current active, about to be parent of external segment */ -function createExternalSegment({ shim, request, parentSegment }) { +function createExternalSegment({ shim, request, segment }) { const url = new URL(request.origin + request.path) const obfuscatedPath = urltils.obfuscatePath(shim.agent.config, url.pathname) const name = NAMES.EXTERNAL.PREFIX + url.host + obfuscatedPath // Metrics for `External/` will have a suffix of undici // We will have to see if this matters for people only using fetch // It's undici under the hood so ¯\_(ツ)_/¯ - const segment = shim.createSegment(name, recordExternal(url.host, 'undici'), parentSegment) + const externalSegment = shim.createSegment(name, recordExternal(url.host, 'undici'), segment) // the captureExternalAttributes expects queryParams to be an object, do conversion // to object see: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams const queryParams = Object.fromEntries(url.searchParams.entries()) - if (segment) { - segment.start() - shim.setActiveSegment(segment) - segment.captureExternalAttributes({ + if (externalSegment) { + externalSegment.start() + shim.setActiveSegment(externalSegment) + externalSegment.captureExternalAttributes({ protocol: url.protocol, hostname: url.hostname, host: url.host, @@ -140,7 +143,7 @@ function createExternalSegment({ shim, request, parentSegment }) { queryParams }) - request[symbols.segment] = segment + request[symbols.segment] = externalSegment } } @@ -156,21 +159,22 @@ function createExternalSegment({ shim, request, parentSegment }) { */ function requestCreateHook(shim, { request }) { const { config } = shim.agent - const parentSegment = getParentSegment(shim) - request[symbols.parentSegment] = parentSegment - if (!parentSegment || (parentSegment && parentSegment.opaque)) { + const { transaction, segment } = getParentContext(shim) + request[symbols.parentSegment] = segment + request[symbols.transaction] = transaction + if (!(segment || transaction) || segment?.opaque) { logger.trace( 'Not capturing data for outbound request (%s) because parent segment opaque (%s)', request.path, - parentSegment && parentSegment.name + segment?.name ) return } try { - addDTHeaders({ transaction: parentSegment.transaction, config, request }) - createExternalSegment({ shim, request, parentSegment }) + addDTHeaders({ transaction, config, request }) + createExternalSegment({ shim, request, segment }) } catch (err) { logger.warn(err, 'Unable to create external segment') } @@ -189,6 +193,7 @@ function requestCreateHook(shim, { request }) { function requestHeadersHook(shim, { request, response }) { const { config } = shim.agent const activeSegment = request[symbols.segment] + const transaction = request[symbols.transaction] if (!activeSegment) { return } @@ -202,7 +207,12 @@ function requestHeadersHook(shim, { request, response }) { const decodedAppData = cat.parseAppData(config, appData) const attrs = activeSegment.getAttributes() const url = new URL(attrs.url) - cat.assignCatToSegment(decodedAppData, activeSegment, url.host) + cat.assignCatToSegment({ + appData: decodedAppData, + segment: activeSegment, + host: url.host, + transaction + }) } catch (err) { logger.warn(err, 'Cannot add CAT data to segment') } @@ -222,11 +232,12 @@ function requestHeadersHook(shim, { request, response }) { function endAndRestoreSegment(shim, { request, error }) { const activeSegment = request[symbols.segment] const parentSegment = request[symbols.parentSegment] + const tx = request[symbols.transaction] if (activeSegment) { activeSegment.end() if (error) { - handleError(shim, activeSegment, error) + handleError(shim, tx, error) } if (parentSegment) { @@ -239,11 +250,10 @@ function endAndRestoreSegment(shim, { request, error }) { * Adds the error to the active transaction * * @param {Shim} shim instance of shim - * @param {TraceSegment} activeSegment active segment + * @param {Transaction} tx active transaction * @param {Error} error error from undici request */ -function handleError(shim, activeSegment, error) { +function handleError(shim, tx, error) { logger.trace(error, 'Captured outbound error on behalf of the user.') - const { transaction } = activeSegment - shim.agent.errors.add(transaction, error) + shim.agent.errors.add(tx, error) } diff --git a/lib/instrumentation/when/contextualizer.js b/lib/instrumentation/when/contextualizer.js index ed95351e1c..05d8344d34 100644 --- a/lib/instrumentation/when/contextualizer.js +++ b/lib/instrumentation/when/contextualizer.js @@ -6,8 +6,9 @@ 'use strict' const symbols = require('../../symbols') -function Context(segment) { +function Context(segment, transaction) { this.segments = [segment] + this.transaction = transaction } Context.prototype = Object.create(null) @@ -86,10 +87,11 @@ function bindChild(ctxlzr, next) { * * @param {Function} prev previous function in chain * @param {Function} next next function in chain - * @param {object} segment proper segment to bind + * @param {TraceSegment} segment proper segment to bind + * @param {Transaction} transaction proper transaction to bind * @returns {void} */ -Contextualizer.link = function link(prev, next, segment) { +Contextualizer.link = function link(prev, next, segment, transaction) { let ctxlzr = prev && prev[symbols.context] if (ctxlzr && !ctxlzr.isActive()) { ctxlzr = prev[symbols.context] = null @@ -100,7 +102,7 @@ Contextualizer.link = function link(prev, next, segment) { } else if (segment) { // This next promise is the root of a chain. Either there was no previous // promise or the promise was created out of context. - next[symbols.context] = new Contextualizer(0, new Context(segment)) + next[symbols.context] = new Contextualizer(0, new Context(segment, transaction)) } } @@ -114,7 +116,7 @@ Contextualizer.prototype = Object.create(null) Contextualizer.prototype.isActive = function isActive() { const segments = this.context.segments const segment = segments[this.idx] || segments[this.parentIdx] || segments[0] - return segment && segment.transaction.isActive() + return segment && this.context.transaction.isActive() } /** @@ -122,7 +124,7 @@ Contextualizer.prototype.isActive = function isActive() { * If there is none it will get the segment at the parent index or the first one * then assign to current index. * - * @returns {object} segment by idx or parentIdx or the first one + * @returns {TraceSegment} segment by idx or parentIdx or the first one */ Contextualizer.prototype.getSegment = function getSegment() { const segments = this.context.segments @@ -133,11 +135,20 @@ Contextualizer.prototype.getSegment = function getSegment() { return segment } +/** + * Gets the transaction from the context. + * + * @returns {Transaction} transaction from context + */ +Contextualizer.prototype.getTransaction = function getTransaction() { + return this.context.transaction +} + /** * Sets the set to the appropriate index * - * @param {object} segment segment to assign to index - * @returns {object} same segment passed in + * @param {TraceSegment} segment segment to assign to index + * @returns {TraceSegment} same segment passed in */ Contextualizer.prototype.setSegment = function setSegment(segment) { return (this.context.segments[this.idx] = segment) diff --git a/lib/instrumentation/when/index.js b/lib/instrumentation/when/index.js index ddace24ee9..4786b3047c 100644 --- a/lib/instrumentation/when/index.js +++ b/lib/instrumentation/when/index.js @@ -81,11 +81,13 @@ module.exports = function initialize(shim, when) { return Promise(executor) // eslint-disable-line new-cap } - const parent = agent.tracer.getSegment() + const context = agent.tracer.getContext() + const parent = context.segment + const transaction = context.transaction let promise = null if ( !parent || - !parent.transaction.isActive() || + !transaction.isActive() || typeof executor !== 'function' || arguments.length !== 1 ) { @@ -97,21 +99,22 @@ module.exports = function initialize(shim, when) { promise = new (Promise.bind.apply(Promise, cnstrctArgs))() } else { const segmentName = 'Promise ' + (executor.name || ANONYMOUS) - const context = { + const ctx = { promise: null, self: null, args: null } - promise = new Promise(wrapExecutorContext(context)) - context.promise = promise - const segment = _createSegment(segmentName) - Contextualizer.link(null, promise, segment) + promise = new Promise(wrapExecutorContext(ctx)) + ctx.promise = promise + const segment = _createSegment({ name: segmentName, transaction, parent }) + const newContext = context.enterSegment({ segment }) + Contextualizer.link(null, promise, segment, transaction) try { // Must run after promise is defined so that `__NR_wrapper` can be set. - agent.tracer.bindFunction(executor, segment, true).apply(context.self, context.args) + agent.tracer.bindFunction(executor, newContext, true).apply(ctx.self, ctx.args) } catch (e) { - context.args[1](e) + ctx.args[1](e) } } @@ -234,6 +237,7 @@ module.exports = function initialize(shim, when) { const segmentNamePrefix = 'Promise#' + name + ' ' const thenSegment = agent.tracer.getSegment() + const transaction = agent.tracer.getTransaction() const promise = this const ctx = { next: undefined, useAllParams, isWrapped: false, segmentNamePrefix } @@ -243,7 +247,7 @@ module.exports = function initialize(shim, when) { // If we got a promise (which we should have), link the parent's context. if (!ctx.isWrapped && ctx.next instanceof Promise && ctx.next !== promise) { - Contextualizer.link(promise, ctx.next, thenSegment) + Contextualizer.link(promise, ctx.next, thenSegment, transaction) } return ctx.next } @@ -276,15 +280,19 @@ module.exports = function initialize(shim, when) { let promSegment = ctx.next[symbols.context].getSegment() const segmentName = ctx.segmentNamePrefix + (fn.name || ANONYMOUS) - const segment = _createSegment(segmentName, promSegment) + const transaction = ctx.next[symbols.context].getTransaction() + const segment = _createSegment({ name: segmentName, parent: promSegment, transaction }) if (segment && segment !== promSegment) { ctx.next[symbols.context].setSegment(segment) promSegment = segment } + let context = agent.tracer.getContext() + context = context.enterSegment({ transaction, segment: promSegment }) + let ret = null try { - ret = agent.tracer.bindFunction(fn, promSegment, true).apply(this, arguments) + ret = agent.tracer.bindFunction(fn, context, true).apply(this, arguments) } finally { if (ret && typeof ret.then === 'function') { ret = ctx.next[symbols.context].continue(ret) @@ -309,10 +317,12 @@ module.exports = function initialize(shim, when) { const CAST_SEGMENT_NAME = 'Promise.' + name // eslint-disable-next-line camelcase return function __NR_wrappedCast() { - const segment = _createSegment(CAST_SEGMENT_NAME) + const transaction = agent.tracer.getTransaction() + const parent = agent.tracer.getSegment() + const segment = _createSegment({ name: CAST_SEGMENT_NAME, transaction, parent }) const prom = cast.apply(this, arguments) if (segment) { - Contextualizer.link(null, prom, segment) + Contextualizer.link(null, prom, segment, transaction) } return prom } @@ -323,13 +333,15 @@ module.exports = function initialize(shim, when) { * if `config.feature_flag.promise_segments` is true * Otherwise it just returns the current if existing or gets the current * - * @param {string} name name of segment to create - * @param {object} parent current parent segment - * @returns {object} segment + * @param {object} params to function + * @param {string} params.name name of segment to create + * @param {TraceSegment} params.parent current parent segment + * @param {Transaction} params.transaction active transaction + * @returns {TraceSegment} segment */ - function _createSegment(name, parent) { + function _createSegment({ name, parent, transaction }) { return agent.config.feature_flag.promise_segments === true - ? agent.tracer.createSegment(name, null, parent) + ? agent.tracer.createSegment({ name, parent, transaction }) : parent || agent.tracer.getSegment() } } diff --git a/lib/llm-events/aws-bedrock/chat-completion-summary.js b/lib/llm-events/aws-bedrock/chat-completion-summary.js index 20c4edd6c2..36492a0e73 100644 --- a/lib/llm-events/aws-bedrock/chat-completion-summary.js +++ b/lib/llm-events/aws-bedrock/chat-completion-summary.js @@ -10,6 +10,7 @@ const LlmEvent = require('./event') /** * @typedef {object} LlmChatCompletionSummaryParams * @augments LlmEventParams + * @property */ /** * @type {LlmChatCompletionSummaryParams} diff --git a/lib/llm-events/aws-bedrock/embedding.js b/lib/llm-events/aws-bedrock/embedding.js index 3cfa9e3b88..a1eec6e1bf 100644 --- a/lib/llm-events/aws-bedrock/embedding.js +++ b/lib/llm-events/aws-bedrock/embedding.js @@ -10,6 +10,7 @@ const LlmEvent = require('./event') /** * @typedef {object} LlmEmbeddingParams * @augments LlmEventParams + * @property */ /** * @type {LlmEmbeddingParams} diff --git a/lib/llm-events/aws-bedrock/event.js b/lib/llm-events/aws-bedrock/event.js index d5434b6546..946ba40af7 100644 --- a/lib/llm-events/aws-bedrock/event.js +++ b/lib/llm-events/aws-bedrock/event.js @@ -22,9 +22,8 @@ const defaultParams = { agent: {}, bedrockCommand: {}, bedrockResponse: {}, - segment: { - transaction: {} - } + transaction: {}, + segment: {} } /** @@ -47,7 +46,7 @@ class LlmEvent { params = Object.assign({}, defaultParams, params) this.constructionParams = params - const { agent, bedrockCommand, bedrockResponse, segment } = params + const { agent, bedrockCommand, bedrockResponse, segment, transaction } = params this.bedrockCommand = bedrockCommand this.bedrockResponse = bedrockResponse @@ -56,7 +55,7 @@ class LlmEvent { this.ingest_source = 'Node' this.appName = agent.config.applications()[0] this.span_id = segment.id - this.trace_id = segment.transaction.traceId + this.trace_id = transaction.traceId this.request_id = this.bedrockResponse.requestId this.metadata = agent diff --git a/lib/llm-events/langchain/event.js b/lib/llm-events/langchain/event.js index e0e28ca7fd..ab1d688926 100644 --- a/lib/llm-events/langchain/event.js +++ b/lib/llm-events/langchain/event.js @@ -28,9 +28,8 @@ const { isSimpleObject } = require('../../util/objects') */ const defaultParams = { agent: {}, - segment: { - transaction: {} - }, + transaction: {}, + segment: {}, tags: [], runId: '', metadata: {}, @@ -54,12 +53,12 @@ class LangChainEvent extends BaseEvent { constructor(params = defaultParams) { params = Object.assign({}, defaultParams, params) super(params) - const { agent, segment } = params + const { agent, segment, transaction } = params this.appName = agent.config.applications()[0] this.span_id = segment?.id this.request_id = params.runId - this.trace_id = segment?.transaction?.traceId + this.trace_id = transaction?.traceId this.langchainMeta = params.metadata this.metadata = agent this.tags = Array.isArray(params.tags) ? params.tags.join(',') : params.tags diff --git a/lib/llm-events/openai/chat-completion-message.js b/lib/llm-events/openai/chat-completion-message.js index 61f101a464..14465229a3 100644 --- a/lib/llm-events/openai/chat-completion-message.js +++ b/lib/llm-events/openai/chat-completion-message.js @@ -7,8 +7,17 @@ const LlmEvent = require('./event') module.exports = class LlmChatCompletionMessage extends LlmEvent { - constructor({ agent, segment, request = {}, response = {}, index = 0, message, completionId }) { - super({ agent, segment, request, response }) + constructor({ + agent, + segment, + request = {}, + response = {}, + index = 0, + message, + completionId, + transaction + }) { + super({ agent, segment, request, response, transaction }) this.id = `${response.id}-${index}` this.role = message?.role this.sequence = index diff --git a/lib/llm-events/openai/chat-completion-summary.js b/lib/llm-events/openai/chat-completion-summary.js index 0d84982bd9..0b5435c150 100644 --- a/lib/llm-events/openai/chat-completion-summary.js +++ b/lib/llm-events/openai/chat-completion-summary.js @@ -7,8 +7,8 @@ const LlmEvent = require('./event') module.exports = class LlmChatCompletionSummary extends LlmEvent { - constructor({ agent, segment, request = {}, response = {}, withError = false }) { - super({ agent, segment, request, response, responseAttrs: true }) + constructor({ agent, segment, request = {}, response = {}, withError = false, transaction }) { + super({ agent, segment, request, response, responseAttrs: true, transaction }) this.error = withError this['request.max_tokens'] = request.max_tokens this['request.temperature'] = request.temperature diff --git a/lib/llm-events/openai/embedding.js b/lib/llm-events/openai/embedding.js index 1f2ee78bfb..004088cbd7 100644 --- a/lib/llm-events/openai/embedding.js +++ b/lib/llm-events/openai/embedding.js @@ -7,8 +7,8 @@ const LlmEvent = require('./event') module.exports = class LlmEmbedding extends LlmEvent { - constructor({ agent, segment, request = {}, response = {}, withError = false }) { - super({ agent, segment, request, response, responseAttrs: true }) + constructor({ agent, segment, request = {}, response = {}, withError = false, transaction }) { + super({ agent, segment, request, response, responseAttrs: true, transaction }) this.error = withError if (agent.config.ai_monitoring.record_content.enabled === true) { diff --git a/lib/llm-events/openai/event.js b/lib/llm-events/openai/event.js index e31e491e29..6a84681d82 100644 --- a/lib/llm-events/openai/event.js +++ b/lib/llm-events/openai/event.js @@ -9,13 +9,13 @@ const BaseEvent = require('../event') const { makeId } = require('../../util/hashes') module.exports = class LlmEvent extends BaseEvent { - constructor({ agent, segment, request, response, responseAttrs = false }) { + constructor({ agent, segment, request, response, responseAttrs = false, transaction }) { super() this.id = makeId(36) this.appName = agent.config.applications()[0] this.request_id = response?.headers?.['x-request-id'] - this.trace_id = segment?.transaction?.traceId + this.trace_id = transaction?.traceId this.span_id = segment?.id this['response.model'] = response.model this.vendor = 'openai' diff --git a/lib/metrics/recorders/custom.js b/lib/metrics/recorders/custom.js index b2d1c588c2..c36cebe44b 100644 --- a/lib/metrics/recorders/custom.js +++ b/lib/metrics/recorders/custom.js @@ -7,10 +7,9 @@ const NAMES = require('../names') -function record(segment, scope) { +function record(segment, scope, transaction) { const duration = segment.getDurationInMillis() const exclusive = segment.getExclusiveDurationInMillis() - const transaction = segment.transaction const name = NAMES.CUSTOM + NAMES.ACTION_DELIMITER + segment.name if (scope) { diff --git a/lib/metrics/recorders/database-operation.js b/lib/metrics/recorders/database-operation.js index 11e4c6ea46..a1b3135bbb 100644 --- a/lib/metrics/recorders/database-operation.js +++ b/lib/metrics/recorders/database-operation.js @@ -20,11 +20,7 @@ const metrics = require('../names') * @see DatastoreShim#recordOperation * @see MetricFunction */ -function recordOperationMetrics(segment, scope) { - if (!segment) { - return - } - +function recordOperationMetrics(segment, scope, transaction) { const duration = segment.getDurationInMillis() const exclusive = segment.getExclusiveDurationInMillis() const transaction = segment.transaction diff --git a/lib/metrics/recorders/database.js b/lib/metrics/recorders/database.js index 7d4e172ec6..0a5c050ca0 100644 --- a/lib/metrics/recorders/database.js +++ b/lib/metrics/recorders/database.js @@ -14,10 +14,9 @@ const { DESTINATIONS } = require('../../config/attribute-filter') * @param {string} [scope] - The scope of the segment. */ -function recordQueryMetrics(segment, scope) { +function recordQueryMetrics(segment, scope, transaction) { const duration = segment.getDurationInMillis() const exclusive = segment.getExclusiveDurationInMillis() - const transaction = segment.transaction const type = transaction.isWeb() ? DB.WEB : DB.OTHER const thisTypeSlash = this.type + '/' const operation = DB.OPERATION + '/' + thisTypeSlash + this.operation diff --git a/lib/metrics/recorders/generic.js b/lib/metrics/recorders/generic.js index 3deed95d32..f46293c16c 100644 --- a/lib/metrics/recorders/generic.js +++ b/lib/metrics/recorders/generic.js @@ -5,10 +5,9 @@ 'use strict' -function record(segment, scope) { +function record(segment, scope, transaction) { const duration = segment.getDurationInMillis() const exclusive = segment.getExclusiveDurationInMillis() - const transaction = segment.transaction if (scope) { transaction.measure(segment.name, scope, duration, exclusive) diff --git a/lib/metrics/recorders/http.js b/lib/metrics/recorders/http.js index 83a0880bb4..777424f6bf 100644 --- a/lib/metrics/recorders/http.js +++ b/lib/metrics/recorders/http.js @@ -10,13 +10,12 @@ const recordDistributedTrace = require('./distributed-trace') const TO_MILLIS = 1e3 -function recordWeb(segment, scope) { +function recordWeb(segment, scope, tx) { // in web metrics, scope is required if (!scope) { return } - const tx = segment.transaction // if there was a nested webTransaction use its recorder instead if (tx.type === 'web' && tx.baseSegment && segment !== tx.baseSegment) { return @@ -26,7 +25,7 @@ function recordWeb(segment, scope) { const totalTime = tx.trace.getTotalTimeDurationInMillis() const exclusive = segment.getExclusiveDurationInMillis() const partial = segment.partialName - const config = segment.transaction.agent.config + const config = tx.agent.config // named / key transaction support requires per-name apdexT const keyApdexInMillis = config.web_transactions_apdex[scope] * TO_MILLIS || 0 diff --git a/lib/metrics/recorders/http_external.js b/lib/metrics/recorders/http_external.js index 2e3bd7e2e4..252a97f1f0 100644 --- a/lib/metrics/recorders/http_external.js +++ b/lib/metrics/recorders/http_external.js @@ -8,10 +8,9 @@ const EXTERNAL = require('../../metrics/names').EXTERNAL function recordExternal(host, library) { - return function externalRecorder(segment, scope) { + return function externalRecorder(segment, scope, transaction) { const duration = segment.getDurationInMillis() const exclusive = segment.getExclusiveDurationInMillis() - const transaction = segment.transaction const metricName = EXTERNAL.PREFIX + host + '/' + library const rollupType = transaction.isWeb() ? EXTERNAL.WEB : EXTERNAL.OTHER const rollupHost = EXTERNAL.PREFIX + host + '/all' diff --git a/lib/metrics/recorders/message-transaction.js b/lib/metrics/recorders/message-transaction.js index ed7172d953..c880226b1d 100644 --- a/lib/metrics/recorders/message-transaction.js +++ b/lib/metrics/recorders/message-transaction.js @@ -7,15 +7,14 @@ const NAMES = require('../../metrics/names.js') -function recordMessageTransaction(segment, scope) { - const tx = segment.transaction +function recordMessageTransaction(segment, scope, tx) { if (tx.type !== 'message' || tx.baseSegment !== segment) { return } const duration = segment.getDurationInMillis() const exclusive = segment.getExclusiveDurationInMillis() - const totalTime = segment.transaction.trace.getTotalTimeDurationInMillis() + const totalTime = tx.trace.getTotalTimeDurationInMillis() if (scope) { tx.measure(scope, null, duration, exclusive) diff --git a/lib/metrics/recorders/middleware.js b/lib/metrics/recorders/middleware.js index 61693cfb88..d8ec3e0493 100644 --- a/lib/metrics/recorders/middleware.js +++ b/lib/metrics/recorders/middleware.js @@ -14,10 +14,9 @@ * @returns {Function} recorder for middleware */ function makeMiddlewareRecorder(_shim, metricName) { - return function middlewareMetricRecorder(segment, scope) { + return function middlewareMetricRecorder(segment, scope, transaction) { const duration = segment.getDurationInMillis() const exclusive = segment.getExclusiveDurationInMillis() - const transaction = segment.transaction if (scope) { transaction.measure(metricName, scope, duration, exclusive) diff --git a/lib/metrics/recorders/other.js b/lib/metrics/recorders/other.js index 11d472d394..0637ccaaa9 100644 --- a/lib/metrics/recorders/other.js +++ b/lib/metrics/recorders/other.js @@ -8,16 +8,15 @@ const NAMES = require('../../metrics/names') const recordDistributedTrace = require('./distributed-trace') -function recordBackground(segment, scope) { +function recordBackground(segment, scope, tx) { // if there was a nested otherTransaction use its recorder instead - const tx = segment.transaction if (tx.type === 'bg' && tx.baseSegment && segment !== tx.baseSegment) { return } const duration = segment.getDurationInMillis() const exclusive = segment.getExclusiveDurationInMillis() - const totalTime = segment.transaction.trace.getTotalTimeDurationInMillis() + const totalTime = tx.trace.getTotalTimeDurationInMillis() const name = segment.partialName if (scope) { diff --git a/lib/serverless/aws-lambda.js b/lib/serverless/aws-lambda.js index 8df11be080..edf25182b1 100644 --- a/lib/serverless/aws-lambda.js +++ b/lib/serverless/aws-lambda.js @@ -138,12 +138,13 @@ class AwsLambda { if (!transaction) { return handler.apply(this, arguments) } + const activeSegment = shim.tracer.getSegment() transaction.setPartialName(transactionName) const isApiGatewayLambdaProxy = apiGateway.isLambdaProxyEvent(event) const segmentRecorder = isApiGatewayLambdaProxy ? recordWeb : recordBackground - const segment = shim.createSegment(functionName, segmentRecorder) + const segment = shim.createSegment(functionName, segmentRecorder, activeSegment) transaction.baseSegment = segment // resultProcessor is used to execute additional logic based on the // payload supplied to the callback. diff --git a/lib/shim/datastore-shim.js b/lib/shim/datastore-shim.js index 70d103ee0e..068db6b763 100644 --- a/lib/shim/datastore-shim.js +++ b/lib/shim/datastore-shim.js @@ -438,7 +438,7 @@ function bindRowCallbackSegment(args, cbIdx, parentSegment) { if (++callCounter === 1) { const realParent = parentSegment || shim.getSegment() realParent && realParent.touch() - segment = shim.createSegment(segmentName, realParent) + segment = shim.createSegment({ name: segmentName, parent: realParent }) if (segment) { segment.async = false @@ -470,7 +470,7 @@ function bindRowCallbackSegment(args, cbIdx, parentSegment) { function captureInstanceAttributes(host, port, database) { // See if we are currently in a segment created by us. const segment = this.getSegment() - if (!segment || segment.shim !== this) { + if (!segment || segment.shimId !== this.id) { this.logger.trace( 'Not adding db instance metric attributes to segment %j', segment && segment.name @@ -553,7 +553,6 @@ function _recordQuery(suffix, nodule, properties, querySpec) { return this.record(nodule, properties, function queryRecorder(shim, fn, fnName, args) { shim.logger.trace('Determining query information for %j', fnName) - const segDesc = _getSpec.call(this, { spec: querySpec, shim, fn, fnName, args }) // Adjust the segment name with the metric prefix and add a recorder. diff --git a/lib/shim/message-shim/subscribe-consume.js b/lib/shim/message-shim/subscribe-consume.js index 8a38e805f5..cefd59f5c6 100644 --- a/lib/shim/message-shim/subscribe-consume.js +++ b/lib/shim/message-shim/subscribe-consume.js @@ -151,7 +151,7 @@ function createConsumerWrapper({ shim, spec, consumer }) { return consumer.apply(this, args) } - const msgDesc = spec.messageHandler.call(this, shim, args) + const msgDesc = spec.messageHandler.call(this, shim, args, tx) // If message could not be handled, immediately kill this transaction. if (!msgDesc) { @@ -167,7 +167,8 @@ function createConsumerWrapper({ shim, spec, consumer }) { tx.setPartialName(txName) tx.baseSegment = shim.createSegment({ name: tx.getFullName(), - recorder: messageTransactionRecorder + recorder: messageTransactionRecorder, + parent: tx.trace.root }) // Add would-be baseSegment attributes to transaction trace @@ -212,7 +213,7 @@ function createConsumerWrapper({ shim, spec, consumer }) { } } if (msgDesc.headers) { - shim.handleMqTracingHeaders(msgDesc.headers, tx.baseSegment, shim._transportType) + shim.handleMqTracingHeaders(msgDesc.headers, tx.baseSegment, tx, shim._transportType) } shim.logger.trace('Started message transaction %s named %s', tx.id, txName) diff --git a/lib/shim/promise-shim.js b/lib/shim/promise-shim.js index ef3c864927..729fa273ff 100644 --- a/lib/shim/promise-shim.js +++ b/lib/shim/promise-shim.js @@ -116,6 +116,7 @@ class PromiseShim extends Shim { _wrapExecutorContext(shim, args) }, post: function postPromise(shim, Promise, name, args) { + const transaction = shim.tracer.getTransaction() // This extra property is added by `_wrapExecutorContext` in the pre step. const executor = args[0] const context = executor && executor[symbols.executorContext] @@ -124,7 +125,7 @@ class PromiseShim extends Shim { } context.promise = this - Contextualizer.link(null, this, shim.getSegment()) + Contextualizer.link(null, this, shim.getSegment(), transaction) try { // Must run after promise is defined so that `__NR_wrapper` can be set. context.executor.apply(context.self, context.args) @@ -163,12 +164,13 @@ class PromiseShim extends Shim { return function wrappedExecutorCaller(executor) { const parent = shim.getActiveSegment() + const transaction = shim.tracer.getTransaction() if (!this || !parent) { return caller.apply(this, arguments) } if (!this[symbols.context]) { - Contextualizer.link(null, this, parent) + Contextualizer.link(null, this, parent, transaction) } const args = shim.argsToArray.apply(shim, arguments) @@ -220,9 +222,10 @@ class PromiseShim extends Shim { return function __NR_wrappedCast() { const segment = shim.getSegment() + const transaction = shim.tracer.getTransaction() const prom = cast.apply(this, arguments) if (segment) { - Contextualizer.link(null, prom, segment) + Contextualizer.link(null, prom, segment, transaction) } return prom } @@ -319,13 +322,21 @@ class PromiseShim extends Shim { * @returns {Promise} promise bound to active segment */ function __NR_wrappedPromisified() { + const context = shim.tracer.getContext() const segment = shim.getActiveSegment() + const transaction = shim.tracer.getTransaction() if (!segment) { return promisified.apply(this, arguments) } - const prom = shim.applySegment(promisified, segment, true, this, arguments) - Contextualizer.link(null, prom, segment) + const prom = shim.applyContext({ + func: promisified, + context, + full: true, + boundThis: this, + args: arguments + }) + Contextualizer.link(null, prom, segment, transaction) return prom } } @@ -413,10 +424,19 @@ function wrapHandler({ handler, index, argsLength, useAllParams, ctx, shim }) { ctx.handler[symbols.context].setSegment(segment) promSegment = segment } + const transaction = ctx.handler[symbols.context].getTransaction() let ret = null try { - ret = shim.applySegment(handler, promSegment, true, this, arguments) + let context = shim.tracer.getContext() + context = context.enterSegment({ transaction, segment: promSegment }) + ret = shim.applyContext({ + func: handler, + context, + full: true, + boundThis: this, + args: arguments + }) } finally { if (ret && typeof ret.then === 'function') { ret = ctx.handler[symbols.context].continueContext(ret) @@ -445,6 +465,8 @@ function _wrapThen(shim, fn, _name, useAllParams) { return fn.apply(this, arguments) } + const transaction = shim.tracer.getTransaction() + const thenSegment = shim.getSegment() const promise = this @@ -466,7 +488,7 @@ function _wrapThen(shim, fn, _name, useAllParams) { // If we got a promise (which we should have), link the parent's context. if (!ctx.isWrapped && ctx.handler instanceof shim._class && ctx.handler !== promise) { - Contextualizer.link(promise, ctx.handler, thenSegment) + Contextualizer.link(promise, ctx.handler, thenSegment, transaction) } return ctx.handler } @@ -476,8 +498,9 @@ function _wrapThen(shim, fn, _name, useAllParams) { * @private */ class Context { - constructor(segment) { + constructor(segment, transaction) { this.segments = [segment] + this.transaction = transaction } branch() { @@ -535,7 +558,7 @@ class Contextualizer { ctxlzr.child = false } - static link(prev, next, segment) { + static link(prev, next, segment, transaction) { let ctxlzr = prev && prev[symbols.context] if (ctxlzr && !ctxlzr.isActive()) { ctxlzr = prev[symbols.context] = null @@ -562,14 +585,18 @@ class Contextualizer { } else if (segment) { // This next promise is the root of a chain. Either there was no previous // promise or the promise was created out of context. - next[symbols.context] = new Contextualizer(0, new Context(segment)) + next[symbols.context] = new Contextualizer(0, new Context(segment, transaction)) } } isActive() { const segments = this.context.segments const segment = segments[this.idx] || segments[this.parentIdx] || segments[0] - return segment && segment.transaction.isActive() + return segment && this.context.transaction.isActive() + } + + getTransaction() { + return this.context.transaction } getSegment() { diff --git a/lib/shim/shim.js b/lib/shim/shim.js index 0a393500f1..3a9e5ffa62 100644 --- a/lib/shim/shim.js +++ b/lib/shim/shim.js @@ -15,6 +15,7 @@ const symbols = require('../symbols') const { addCLMAttributes: maybeAddCLMAttributes } = require('../util/code-level-metrics') const { makeId } = require('../util/hashes') const { isBuiltin } = require('module') +const TraceSegment = require('../transaction/trace/segment') // Some modules do terrible things, like change the prototype of functions. To // avoid crashing things we'll use a cached copy of apply everywhere. @@ -39,7 +40,6 @@ function Shim(agent, moduleName, resolvedName, shimName, pkgVersion) { this._logger = logger.child({ module: moduleName }) this._agent = agent - this._contextManager = agent._contextManager this._toExport = null this._debug = false this.defineProperty(this, 'moduleName', moduleName) @@ -97,6 +97,7 @@ defineProperties(Shim.prototype, { Shim.prototype.wrap = wrap Shim.prototype.bindSegment = bindSegment +Shim.prototype.bindContext = bindContext Shim.prototype.bindPromise = bindPromise Shim.prototype.execute = execute @@ -116,6 +117,7 @@ Shim.prototype.setActiveSegment = setActiveSegment Shim.prototype.storeSegment = storeSegment Shim.prototype.bindCallbackSegment = bindCallbackSegment Shim.prototype.applySegment = applySegment +Shim.prototype.applyContext = applyContext Shim.prototype.createSegment = createSegment Shim.prototype.getName = getName Shim.prototype.isObject = isObject @@ -668,23 +670,22 @@ function recordWrapper({ shim, fn, name, recordNamer }) { return function wrapper() { // Create the segment that will be recorded. const args = argsToArray.apply(shim, arguments) - const segDesc = recordNamer.call(this, shim, fn, name, args) - if (!segDesc) { + const spec = recordNamer.call(this, shim, fn, name, args) + if (!spec) { shim.logger.trace('No segment descriptor for "%s", not recording.', name) return fnApply.call(fn, this, args) } - // See if we're in an active transaction. - let parent - if (segDesc.parent) { - // We only want to continue recording in a transaction if the - // transaction is active. - parent = segDesc.parent.transaction.isActive() ? segDesc.parent : null - } else { - parent = shim.getActiveSegment() - } + // middleweare recorders pass in parent segment + // we need to destructure this as it is not needed past this function + // and will overwhelm trace level loggers with logging the entire spec + const { parent: specParent, ...segDesc } = spec + + const context = shim.tracer.getContext() + const transaction = context.transaction + const parent = transaction?.isActive() && specParent ? specParent : context.segment - if (!parent) { + if (!transaction?.isActive()) { shim.logger.debug('Not recording function %s, not in a transaction.', name) return fnApply.call(fn, this, arguments) } @@ -698,13 +699,25 @@ function recordWrapper({ shim, fn, name, recordNamer }) { // - OR the parent segment is either not internal or not from this shim. const shouldCreateSegment = !( parent.opaque || - (segDesc.internal && parent.internal && shim === parent.shim) + (segDesc.internal && parent.internal && shim.id === parent.shimId) ) - const segment = shouldCreateSegment ? _rawCreateSegment(shim, segDesc) : parent + const segment = shouldCreateSegment + ? _rawCreateSegment({ shim, spec: segDesc, parent, transaction }) + : parent + + const newContext = context.enterSegment({ segment }) maybeAddCLMAttributes(fn, segment) - return _doRecord.call(this, { segment, args, segDesc, shouldCreateSegment, shim, fn, name }) + return _doRecord.call(this, { + context: newContext, + args, + segDesc, + shouldCreateSegment, + shim, + fn, + name + }) } } @@ -736,29 +749,38 @@ function _hasValidCallbackArg(shim, args, specCallback) { * * @private * @param {object} params to function - * @param {TraceSegment} params.segment The trace segment to be recorded * @param {Array} params.args The arguments to the wrapped callback * @param {Spec} params.segDesc Segment descriptor spec * @param {boolean} params.shouldCreateSegment Whether the recorder should create a segment * @param {Shim} params.shim instance of shim * @param {Function} params.fn function being wrapped + * @param {Context} params.context agent context to run in * @param {string} params.name name of function being wrapped * @returns {shim|promise} Returns a shim or promise with recorder segment and * bound callbacks, if applicable */ -function _doRecord({ segment, args, segDesc, shouldCreateSegment, shim, fn, name }) { +function _doRecord({ context, args, segDesc, shouldCreateSegment, shim, fn, name }) { + const { segment } = context // Now bind any callbacks specified in the segment descriptor. _bindAllCallbacks.call(this, shim, fn, name, args, { spec: segDesc, - segment: segment, - shouldCreateSegment: shouldCreateSegment + segment, + shouldCreateSegment }) // Apply the function, and (if it returned a stream) bind that too. // The reason there is no check for `segment` is because it should // be guaranteed by the parent and active transaction check // at the beginning of this function. - let ret = _applyRecorderSegment({ segment, ctx: this, args, segDesc, shim, fn, name }) + let ret = _applyRecorderSegment({ + context, + boundThis: this, + args, + segDesc, + shim, + fn, + name + }) if (ret) { if (segDesc.stream) { shim.logger.trace('Binding return value as stream.') @@ -779,33 +801,41 @@ function _doRecord({ segment, args, segDesc, shouldCreateSegment, shim, fn, name * * @private * @param {object} params to function - * @param {TraceSegment} params.segment The trace segment being applied to the wrapped function - * @param {context} params.ctx Context supplied to + * @param {Context} params.context agent context to run in * @param {Array} params.args The arguments to the wrapped callback * @param {Spec} params.segDesc Segment descriptor spec * @param {Shim} params.shim instance of shim * @param {Function} params.fn function being wrapped + * @param {*} params.boundThis the function context to run in * @param {string} params.name name of function being wrapped * @returns {*} return value of wrapped function */ -function _applyRecorderSegment({ segment, ctx, args, segDesc, shim, fn, name }) { +function _applyRecorderSegment({ context, boundThis, args, segDesc, shim, fn, name }) { + const { segment, transaction } = context let error = null let promised = false let ret try { - ret = shim.applySegment(fn, segment, true, ctx, args, segDesc.inContext) + ret = shim.applyContext({ + func: fn, + context, + full: true, + boundThis, + args, + inContextCB: segDesc.inContext + }) if (segDesc.after && segDesc.promise && shim.isPromise(ret)) { promised = true return ret.then( function onThen(val) { segment.touch() // passing in error as some instrumentation checks if it's not equal to `null` - segDesc.after({ shim, fn, name, error, result: val, segment }) + segDesc.after({ shim, fn, name, error, result: val, segment, transaction }) return val }, function onCatch(err) { segment.touch() - segDesc.after({ shim, fn, name, error: err, segment }) + segDesc.after({ shim, fn, name, error: err, segment, transaction }) throw err // NOTE: This is not an error from our instrumentation. } ) @@ -816,7 +846,7 @@ function _applyRecorderSegment({ segment, ctx, args, segDesc, shim, fn, name }) throw err // Just rethrowing this error, not our error! } finally { if (segDesc.after && (error || !promised)) { - segDesc.after({ shim, fn, name, error, result: ret, segment }) + segDesc.after({ shim, fn, name, error, result: ret, segment, transaction }) } } } @@ -949,11 +979,40 @@ function bindSegment(nodule, property, segment, full) { property = null } - // This protects against the `bindSegment(func, null, true)` case, where the - // segment is `null`, and thus `true` (the full param) is detected as the - // segment. - if (segment != null && !this.isObject(segment)) { - this.logger.debug({ segment: segment }, 'Segment is not a segment, not binding.') + const context = this.tracer.getContext() + segment = segment || context?.segment + const newContext = context.enterSegment({ segment }) + return this.bindContext({ nodule, property, context: newContext, full }) +} + +/** + * + * Binds the execution of a function to a context instance. + * Similar to bindSegment but this requires passing in of an instance of Context. + * @memberof Shim.prototype + * @param {object} params to function + * @param {object | Function} params.nodule + * The source for the property or a single function to bind to a segment. + * @param {string} [params.property] + * The property to bind. If omitted, the `nodule` parameter is assumed + * to be the function to bind the segment to. + * @param {Context} [params.context] + * The context to bind the execution of the function to. + * @param {boolean} [params.full] + * Indicates if the full lifetime of the segment is bound to this function. + * @returns {object | Function} The first parameter after wrapping. + */ +function bindContext({ nodule, property, context, full = false }) { + const { segment } = context + // Don't bind to null arguments. + if (!nodule) { + return nodule + } + + // This protects against the case where the + // segment is `null`. + if (!(segment instanceof TraceSegment)) { + this.logger.debug({ segment }, 'Segment is not a segment, not binding.') return nodule } @@ -962,13 +1021,7 @@ function bindSegment(nodule, property, segment, full) { return func } - // Wrap up the function with this segment. - segment = segment || shim.getSegment() - if (!segment) { - return func - } - - const binder = _makeBindWrapper(shim, func, segment, full || false) + const binder = _makeBindWrapper(shim, func, context, full) shim.storeSegment(binder, segment) return binder }) @@ -1037,30 +1090,41 @@ function bindCallbackSegment(spec, args, cbIdx, parentSegment) { function wrapCallback({ shim, args, cbIdx, parentSegment, spec }) { const cb = args[cbIdx] const realParent = parentSegment || shim.getSegment() + const context = shim.tracer.getContext() + const transaction = context?.transaction + args[cbIdx] = shim.wrap(cb, null, function callbackWrapper(shim, fn, name) { return function wrappedCallback() { if (realParent) { realParent.opaque = false } - const segment = _rawCreateSegment( + const segment = _rawCreateSegment({ shim, - new specs.SegmentSpec({ - name: 'Callback: ' + name, - parent: realParent + parent: realParent, + transaction, + spec: new specs.SegmentSpec({ + name: 'Callback: ' + name }) - ) + }) if (segment) { segment.async = false } if (spec?.after) { - spec.after({ shim, fn, name, args: arguments, segment: realParent }) + spec.after({ shim, fn, name, args: arguments, segment: realParent, transaction }) } // CB may end the transaction so update the parent's time preemptively. realParent && realParent.touch() - return shim.applySegment(cb, segment, true, this, arguments) + const newContext = context.enterSegment({ segment }) + return shim.applyContext({ + func: cb, + context: newContext, + full: true, + boundThis: this, + args: arguments + }) } }) shim.storeSegment(args[cbIdx], realParent) @@ -1103,7 +1167,8 @@ function getSegment(obj) { */ function getActiveSegment(obj) { const segment = this.getSegment(obj) - if (segment && segment.transaction && segment.transaction.isActive()) { + const transaction = this.tracer.getTransaction() + if (transaction?.isActive()) { return segment } return null @@ -1121,7 +1186,8 @@ function getActiveSegment(obj) { * @returns {TraceSegment} - The segment set as active on the context. */ function setActiveSegment(segment) { - this._contextManager.setContext(segment) + const transaction = this.tracer.getTransaction() + this.tracer.setSegment({ segment, transaction }) return segment } @@ -1142,32 +1208,32 @@ function storeSegment(obj, segment) { } } -/* eslint-disable max-params */ /** - * Sets the given segment as the active one for the duration of the function's - * execution. + * Binds a function to the async context manager with the passed in context. * - * - `applySegment(func, segment, full, context, args[, inContextCB])` + * - `applyContext({ func, context , full, boundThis, args, inContextCB })` * * @memberof Shim.prototype - * @param {Function} func The function to execute in the context of the given segment. - * @param {TraceSegment} segment The segment to make active for the duration of the function. - * @param {boolean} full Indicates if the full lifetime of the segment is bound to this function. - * @param {*} context The `this` argument for the function. - * @param {Array.<*>} args The arguments to be passed into the function. - * @param {Function} [inContextCB] The function used to do more instrumentation work. This function is + * @param {object} params to function + * @param {Function} params.func The function to execute in given async context. + * @param {Context} params.context This context you want to run a function in + * @param {boolean} params.full Indicates if the full lifetime of the segment is bound to this function. + * @param {*} params.boundThis The `this` argument for the function. + * @param {Array.<*>} params.args The arguments to be passed into the function. + * @param {Function} [params.inContextCB] The function used to do more instrumentation work. This function is * guaranteed to be executed with the segment associated with. * @returns {*} Whatever value `func` returned. */ -function applySegment(func, segment, full, context, args, inContextCB) { - // Exist fast for bad arguments. +function applyContext({ func, context, full, boundThis, args, inContextCB }) { + const { segment } = context + // Exit fast for bad arguments. if (!this.isFunction(func)) { return } if (!segment) { this.logger.trace('No segment to apply to function.') - return fnApply.call(func, context, args) + return fnApply.call(func, boundThis, args) } this.logger.trace('Applying segment %s', segment.name) @@ -1183,7 +1249,30 @@ function applySegment(func, segment, full, context, args, inContextCB) { return fnApply.call(func, this, arguments) } - return this.tracer.bindFunction(runInContextCb, segment, full).apply(context, args) + return this.tracer.bindFunction(runInContextCb, context, full).apply(boundThis, args) +} + +/* eslint-disable max-params */ +/** + * Binds a function to the async context manager with the segment passed in. It'll pull + * the active transaction from the context manager. + * + * - `applySegment(func, segment, full, context, args[, inContextCB])` + * + * @memberof Shim.prototype + * @param {Function} func The function to execute in the context of the given segment. + * @param {TraceSegment} segment The segment to make active for the duration of the function. + * @param {boolean} full Indicates if the full lifetime of the segment is bound to this function. + * @param {*} boundThis The `this` argument for the function. + * @param {Array.<*>} args The arguments to be passed into the function. + * @param {Function} [inContextCB] The function used to do more instrumentation work. This function is + * guaranteed to be executed with the segment associated with. + * @returns {*} Whatever value `func` returned. + */ +function applySegment(func, segment, full, boundThis, args, inContextCB) { + const context = this.tracer.getContext() + const newContext = context.enterSegment({ segment }) + return this.applyContext({ func, context: newContext, full, boundThis, args, inContextCB }) } /* eslint-enable max-params */ @@ -1206,62 +1295,67 @@ function applySegment(func, segment, full, context, args, inContextCB) { * `null` is returned. */ function createSegment(name, recorder, parent) { - let opts = null + let opts = {} if (this.isString(name)) { // createSegment(name [, recorder] [, parent]) - opts = new specs.SegmentSpec({ name }) + opts.name = name // if the recorder arg is not used, it can either be omitted or null if (this.isFunction(recorder) || this.isNull(recorder)) { // createSegment(name, recorder [, parent]) opts.recorder = recorder - opts.parent = parent } else { // createSegment(name [, parent]) - opts.parent = recorder + parent = recorder } } else { // createSegment(opts) opts = name + parent = opts.parent } - return _rawCreateSegment(this, opts) + const transaction = this.tracer.getTransaction() + const spec = new specs.SegmentSpec(opts) + return _rawCreateSegment({ shim: this, spec, parent, transaction }) } /** * @private - * @param {Shim} shim instance of shim - * @param {string|specs.SegmentSpec} opts options for creating segment + * @param {object} params to function + * @param {Shim} params.shim instance of shim + * @param {Transaction} params.transaction active transaction + * @param {TraceSegment} params.parent the segment that will be the parent of the newly created segment + * @param params.spec + * @param {string|specs.SegmentSpec} spec options for creating segment * @returns {?TraceSegment} A new trace segment if a transaction is active, else * `null` is returned. */ -function _rawCreateSegment(shim, opts) { - // Grab parent segment when none in opts so we can check opaqueness. - // Also, saving reference and not assigning to opts to avoid hoisting - // this value to other executions of the same method or a shared spec - // definition - const parent = opts.parent || shim.getActiveSegment() - +function _rawCreateSegment({ shim, spec, parent, transaction }) { // When parent exists and is opaque, no new segment will be created // by tracer.createSegment and the parent will be returned. We bail // out early so we do not risk modifying the parent segment. if (parent?.opaque) { - shim.logger.trace(opts, 'Did not create segment because parent is opaque') + shim.logger.trace(spec, 'Did not create segment because parent is opaque') return parent } - const segment = shim.tracer.createSegment(opts.name, opts.recorder, parent) + const segment = shim.tracer.createSegment({ + name: spec.name, + recorder: spec.recorder, + parent, + transaction + }) if (segment) { - segment.internal = opts.internal - segment.opaque = opts.opaque - segment.shim = shim + segment.internal = spec.internal + segment.opaque = spec.opaque + segment.shimId = shim.id - if (hasOwnProperty(opts, 'parameters')) { - shim.copySegmentParameters(segment, opts.parameters) + if (hasOwnProperty(spec, 'parameters')) { + shim.copySegmentParameters(segment, spec.parameters) } - shim.logger.trace(opts, 'Created segment') + shim.logger.trace(spec, 'Created segment') } else { - shim.logger.debug(opts, 'Failed to create segment') + shim.logger.debug(spec, 'Failed to create segment') } return segment @@ -1755,18 +1849,18 @@ function _wrap(shim, original, name, spec, args) { * @private * @param {Shim} shim * The shim used for the binding. - * @param {Function} fn + * @param {Function} func * The function to be bound to the segment. - * @param {TraceSegment} segment - * The segment the function is bound to. + * @param {Context} context + * The agent context that the function is bound to. * @param {boolean} full * Indicates if the segment's full lifetime is bound to the function. - * @returns {Function} A function which wraps `fn` and makes the given segment + * @returns {Function} A function which wraps `func` and makes the given segment * active for the duration of its execution. */ -function _makeBindWrapper(shim, fn, segment, full) { +function _makeBindWrapper(shim, func, context, full) { return function wrapper() { - return shim.applySegment(fn, segment, full, this, arguments) + return shim.applyContext({ func, context, full, boundThis: this, args: arguments }) } } @@ -1916,8 +2010,10 @@ function wrapStreamEmit({ stream, shim, segment, specEvent, shouldCreateSegment, // Wrap emit such that each event handler is executed within context of this // segment or the event-specific segment. shim.wrap(stream, 'emit', function wrapEmit(shim, emit) { - const tx = segment.transaction - const streamBoundEmit = shim.bindSegment(emit, segment, true) + const context = shim.tracer.getContext() + const tx = context.transaction + const newContext = context.enterSegment({ segment }) + const streamBoundEmit = shim.bindContext({ nodule: emit, context: newContext, full: true }) let eventSegment = null let eventBoundEmit = null let emitCount = 0 @@ -1930,8 +2026,9 @@ function wrapStreamEmit({ stream, shim, segment, specEvent, shouldCreateSegment, let emitToCall = streamBoundEmit if (evnt === specEvent && tx.isActive()) { if (!eventBoundEmit) { - eventSegment = shim.createSegment(segmentName, segment) - eventBoundEmit = shim.bindSegment(emit, eventSegment, true) + eventSegment = shim.createSegment({ name: segmentName, parent: segment }) + const newContext = context.enterSegment({ segment: eventSegment }) + eventBoundEmit = shim.bindContext({ nodule: emit, full: true, context: newContext }) } eventSegment.addAttribute('count', ++emitCount) emitToCall = eventBoundEmit diff --git a/lib/shim/transaction-shim.js b/lib/shim/transaction-shim.js index d54a8b2341..b42254986e 100644 --- a/lib/shim/transaction-shim.js +++ b/lib/shim/transaction-shim.js @@ -195,10 +195,11 @@ function setTransactionName(name) { * @param {TraceSegment} [segment] * The trace segment to associate the header data with. If no segment is * provided then the currently active segment is used. + * @param {Transaction} transaction active transaction * @param {string} [transportType] * The transport type that brought the headers. Usually `HTTP` or `HTTPS`. */ -function handleMqTracingHeaders(headers, segment, transportType) { +function handleMqTracingHeaders(headers, segment, transaction, transportType) { // TODO: replace functionality when CAT fully removed. if (!headers) { @@ -215,13 +216,11 @@ function handleMqTracingHeaders(headers, segment, transportType) { // Check that we're in an active transaction. const currentSegment = segment || this.getSegment() - if (!currentSegment || !currentSegment.transaction.isActive()) { + if (!currentSegment || !transaction.isActive()) { this.logger.trace('Not processing headers for CAT or DT, not in an active transaction.') return } - const transaction = currentSegment.transaction - if (config.distributed_tracing.enabled) { transaction.acceptDistributedTraceHeaders(transportType, headers) return @@ -237,7 +236,7 @@ function handleMqTracingHeaders(headers, segment, transportType) { ) cat.assignCatToTransaction(externalId, externalTransaction, transaction) const decodedAppData = cat.parseAppData(config, appData) - cat.assignCatToSegment(decodedAppData, currentSegment) + cat.assignCatToSegment({ appData: decodedAppData, segment: currentSegment, transaction }) // TODO: Handle adding ExternalTransaction metrics for this segment. } @@ -316,11 +315,11 @@ function insertCATReplyHeader(headers, useAlternateHeaderNames) { // Are we in a transaction? const segment = this.getSegment() - if (!segment || !segment.transaction.isActive()) { + const transaction = this.tracer.getTransaction() + if (!segment || !transaction?.isActive()) { this.logger.trace('Not adding CAT reply header, not in an active transaction.') return } - const tx = segment.transaction // Hunt down the content length. // NOTE: In AMQP, content-type and content-encoding are guaranteed fields, but @@ -334,11 +333,16 @@ function insertCATReplyHeader(headers, useAlternateHeaderNames) { } } - const { key, data } = cat.encodeAppData(config, tx, contentLength, useAlternateHeaderNames) + const { key, data } = cat.encodeAppData( + config, + transaction, + contentLength, + useAlternateHeaderNames + ) // Add the header. if (key && data) { headers[key] = data - this.logger.trace('Added outbound response CAT headers for transaction %s', tx.id) + this.logger.trace('Added outbound response CAT headers for transaction %s', transaction.id) } } @@ -351,7 +355,7 @@ function insertCATReplyHeader(headers, useAlternateHeaderNames) { * @private * @param {Shim} shim * The shim used for the binding. - * @param {Function} fn + * @param {Function} func * The function link with the transaction. * @param {string} name * The name of the wrapped function. @@ -360,26 +364,24 @@ function insertCATReplyHeader(headers, useAlternateHeaderNames) { * @returns {Function} A function which wraps `fn` and creates potentially nested * transactions linked to its execution. */ -function _makeNestedTransWrapper(shim, fn, name, spec) { +function _makeNestedTransWrapper(shim, func, name, spec) { return function nestedTransactionWrapper() { if (!shim.agent.canCollectData()) { - return fn.apply(this, arguments) + return func.apply(this, arguments) } - // Reuse existing transactions only if the type matches. - let transaction = shim.tracer.getTransaction() - let segment = shim.getSegment() + let context = shim.tracer.getContext() // Only create a new transaction if we either do not have a current // transaction _or_ the current transaction is not of the type we want. - if (!transaction || spec.type !== transaction.type) { + if (!context?.transaction || spec.type !== context?.transaction?.type) { shim.logger.trace('Creating new nested %s transaction for %s', spec.type, name) - transaction = new Transaction(shim.agent) + const transaction = new Transaction(shim.agent) transaction.type = spec.type - segment = transaction.trace.root + context = context.enterTransaction(transaction) } - return shim.applySegment(fn, segment, false, this, arguments) + return shim.applyContext({ func, context, full: false, boundThis: this, args: arguments }) } } @@ -391,7 +393,7 @@ function _makeNestedTransWrapper(shim, fn, name, spec) { * @private * @param {Shim} shim * The shim used for the binding. - * @param {Function} fn + * @param {Function} func * The function link with the transaction. * @param {string} name * The name of the wrapped function. @@ -400,17 +402,19 @@ function _makeNestedTransWrapper(shim, fn, name, spec) { * @returns {Function} A function which wraps `fn` and potentially creates a new * transaction linked to the function's execution. */ -function _makeTransWrapper(shim, fn, name, spec) { +function _makeTransWrapper(shim, func, name, spec) { return function transactionWrapper() { // Don't nest transactions, reuse existing ones! - const existingTransaction = shim.tracer.getTransaction() + let context = shim.tracer.getContext() + const existingTransaction = context.transaction if (!shim.agent.canCollectData() || existingTransaction) { - return fn.apply(this, arguments) + return func.apply(this, arguments) } shim.logger.trace('Creating new %s transaction for %s', spec.type, name) const transaction = new Transaction(shim.agent) transaction.type = spec.type - return shim.applySegment(fn, transaction.trace.root, false, this, arguments) + context = context.enterTransaction(transaction) + return shim.applyContext({ func, context, full: false, boundThis: this, args: arguments }) } } diff --git a/lib/shim/webframework-shim/middleware.js b/lib/shim/webframework-shim/middleware.js index ed7cde37e7..837eb74253 100644 --- a/lib/shim/webframework-shim/middleware.js +++ b/lib/shim/webframework-shim/middleware.js @@ -428,7 +428,7 @@ function wrapNextFn({ shim, txInfo, nextDetails, segment }, nodule, property, is if (isError(shim, err)) { assignError(txInfo, err) } else if (!isFinal && !nextDetails.isErrorWare && nextDetails.appendPath) { - segment.transaction.nameState.popPath(nextDetails.route) + txInfo.transaction.nameState.popPath(nextDetails.route) } // The next call does not signify the end of the segment @@ -446,7 +446,7 @@ function wrapNextFn({ shim, txInfo, nextDetails, segment }, nodule, property, is // more work to do in that scope. return ret.then(function onNextFinish(v) { if (nextDetails.appendPath) { - segment.transaction.nameState.appendPath(nextDetails.route) + txInfo.transaction.nameState.appendPath(nextDetails.route) } txInfo.segmentStack.push(segment) diff --git a/lib/spans/base-span-streamer.js b/lib/spans/base-span-streamer.js index b8ea567442..3f6146d17c 100644 --- a/lib/spans/base-span-streamer.js +++ b/lib/spans/base-span-streamer.js @@ -78,7 +78,7 @@ class BaseSpanStreamer { * span, a drain event handler is setup to continue writing when possible. * * @param {*} data spans or span - * @param {number} [spanLen=1] number of spans sent in a stream(defaults to 1) + * @param {number} [spanLen] number of spans sent in a stream(defaults to 1) */ send(data, spanLen = 1) { // false indicates the stream has reached the highWaterMark diff --git a/lib/spans/span-event-aggregator.js b/lib/spans/span-event-aggregator.js index 5f1fd2e601..101806adc1 100644 --- a/lib/spans/span-event-aggregator.js +++ b/lib/spans/span-event-aggregator.js @@ -61,23 +61,24 @@ class SpanEventAggregator extends EventAggregator { /** * Attempts to add the given segment to the collection. * - * @param {TraceSegment} segment - The segment to add. - * @param {string} [parentId=null] - The GUID of the parent span. - * @param isRoot + * @param {object} params to function + * @param {TraceSegment} params.segment segment to add. + * @param {Transaction} params.transaction active transaction + * @param {string} [params.parentId] GUID of the parent span. + * @param {boolean} params.isRoot if segment is root segment * @returns {boolean} True if the segment was added, or false if it was discarded. */ - addSegment(segment, parentId, isRoot) { + addSegment({ segment, transaction, parentId, isRoot }) { // Check if the priority would be accepted before creating the event object. - const tx = segment.transaction - if (tx.priority < this._items.getMinimumPriority()) { + if (transaction.priority < this._items.getMinimumPriority()) { ++this.events.seen this._metrics.getOrCreateMetric(this._metricNames.SEEN).incrementCallCount() return false } - const span = SpanEvent.fromSegment(segment, parentId || null, isRoot) - return this.add(span, tx.priority) + const span = SpanEvent.fromSegment(segment, transaction, parentId || null, isRoot) + return this.add(span, transaction.priority) } /** diff --git a/lib/spans/span-event.js b/lib/spans/span-event.js index fcc04064e9..a193a9ff33 100644 --- a/lib/spans/span-event.js +++ b/lib/spans/span-event.js @@ -92,17 +92,18 @@ class SpanEvent { * The constructed span event will contain extra data depending on the * category of the segment. * - * @param {TraceSegment} segment - The segment to turn into a span event. - * @param {?string} [parentId] - The ID of the segment's parent. - * @param isRoot + * @param {TraceSegment} segment segment to turn into a span event. + * @param {Transaction} transaction active transaction + * @param {?string} [parentId] ID of the segment's parent. + * @param {boolean} isRoot if segment is root segment * @returns {SpanEvent} The constructed event. */ - static fromSegment(segment, parentId = null, isRoot = false) { + static fromSegment(segment, transaction, parentId = null, isRoot = false) { const spanContext = segment.getSpanContext() // Since segments already hold span agent attributes and we want to leverage // filtering, we add to the segment attributes prior to processing. - if (spanContext.hasError && !segment.transaction.hasIgnoredErrorStatusCode()) { + if (spanContext.hasError && !transaction.hasIgnoredErrorStatusCode()) { const details = spanContext.errorDetails segment.addSpanAttribute('error.message', details.message) segment.addSpanAttribute('error.class', details.type) @@ -128,25 +129,23 @@ class SpanEvent { span.intrinsics[key] = value } - const tx = segment.transaction - - span.intrinsics.traceId = tx.traceId + span.intrinsics.traceId = transaction.traceId span.intrinsics.guid = segment.id span.intrinsics.parentId = parentId - span.intrinsics.transactionId = tx.id - span.intrinsics.sampled = tx.sampled - span.intrinsics.priority = tx.priority + span.intrinsics.transactionId = transaction.id + span.intrinsics.sampled = transaction.sampled + span.intrinsics.priority = transaction.priority span.intrinsics.name = segment.name if (isRoot) { - span.intrinsics.trustedParentId = tx.traceContext.trustedParentId - if (tx.traceContext.tracingVendors) { - span.intrinsics.tracingVendors = tx.traceContext.tracingVendors + span.intrinsics.trustedParentId = transaction.traceContext.trustedParentId + if (transaction.traceContext.tracingVendors) { + span.intrinsics.tracingVendors = transaction.traceContext.tracingVendors } } // Only set this if it will be `true`. Must be `null` otherwise. - if (tx.baseSegment === segment) { + if (transaction.baseSegment === segment) { span.intrinsics['nr.entryPoint'] = true } diff --git a/lib/spans/streaming-span-event-aggregator.js b/lib/spans/streaming-span-event-aggregator.js index 7c05c6f99c..143d7d1320 100644 --- a/lib/spans/streaming-span-event-aggregator.js +++ b/lib/spans/streaming-span-event-aggregator.js @@ -100,18 +100,21 @@ class StreamingSpanEventAggregator extends Aggregator { /** * Attempts to add the given segment to the collection. * - * @param {TraceSegment} segment - The segment to add. - * @param {string} [parentId] - The GUID of the parent span. - * @param isRoot + * @param {object} params to function + * @param {TraceSegment} params.segment segment to add. + * @param {string} [parms.parentId] GUID of the parent span. + * @param {Transaction} params.transaction active transaction + * @param {boolean} params.isRoot is segment root segment + * @param params.parentId * @returns {boolean} True if the segment was added, or false if it was discarded. */ - addSegment(segment, parentId, isRoot) { + addSegment({ segment, transaction, parentId, isRoot }) { if (!this.started) { logger.trace('Aggregator has not yet started, dropping span (%s).', segment.name) return } - const span = StreamingSpanEvent.fromSegment(segment, parentId, isRoot) + const span = StreamingSpanEvent.fromSegment(segment, transaction, parentId, isRoot) this.stream.write(span) } diff --git a/lib/spans/streaming-span-event.js b/lib/spans/streaming-span-event.js index 4d435be4e2..da7432bfef 100644 --- a/lib/spans/streaming-span-event.js +++ b/lib/spans/streaming-span-event.js @@ -114,12 +114,12 @@ class StreamingSpanEvent { } } - static fromSegment(segment, parentId = null, isRoot = false) { + static fromSegment(segment, transaction, parentId = null, isRoot = false) { const spanContext = segment.getSpanContext() // Since segments already hold span agent attributes and we want to leverage // filtering, we add to the segment attributes prior to processing. - if (spanContext.hasError && !segment.transaction.hasIgnoredErrorStatusCode()) { + if (spanContext.hasError && !transaction.hasIgnoredErrorStatusCode()) { const details = spanContext.errorDetails segment.addSpanAttribute('error.message', details.message) segment.addSpanAttribute('error.class', details.type) @@ -132,7 +132,6 @@ class StreamingSpanEvent { const customAttributes = spanContext.customAttributes.get(DESTINATIONS.SPAN_EVENT) - const transaction = segment.transaction const traceId = transaction.traceId let span = null diff --git a/lib/transaction/index.js b/lib/transaction/index.js index d3c4214b4b..7d9f20c7c5 100644 --- a/lib/transaction/index.js +++ b/lib/transaction/index.js @@ -78,13 +78,6 @@ function Transaction(agent) { throw new Error('every transaction must be bound to the agent') } - this.traceFlag = false - if (agent.config.logging.diagnostics) { - this.traceStacks = [] - } else { - this.traceStacks = null - } - this.agent = agent this.metrics = new Metrics(agent.config.apdex_t, agent.mapper, agent.metricNameNormalizer) @@ -162,7 +155,6 @@ function Transaction(agent) { this.ignoreApdex = false agent.emit('transactionStarted', this) - this.probe('Transaction created', { id: this.id }) } Transaction.TYPES = TYPES @@ -171,15 +163,6 @@ Transaction.TRANSPORT_TYPES = TRANSPORT_TYPES Transaction.TRANSPORT_TYPES_SET = TRANSPORT_TYPES_SET Transaction.TRACE_CONTEXT_PARENT_HEADER = TRACE_CONTEXT_PARENT_HEADER -Transaction.prototype.probe = function probe(action, extra) { - if (this.traceStacks) { - this.traceStacks.push({ - stack: new Error(action).stack.split('\n'), - extra: extra - }) - } -} - /** * Add a clear API method for determining whether a transaction is web or * background. @@ -209,12 +192,6 @@ Transaction.prototype.end = function end() { if (!this.timer.isActive()) { return } - if (this.traceFlag) { - logger.warn( - { segment: { name: this.name, stacks: this.traceStacks } }, - 'Flagged transaction ended.' - ) - } if (!this.name) { this.finalizeName(null) // Use existing partial name. @@ -518,7 +495,8 @@ Transaction.prototype._markAsWeb = function _markAsWeb(rawURL) { } } } - this.baseSegment.markAsWeb() + + this.baseSegment.markAsWeb(this) } /** @@ -553,7 +531,7 @@ Transaction.prototype.finalizeName = function finalizeName(name) { this.ignore = this.forceIgnore } - this.baseSegment && this.baseSegment.setNameFromTransaction() + this.baseSegment && this.baseSegment.setNameFromTransaction(this) this._copyNameToActiveSpan(this.name) @@ -697,7 +675,7 @@ Transaction.prototype.addRecorder = function addRecorder(recorder) { Transaction.prototype.record = function record() { const name = this.name for (let i = 0, l = this._recorders.length; i < l; ++i) { - this._recorders[i](name) + this._recorders[i](name, this) } } @@ -1411,4 +1389,15 @@ function addRequestParameters(requestParameters) { } } +/** + * Increments counters used to report details. + * `numSegments` - recorded as supportability metric when transaction ends + * `agent.totalActiveSegments` used as a trace level log value when metrics are harvested + * `agent.segmentsCreatedInHarvest` used as a trace level log value when metrics are harvested + */ +Transaction.prototype.incrementCounters = function incrementCounters() { + ++this.numSegments + this.agent.incrementCounters() +} + module.exports = Transaction diff --git a/lib/transaction/trace/index.js b/lib/transaction/trace/index.js index 83cb05f86e..c7c22121c1 100644 --- a/lib/transaction/trace/index.js +++ b/lib/transaction/trace/index.js @@ -7,10 +7,9 @@ const codec = require('../../util/codec') const urltils = require('../../util/urltils') -const Segment = require('./segment') +const TraceSegment = require('./segment') const { Attributes, MAXIMUM_CUSTOM_ATTRIBUTES } = require('../../attributes') const logger = require('../../logger').child({ component: 'trace' }) - const { DESTINATIONS } = require('../../config/attribute-filter') const FROM_MILLIS = 1e-3 const ATTRIBUTE_SCOPE = 'transaction' @@ -31,7 +30,14 @@ function Trace(transaction) { this.transaction = transaction - this.root = new Segment(transaction, 'ROOT') + transaction.incrementCounters() + + this.root = new TraceSegment({ + config: transaction.agent.config, + name: 'ROOT', + collect: transaction.collect, + isRoot: true + }) this.root.start() this.intrinsics = Object.create(null) @@ -115,7 +121,12 @@ Trace.prototype.generateSpanEvents = function generateSpanEvents() { // Even though at some point we might want to stop adding events because all the priorities // should be the same, we need to count the spans as seen. - spanAggregator.addSegment(segment, segmentInfo.parentId, segmentInfo.isRoot) + spanAggregator.addSegment({ + segment, + transaction: this.transaction, + parentId: segmentInfo.parentId, + isRoot: segmentInfo.isRoot + }) const nodes = segment.getChildren() for (let i = 0; i < nodes.length; ++i) { @@ -148,10 +159,18 @@ function DTTraceNode(segment, parentId, isRoot = false) { * * @param {string} childName Name for the new segment. * @param {Function} callback Callback function to record metrics related to the trace + * @param {TraceSegment} parent parent of new segment * @returns {TraceSegment} Newly-created segment. */ -Trace.prototype.add = function add(childName, callback) { - return this.root.add(childName, callback) +Trace.prototype.add = function add(childName, callback, parent) { + const { tracer } = this.transaction.agent + parent = parent || this.root + return tracer.createSegment({ + name: childName, + recorder: callback, + parent, + transaction: this.transaction + }) } /** diff --git a/lib/transaction/trace/segment.js b/lib/transaction/trace/segment.js index 2fee25fb98..bc3f0f9bed 100644 --- a/lib/transaction/trace/segment.js +++ b/lib/transaction/trace/segment.js @@ -26,34 +26,25 @@ const ATTRIBUTE_SCOPE = 'segment' * * @class * @classdesc - * TraceSegments are inserted to track instrumented function calls. Each one is - * bound to a transaction, given a name (used only internally to the framework + * TraceSegments are inserted to track instrumented function calls. They + * are reported as part of a transaction trace. It has name (used only internally to the framework * for now), and has one or more children (that are also part of the same - * transaction), as well as an associated timer. - * @param {Transaction} transaction - * The transaction to which this segment will be bound. - * @param {string} name - * Human-readable name for this segment (e.g. 'http', 'net', 'express', + * transaction trace), as well as an associated timer. + * @param {object} params to function + * @param {object} params.config agent config + * @param {string} params.name Human-readable name for this segment (e.g. 'http', 'net', 'express', * 'mysql', etc). - * @param {?Function} recorder - * Callback that takes a segment and a scope name as attributes (intended to be - * used to record metrics related to the segment). + * @param {boolean} params.collect flag to collect as part of transaction trace + * @param {TraceSegment} params.root root segment + * @param {boolean} params.isRoot flag to indicate it is the root segment */ -function TraceSegment(transaction, name, recorder) { +function TraceSegment({ config, name, collect, root, isRoot = false }) { + this.isRoot = isRoot + this.root = root this.name = name - this.transaction = transaction - - ++transaction.numSegments - ++transaction.agent.totalActiveSegments - ++transaction.agent.segmentsCreatedInHarvest - - if (recorder) { - transaction.addRecorder(recorder.bind(null, this)) - } - this.attributes = new Attributes(ATTRIBUTE_SCOPE) - this.children = [] + this.spansEnabled = config?.distributed_tracing?.enabled && config?.span_events?.enabled // Generate a unique id for use in span events. this.id = hashes.makeId() @@ -61,26 +52,21 @@ function TraceSegment(transaction, name, recorder) { this.internal = false this.opaque = false - this.shim = null + this.shimId = null // hidden class optimization this.partialName = null this._exclusiveDuration = null - this._collect = true + this._collect = collect this.host = null this.port = null this.state = STATE.EXTERNAL this.async = true this.ignore = false - - this.probe('new TraceSegment') } TraceSegment.prototype.getSpanContext = function getSpanContext() { - const config = this.transaction.agent.config - const spansEnabled = config.distributed_tracing.enabled && config.span_events.enabled - - if (!this._spanContext && spansEnabled) { + if (!this._spanContext && this.spansEnabled) { this._spanContext = new SpanContext() } @@ -108,9 +94,7 @@ TraceSegment.prototype.getAttributes = function getAttributes() { } TraceSegment.prototype.getSpanId = function getSpanId() { - const conf = this.transaction.agent.config - const enabled = conf.span_events.enabled && conf.distributed_tracing.enabled - if (enabled) { + if (this.spansEnabled) { return this.id } @@ -125,21 +109,14 @@ TraceSegment.prototype.isInCallbackState = function isInCallbackState() { return this.state === STATE.CALLBACK } -TraceSegment.prototype.probe = function probe(action) { - if (this.transaction.traceStacks) { - this.transaction.probe(action, { segment: this.name }) - } -} - /** * For use when a transaction is ending. The transaction segment should * be named after the transaction it belongs to (which is only known by * the end). + * @param {Transaction} transaction The transaction to which this segment will be bound. */ -TraceSegment.prototype.setNameFromTransaction = function setNameFromTransaction() { - const transaction = this.transaction - - // transaction name and transaciton segment name must match +TraceSegment.prototype.setNameFromTransaction = function setNameFromTransaction(transaction) { + // transaction name and transaction segment name must match this.name = transaction.getFullName() // partialName is used to name apdex metrics when recording @@ -153,10 +130,10 @@ TraceSegment.prototype.setNameFromTransaction = function setNameFromTransaction( * recording, it's also necessary to copy the transaction's partial name. And * finally, marking the trace segment as being a web segment copies the * segment's parameters onto the transaction. + * @param {Transaction} transaction The transaction to which this segment will be bound. */ -TraceSegment.prototype.markAsWeb = function markAsWeb() { - const transaction = this.transaction - this.setNameFromTransaction() +TraceSegment.prototype.markAsWeb = function markAsWeb(transaction) { + this.setNameFromTransaction(transaction) const traceAttrs = transaction.trace.attributes.get(DESTINATIONS.TRANS_TRACE) Object.keys(traceAttrs).forEach((key) => { @@ -172,7 +149,6 @@ TraceSegment.prototype.markAsWeb = function markAsWeb() { * the timer as having a stop time. */ TraceSegment.prototype.touch = function touch() { - this.probe('Touched') this.timer.touch() this._updateRootTimer() } @@ -193,7 +169,6 @@ TraceSegment.prototype.end = function end() { if (!this.timer.isActive()) { return } - this.probe('Ended') this.timer.end() this._updateRootTimer() } @@ -213,8 +188,9 @@ TraceSegment.prototype.finalize = function finalize() { * Helper to set the end of the root timer to this segment's root if it is later * in time. */ + TraceSegment.prototype._updateRootTimer = function _updateRootTimer() { - const root = this.transaction.trace.root + const root = this.isRoot ? this : this.root if (this.timer.endsAfter(root.timer)) { const newDuration = this.timer.start + this.getDurationInMillis() - root.timer.start root.overwriteDurationInMillis(newDuration) @@ -231,26 +207,28 @@ TraceSegment.prototype._isEnded = function _isEnded() { } /** - * Add a new segment to a scope implicitly bounded by this segment. + * Add a new segment to a parent(scope) implicitly bounded by this segment. * - * @param {string} childName New human-readable name for the segment. - * @param recorder + * @param {object} params to function + * @param {object} params.config agent config + * @param {string} params.name Human-readable name for this segment (e.g. 'http', 'net', 'express', + * 'mysql', etc). + * @param {boolean} params.collect flag to collect as part of transaction trace + * @param {TraceSegment} params.root root segment * @returns {TraceSegment} New nested TraceSegment. */ -TraceSegment.prototype.add = function add(childName, recorder) { +TraceSegment.prototype.add = function add({ config, name, collect, root }) { + // this is needed here to check when add is called directly on segment if (this.opaque) { logger.trace('Skipping child addition on opaque segment') return this } - logger.trace('Adding segment %s to %s in %s', childName, this.name, this.transaction.id) - const segment = new TraceSegment(this.transaction, childName, recorder) - const config = this.transaction.agent.config - if (this.transaction.trace.segmentsSeen++ >= config.max_trace_segments) { - segment._collect = false - } + const segment = new TraceSegment({ config, name, collect, root }) + this.children.push(segment) + // This should only be used in testing if (config.debug && config.debug.double_linked_transactions) { segment.parent = this } @@ -385,6 +363,7 @@ TraceSegment.prototype._getChildPairs = function _getChildPairs(end) { * FIXME: I don't know if it makes sense to add custom fields for Node. TBD */ TraceSegment.prototype.toJSON = function toJSON() { + const root = this.isRoot ? this : this.root // use depth-first search on the segment tree using stack const resultDest = [] // array of objects relating a segment and the destination for its @@ -399,7 +378,7 @@ TraceSegment.prototype.toJSON = function toJSON() { while (segmentsToProcess.length !== 0) { const { segment, destination } = segmentsToProcess.pop() - const start = segment.timer.startedRelativeTo(segment.transaction.trace.root.timer) + const start = segment.timer.startedRelativeTo(root.timer) const duration = segment.getDurationInMillis() const segmentChildren = segment.getCollectedChildren() diff --git a/lib/transaction/tracer/index.js b/lib/transaction/tracer/index.js index 495862c5bd..72e789d1bf 100644 --- a/lib/transaction/tracer/index.js +++ b/lib/transaction/tracer/index.js @@ -12,18 +12,20 @@ const INACTIVE_TRANSACTION_MESSAGE = 'Not creating segment "%s" because no trans const SKIP_WRAPPING_FUNCTION_MESSAGE = 'Not wrapping "%s" because it was not a function' const CREATE_SEGMENT_MESSAGE = 'Creating "%s" segment for transaction %s.' const { addCLMAttributes: maybeAddCLMAttributes } = require('../../util/code-level-metrics') +const AsyncLocalContextManager = require('../../context-manager/async-local-context-manager') module.exports = Tracer -function Tracer(agent, contextManager) { +function Tracer(agent) { if (!agent) { throw new Error('Must be initialized with an agent.') } this.agent = agent - this._contextManager = contextManager + this._contextManager = new AsyncLocalContextManager() } +Tracer.prototype.getContext = getContext Tracer.prototype.getTransaction = getTransaction Tracer.prototype.getSegment = getSegment Tracer.prototype.setSegment = setSegment @@ -36,7 +38,6 @@ Tracer.prototype.bindFunction = bindFunction Tracer.prototype.bindEmitter = bindEmitter Tracer.prototype.getOriginal = getOriginal Tracer.prototype.slice = argSlice -Tracer.prototype.wrapFunctionNoSegment = wrapFunctionNoSegment Tracer.prototype.wrapFunctionFirstNoSegment = wrapFunctionFirstNoSegment Tracer.prototype.wrapFunction = wrapFunction Tracer.prototype.wrapFunctionLast = wrapFunctionLast @@ -44,10 +45,14 @@ Tracer.prototype.wrapFunctionFirst = wrapFunctionFirst Tracer.prototype.wrapSyncFunction = wrapSyncFunction Tracer.prototype.wrapCallback = wrapCallback +function getContext() { + return this._contextManager.getContext() +} + function getTransaction() { - const currentSegment = this._contextManager.getContext() - if (currentSegment && currentSegment.transaction && currentSegment.transaction.isActive()) { - return currentSegment.transaction + const context = this.getContext() + if (context?.transaction && context?.transaction?.isActive()) { + return context.transaction } return null @@ -55,11 +60,19 @@ function getTransaction() { // TODO: Remove/replace external uses to tracer.getSegment() function getSegment() { - return this._contextManager.getContext() + const context = this.getContext() + return context?.segment || null } -function setSegment(segment) { - this._contextManager.setContext(segment) +// TODO: update to setNewContext or something like that +function setSegment({ transaction, segment } = {}) { + const context = this.getContext() + const newContext = context.enterSegment({ + transaction: transaction !== undefined ? transaction : context.transaction, + segment: segment !== undefined ? segment : context.segment + }) + + this._contextManager.setContext(newContext) } // TODO: Remove/replace external uses to tracer.getSpanContext() @@ -67,21 +80,45 @@ function getSpanContext() { const currentSegment = this.getSegment() return currentSegment && currentSegment.getSpanContext() } - -function createSegment(name, recorder, _parent) { - const parent = _parent || this.getSegment() - if (!parent || !parent.transaction.isActive()) { +function createSegment({ name, recorder, parent, transaction }) { + if (!parent || !transaction?.isActive()) { logger.trace( { hasParent: !!parent, - transactionActive: parent && parent.transaction.isActive() + transactionActive: transaction?.isActive() }, 'Not creating segment %s, no parent or active transaction available.', name ) return null } - return parent.add(name, recorder) + + if (parent.opaque) { + logger.trace('Skipping child addition on opaque segment') + return parent + } + + logger.trace('Adding segment %s to %s in %s', name, parent.name, transaction.id) + + let collect = true + if (transaction.trace.segmentsSeen++ >= this.agent.config.max_trace_segments) { + collect = false + } + + transaction.incrementCounters() + + const segment = parent.add({ + config: this.agent.config, + name, + collect, + root: transaction.trace.root + }) + + if (recorder) { + transaction.addRecorder(recorder.bind(null, segment)) + } + + return segment } function addSegment(name, recorder, parent, full, task) { @@ -89,10 +126,14 @@ function addSegment(name, recorder, parent, full, task) { throw new Error('task must be a function') } - const segment = this.createSegment(name, recorder, parent) - + const context = this.getContext() + const segment = this.createSegment({ name, recorder, parent, transaction: context.transaction }) + let newContext = context + if (segment) { + newContext = context.enterSegment({ segment }) + } maybeAddCLMAttributes(task, segment) - return this.bindFunction(task, segment, full)(segment) + return this.bindFunction(task, newContext, full)(segment, context?.transaction) } function transactionProxy(handler) { @@ -108,15 +149,13 @@ function transactionProxy(handler) { } // don't nest transactions, reuse existing ones - const segment = tracer.getSegment() + const context = tracer.getContext() + const segment = context?.segment + const currentTx = context?.transaction if (segment) { - if (segment.transaction.traceStacks) { - segment.probe('!!! Nested transaction creation !!!') - segment.transaction.traceFlag = true // Will log the stacks when it ends. - } logger.warn( { - transaction: { id: segment.transaction.id, name: segment.transaction.getName() }, + transaction: { id: currentTx.id, name: currentTx.getName() }, segment: segment.name }, 'Active transaction when creating non-nested transaction' @@ -124,8 +163,10 @@ function transactionProxy(handler) { tracer.agent.recordSupportability('Nodejs/Transactions/Nested') return handler.apply(this, arguments) } + const transaction = new Transaction(tracer.agent) - return tracer.bindFunction(handler, transaction.trace.root, true).apply(this, arguments) + const newContext = context.enterTransaction(transaction) + return tracer.bindFunction(handler, newContext, true).apply(this, arguments) } wrapped[symbols.original] = handler @@ -161,22 +202,21 @@ function transactionNestProxy(type, handler) { } // don't nest transactions, reuse existing ones - let transaction = tracer.getTransaction() - let segment = tracer.getSegment() + let context = tracer.getContext() let createNew = false - if (!transaction || transaction.type !== type) { + if (!context?.transaction || context?.transaction.type !== type) { createNew = true } if (createNew) { - transaction = new Transaction(tracer.agent) + const transaction = new Transaction(tracer.agent) transaction.type = type - segment = transaction.trace.root + context = context.enterTransaction(transaction) } - return tracer.bindFunction(handler, segment).apply(this, arguments) + return tracer.bindFunction(handler, context).apply(this, arguments) } wrapped[symbols.original] = handler @@ -184,39 +224,33 @@ function transactionNestProxy(type, handler) { return wrapped } -function bindFunction(handler, segment, full) { +function bindFunction(handler, context, full) { if (typeof handler !== 'function') { return handler } - return _makeWrapped(this, handler, segment || this.getSegment(), !!full) + return _makeWrapped({ tracer: this, handler, context, full: !!full }) } -function _makeWrapped(tracer, handler, active, full) { +function _makeWrapped({ tracer, handler, context, full }) { + const { segment } = context wrapped[symbols.original] = getOriginal(handler) - wrapped[symbols.segment] = active + wrapped[symbols.segment] = segment return wrapped function wrapped() { - const prev = tracer.getSegment() - - if (active && full) { - active.start() + if (segment && full) { + segment.start() } try { - return tracer._contextManager.runInContext(active, handler, this, arguments) + return tracer._contextManager.runInContext(context, handler, this, arguments) } catch (err) { logger.trace(err, 'Error from wrapped function:') - - if (prev === null && process.domain != null) { - process.domain[symbols.segment] = tracer.getSegment() - } - throw err // Re-throwing application error, this is not an agent error. } finally { - if (active && full) { - active.touch() + if (segment && full) { + segment.touch() } } } @@ -236,7 +270,9 @@ function bindEmitter(emitter, segment) { } const emit = getOriginal(emitter.emit) - emitter.emit = this.bindFunction(emit, segment) + const context = this.getContext() + const newContext = context.enterSegment({ segment }) + emitter.emit = this.bindFunction(emit, newContext) return emitter } @@ -267,35 +303,6 @@ function argSlice(args) { return array } -function wrapFunctionNoSegment(original, name, wrapper) { - if (typeof original !== 'function') { - return original - } - - logger.trace('Wrapping function %s (no segment)', name || original.name || 'anonymous') - const tracer = this - - return wrappedFunction - - function wrappedFunction() { - if (!tracer.getTransaction()) { - return original.apply(this, arguments) - } - let args = tracer.slice(arguments) - - if (wrapper === undefined) { - const last = args.length - 1 - const cb = args[last] - if (typeof cb === 'function') { - args[last] = tracer.bindFunction(cb) - } - } else { - args = wrapper(args) - } - return original.apply(this, args) - } -} - function wrapFunctionFirstNoSegment(original, name) { if (typeof original !== 'function') { return original @@ -310,10 +317,11 @@ function wrapFunctionFirstNoSegment(original, name) { if (!tracer.getTransaction()) { return original.apply(this, arguments) } + const context = tracer.getContext() const args = tracer.slice(arguments) const cb = args[0] if (typeof cb === 'function') { - args[0] = tracer.bindFunction(cb) + args[0] = tracer.bindFunction(cb, context) } return original.apply(this, args) } @@ -331,6 +339,7 @@ function wrapFunctionLast(name, recorder, original) { return wrappedFunction function wrappedFunction() { + const context = tracer.getContext() const transaction = tracer.getTransaction() if (!transaction) { logger.trace(INACTIVE_TRANSACTION_MESSAGE, name) @@ -344,14 +353,20 @@ function wrapFunctionLast(name, recorder, original) { if (typeof cb !== 'function') { return original.apply(this, arguments) } - const child = tracer.createSegment(name, recorder) + const child = tracer.createSegment({ + name, + recorder, + parent: context.segment, + transaction: context.transaction + }) args[last] = tracer.wrapCallback(cb, child, function wrappedCallback() { logger.trace('Ending "%s" segment for transaction %s.', name, transaction.id) child.touch() return cb.apply(this, arguments) }) child.start() - return tracer.bindFunction(original, child).apply(this, args) + const newContext = context.enterSegment({ segment: child }) + return tracer.bindFunction(original, newContext).apply(this, args) } } @@ -367,6 +382,7 @@ function wrapFunctionFirst(name, recorder, original) { return wrappedFunction function wrappedFunction() { + const context = tracer.getContext() const transaction = tracer.getTransaction() if (!transaction) { logger.trace(INACTIVE_TRANSACTION_MESSAGE, name) @@ -379,14 +395,20 @@ function wrapFunctionFirst(name, recorder, original) { if (typeof cb !== 'function') { return original.apply(this, arguments) } - const child = tracer.createSegment(name, recorder) + const child = tracer.createSegment({ + name, + recorder, + parent: context.segment, + transaction: context.transaction + }) args[0] = tracer.wrapCallback(cb, child, function wrappedCallback() { logger.trace('Ending "%s" segment for transaction %s.', name, transaction.id) child.touch() return cb.apply(this, arguments) }) child.start() - return tracer.bindFunction(original, child).apply(this, args) + const newContext = context.enterSegment({ segment: child }) + return tracer.bindFunction(original, newContext).apply(this, args) } } @@ -403,6 +425,7 @@ function wrapFunction(name, recorder, original, wrapper, resp) { return wrappedFunction function wrappedFunction() { + const context = tracer.getContext() const transaction = tracer.getTransaction() if (!transaction) { logger.trace(INACTIVE_TRANSACTION_MESSAGE, name) @@ -411,10 +434,11 @@ function wrapFunction(name, recorder, original, wrapper, resp) { logger.trace(CREATE_SEGMENT_MESSAGE, name, transaction.id) - const child = tracer.createSegment(name, recorder) + const child = tracer.createSegment({ name, recorder, parent: context.segment, transaction }) const args = wrapper.call(this, child, tracer.slice(arguments), bind) child.start() - let result = tracer.bindFunction(original, child).apply(this, args) + const newContext = context.enterSegment({ segment: child }) + let result = tracer.bindFunction(original, newContext).apply(this, args) if (resp) { result = resp.call(this, child, result, bind) } @@ -446,22 +470,25 @@ function wrapSyncFunction(name, recorder, original) { return wrappedFunction function wrappedFunction() { + const context = tracer.getContext() const transaction = tracer.getTransaction() if (!transaction) { logger.trace(INACTIVE_TRANSACTION_MESSAGE, name) return original.apply(this, arguments) } logger.trace('Creating "%s" sync segment for transaction %s.', name, transaction.id) - const child = tracer.createSegment(name, recorder) + const child = tracer.createSegment({ name, recorder, parent: context.segment, transaction }) if (child) { child.async = false } - return tracer.bindFunction(original, child, true).apply(this, arguments) + const newContext = context.enterSegment({ segment: child }) + return tracer.bindFunction(original, newContext, true).apply(this, arguments) } } function wrapCallback(original, segment, wrapped) { const tracer = this + const context = this.getContext() if (typeof original !== 'function') { return original @@ -475,19 +502,20 @@ function wrapCallback(original, segment, wrapped) { wrapped[symbols.original] = original } - const child = tracer.createSegment( - 'Callback: ' + (original.name || 'anonymous'), - null, - segment - ) + const child = tracer.createSegment({ + name: 'Callback: ' + (original.name || 'anonymous'), + parent: segment, + transaction: context.transaction + }) if (child) { child.async = false } - return tracer.bindFunction(wrapped || original, child, true).apply(this, arguments) + const newContext = context.enterSegment({ segment: child }) + return tracer.bindFunction(wrapped || original, newContext, true).apply(this, arguments) }, - segment, + context, false ) } diff --git a/lib/util/cat.js b/lib/util/cat.js index 78cb15d0cc..ae444279c1 100644 --- a/lib/util/cat.js +++ b/lib/util/cat.js @@ -273,11 +273,13 @@ cat.parseAppData = function parseAppData(config, obfAppData) { * Assigns the CAT id, transaction to segment and adds `transaction_guid` when it exists. * It also renames the segment name based on the newly decoded app data when host is present * - * @param {Array} appData decodes CAT app data - * @param {TraceSegment} segment - * @param {string} [host] if host is present it will rename segment with app data and host + * @param {object} params to function + * @param {Array} params.appData decodes CAT app data + * @param {TraceSegment} params.segment segment to assign CAT data + * @param {string} [params.host] if host is present it will rename segment with app data and host + * @param {Transaction} params.transaction active transaction */ -cat.assignCatToSegment = function assignCatToSegment(appData, segment, host) { +cat.assignCatToSegment = function assignCatToSegment({ appData, segment, host, transaction }) { if (!Array.isArray(appData) || typeof appData[0] !== 'string') { logger.trace(`Unknown format for CAT header ${HTTP_CAT_APP_DATA_HEADER}.`) return @@ -297,7 +299,7 @@ cat.assignCatToSegment = function assignCatToSegment(appData, segment, host) { } logger.trace( 'Got inbound response CAT headers in transaction %s from %s', - segment.transaction.id, + transaction.id, transactionGuid ) } diff --git a/lib/util/copy.js b/lib/util/copy.js index 0930138f91..9bc8ecd316 100644 --- a/lib/util/copy.js +++ b/lib/util/copy.js @@ -13,7 +13,7 @@ exports.shallow = shallowCopy * Performs a shallow copy of all properties on the source object. * * @param {object} source - The object to copy the properties from. - * @param {object} [dest={}] - The object to copy the properties to. + * @param {object} [dest] - The object to copy the properties to. * @returns {object} The destination object. */ function shallowCopy(source, dest) { diff --git a/lib/util/trace-stacks.js b/lib/util/trace-stacks.js new file mode 100644 index 0000000000..48cd4f8052 --- /dev/null +++ b/lib/util/trace-stacks.js @@ -0,0 +1,25 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +module.exports = class TraceStacks { + constructor(config) { + this.stack = config?.logging.diagnostics ? [] : null + } + + probe(action, data) { + if (this.stack) { + this.stack.push({ + stack: new Error(action).stack.split('\n'), + extra: data + }) + } + } + + serialize(name) { + return { segment: name, stacks: this.stack } + } +} diff --git a/test/benchmark/events/span-event.bench.js b/test/benchmark/events/span-event.bench.js index f1e53bbbe2..6e8420ad87 100644 --- a/test/benchmark/events/span-event.bench.js +++ b/test/benchmark/events/span-event.bench.js @@ -11,16 +11,17 @@ const SpanEvent = require('../../../lib/spans/span-event') const suite = benchmark.createBenchmark({ name: 'SpanEvent' }) let segment = null +let transaction = null suite.add({ name: 'from generic segment', agent: {}, before: (agent) => { - segment = makeSegment(agent) + ;({ segment, transaction } = makeSegment(agent)) segment.name = 'some random segment' }, fn: () => { - return SpanEvent.fromSegment(segment) + return SpanEvent.fromSegment(segment, transaction) } }) @@ -28,11 +29,11 @@ suite.add({ name: 'from external segment', agent: {}, before: (agent) => { - segment = makeSegment(agent) + ;({ segment, transaction } = makeSegment(agent)) segment.name = 'External/www.foobar.com/' }, fn: () => { - return SpanEvent.fromSegment(segment) + return SpanEvent.fromSegment(segment, transaction) } }) @@ -40,30 +41,31 @@ suite.add({ name: 'from db segment', agent: {}, before: (agent) => { - segment = makeSegment(agent) + ;({ segment, transaction } = makeSegment(agent)) segment.name = 'Datastore/statement/SELECT' }, fn: () => { - return SpanEvent.fromSegment(segment) + return SpanEvent.fromSegment(segment, transaction) } }) suite.run() function makeSegment(agent) { - const s = helper.runInTransaction(agent, (tx) => tx.trace.root) - s.addAttribute('foo', 'bar') - s.addAttribute('request.headers.x-customer-header', 'some header value') - s.addAttribute('library', 'my great library') - s.addAttribute('url', 'http://my-site.com') - s.addAttribute('procedure', 'GET') - s.addAttribute('product', 'BestDB') - s.addAttribute('sql', 'SELECT * FROM the_best') - s.addAttribute('database_name', 'users_db') - s.addAttribute('host', '123.123.123.123') - s.addAttribute('port_path_or_id', '3306') - s.end() - s.transaction.end() + const transaction = helper.runInTransaction(agent, (tx) => tx) + const segment = transaction.trace.root + segment.addAttribute('foo', 'bar') + segment.addAttribute('request.headers.x-customer-header', 'some header value') + segment.addAttribute('library', 'my great library') + segment.addAttribute('url', 'http://my-site.com') + segment.addAttribute('procedure', 'GET') + segment.addAttribute('product', 'BestDB') + segment.addAttribute('sql', 'SELECT * FROM the_best') + segment.addAttribute('database_name', 'users_db') + segment.addAttribute('host', '123.123.123.123') + segment.addAttribute('port_path_or_id', '3306') + segment.end() + transaction.end() - return s + return { segment, transaction } } diff --git a/test/benchmark/shim/record.bench.js b/test/benchmark/shim/record.bench.js index db8354c4fc..e7003e3640 100644 --- a/test/benchmark/shim/record.bench.js +++ b/test/benchmark/shim/record.bench.js @@ -41,7 +41,7 @@ const wrapped = shim.record(getTest(), 'func', function () { suite.add({ name: 'wrapper - no transaction', fn: function () { - tracer.setSegment(null) + tracer.setSegment({ segment: null, transaction: null }) wrapped.func(noop) } }) @@ -49,7 +49,7 @@ suite.add({ suite.add({ name: 'wrapper - in transaction', fn: function () { - tracer.setSegment(transaction.trace.root) + tracer.setSegment({ transaction, segment: transaction.trace.root }) wrapped.func(noop) } }) diff --git a/test/benchmark/trace/segment.bench.js b/test/benchmark/trace/segment.bench.js index 514559c8af..6794b9f155 100644 --- a/test/benchmark/trace/segment.bench.js +++ b/test/benchmark/trace/segment.bench.js @@ -10,8 +10,6 @@ const benchmark = require('../../lib/benchmark') const Segment = require('../../../lib/transaction/trace/segment') const agent = helper.loadMockedAgent() -const tx = helper.runInTransaction(agent, (_tx) => _tx) - const suite = benchmark.createBenchmark({ name: 'trace segments' }) @@ -23,7 +21,12 @@ function addChildren(rootSegment, numChildren) { for (let numSegments = 1; numSegments < 900; numSegments += numChildren) { const parent = queue.shift() for (let i = 0; i < numChildren; ++i) { - const child = parent.add('child ' + (numSegments + i)) + const child = parent.add({ + name: 'child ' + (numSegments + i), + root: rootSegment, + collect: true, + config: agent.config + }) child.timer.setDurationInMillis( (0.99 + Math.random() / 100) * parent.timer.durationInMillis, parent.timer.start + 1 @@ -37,7 +40,7 @@ suite.add({ name: 'toJSON flat', before: function buildTree() { - root = new Segment(tx, 'ROOT') + root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) root.timer.setDurationInMillis(10000, Date.now()) addChildren(root, 899) }, @@ -50,7 +53,7 @@ suite.add({ name: 'toJSON linear', before: function buildTree() { - root = new Segment(tx, 'ROOT') + root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) root.timer.setDurationInMillis(10000, Date.now()) addChildren(root, 1) }, @@ -63,7 +66,7 @@ suite.add({ name: 'toJSON binary', before: function buildTree() { - root = new Segment(tx, 'ROOT') + root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) root.timer.setDurationInMillis(10000, Date.now()) addChildren(root, 2) }, @@ -76,7 +79,7 @@ suite.add({ name: 'getExclusiveDurationInMillis flat', before: function buildTree() { - root = new Segment(tx, 'ROOT') + root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) root.timer.setDurationInMillis(10000, Date.now()) addChildren(root, 899) }, @@ -89,7 +92,7 @@ suite.add({ name: 'getExclusiveDurationInMillis linear', before: function buildTree() { - root = new Segment(tx, 'ROOT') + root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) root.timer.setDurationInMillis(10000, Date.now()) addChildren(root, 1) }, @@ -102,7 +105,7 @@ suite.add({ name: 'getExclusiveDurationInMillis binary', before: function buildTree() { - root = new Segment(tx, 'ROOT') + root = new Segment({ name: 'ROOT', isRoot: true, config: agent.config }) root.timer.setDurationInMillis(10000, Date.now()) addChildren(root, 2) }, diff --git a/test/benchmark/tracer/bindFunction.bench.js b/test/benchmark/tracer/bindFunction.bench.js index 3feb0b1433..bcb2ea6026 100644 --- a/test/benchmark/tracer/bindFunction.bench.js +++ b/test/benchmark/tracer/bindFunction.bench.js @@ -14,10 +14,11 @@ const tracer = helper.getTracer() const tx = helper.runInTransaction(s.agent, function (_tx) { return _tx }) -tracer.setSegment(tx.root) +tracer.setSegment({ transaction: tx, segment: tx.trace.root }) preOptBind() -const bound = tracer.bindFunction(shared.getTest().func, tx.root, true) +const ctx = tracer.getContext() +const bound = tracer.bindFunction(shared.getTest().func, ctx, true) setTimeout(function () { suite.add({ @@ -30,16 +31,6 @@ setTimeout(function () { fn: twoParamBind }) - suite.add({ - name: 'just fn', - fn: oneParamBind - }) - - suite.add({ - name: 'null segment', - fn: nullSegmentBind - }) - suite.add({ name: 'mixed', fn: randomBind @@ -57,24 +48,15 @@ setTimeout(function () { function allParamBind() { const test = shared.getTest() - test.func = tracer.bindFunction(test.func, tx.root, Math.random() > 0.5) + const ctx = tracer.getContext() + test.func = tracer.bindFunction(test.func, ctx, Math.random() > 0.5) } function twoParamBind() { const test = shared.getTest() Math.random() > 0.5 // rand call so all tests perform same amount of work. - test.func = tracer.bindFunction(test.func, tx.root) -} - -function oneParamBind() { - const test = shared.getTest() - Math.random() > 0.5 // rand call so all tests perform same amount of work. - test.func = tracer.bindFunction(test.func) -} - -function nullSegmentBind() { - const test = shared.getTest() - test.func = tracer.bindFunction(test.func, null, Math.random() > 0.5) + const ctx = tracer.getContext() + test.func = tracer.bindFunction(test.func, ctx) } function randomBind() { @@ -83,10 +65,6 @@ function randomBind() { allParamBind() } else if (n >= 0.5) { twoParamBind() - } else if (n >= 0.25) { - oneParamBind() - } else { - nullSegmentBind() } } diff --git a/test/benchmark/tracer/segments.bench.js b/test/benchmark/tracer/segments.bench.js index 2d636dfde6..b3834f3e26 100644 --- a/test/benchmark/tracer/segments.bench.js +++ b/test/benchmark/tracer/segments.bench.js @@ -16,7 +16,7 @@ const tx = helper.runInTransaction(s.agent, function (_tx) { return _tx }) -tracer.setSegment(tx.root) +tracer.setSegment({ transaction: tx.root, segment: tx.root }) suite.add({ name: 'tracer.getSegment', diff --git a/test/benchmark/tracer/transaction.bench.js b/test/benchmark/tracer/transaction.bench.js index c2e668db7c..7ce74e470f 100644 --- a/test/benchmark/tracer/transaction.bench.js +++ b/test/benchmark/tracer/transaction.bench.js @@ -15,7 +15,7 @@ const tx = helper.runInTransaction(s.agent, function (_tx) { return _tx }) -tracer.setSegment(tx.root) +tracer.setSegment({ transaction: tx, segment: tx.root }) suite.add({ name: 'tracer.getTransaction', diff --git a/test/benchmark/tracer/utilities.bench.js b/test/benchmark/tracer/utilities.bench.js index 4866e07e95..46e85bcd24 100644 --- a/test/benchmark/tracer/utilities.bench.js +++ b/test/benchmark/tracer/utilities.bench.js @@ -16,7 +16,7 @@ const tx = helper.runInTransaction(s.agent, function (_tx) { return _tx }) -tracer.setSegment(tx.root) +tracer.setSegment({ transaction: tx, segment: tx.root }) suite.add({ name: 'tracer.slice', diff --git a/test/benchmark/tracer/wrapping.bench.js b/test/benchmark/tracer/wrapping.bench.js index 2a420de2bf..21258dc014 100644 --- a/test/benchmark/tracer/wrapping.bench.js +++ b/test/benchmark/tracer/wrapping.bench.js @@ -20,22 +20,16 @@ suite.add({ name: 'tracer.bindFunction', fn: function () { const test = shared.getTest() - return tracer.bindFunction(test.func, tx.root, true) + let ctx = tracer.getContext() + ctx = ctx.enterSegment({ transaction: tx, segment: tx.trace.root }) + return tracer.bindFunction(test.func, ctx, true) } }) suite.add({ name: 'tracer.bindEmitter', fn: function () { - return tracer.bindEmitter(new EventEmitter(), tx.root) - } -}) - -suite.add({ - name: 'tracer.wrapFunctionNoSegment', - fn: function () { - const test = shared.getTest() - return tracer.wrapFunctionNoSegment(test.func, 'func', function () {}) + return tracer.bindEmitter(new EventEmitter(), tx.trace.root) } }) @@ -83,7 +77,7 @@ suite.add({ name: 'tracer.wrapCallback', fn: function () { const test = shared.getTest() - return tracer.wrapCallback(test.func, tx.root, null) + return tracer.wrapCallback(test.func, tx.trace.root, null) } }) diff --git a/test/benchmark/webframework-shim/recordMiddleware.bench.js b/test/benchmark/webframework-shim/recordMiddleware.bench.js index 3c72ddf7bf..d08d0eac6e 100644 --- a/test/benchmark/webframework-shim/recordMiddleware.bench.js +++ b/test/benchmark/webframework-shim/recordMiddleware.bench.js @@ -59,7 +59,7 @@ function addTests(name, speccer) { suite.add({ name: name + ' - wrapper (no tx) ', fn: function () { - tracer.setSegment(null) + tracer.setSegment({ segment: null, transaction: null }) middleware(getReqd(), {}, noop) } }) @@ -67,7 +67,7 @@ function addTests(name, speccer) { suite.add({ name: name + ' - wrapper (tx) ', fn: function () { - tracer.setSegment(transaction.trace.root) + tracer.setSegment({ transaction, segment: transaction.trace.root }) middleware(getReqd(), {}, noop) } }) diff --git a/test/integration/agent/serverless-harvest.tap.js b/test/integration/agent/serverless-harvest.tap.js index 0e1767f219..402db0a0b3 100644 --- a/test/integration/agent/serverless-harvest.tap.js +++ b/test/integration/agent/serverless-harvest.tap.js @@ -336,7 +336,13 @@ tap.test('Serverless mode harvest', (t) => { const expectedSql = 'select pg_sleep(1)' - agent.queries.add(tx.trace.root, 'postgres', expectedSql, 'FAKE STACK') + agent.queries.add({ + transaction: tx, + segment: tx.trace.root, + type: 'postgres', + query: expectedSql, + trace: 'FAKE STACK' + }) tx.end() agent.once('harvestFinished', () => { diff --git a/test/integration/core/native-promises/native-promises.tap.js b/test/integration/core/native-promises/native-promises.tap.js index c7cac141dc..a9a7d52bbb 100644 --- a/test/integration/core/native-promises/native-promises.tap.js +++ b/test/integration/core/native-promises/native-promises.tap.js @@ -36,15 +36,15 @@ test('AsyncLocalStorage based tracking', (t) => { helper.runInTransaction(agent, function (txn) { t.ok(txn, 'transaction should not be null') - const segment = txn.trace.root - agent.tracer.bindFunction(one, segment)() + const ctx = agent.tracer.getContext() + agent.tracer.bindFunction(one, ctx)() const wrapperTwo = agent.tracer.bindFunction(function () { return two() - }, segment) + }, ctx) const wrapperThree = agent.tracer.bindFunction(function () { return three() - }, segment) + }, ctx) function one() { return new Promise(executor).then(() => { @@ -287,19 +287,19 @@ function createPromiseTests(t, config) { t.test('handles multi-entry callbacks correctly', function (t) { const { agent, tracer } = setupAgent(t, config) - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const root = tracer.getSegment() - const aSeg = agent.tracer.createSegment('A') - tracer.setSegment(aSeg) + const aSeg = agent.tracer.createSegment({ name: 'A', parent: root, transaction: tx }) + tracer.setSegment({ segment: aSeg, transaction: tx }) const resA = new TestResource(1) - const bSeg = agent.tracer.createSegment('B') - tracer.setSegment(bSeg) + const bSeg = agent.tracer.createSegment({ name: 'B', parent: root, transaction: tx }) + tracer.setSegment({ segment: bSeg, transaction: tx }) const resB = new TestResource(2) - tracer.setSegment(root) + tracer.setSegment({ segment: root, transaction: tx }) resA.doStuff(() => { t.equal( @@ -339,22 +339,22 @@ function createPromiseTests(t, config) { helper.runInTransaction(agent, function (txn) { t.ok(txn, 'transaction should not be null') + const ctx = agent.tracer.getContext() - const segment = txn.trace.root agent.tracer.bindFunction(function one() { return new Promise(executor).then(() => { const tx = agent.getTransaction() t.equal(tx ? tx.id : null, txn.id) t.end() }) - }, segment)() + }, ctx)() const wrapperTwo = agent.tracer.bindFunction(function () { return two() - }, segment) + }, ctx) const wrapperThree = agent.tracer.bindFunction(function () { return three() - }, segment) + }, ctx) function executor(resolve) { setImmediate(() => { @@ -387,22 +387,22 @@ function createPromiseTests(t, config) { helper.runInTransaction(agent, function (txn) { t.ok(txn, 'transaction should not be null') + const ctx = agent.tracer.getContext() - const segment = txn.trace.root agent.tracer.bindFunction(function one() { return new Promise(executor).then(() => { const tx = agent.getTransaction() t.equal(tx ? tx.id : null, txn.id) t.end() }) - }, segment)() + }, ctx)() const wrapperTwo = agent.tracer.bindFunction(function () { return two() - }, segment) + }, ctx) const wrapperThree = agent.tracer.bindFunction(function () { return three() - }, segment) + }, ctx) function executor(resolve) { process.nextTick(() => { @@ -435,22 +435,22 @@ function createPromiseTests(t, config) { helper.runInTransaction(agent, function (txn) { t.ok(txn, 'transaction should not be null') + const ctx = agent.tracer.getContext() - const segment = txn.trace.root agent.tracer.bindFunction(function one() { return new Promise(executor).then(() => { const tx = agent.getTransaction() t.equal(tx ? tx.id : null, txn.id) t.end() }) - }, segment)() + }, ctx)() const wrapperTwo = agent.tracer.bindFunction(function () { return two() - }, segment) + }, ctx) const wrapperThree = agent.tracer.bindFunction(function () { return three() - }, segment) + }, ctx) function executor(resolve) { setTimeout(() => { @@ -483,22 +483,22 @@ function createPromiseTests(t, config) { helper.runInTransaction(agent, function (txn) { t.ok(txn, 'transaction should not be null') + const ctx = agent.tracer.getContext() - const segment = txn.trace.root agent.tracer.bindFunction(function one() { return new Promise(executor).then(() => { const tx = agent.getTransaction() t.equal(tx ? tx.id : null, txn.id) t.end() }) - }, segment)() + }, ctx)() const wrapperTwo = agent.tracer.bindFunction(function () { return two() - }, segment) + }, ctx) const wrapperThree = agent.tracer.bindFunction(function () { return three() - }, segment) + }, ctx) function executor(resolve) { const ref = setInterval(() => { diff --git a/test/integration/core/net.tap.js b/test/integration/core/net.tap.js index 78237cdf01..b75da4afdc 100644 --- a/test/integration/core/net.tap.js +++ b/test/integration/core/net.tap.js @@ -20,8 +20,6 @@ test('createServer', function createServerTest(t) { const server = net.createServer(handler) server.listen(4123, function listening() { - // leave transaction - tracer.setSegment(null) const socket = net.connect({ port: 4123 }) socket.write('test123') socket.end() @@ -46,8 +44,8 @@ test('createServer', function createServerTest(t) { function onClose() { const root = agent.getTransaction().trace.root - t.equal(root.children.length, 1, 'should have a single child') - const child = root.children[0] + t.equal(root.children.length, 2, 'should have a single child') + const child = root.children[1] t.equal(child.name, 'net.Server.onconnection', 'child segment should have correct name') t.ok(child.timer.touched, 'child should started and ended') t.equal(child.children.length, 1, 'child should have a single child segment') diff --git a/test/integration/distributed-tracing/trace-context-cross-agent.tap.js b/test/integration/distributed-tracing/trace-context-cross-agent.tap.js index ddf7b078ea..9c796aea69 100644 --- a/test/integration/distributed-tracing/trace-context-cross-agent.tap.js +++ b/test/integration/distributed-tracing/trace-context-cross-agent.tap.js @@ -335,7 +335,7 @@ const runTestCase = function (testCase, parentTest) { const transactionType = testCase.web_transaction ? TYPES.WEB : TYPES.BG helper.runInTransaction(agent, transactionType, function (transaction) { - transaction.baseSegment = transaction.trace.root.add('MyBaseSegment', (segment) => { + transaction.baseSegment = transaction.trace.add('MyBaseSegment', (segment) => { recorder( transaction, testCase.web_transaction ? 'Web' : 'Other', diff --git a/test/integration/newrelic-response-handling.tap.js b/test/integration/newrelic-response-handling.tap.js index 2dbc37bc36..c9e358241d 100644 --- a/test/integration/newrelic-response-handling.tap.js +++ b/test/integration/newrelic-response-handling.tap.js @@ -324,7 +324,13 @@ function createTestData(agent, callback) { helper.runInTransaction(agent, (transaction) => { const segment = transaction.trace.add('MySegment') segment.overwriteDurationInMillis(1) - agent.queries.add(segment, 'mysql', 'select * from foo', new Error().stack) + agent.queries.add({ + segment, + transaction, + type: 'mysql', + query: 'select * from foo', + trace: new Error().stack + }) transaction.finalizeNameFromUri('/some/test/url', 200) transaction.end() diff --git a/test/integration/transaction/total-time.tap.js b/test/integration/transaction/total-time.tap.js index 78f7f8a46d..9bbd1f1ec5 100644 --- a/test/integration/transaction/total-time.tap.js +++ b/test/integration/transaction/total-time.tap.js @@ -12,9 +12,7 @@ test('totaltime: single segment', function (t) { const agent = getAgent(t) helper.runInTransaction(agent, function (transaction) { const start = Date.now() - const root = transaction.trace.root - - const only = root.add('only') + const only = transaction.trace.add('only') only.timer.setDurationInMillis(1000, start) t.equal(transaction.trace.getTotalTimeDurationInMillis(), 1000) @@ -26,12 +24,10 @@ test('totaltime: parent with child not overlapping', function (t) { const agent = getAgent(t) helper.runInTransaction(agent, function (transaction) { const start = Date.now() - const root = transaction.trace.root - - const parent = root.add('parent') + const parent = transaction.trace.add('parent') parent.timer.setDurationInMillis(1000, start) - const child = parent.add('child') + const child = transaction.trace.add('child') child.timer.setDurationInMillis(1000, start + 1000) t.equal(transaction.trace.getTotalTimeDurationInMillis(), 2000) @@ -43,12 +39,11 @@ test('totaltime: parent with a child overlapping by 500ms', function (t) { const agent = getAgent(t) helper.runInTransaction(agent, function (transaction) { const start = Date.now() - const root = transaction.trace.root - const parent = root.add('parent') + const parent = transaction.trace.add('parent') parent.timer.setDurationInMillis(1000, start) - const child = parent.add('child') + const child = transaction.trace.add('child', null, parent) child.timer.setDurationInMillis(1000, start + 500) t.equal(transaction.trace.getTotalTimeDurationInMillis(), 1500) @@ -60,15 +55,13 @@ test('totaltime: 1 parent, 2 parallel equal children no overlap with parent', (t const agent = getAgent(t) helper.runInTransaction(agent, function (transaction) { const start = Date.now() - const root = transaction.trace.root - - const parent = root.add('parent') + const parent = transaction.trace.add('parent') parent.timer.setDurationInMillis(1000, start) - const first = parent.add('first') + const first = transaction.trace.add('first', null, parent) first.timer.setDurationInMillis(1000, start + 1000) - const second = parent.add('second') + const second = transaction.trace.add('second', null, parent) second.timer.setDurationInMillis(1000, start + 1000) t.equal(transaction.trace.getTotalTimeDurationInMillis(), 3000) @@ -80,15 +73,13 @@ test('totaltime: 1 parent, 2 parallel equal children one overlaps with parent by const agent = getAgent(t) helper.runInTransaction(agent, function (transaction) { const start = Date.now() - const root = transaction.trace.root - - const parent = root.add('parent') + const parent = transaction.trace.add('parent') parent.timer.setDurationInMillis(1000, start) - const first = parent.add('first') + const first = transaction.trace.add('first', null, parent) first.timer.setDurationInMillis(1000, start + 1000) - const second = parent.add('second') + const second = transaction.trace.add('second', null, parent) second.timer.setDurationInMillis(1000, start + 500) t.equal(transaction.trace.getTotalTimeDurationInMillis(), 2500) @@ -100,15 +91,13 @@ test('totaltime: 1 parent, 1 child, 1 grand child, all at same time', function ( const agent = getAgent(t) helper.runInTransaction(agent, function (transaction) { const start = Date.now() - const root = transaction.trace.root - - const parent = root.add('parent') + const parent = transaction.trace.add('parent') parent.timer.setDurationInMillis(1000, start) - const child = parent.add('child') + const child = transaction.trace.add('child', null, parent) child.timer.setDurationInMillis(1000, start) - const grandchild = child.add('grandchild') + const grandchild = transaction.trace.add('grandchild', null, child) grandchild.timer.setDurationInMillis(1000, start) t.equal(transaction.trace.getTotalTimeDurationInMillis(), 1000) @@ -120,15 +109,13 @@ test('totaltime: 1 parent, 1 child, 1 grand child, 500ms at each step', function const agent = getAgent(t) helper.runInTransaction(agent, function (transaction) { const start = Date.now() - const root = transaction.trace.root - - const parent = root.add('parent') + const parent = transaction.trace.add('parent') parent.timer.setDurationInMillis(1000, start) - const child = parent.add('child') + const child = transaction.trace.add('child', null, parent) child.timer.setDurationInMillis(1000, start + 500) - const grandchild = child.add('grandchild') + const grandchild = transaction.trace.add('grandchild', null, child) grandchild.timer.setDurationInMillis(1000, start + 1000) t.equal(transaction.trace.getTotalTimeDurationInMillis(), 2000) @@ -140,15 +127,14 @@ test('totaltime: 1 parent, 1 child, 1 grand child, 250ms after previous start', const agent = getAgent(t) helper.runInTransaction(agent, function (transaction) { const start = Date.now() - const root = transaction.trace.root - const parent = root.add('parent') + const parent = transaction.trace.add('parent') parent.timer.setDurationInMillis(1000, start) - const child = parent.add('child') + const child = transaction.trace.add('child', null, parent) child.timer.setDurationInMillis(1000, start + 250) - const grandchild = child.add('grandchild') + const grandchild = transaction.trace.add('grandchild', null, child) grandchild.timer.setDurationInMillis(1000, start + 500) t.equal(transaction.trace.getTotalTimeDurationInMillis(), 1500) @@ -160,15 +146,13 @@ test('totaltime: 1 child ending before parent, 1 grand child ending after parent const agent = getAgent(t) helper.runInTransaction(agent, function (transaction) { const start = Date.now() - const root = transaction.trace.root - - const parent = root.add('parent') + const parent = transaction.trace.add('parent') parent.timer.setDurationInMillis(1000, start) - const child = parent.add('child') + const child = transaction.trace.add('child', null, parent) child.timer.setDurationInMillis(200, start + 100) - const grandchild = child.add('grandchild') + const grandchild = transaction.trace.add('grandchild', null, child) grandchild.timer.setDurationInMillis(1000, start + 200) t.equal(transaction.trace.getTotalTimeDurationInMillis(), 1200) diff --git a/test/integration/transaction/tracer.tap.js b/test/integration/transaction/tracer.tap.js index 0a97294c21..e1356d7f04 100644 --- a/test/integration/transaction/tracer.tap.js +++ b/test/integration/transaction/tracer.tap.js @@ -10,6 +10,7 @@ const test = tap.test const helper = require('../../lib/agent_helper') const EventEmitter = require('events').EventEmitter const symbols = require('../../../lib/symbols') +const Context = require('../../../lib/context-manager/context') test('bind in transaction', function testBind(t) { const { agent, tracer } = setupAgent(t) @@ -19,27 +20,28 @@ test('bind in transaction', function testBind(t) { helper.runInTransaction(agent, function inTrans(transaction) { const root = transaction.trace.root - const other = tracer.createSegment('other') + const ctx = tracer.getContext() + const other = tracer.createSegment({ name: 'other', parent: root, transaction }) + const otherCtx = ctx.enterSegment({ segment: other }) t.equal(tracer.getTransaction(), transaction, 'should start in transaction') - t.comment('implicit segment bind') t.equal(tracer.getSegment(), root, 'should start at root segment') - let bound = tracer.bindFunction(compare) + let bound = tracer.bindFunction(compare, ctx) - tracer.setSegment(null) + tracer.setSegment({ transaction: null, segment: null }) bound.call(context, root) t.equal(tracer.getSegment(), null, 'should reset segment after being called') t.comment('explicit segment bind') - bound = tracer.bindFunction(compare, other) + bound = tracer.bindFunction(compare, otherCtx) bound.call(context, other) t.comment('null segment bind') - tracer.setSegment(root) - bound = tracer.bindFunction(compare, null) + tracer.setSegment({ transaction, segment: root }) + bound = tracer.bindFunction(compare, new Context()) t.equal(tracer.getSegment(), root, 'should be back to root segment') - bound.call(context, root) + bound.call(context, null) t.end() @@ -54,18 +56,21 @@ test('bind outside transaction', function testBind(t) { const { agent, tracer } = setupAgent(t) let root + let rootCtx t.plan(5) - let bound = tracer.bindFunction(compare) + const context = tracer.getContext() + let bound = tracer.bindFunction(compare, context) compare(null) bound(null) helper.runInTransaction(agent, function inTrans(transaction) { + rootCtx = tracer.getContext() root = transaction.trace.root bound(null) }) - bound = tracer.bindFunction(compare, root) + bound = tracer.bindFunction(compare, rootCtx) bound(root) compare(null) @@ -85,13 +90,14 @@ test('bind + throw', function testThrows(t) { helper.runInTransaction(agent, function inTrans(transaction) { t.comment('root is active') const root = transaction.trace.root - compare(dangerous(root, root), root) - compare(dangerous(null, root), root) + const rootCtx = tracer.getContext() + compare(dangerous(rootCtx, root), root) + compare(dangerous(new Context(), null), root) t.comment('null is active') - tracer.setSegment(null) - compare(dangerous(root, root), null) - compare(dangerous(null, null), null) + tracer.setSegment({ transaction: null, segment: null }) + compare(dangerous(rootCtx, root), null) + compare(dangerous(new Context(), null), null) t.end() }) @@ -105,11 +111,11 @@ test('bind + throw', function testThrows(t) { } } - function dangerous(segment, expected) { + function dangerous(ctx, expected) { return tracer.bindFunction(function bound() { t.equal(tracer.getSegment(), expected, 'should have expected segment') throw error - }, segment) + }, ctx) } }) @@ -136,7 +142,13 @@ test('bind + capture error', function testThrows(t) { }) function inTrans(transaction) { - const other = tracer.createSegment('other') + const other = tracer.createSegment({ + name: 'other', + transaction, + parent: transaction.trace.root + }) + const context = tracer.getContext() + const otherCtx = context.enterSegment({ segment: other }) transaction.name = name process.once('uncaughtException', function onUncaughtException(err) { const logged = agent.errors.traceAggregator.errors[0] @@ -154,17 +166,17 @@ test('bind + capture error', function testThrows(t) { } t.end() }) - dangerous(other)() + dangerous(otherCtx, other)() } - function dangerous(segment) { + function dangerous(ctx, segment) { return tracer.bindFunction(function bound() { // next tick to avoid tap error handler process.nextTick(function ohno() { t.equal(tracer.getSegment(), segment) throw error }) - }, segment) + }, ctx) } }) @@ -173,16 +185,27 @@ test('bind + full', function testThrows(t) { t.plan(10) - helper.runInTransaction(agent, function inTrans() { - const segment = tracer.createSegment('segment') - const notStarted = tracer.createSegment('notStarted') - let bound = tracer.bindFunction(check, segment, true) + helper.runInTransaction(agent, function inTrans(transaction) { + const segment = tracer.createSegment({ + name: 'segment', + transaction, + parent: transaction.trace.root + }) + const context = tracer.getContext() + const ctx = context.enterSegment({ segment }) + const notStarted = tracer.createSegment({ + name: 'notStarted', + transaction, + parent: transaction.trace.root + }) + const notStartedCtx = ctx.enterSegment({ segment: notStarted }) + let bound = tracer.bindFunction(check, ctx, true) t.notOk(segment.timer.hrstart) bound() t.ok(segment.timer.hrDuration) - bound = tracer.bindFunction(checkNotStarted, notStarted, false) + bound = tracer.bindFunction(checkNotStarted, notStartedCtx, false) t.notOk(notStarted.timer.hrstart) bound() @@ -227,9 +250,10 @@ test('bind + args', function testThrows(t) { t.plan(1) helper.runInTransaction(agent, function inTrans() { + const ctx = tracer.getContext() bound = tracer.bindFunction(function withArgs() { t.same([].slice.call(arguments), [1, 2, 3]) - }) + }, ctx) }) bound(1, 2, 3) @@ -239,16 +263,14 @@ test('bind + args', function testThrows(t) { test('getTransaction', function testGetTransaction(t) { const { agent, tracer } = setupAgent(t) - t.plan(5) + t.plan(3) t.notOk(tracer.getTransaction()) helper.runInTransaction(agent, function inTrans(transaction) { t.equal(tracer.getTransaction(), transaction) - t.equal(tracer.getSegment().transaction, transaction) transaction.end() t.notOk(tracer.getTransaction()) - t.equal(tracer.getSegment().transaction, transaction) t.end() }) }) @@ -278,11 +300,17 @@ test('createSegment', function testCreateSegment(t) { let root t.plan(10) - const noSegment = tracer.createSegment('outside transaction') + const noSegment = tracer.createSegment({ name: 'outside transaction' }) t.equal(noSegment, null) helper.runInTransaction(agent, function inTrans(transaction) { - const segment = tracer.createSegment('inside transaction') + const segment = tracer.createSegment({ + name: 'inside transaction', + transaction, + parent: transaction.trace.root + }) + const context = tracer.getContext() + const ctx = context.enterSegment({ segment }) root = transaction.trace.root t.equal(tracer.getSegment(), root) t.equal(segment.name, 'inside transaction') @@ -291,19 +319,24 @@ test('createSegment', function testCreateSegment(t) { t.equal(segment.timer.hrstart, null) t.equal(segment.timer.hrDuration, null) t.equal(tracer.getSegment(), segment) - }, segment)() - }) + }, ctx)() - const outerSegment = tracer.createSegment('outside with parent', null, root) + const outerSegment = tracer.createSegment({ + name: 'outside with parent', + transaction: transaction, + parent: root + }) + const outerCtx = context.enterSegment({ segment: outerSegment }) - tracer.bindFunction(function bound() { - t.equal(outerSegment.name, 'outside with parent') - t.equal(outerSegment.timer.hrstart, null) - t.equal(outerSegment.timer.hrDuration, null) - t.equal(tracer.getSegment(), outerSegment) - }, outerSegment)() + tracer.bindFunction(function bound() { + t.equal(outerSegment.name, 'outside with parent') + t.equal(outerSegment.timer.hrstart, null) + t.equal(outerSegment.timer.hrDuration, null) + t.equal(tracer.getSegment(), outerSegment) + }, outerCtx)() - t.end() + t.end() + }) }) test('createSegment + recorder', function testCreateSegment(t) { @@ -312,7 +345,12 @@ test('createSegment + recorder', function testCreateSegment(t) { t.plan(2) helper.runInTransaction(agent, function inTrans(transaction) { - const segment = tracer.createSegment('inside transaction', recorder) + const segment = tracer.createSegment({ + name: 'inside transaction', + recorder, + transaction, + parent: transaction.trace.root + }) t.equal(segment.name, 'inside transaction') transaction.end() @@ -333,19 +371,19 @@ test('addSegment', function addSegmentTest(t) { t.equal(tracer.addSegment('outside', null, null, false, check), null) helper.runInTransaction(agent, function inTrans(transaction) { - const segment = tracer.addSegment('inside', null, null, false, check) + const segment = tracer.addSegment('inside', null, transaction.trace.root, false, check) t.equal(segment.name, 'inside') root = transaction.trace.root t.equal(root.children[0], segment) - }) - const outside = tracer.addSegment('outside', null, root, false, check) + const outside = tracer.addSegment('outside', null, root, false, check) - t.equal(outside.name, 'outside') - t.equal(root.children[1], outside) + t.equal(outside.name, 'outside') + t.equal(root.children[1], outside) - t.end() + t.end() + }) function check(segment) { t.equal(segment, tracer.getSegment()) @@ -360,7 +398,7 @@ test('addSegment + recorder', function addSegmentTest(t) { t.plan(7) helper.runInTransaction(agent, function inTrans(transaction) { - segment = tracer.addSegment('inside', record, null, false, check) + segment = tracer.addSegment('inside', record, transaction.trace.root, false, check) const root = transaction.trace.root t.equal(segment.name, 'inside') @@ -390,7 +428,7 @@ test('addSegment + full', function addSegmentTest(t) { t.plan(7) helper.runInTransaction(agent, function inTrans(transaction) { - const segment = tracer.addSegment('inside', null, null, true, check) + const segment = tracer.addSegment('inside', null, transaction.trace.root, true, check) const root = transaction.trace.root t.equal(segment.name, 'inside') @@ -513,7 +551,7 @@ test('bindEmitter', function testbindEmitter(t) { emitter.emit('after', data) emitter2.on('before', check(root)) - tracer.bindEmitter(emitter2) + tracer.bindEmitter(emitter2, root) emitter2.on('after', check(root)) emitter2.emit('before', data) emitter2.emit('after', data) @@ -554,44 +592,6 @@ test('tracer.slice', function testSlice(t) { t.end() }) -test('wrapFunctionNoSegment', function testwrapFunctionNoSegment(t) { - const { agent, tracer } = setupAgent(t) - - const outer = {} - const inner = {} - let segment = null - - t.plan(15) - - const wrapped = tracer.wrapFunctionNoSegment(doSomething) - - wrapped.call(outer, segment, [4], 4, check) - - helper.runInTransaction(agent, function runInTransaction(transaction) { - segment = transaction.trace.root - wrapped.call(outer, segment, [1, 2, 3], 1, 2, 3, check) - }) - - wrapped.call(outer, null, [4, 5], 4, 5, check) - - function doSomething(seg) { - const args = tracer.slice(arguments) - const callback = args.pop() - t.equal(this, outer) - t.equal(tracer.getSegment(), seg) - process.nextTick(function next() { - tracer.setSegment(null) - callback.apply(inner, args) - }) - } - - function check(seg, expected) { - t.same([].slice.call(arguments, 2), expected) - t.equal(tracer.getSegment(), seg) - t.equal(this, inner) - } -}) - test('wrapFunction', function testwrapFunction(t) { const { agent, tracer } = setupAgent(t) @@ -629,6 +629,7 @@ test('wrapFunction', function testwrapFunction(t) { function callAll(name, a, b, c) { const segment = tracer.getSegment() + const transaction = tracer.getTransaction() if (name) { t.equal(segment.name, name) @@ -654,7 +655,7 @@ test('wrapFunction', function testwrapFunction(t) { t.ok(child.timer.hrDuration) }) t.ok(segment.timer.hrDuration) - segment.transaction.end() + transaction.end() } }) @@ -721,6 +722,7 @@ test('wrapFunctionLast', function testwrapFunctionLast(t) { function takesCallback(name) { const segment = tracer.getSegment() + const transaction = tracer.getTransaction() const cbArgs = [].slice.call(arguments, 1, -1) const cb = arguments[arguments.length - 1] @@ -745,7 +747,7 @@ test('wrapFunctionLast', function testwrapFunctionLast(t) { t.ok(segment.children[0].timer.hrstart) t.ok(segment.children[0].timer.hrDuration) t.ok(segment.timer.hrDuration) - segment.transaction.end() + transaction.end() } }) @@ -794,6 +796,7 @@ test('wrapFunctionFirst', function testwrapFunctionFirst(t) { function takesCallback(cb, name) { const segment = tracer.getSegment() + const transaction = tracer.getTransaction() const args = [].slice.call(arguments, 2) if (name) { @@ -817,7 +820,7 @@ test('wrapFunctionFirst', function testwrapFunctionFirst(t) { t.ok(segment.children[0].timer.hrstart) t.ok(segment.children[0].timer.hrDuration) t.ok(segment.timer.hrDuration) - segment.transaction.end() + transaction.end() } }) @@ -855,7 +858,7 @@ test('wrapSyncFunction', function testwrapSyncFunction(t) { } function record(segment) { - t.equal(segment, segment.transaction.trace.root.children[0]) + t.equal(segment, segment.root.children[0]) t.equal(segment.name, 'my segment') t.end() } diff --git a/test/integration/transaction/wide-segment-tree.js b/test/integration/transaction/wide-segment-tree.js index e155255c9a..ce69b61d31 100644 --- a/test/integration/transaction/wide-segment-tree.js +++ b/test/integration/transaction/wide-segment-tree.js @@ -31,14 +31,12 @@ const distributedTracingConfig = { const agent = helper.loadMockedAgent(distributedTracingConfig) helper.runInTransaction(agent, function (transaction) { - const root = transaction.trace.root - // Avoid special casing of root that can result in avoiding // bugs deeper in the tree with wide segment trees - const child = root.add('child1') + const child = transaction.trace.add('child1') for (let index = 0; index < DANGEROUS_SEGMENT_WIDTH; index++) { - child.add('segment: ' + index) + transaction.trace.add('segment: ' + index, null, child) } try { diff --git a/test/lib/agent_helper.js b/test/lib/agent_helper.js index 3183b81118..54cd3db684 100644 --- a/test/lib/agent_helper.js +++ b/test/lib/agent_helper.js @@ -309,8 +309,9 @@ helper.runInNamedTransaction = (agent, type, callback) => { helper.runInSegment = (agent, name, callback) => { const tracer = agent.tracer + const parent = tracer.getSegment() - return tracer.addSegment(name, null, null, null, callback) + return tracer.addSegment(name, null, parent, null, callback) } /** diff --git a/test/unit/agent/agent.test.js b/test/unit/agent/agent.test.js index 5f52c078cd..044cbd0b74 100644 --- a/test/unit/agent/agent.test.js +++ b/test/unit/agent/agent.test.js @@ -654,8 +654,15 @@ test('when connected', async (t) => { agent.logs.add([{ key: 'bar' }]) const tx = new helper.FakeTransaction(agent, '/path/to/fake') tx.metrics = { apdexT: 0 } - const segment = new helper.FakeSegment(tx, 2_000) - agent.queries.add(segment, 'mysql', 'select * from foo', 'Stack\nFrames') + const segment = tx.trace.add('FakeSegment') + segment.setDurationInMillis(2000) + agent.queries.add({ + transaction: tx, + segment, + type: 'mysql', + query: 'select * from foo', + trace: 'Stack\nFrames' + }) agent.spanEventAggregator.add(segment) agent.transactionEventAggregator.add(tx) agent.customEventAggregator.add({ key: 'value' }) @@ -693,8 +700,15 @@ test('when connected', async (t) => { agent.logs.add([{ key: 'bar' }]) const tx = new helper.FakeTransaction(agent, '/path/to/fake') tx.metrics = { apdexT: 0 } - const segment = new helper.FakeSegment(tx, 2_000) - agent.queries.add(segment, 'mysql', 'select * from foo', 'Stack\nFrames') + const segment = tx.trace.add('FakeSegment') + segment.setDurationInMillis(2000) + agent.queries.add({ + transaction: tx, + segment, + type: 'mysql', + query: 'select * from foo', + trace: 'Stack\nFrames' + }) agent.spanEventAggregator.add(segment) agent.transactionEventAggregator.add(tx) agent.customEventAggregator.add({ key: 'value' }) diff --git a/test/unit/api/api-set-controller-name.test.js b/test/unit/api/api-set-controller-name.test.js index 46b7323b6a..96d12f40e5 100644 --- a/test/unit/api/api-set-controller-name.test.js +++ b/test/unit/api/api-set-controller-name.test.js @@ -125,7 +125,7 @@ function goldenPathRenameControllerInTransaction({ agent, api }) { return new Promise((resolve) => { agent.on('transactionFinished', function (finishedTransaction) { finishedTransaction.finalizeNameFromUri(TEST_URL, 200) - segment.markAsWeb(TEST_URL) + segment.markAsWeb(finishedTransaction) resolve({ transaction: finishedTransaction, segment }) }) diff --git a/test/unit/api/api-set-transaction-name.test.js b/test/unit/api/api-set-transaction-name.test.js index 644b7e96be..cc28fcca5b 100644 --- a/test/unit/api/api-set-transaction-name.test.js +++ b/test/unit/api/api-set-transaction-name.test.js @@ -80,7 +80,7 @@ function setTranasactionNameGoldenPath({ agent, api }) { return new Promise((resolve) => { agent.on('transactionFinished', function (finishedTransaction) { finishedTransaction.finalizeNameFromUri(TEST_URL, 200) - segment.markAsWeb(TEST_URL) + segment.markAsWeb(finishedTransaction) resolve({ transaction: finishedTransaction, segment }) }) diff --git a/test/unit/context-manager/async-local-context-manager.test.js b/test/unit/context-manager/async-local-context-manager.test.js index a935bc30fb..a68dc17d42 100644 --- a/test/unit/context-manager/async-local-context-manager.test.js +++ b/test/unit/context-manager/async-local-context-manager.test.js @@ -8,20 +8,22 @@ const test = require('node:test') const assert = require('node:assert') const AsyncLocalContextManager = require('../../../lib/context-manager/async-local-context-manager') +const Context = require('../../../lib/context-manager/context') test('Should default to null context', () => { - const contextManager = new AsyncLocalContextManager({}) + const contextManager = new AsyncLocalContextManager() const context = contextManager.getContext() - assert.equal(context, null) + assert.ok(context instanceof Context) + assert.equal(context.transaction, null) + assert.equal(context.segment, null) }) test('setContext should update the current context', () => { - const contextManager = new AsyncLocalContextManager({}) - - const expectedContext = { name: 'new context' } + const contextManager = new AsyncLocalContextManager() + const expectedContext = new Context('tx', 'new context') contextManager.setContext(expectedContext) const context = contextManager.getContext() @@ -30,10 +32,11 @@ test('setContext should update the current context', () => { test('runInContext()', async (t) => { await t.test('should execute callback synchronously', () => { - const contextManager = new AsyncLocalContextManager({}) + const contextManager = new AsyncLocalContextManager() + const context = contextManager.getContext() let callbackCalled = false - contextManager.runInContext({}, () => { + contextManager.runInContext(context, () => { callbackCalled = true }) @@ -41,12 +44,13 @@ test('runInContext()', async (t) => { }) await t.test('should set context to active for life of callback', (t, end) => { - const contextManager = new AsyncLocalContextManager({}) + const contextManager = new AsyncLocalContextManager() - const previousContext = { name: 'previous' } + const context = contextManager.getContext() + const previousContext = context.enterSegment({ segment: 'previous', transaction: 'tx' }) contextManager.setContext(previousContext) - const newContext = { name: 'new' } + const newContext = context.enterSegment({ segment: 'new', transaction: 'tx1' }) contextManager.runInContext(newContext, () => { const context = contextManager.getContext() @@ -57,26 +61,40 @@ test('runInContext()', async (t) => { }) await t.test('should restore previous context when callback completes', () => { - const contextManager = new AsyncLocalContextManager({}) + const contextManager = new AsyncLocalContextManager() - const previousContext = { name: 'previous' } + const context = contextManager.getContext() + const previousContext = context.enterSegment({ segment: 'previous', transaction: 'tx' }) contextManager.setContext(previousContext) - const newContext = { name: 'new' } + const newContext = context.enterSegment({ segment: 'new', transaction: 'tx1' }) contextManager.runInContext(newContext, () => {}) - const context = contextManager.getContext() + assert.deepEqual(contextManager.getContext(), previousContext) + }) + + await t.test('should run a function in a transaction', () => { + const contextManager = new AsyncLocalContextManager() - assert.equal(context, previousContext) + let context = contextManager.getContext() + const transaction = { name: 'tx', trace: { root: { name: 'foo' } } } + context = context.enterTransaction(transaction) + + contextManager.runInContext(context, () => { + const curContext = contextManager.getContext() + assert.equal(curContext.transaction, transaction) + assert.equal(curContext.segment, transaction.trace.root) + }) }) await t.test('should restore previous context on exception', () => { const contextManager = new AsyncLocalContextManager({}) - const previousContext = { name: 'previous' } + const context = contextManager.getContext() + const previousContext = context.enterSegment({ segment: 'previous', transaction: 'tx' }) contextManager.setContext(previousContext) - const newContext = { name: 'new' } + const newContext = context.enterSegment({ segment: 'new', transaction: 'tx1' }) try { contextManager.runInContext(newContext, () => { @@ -87,21 +105,16 @@ test('runInContext()', async (t) => { // swallowing error } - const context = contextManager.getContext() - - assert.equal(context, previousContext) + assert.deepEqual(contextManager.getContext(), previousContext) }) await t.test('should apply `cbThis` arg to execution', (t, end) => { const contextManager = new AsyncLocalContextManager({}) - const previousContext = { name: 'previous' } - contextManager.setContext(previousContext) - - const newContext = { name: 'new' } + const context = contextManager.getContext() const expectedThis = () => {} - contextManager.runInContext(newContext, functionRunInContext, expectedThis) + contextManager.runInContext(context, functionRunInContext, expectedThis) function functionRunInContext() { assert.equal(this, expectedThis) @@ -112,15 +125,12 @@ test('runInContext()', async (t) => { await t.test('should apply args array to execution', (t, end) => { const contextManager = new AsyncLocalContextManager({}) - const previousContext = { name: 'previous' } - contextManager.setContext(previousContext) - - const newContext = { name: 'new' } + const context = contextManager.getContext() const expectedArg1 = 'first arg' const expectedArg2 = 'second arg' const args = [expectedArg1, expectedArg2] - contextManager.runInContext(newContext, functionRunInContext, null, args) + contextManager.runInContext(context, functionRunInContext, null, args) function functionRunInContext(arg1, arg2) { assert.equal(arg1, expectedArg1) @@ -131,11 +141,7 @@ test('runInContext()', async (t) => { await t.test('should apply arguments construct to execution', (t, end) => { const contextManager = new AsyncLocalContextManager({}) - - const previousContext = { name: 'previous' } - contextManager.setContext(previousContext) - - const newContext = { name: 'new' } + const context = contextManager.getContext() const expectedArg1 = 'first arg' const expectedArg2 = 'second arg' @@ -143,7 +149,7 @@ test('runInContext()', async (t) => { function executingFunction() { contextManager.runInContext( - newContext, + context, function functionRunInContext(arg1, arg2) { assert.equal(arg1, expectedArg1) assert.equal(arg2, expectedArg2) diff --git a/test/unit/context-manager/create-context-manager.test.js b/test/unit/context-manager/create-context-manager.test.js deleted file mode 100644 index 542ff11127..0000000000 --- a/test/unit/context-manager/create-context-manager.test.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2021 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const test = require('node:test') -const assert = require('node:assert') - -const createImplementation = require('../../../lib/context-manager/create-context-manager') -const AsyncLocalContextManager = require('../../../lib/context-manager/async-local-context-manager') - -test('Should return AsyncLocalContextManager by default', () => { - const contextManager = createImplementation({ - logging: {}, - feature_flag: {} - }) - - assert.equal(contextManager instanceof AsyncLocalContextManager, true) -}) diff --git a/test/unit/db/query-sample.test.js b/test/unit/db/query-sample.test.js index 93b85793b1..1a522cd8d5 100644 --- a/test/unit/db/query-sample.test.js +++ b/test/unit/db/query-sample.test.js @@ -83,9 +83,8 @@ test('Query Sample', async (t) => { } } const fakeSample = { - segment: { - transaction: {} - } + transaction: {}, + segment: {} } let codecCalled = false @@ -118,13 +117,12 @@ test('Query Sample', async (t) => { let getFullNameCalled = false const fakeSample = { - segment: { - transaction: { - getFullName: () => { - getFullNameCalled = true - } + transaction: { + getFullName: () => { + getFullNameCalled = true } - } + }, + segment: {} } const clock = sinon.useFakeTimers({ @@ -162,11 +160,11 @@ test('Query Sample', async (t) => { const fakeGetAttributes = () => expectedParams const fakeSample = { trace: {}, + transaction: { + addDistributedTraceIntrinsics: () => {} + }, segment: { - getAttributes: fakeGetAttributes, - transaction: { - addDistributedTraceIntrinsics: () => {} - } + getAttributes: fakeGetAttributes } } @@ -190,13 +188,13 @@ test('Query Sample', async (t) => { } const fakeSample = { trace: {}, - segment: { - getAttributes: () => ({}), - transaction: { - addDistributedTraceIntrinsics: () => { - addDtIntrinsicsCalled = true - } + transaction: { + addDistributedTraceIntrinsics: () => { + addDtIntrinsicsCalled = true } + }, + segment: { + getAttributes: () => ({}) } } @@ -218,13 +216,13 @@ test('Query Sample', async (t) => { } const fakeSample = { trace: {}, - segment: { - getAttributes: () => ({}), - transaction: { - addDistributedTraceIntrinsics: () => { - addDtIntrinsicsCalled = true - } + transaction: { + addDistributedTraceIntrinsics: () => { + addDtIntrinsicsCalled = true } + }, + segment: { + getAttributes: () => ({}) } } diff --git a/test/unit/db/query-trace-aggregator.test.js b/test/unit/db/query-trace-aggregator.test.js index e23ef76423..b95e0224ee 100644 --- a/test/unit/db/query-trace-aggregator.test.js +++ b/test/unit/db/query-trace-aggregator.test.js @@ -1095,7 +1095,13 @@ function addQuery(queries, duration, url, query) { const transaction = new FakeTransaction(null, url) const segment = new FakeSegment(transaction, duration) - queries.add(segment, 'mysql', query || 'select * from foo where a=2', FAKE_STACK) + queries.add({ + transaction, + segment, + type: 'mysql', + query: query || 'select * from foo where a=2', + trace: FAKE_STACK + }) return segment } diff --git a/test/unit/db/trace.test.js b/test/unit/db/trace.test.js index 361fa978e4..87e18b7c86 100644 --- a/test/unit/db/trace.test.js +++ b/test/unit/db/trace.test.js @@ -39,7 +39,13 @@ test('SQL trace attributes', async (t) => { const payload = tx._createDistributedTracePayload().text() tx.isDistributedTrace = null tx._acceptDistributedTracePayload(payload) - agent.queries.add(tx.trace.root, 'postgres', 'select pg_sleep(1)', 'FAKE STACK') + agent.queries.add({ + transaction: tx, + segment: tx.trace.root, + type: 'postgres', + query: 'select pg_sleep(1)', + trace: 'FAKE STACK' + }) agent.queries.prepareJSON((err, samples) => { const sample = samples[0] const attributes = sample[sample.length - 1] @@ -62,7 +68,13 @@ test('SQL trace attributes', async (t) => { const { agent } = t.nr helper.runInTransaction(agent, function (tx) { const query = 'select pg_sleep(1)' - agent.queries.add(tx.trace.root, 'postgres', query, 'FAKE STACK') + agent.queries.add({ + transaction: tx, + segment: tx.trace.root, + type: 'postgres', + query, + trace: 'FAKE STACK' + }) const sampleObj = agent.queries.samples.values().next().value const sample = agent.queries.prepareJSONSync()[0] assert.equal(sample[0], tx.getFullName()) @@ -85,7 +97,13 @@ test('SQL trace attributes', async (t) => { agent.config.account_id = 1 agent.config.simple_compression = true helper.runInTransaction(agent, function (tx) { - agent.queries.add(tx.trace.root, 'postgres', 'select pg_sleep(1)', 'FAKE STACK') + agent.queries.add({ + transaction: tx, + segment: tx.trace.root, + type: 'postgres', + query: 'select pg_sleep(1)', + trace: 'FAKE STACK' + }) agent.queries.prepareJSON((err, samples) => { const sample = samples[0] const attributes = sample[sample.length - 1] diff --git a/test/unit/distributed_tracing/dt-cats.test.js b/test/unit/distributed_tracing/dt-cats.test.js index 45ed17cc5f..a70a87db99 100644 --- a/test/unit/distributed_tracing/dt-cats.test.js +++ b/test/unit/distributed_tracing/dt-cats.test.js @@ -36,7 +36,7 @@ test('distributed tracing', async function (t) { agent.config.span_events.enabled = testCase.span_events_enabled helper.runInTransaction(agent, (tx) => { tx.type = testCase.web_transaction ? 'web' : 'bg' - tx.baseSegment = tx.trace.root.add('MyBaseSegment', (segment) => { + tx.baseSegment = tx.trace.add('MyBaseSegment', (segment) => { recorder( tx, testCase.web_transaction ? 'Web' : 'Other', @@ -44,6 +44,7 @@ test('distributed tracing', async function (t) { segment.getExclusiveDurationInMillis() ) }) + agent.tracer.setSegment({ segment: tx.baseSegment }) if (!Array.isArray(testCase.inbound_payloads)) { testCase.inbound_payloads = [testCase.inbound_payloads] diff --git a/test/unit/distributed_tracing/tracecontext.test.js b/test/unit/distributed_tracing/tracecontext.test.js index 3b4c4f9dfa..d2cac0ac88 100644 --- a/test/unit/distributed_tracing/tracecontext.test.js +++ b/test/unit/distributed_tracing/tracecontext.test.js @@ -97,6 +97,7 @@ test('TraceContext', async function (t) { helper.runInTransaction(agent, function (txn) { const childSegment = txn.trace.add('child') + agent.tracer.setSegment({ segment: childSegment }) childSegment.start() txn.acceptTraceContextPayload(traceparent, undefined) @@ -120,6 +121,7 @@ test('TraceContext', async function (t) { helper.runInTransaction(agent, function (txn) { const childSegment = txn.trace.add('child') + agent.tracer.setSegment({ segment: childSegment }) childSegment.start() txn.acceptTraceContextPayload(traceparent, tracestate) @@ -367,6 +369,7 @@ test('TraceContext', async function (t) { const { agent } = ctx.nr helper.runInTransaction(agent, function (txn) { const childSegment = txn.trace.add('child') + agent.tracer.setSegment({ segment: childSegment }) childSegment.start() const tp1 = txn.traceContext.createTraceparent() @@ -390,6 +393,7 @@ test('TraceContext', async function (t) { helper.runInTransaction(agent, function (txn) { const childSegment = txn.trace.add('child') + agent.tracer.setSegment({ segment: childSegment }) childSegment.start() const headers = getTraceContextHeaders(txn) @@ -422,6 +426,7 @@ test('TraceContext', async function (t) { helper.runInTransaction(agent, function (txn) { const childSegment = txn.trace.add('child') + agent.tracer.setSegment({ segment: childSegment }) childSegment.start() txn.traceContext.acceptTraceContextPayload(traceparent, duplicateAcctTraceState) @@ -470,6 +475,7 @@ test('TraceContext', async function (t) { helper.runInTransaction(agent, function (txn) { const childSegment = txn.trace.add('child') + agent.tracer.setSegment({ segment: childSegment }) childSegment.start() txn.acceptTraceContextPayload(traceparent, tracestate) @@ -691,6 +697,7 @@ test('TraceContext', async function (t) { helper.runInTransaction(agent, function (txn) { const childSegment = txn.trace.add('child') + agent.tracer.setSegment({ segment: childSegment }) childSegment.start() txn.acceptTraceContextPayload(incomingTraceparent, incomingTracestate) @@ -733,6 +740,7 @@ test('TraceContext', async function (t) { helper.runInTransaction(agent, function (txn) { const childSegment = txn.trace.add('child') + agent.tracer.setSegment({ segment: childSegment }) childSegment.start() txn.acceptTraceContextPayload(incomingTraceparent, incomingTracestate) diff --git a/test/unit/instrumentation/http/http.test.js b/test/unit/instrumentation/http/http.test.js index dd1e8829d3..e049e01372 100644 --- a/test/unit/instrumentation/http/http.test.js +++ b/test/unit/instrumentation/http/http.test.js @@ -20,7 +20,11 @@ const encKey = 'gringletoes' function addSegment({ agent }) { const transaction = agent.getTransaction() transaction.type = 'web' - transaction.baseSegment = new Segment(transaction, 'base-segment') + transaction.baseSegment = new Segment({ + config: agent.config, + name: 'base-segment', + root: transaction.trace.root + }) } test('built-in http module instrumentation', async (t) => { diff --git a/test/unit/instrumentation/http/outbound.test.js b/test/unit/instrumentation/http/outbound.test.js index d8c9785577..508b0daba6 100644 --- a/test/unit/instrumentation/http/outbound.test.js +++ b/test/unit/instrumentation/http/outbound.test.js @@ -24,7 +24,11 @@ const testSignatures = require('./outbound-utils') function addSegment({ agent }) { const transaction = agent.getTransaction() transaction.type = 'web' - transaction.baseSegment = new Segment(transaction, 'base-segment') + transaction.baseSegment = new Segment({ + config: agent.config, + name: 'base-segment', + root: transaction.trace.root + }) } test('instrumentOutbound', async (t) => { @@ -343,7 +347,7 @@ test('should add data from cat header to segment', async (t) => { const port = server.address().port http .get({ host: 'localhost', port: port }, function (res) { - const segment = agent.tracer.getTransaction().trace.root.children[0] + const segment = agent.tracer.getSegment() assert.equal(segment.catId, '123#456') assert.equal(segment.catTransaction, 'abc') @@ -365,7 +369,7 @@ test('should add data from cat header to segment', async (t) => { const port = server.address().port http .get({ host: 'localhost', port: port }, function (res) { - const segment = agent.tracer.getTransaction().trace.root.children[0] + const segment = agent.tracer.getSegment() assert.equal(segment.catId, '123#456') assert.equal(segment.catTransaction, 'abc') @@ -513,10 +517,14 @@ test('when working with http.request', async (t) => { nock(host).get(path).reply(200, 'Hello from Google') helper.runInTransaction(agent, (transaction) => { - const parentSegment = agent.tracer.createSegment('ParentSegment') + const parentSegment = agent.tracer.createSegment({ + name: 'ParentSegment', + parent: transaction.trace.root, + transaction + }) parentSegment.opaque = true - tracer.setSegment(parentSegment) // make the current active segment + tracer.setSegment({ transaction, segment: parentSegment }) // make the current active segment http.get(`${host}${path}`, (res) => { const segment = tracer.getSegment() diff --git a/test/unit/instrumentation/undici.test.js b/test/unit/instrumentation/undici.test.js index 1db52ab1c8..fbe9d50ea4 100644 --- a/test/unit/instrumentation/undici.test.js +++ b/test/unit/instrumentation/undici.test.js @@ -88,6 +88,7 @@ test('undici instrumentation', async function (t) { } channels.create.publish({ request }) assert.ok(request[symbols.parentSegment]) + assert.ok(request[symbols.transaction]) assert.equal(request.addHeader.callCount, 2) assert.deepEqual(request.addHeader.args[0], ['x-newrelic-synthetics', 'synthHeader']) assert.deepEqual(request.addHeader.args[1], [ @@ -146,6 +147,11 @@ test('undici instrumentation', async function (t) { request2[symbols.parentSegment].id, 'parent segment should be same' ) + assert.equal( + request[symbols.transaction].id, + request2[symbols.transaction].id, + 'tx should be same' + ) tx.end() end() }) @@ -165,6 +171,7 @@ test('undici instrumentation', async function (t) { const request2 = { path: '/request2', addHeader: sandbox.stub(), origin: HOST } channels.create.publish({ request: request2 }) assert.notEqual(request[symbols.parentSegment], request2[symbols.parentSegment]) + assert.equal(request[symbols.transaction], request2[symbols.transaction]) tx.end() end() }) @@ -190,6 +197,11 @@ test('undici instrumentation', async function (t) { request2[symbols.parentSegment].name, 'parent segment should not be same' ) + assert.equal( + request[symbols.transaction].id, + request2[symbols.transaction].id, + 'tx should be the same' + ) tx.end() end() }) @@ -205,7 +217,10 @@ test('undici instrumentation', async function (t) { origin: 'https://unittesting.com', path: '/foo?a=b&c=d' } - request[symbols.parentSegment] = shim.createSegment('parent') + request[symbols.parentSegment] = shim.createSegment({ + name: 'parent', + parent: tx.trace.root + }) channels.create.publish({ request }) assert.ok(request[symbols.segment]) const segment = shim.getSegment() @@ -228,7 +243,10 @@ test('undici instrumentation', async function (t) { origin: 'http://unittesting.com', path: '/http' } - request[symbols.parentSegment] = shim.createSegment('parent') + request[symbols.parentSegment] = shim.createSegment({ + name: 'parent', + parent: tx.trace.root + }) channels.create.publish({ request }) const segment = shim.getSegment() assert.equal(segment.name, 'External/unittesting.com/http') @@ -246,7 +264,10 @@ test('undici instrumentation', async function (t) { method: 'POST', path: '/port-https' } - request[symbols.parentSegment] = shim.createSegment('parent') + request[symbols.parentSegment] = shim.createSegment({ + name: 'parent', + parent: tx.trace.root + }) channels.create.publish({ request }) const segment = shim.getSegment() assert.equal(segment.name, 'External/unittesting.com:9999/port-https') @@ -264,7 +285,10 @@ test('undici instrumentation', async function (t) { method: 'POST', path: '/port-http' } - request[symbols.parentSegment] = shim.createSegment('parent') + request[symbols.parentSegment] = shim.createSegment({ + name: 'parent', + parent: tx.trace.root + }) channels.create.publish({ request }) const segment = shim.getSegment() assert.equal(segment.name, 'External/unittesting.com:8080/port-http') @@ -282,7 +306,10 @@ test('undici instrumentation', async function (t) { method: 'POST', path: '/port-http' } - request[symbols.parentSegment] = shim.createSegment('parent') + request[symbols.parentSegment] = shim.createSegment({ + name: 'parent', + parent: tx.trace.root + }) channels.create.publish({ request }) const segment = shim.getSegment() assert.equal(segment.name, 'ROOT', 'should not create a new segment if URL fails to parse') @@ -312,7 +339,7 @@ test('undici instrumentation', async function (t) { await t.test('should add statusCode and statusText from response', function (t, end) { helper.runInTransaction(agent, function (tx) { - const segment = shim.createSegment('active') + const segment = shim.createSegment({ name: 'active', parent: tx.trace.root }) const request = { [symbols.segment]: segment } @@ -334,7 +361,7 @@ test('undici instrumentation', async function (t) { agent.config.encoding_key = 'testing-key' agent.config.trusted_account_ids = [111] helper.runInTransaction(agent, function (tx) { - const segment = shim.createSegment('active') + const segment = shim.createSegment({ name: 'active', parent: tx.trace.root }) segment.addAttribute('url', 'https://www.unittesting.com/path') const request = { [symbols.segment]: segment @@ -360,12 +387,13 @@ test('undici instrumentation', async function (t) { await t.test('request:trailers', async function (t) { await t.test('should end current segment and restore to parent', function (t, end) { helper.runInTransaction(agent, function (tx) { - const parentSegment = shim.createSegment('parent') - const segment = shim.createSegment('active') + const parentSegment = shim.createSegment({ name: 'parent', parent: tx.trace.root }) + const segment = shim.createSegment({ name: 'active', parent: tx.trace.root }) shim.setActiveSegment(segment) const request = { [symbols.parentSegment]: parentSegment, - [symbols.segment]: segment + [symbols.segment]: segment, + [symbols.transaction]: tx } channels.send.publish({ request }) assert.equal(segment.timer.state, 3, 'previous active segment timer should be stopped') @@ -382,13 +410,14 @@ test('undici instrumentation', async function (t) { function (t, end) { helper.runInTransaction(agent, function (tx) { sandbox.stub(tx.agent.errors, 'add') - const parentSegment = shim.createSegment('parent') - const segment = shim.createSegment('active') + const parentSegment = shim.createSegment({ name: 'parent', parent: tx.trace.root }) + const segment = shim.createSegment({ name: 'active', parent: tx.trace.root }) shim.setActiveSegment(segment) const error = new Error('request failed') const request = { [symbols.parentSegment]: parentSegment, - [symbols.segment]: segment + [symbols.segment]: segment, + [symbols.transaction]: tx } channels.error.publish({ request, error }) assert.equal(segment.timer.state, 3, 'previous active segment timer should be stopped') diff --git a/test/unit/llm-events/aws-bedrock/chat-completion-message.test.js b/test/unit/llm-events/aws-bedrock/chat-completion-message.test.js index 4e484e84e6..1b9a94cd98 100644 --- a/test/unit/llm-events/aws-bedrock/chat-completion-message.test.js +++ b/test/unit/llm-events/aws-bedrock/chat-completion-message.test.js @@ -49,12 +49,12 @@ test.beforeEach((ctx) => { ctx.nr.content = 'a prompt' + ctx.nr.transaction = { + id: 'tx-1', + traceId: 'trace-1' + } ctx.nr.segment = { - id: 'segment-1', - transaction: { - id: 'tx-1', - traceId: 'trace-1' - } + id: 'segment-1' } ctx.nr.bedrockResponse = { diff --git a/test/unit/llm-events/aws-bedrock/chat-completion-summary.test.js b/test/unit/llm-events/aws-bedrock/chat-completion-summary.test.js index 0bc79f281f..0df281f198 100644 --- a/test/unit/llm-events/aws-bedrock/chat-completion-summary.test.js +++ b/test/unit/llm-events/aws-bedrock/chat-completion-summary.test.js @@ -38,10 +38,10 @@ test.beforeEach((ctx) => { } } + ctx.nr.transaction = { + id: 'tx-1' + } ctx.nr.segment = { - transaction: { - id: 'tx-1' - }, getDurationInMillis() { return 100 } diff --git a/test/unit/llm-events/aws-bedrock/embedding.test.js b/test/unit/llm-events/aws-bedrock/embedding.test.js index 801e7f64a4..e2d2d8d4cf 100644 --- a/test/unit/llm-events/aws-bedrock/embedding.test.js +++ b/test/unit/llm-events/aws-bedrock/embedding.test.js @@ -54,8 +54,10 @@ test.beforeEach((ctx) => { 'x-amzn-requestid': 'request-1' } } + ctx.nr.transaction = { + traceId: 'id' + } ctx.nr.segment = { - transaction: { traceId: 'id' }, getDurationInMillis() { return 1.008 } diff --git a/test/unit/llm-events/aws-bedrock/event.test.js b/test/unit/llm-events/aws-bedrock/event.test.js index 1d062b0bee..a613a0f18b 100644 --- a/test/unit/llm-events/aws-bedrock/event.test.js +++ b/test/unit/llm-events/aws-bedrock/event.test.js @@ -39,11 +39,11 @@ test.beforeEach((ctx) => { } } + ctx.nr.transaction = { + traceId: 'trace-1' + } ctx.nr.segment = { - id: 'segment-1', - transaction: { - traceId: 'trace-1' - } + id: 'segment-1' } ctx.nr.bedrockResponse = { diff --git a/test/unit/llm-events/langchain/chat-completion-message.test.js b/test/unit/llm-events/langchain/chat-completion-message.test.js index 78dfe19f9b..698960b996 100644 --- a/test/unit/llm-events/langchain/chat-completion-message.test.js +++ b/test/unit/llm-events/langchain/chat-completion-message.test.js @@ -11,7 +11,8 @@ const LangChainCompletionMessage = require('../../../../lib/llm-events/langchain test.beforeEach((ctx) => { ctx.nr = {} - ctx.nr._tx = { + ctx.nr.transaction = { + traceId: 'trace-1', trace: { custom: { get() { @@ -36,16 +37,13 @@ test.beforeEach((ctx) => { }, tracer: { getTransaction() { - return ctx.nr._tx + return ctx.nr.transaction } } } ctx.nr.segment = { - id: 'segment-1', - transaction: { - traceId: 'trace-1' - } + id: 'segment-1' } ctx.nr.runId = 'run-1' diff --git a/test/unit/llm-events/langchain/chat-completion-summary.test.js b/test/unit/llm-events/langchain/chat-completion-summary.test.js index 83d770fcad..37a0246990 100644 --- a/test/unit/llm-events/langchain/chat-completion-summary.test.js +++ b/test/unit/llm-events/langchain/chat-completion-summary.test.js @@ -11,7 +11,8 @@ const LangChainCompletionSummary = require('../../../../lib/llm-events/langchain test.beforeEach((ctx) => { ctx.nr = {} - ctx.nr._tx = { + ctx.nr.transaction = { + traceId: 'trace-1', trace: { custom: { get() { @@ -31,16 +32,13 @@ test.beforeEach((ctx) => { }, tracer: { getTransaction() { - return ctx.nr._tx + return ctx.nr.transaction } } } ctx.nr.segment = { id: 'segment-1', - transaction: { - traceId: 'trace-1' - }, getDurationInMillis() { return 42 } diff --git a/test/unit/llm-events/langchain/event.test.js b/test/unit/llm-events/langchain/event.test.js index 2fe2d064d8..f9e9adda2d 100644 --- a/test/unit/llm-events/langchain/event.test.js +++ b/test/unit/llm-events/langchain/event.test.js @@ -11,7 +11,8 @@ const LangChainEvent = require('../../../../lib/llm-events/langchain/event') test.beforeEach((ctx) => { ctx.nr = {} - ctx.nr._tx = { + ctx.nr.transaction = { + traceId: 'trace-1', trace: { custom: { get() { @@ -34,16 +35,13 @@ test.beforeEach((ctx) => { }, tracer: { getTransaction() { - return ctx.nr._tx + return ctx.nr.transaction } } } ctx.nr.segment = { - id: 'segment-1', - transaction: { - traceId: 'trace-1' - } + id: 'segment-1' } ctx.nr.runId = 'run-1' diff --git a/test/unit/llm-events/langchain/tool.test.js b/test/unit/llm-events/langchain/tool.test.js index 639a4a7b22..049c2ecee4 100644 --- a/test/unit/llm-events/langchain/tool.test.js +++ b/test/unit/llm-events/langchain/tool.test.js @@ -11,18 +11,6 @@ const LangChainTool = require('../../../../lib/llm-events/langchain/tool') test.beforeEach((ctx) => { ctx.nr = {} - ctx.nr._tx = { - trace: { - custom: { - get() { - return { - 'llm.conversation_id': 'test-conversation' - } - } - } - } - } - ctx.nr.agent = { config: { ai_monitoring: { @@ -36,7 +24,20 @@ test.beforeEach((ctx) => { }, tracer: { getTransaction() { - return ctx.nr._tx + return ctx.nr.transaction + } + } + } + + ctx.nr.transaction = { + traceId: 'trace-1', + trace: { + custom: { + get() { + return { + 'llm.conversation_id': 'test-conversation' + } + } } } } @@ -45,10 +46,7 @@ test.beforeEach((ctx) => { getDurationInMillis() { return 1.01 }, - id: 'segment-1', - transaction: { - traceId: 'trace-1' - } + id: 'segment-1' } ctx.nr.runId = 'run-1' diff --git a/test/unit/llm-events/langchain/vector-search-result.test.js b/test/unit/llm-events/langchain/vector-search-result.test.js index 7189d32d90..794489e560 100644 --- a/test/unit/llm-events/langchain/vector-search-result.test.js +++ b/test/unit/llm-events/langchain/vector-search-result.test.js @@ -12,18 +12,6 @@ const LangChainVectorSearch = require('../../../../lib/llm-events/langchain/vect test.beforeEach((ctx) => { ctx.nr = {} - ctx.nr._tx = { - trace: { - custom: { - get() { - return { - 'llm.conversation_id': 'test-conversation' - } - } - } - } - } - ctx.nr.agent = { config: { ai_monitoring: { @@ -37,16 +25,25 @@ test.beforeEach((ctx) => { }, tracer: { getTransaction() { - return ctx.nr._tx + return ctx.nr.transaction } } } + ctx.nr.transaction = { + traceId: 'trace-1', + trace: { + custom: { + get() { + return { + 'llm.conversation_id': 'test-conversation' + } + } + } + } + } ctx.nr.segment = { id: 'segment-1', - transaction: { - traceId: 'trace-1' - }, getDurationInMillis() { return 42 } diff --git a/test/unit/llm-events/langchain/vector-search.test.js b/test/unit/llm-events/langchain/vector-search.test.js index 73c04a938f..771f118308 100644 --- a/test/unit/llm-events/langchain/vector-search.test.js +++ b/test/unit/llm-events/langchain/vector-search.test.js @@ -41,11 +41,11 @@ test.beforeEach((ctx) => { } } + ctx.nr.transaction = { + traceId: 'trace-1' + } ctx.nr.segment = { id: 'segment-1', - transaction: { - traceId: 'trace-1' - }, getDurationInMillis() { return 42 } diff --git a/test/unit/llm-events/openai/chat-completion-message.test.js b/test/unit/llm-events/openai/chat-completion-message.test.js index f727cd35d7..53b5df2a8a 100644 --- a/test/unit/llm-events/openai/chat-completion-message.test.js +++ b/test/unit/llm-events/openai/chat-completion-message.test.js @@ -28,6 +28,7 @@ test('should create a LlmChatCompletionMessage event', (t, end) => { const segment = api.shim.getActiveSegment() const summaryId = 'chat-summary-id' const chatMessageEvent = new LlmChatCompletionMessage({ + transaction: tx, agent, segment, request: req, @@ -51,6 +52,7 @@ test('should create a LlmChatCompletionMessage from response choices', (t, end) const segment = api.shim.getActiveSegment() const summaryId = 'chat-summary-id' const chatMessageEvent = new LlmChatCompletionMessage({ + transaction: tx, agent, segment, request: req, @@ -77,6 +79,7 @@ test('should set conversation_id from custom attributes', (t, end) => { helper.runInTransaction(agent, () => { api.addCustomAttribute('llm.conversation_id', conversationId) const chatMessageEvent = new LlmChatCompletionMessage({ + transaction: {}, agent, segment: {}, request: {}, @@ -98,6 +101,7 @@ test('respects record_content', (t, end) => { const chatMessageEvent = new LlmChatCompletionMessage({ agent, segment: {}, + transaction: {}, request: {}, response: {} }) @@ -116,12 +120,13 @@ test('should use token_count from tokenCountCallback for prompt message', (t, en return expectedCount } api.setLlmTokenCountCallback(cb) - helper.runInTransaction(agent, () => { + helper.runInTransaction(agent, (tx) => { api.startSegment('fakeSegment', false, () => { const segment = api.shim.getActiveSegment() const summaryId = 'chat-summary-id' delete chatRes.usage const chatMessageEvent = new LlmChatCompletionMessage({ + transaction: tx, agent, segment, request: req, @@ -146,7 +151,7 @@ test('should use token_count from tokenCountCallback for completion messages', ( return expectedCount } api.setLlmTokenCountCallback(cb) - helper.runInTransaction(agent, () => { + helper.runInTransaction(agent, (tx) => { api.startSegment('fakeSegment', false, () => { const segment = api.shim.getActiveSegment() const summaryId = 'chat-summary-id' @@ -154,6 +159,7 @@ test('should use token_count from tokenCountCallback for completion messages', ( const chatMessageEvent = new LlmChatCompletionMessage({ agent, segment, + transaction: tx, request: req, response: chatRes, completionId: summaryId, @@ -169,7 +175,7 @@ test('should use token_count from tokenCountCallback for completion messages', ( test('should not set token_count if not set in usage nor a callback registered', (t, end) => { const { agent } = t.nr const api = helper.getAgentApi() - helper.runInTransaction(agent, () => { + helper.runInTransaction(agent, (tx) => { api.startSegment('fakeSegment', false, () => { const segment = api.shim.getActiveSegment() const summaryId = 'chat-summary-id' @@ -177,6 +183,7 @@ test('should not set token_count if not set in usage nor a callback registered', const chatMessageEvent = new LlmChatCompletionMessage({ agent, segment, + transaction: tx, request: req, response: chatRes, completionId: summaryId, @@ -196,7 +203,7 @@ test('should not set token_count if not set in usage nor a callback registered r // empty cb } api.setLlmTokenCountCallback(cb) - helper.runInTransaction(agent, () => { + helper.runInTransaction(agent, (tx) => { api.startSegment('fakeSegment', false, () => { const segment = api.shim.getActiveSegment() const summaryId = 'chat-summary-id' @@ -204,6 +211,7 @@ test('should not set token_count if not set in usage nor a callback registered r const chatMessageEvent = new LlmChatCompletionMessage({ agent, segment, + transaction: tx, request: req, response: chatRes, completionId: summaryId, diff --git a/test/unit/llm-events/openai/chat-completion-summary.test.js b/test/unit/llm-events/openai/chat-completion-summary.test.js index ca9e24823a..984cabe802 100644 --- a/test/unit/llm-events/openai/chat-completion-summary.test.js +++ b/test/unit/llm-events/openai/chat-completion-summary.test.js @@ -30,6 +30,7 @@ test('should properly create a LlmChatCompletionSummary event', (t, end) => { const chatSummaryEvent = new LlmChatCompletionSummary({ agent, segment, + transaction: tx, request: req, response: chatRes }) @@ -45,6 +46,7 @@ test('should set error to true', (ctx, end) => { helper.runInTransaction(agent, () => { const chatSummaryEvent = new LlmChatCompletionSummary({ agent, + transaction: null, segment: null, request: {}, response: {}, @@ -67,6 +69,7 @@ test('should set `llm.` attributes from custom attributes', (t, end) => { const chatSummaryEvent = new LlmChatCompletionSummary({ agent, segment: null, + transaction: null, request: {}, response: {} }) diff --git a/test/unit/llm-events/openai/embedding.test.js b/test/unit/llm-events/openai/embedding.test.js index ca8f3d75ae..eee4b5fc6f 100644 --- a/test/unit/llm-events/openai/embedding.test.js +++ b/test/unit/llm-events/openai/embedding.test.js @@ -32,7 +32,13 @@ test('should properly create a LlmEmbedding event', (t, end) => { api.startSegment('fakeSegment', false, () => { const segment = api.shim.getActiveSegment() segment.end() - const embeddingEvent = new LlmEmbedding({ agent, segment, request: req, response: res }) + const embeddingEvent = new LlmEmbedding({ + agent, + segment, + transaction: tx, + request: req, + response: res + }) const expected = getExpectedResult(tx, embeddingEvent, 'embedding') assert.deepEqual(embeddingEvent, expected) end() @@ -62,6 +68,7 @@ test('should properly create a LlmEmbedding event', (t, end) => { const embeddingEvent = new LlmEmbedding({ agent, segment: null, + transaction: null, request: { input: value }, response: {} }) diff --git a/test/unit/metrics-recorder/database-metrics-recorder.test.js b/test/unit/metrics-recorder/database-metrics-recorder.test.js index 6625c3cf6d..4757e26765 100644 --- a/test/unit/metrics-recorder/database-metrics-recorder.test.js +++ b/test/unit/metrics-recorder/database-metrics-recorder.test.js @@ -33,7 +33,7 @@ test('recording database metrics', async (t) => { transaction.type = Transaction.TYPES.BG segment.setDurationInMillis(333) - recordQueryMetrics.bind(ps)(segment, 'TEST') + recordQueryMetrics.bind(ps)(segment, 'TEST', transaction) transaction.end() ctx.nr.metrics = transaction.metrics @@ -101,7 +101,7 @@ test('recording database metrics', async (t) => { transaction.type = Transaction.TYPES.BG segment.setDurationInMillis(333) - recordQueryMetrics.bind(ps)(segment, 'TEST') + recordQueryMetrics.bind(ps)(segment, 'TEST', transaction) transaction.end() ctx.nr.metrics = transaction.metrics @@ -166,7 +166,7 @@ test('recording database metrics', async (t) => { transaction.type = Transaction.TYPES.BG segment.setDurationInMillis(333) - recordQueryMetrics.bind(ps)(segment, null) + recordQueryMetrics.bind(ps)(segment, null, transaction) transaction.end() ctx.nr.metrics = transaction.metrics @@ -229,7 +229,7 @@ test('recording database metrics', async (t) => { transaction.type = Transaction.TYPES.BG segment.setDurationInMillis(333) - recordQueryMetrics.bind(ps)(segment, null) + recordQueryMetrics.bind(ps)(segment, null, transaction) transaction.end() ctx.nr.metrics = transaction.metrics @@ -298,13 +298,13 @@ test('recording slow queries', async (t) => { ctx.nr.segment = segment segment.setDurationInMillis(503) - recordQueryMetrics.bind(ps)(segment, 'TEST') + recordQueryMetrics.bind(ps)(segment, 'TEST', transaction) const ps2 = new ParsedStatement('MySql', 'select', 'foo', 'select * from foo where b=2') const segment2 = transaction.trace.add('test') segment2.setDurationInMillis(501) - recordQueryMetrics.bind(ps2)(segment2, 'TEST') + recordQueryMetrics.bind(ps2)(segment2, 'TEST', transaction) transaction.end() }) @@ -359,13 +359,13 @@ test('recording slow queries', async (t) => { ctx.nr.segment = segment segment.setDurationInMillis(503) - recordQueryMetrics.bind(ps)(segment, 'TEST') + recordQueryMetrics.bind(ps)(segment, 'TEST', transaction) const ps2 = new ParsedStatement('MySql', 'select', null, 'select * from foo where b=2') const segment2 = transaction.trace.add('test') segment2.setDurationInMillis(501) - recordQueryMetrics.bind(ps2)(segment2, 'TEST') + recordQueryMetrics.bind(ps2)(segment2, 'TEST', transaction) transaction.end() }) @@ -428,13 +428,13 @@ test('recording slow queries', async (t) => { ctx.nr.segment = segment segment.setDurationInMillis(503) - recordQueryMetrics.bind(ps)(segment, 'TEST') + recordQueryMetrics.bind(ps)(segment, 'TEST', transaction) const ps2 = new ParsedStatement('MySql', 'select', null, null) const segment2 = transaction.trace.add('test') segment2.setDurationInMillis(501) - recordQueryMetrics.bind(ps2)(segment2, 'TEST') + recordQueryMetrics.bind(ps2)(segment2, 'TEST', transaction) transaction.end() }) diff --git a/test/unit/metrics-recorder/distributed-trace.test.js b/test/unit/metrics-recorder/distributed-trace.test.js index f41d95991a..40574f95f2 100644 --- a/test/unit/metrics-recorder/distributed-trace.test.js +++ b/test/unit/metrics-recorder/distributed-trace.test.js @@ -12,7 +12,7 @@ const recordDistributedTrace = require('../../../lib/metrics/recorders/distribut const Transaction = require('../../../lib/transaction') const makeSegment = (opts) => { - const segment = opts.tx.trace.root.add('placeholder') + const segment = opts.tx.trace.add('placeholder') segment.setDurationInMillis(opts.duration) segment._setExclusiveDurationInMillis(opts.exclusive) diff --git a/test/unit/metrics-recorder/generic.test.js b/test/unit/metrics-recorder/generic.test.js index 35d9e5d687..42c91e9000 100644 --- a/test/unit/metrics-recorder/generic.test.js +++ b/test/unit/metrics-recorder/generic.test.js @@ -11,7 +11,7 @@ const recordGeneric = require('../../../lib/metrics/recorders/generic') const Transaction = require('../../../lib/transaction') function makeSegment(options) { - const segment = options.transaction.trace.root.add('placeholder') + const segment = options.transaction.trace.add('placeholder') segment.setDurationInMillis(options.duration) segment._setExclusiveDurationInMillis(options.exclusive) @@ -27,7 +27,7 @@ function record(options) { const transaction = options.transaction transaction.finalizeNameFromUri(options.url, options.code) - recordGeneric(segment, options.transaction.name) + recordGeneric(segment, options.transaction.name, options.transaction) } test('recordGeneric', async function (t) { @@ -50,7 +50,7 @@ test('recordGeneric', async function (t) { exclusive: 0 }) assert.doesNotThrow(function () { - recordGeneric(segment, undefined) + recordGeneric(segment, undefined, trans) }) }) @@ -61,7 +61,7 @@ test('recordGeneric', async function (t) { duration: 5, exclusive: 5 }) - recordGeneric(segment, undefined) + recordGeneric(segment, undefined, trans) const result = [[{ name: 'placeholder' }, [1, 0.005, 0.005, 0.005, 0.005, 0.000025]]] @@ -93,9 +93,9 @@ test('recordGeneric', async function (t) { await t.test('should report exclusive time correctly', function (t) { const { trans } = t.nr const root = trans.trace.root - const parent = root.add('Test/Parent', recordGeneric) - const child1 = parent.add('Test/Child/1', recordGeneric) - const child2 = parent.add('Test/Child/2', recordGeneric) + const parent = trans.trace.add('Test/Parent', recordGeneric) + const child1 = trans.trace.add('Test/Child/1', recordGeneric, parent) + const child2 = trans.trace.add('Test/Child/2', recordGeneric, parent) root.setDurationInMillis(30, 0) parent.setDurationInMillis(30, 0) diff --git a/test/unit/metrics-recorder/http-external.test.js b/test/unit/metrics-recorder/http-external.test.js index 8ccdcc52ee..d99ae9e3c4 100644 --- a/test/unit/metrics-recorder/http-external.test.js +++ b/test/unit/metrics-recorder/http-external.test.js @@ -10,12 +10,12 @@ const helper = require('../../lib/agent_helper') const generateRecorder = require('../../../lib/metrics/recorders/http_external') const Transaction = require('../../../lib/transaction') -function recordExternal(segment, scope) { - return generateRecorder('test.example.com', 'http')(segment, scope) +function recordExternal(segment, scope, transaction) { + return generateRecorder('test.example.com', 'http')(segment, scope, transaction) } function makeSegment(options) { - const segment = options.transaction.trace.root.add('placeholder') + const segment = options.transaction.trace.add('placeholder') segment.setDurationInMillis(options.duration) segment._setExclusiveDurationInMillis(options.exclusive) @@ -31,7 +31,7 @@ function record(options) { const transaction = options.transaction transaction.finalizeNameFromUri(options.url, options.code) - recordExternal(segment, options.transaction.name) + recordExternal(segment, options.transaction.name, options.transaction) } test('recordExternal', async function (t) { @@ -56,7 +56,7 @@ test('recordExternal', async function (t) { exclusive: 0 }) assert.doesNotThrow(function () { - recordExternal(segment, undefined) + recordExternal(segment, undefined, trans) }) }) @@ -67,7 +67,7 @@ test('recordExternal', async function (t) { duration: 0, exclusive: 0 }) - recordExternal(segment, undefined) + recordExternal(segment, undefined, trans) const result = [ [{ name: 'External/test.example.com/http' }, [1, 0, 0, 0, 0, 0]], @@ -108,9 +108,13 @@ test('recordExternal', async function (t) { await t.test('should report exclusive time correctly', function (t) { const { trans } = t.nr const root = trans.trace.root - const parent = root.add('/parent', recordExternal) - const child1 = parent.add('/child1', generateRecorder('api.twitter.com', 'https')) - const child2 = parent.add('/child2', generateRecorder('oauth.facebook.com', 'http')) + const parent = trans.trace.add('/parent', recordExternal) + const child1 = trans.trace.add('/child1', generateRecorder('api.twitter.com', 'https'), parent) + const child2 = trans.trace.add( + '/child2', + generateRecorder('oauth.facebook.com', 'http'), + parent + ) root.setDurationInMillis(32, 0) parent.setDurationInMillis(32, 0) diff --git a/test/unit/metrics-recorder/http.test.js b/test/unit/metrics-recorder/http.test.js index afacf786ed..cb061f12e3 100644 --- a/test/unit/metrics-recorder/http.test.js +++ b/test/unit/metrics-recorder/http.test.js @@ -12,7 +12,7 @@ const recordWeb = require('../../../lib/metrics/recorders/http') const Transaction = require('../../../lib/transaction') function makeSegment(options) { - const segment = options.transaction.trace.root.add('placeholder') + const segment = options.transaction.trace.add('placeholder') segment.setDurationInMillis(options.duration) segment._setExclusiveDurationInMillis(options.exclusive) @@ -28,8 +28,8 @@ function record(options) { const transaction = options.transaction transaction.finalizeNameFromUri(options.url, options.code) - segment.markAsWeb(options.url) - recordWeb(segment, options.transaction.name) + segment.markAsWeb(transaction) + recordWeb(segment, options.transaction.name, options.transaction) } function beforeEach(ctx) { diff --git a/test/unit/metrics-recorder/queue-time-http.test.js b/test/unit/metrics-recorder/queue-time-http.test.js index adb9aa16e8..c4d007d4bb 100644 --- a/test/unit/metrics-recorder/queue-time-http.test.js +++ b/test/unit/metrics-recorder/queue-time-http.test.js @@ -12,7 +12,7 @@ const recordWeb = require('../../../lib/metrics/recorders/http') const Transaction = require('../../../lib/transaction') function makeSegment(options) { - const segment = options.transaction.trace.root.add('placeholder') + const segment = options.transaction.trace.add('placeholder') segment.setDurationInMillis(options.duration) segment._setExclusiveDurationInMillis(options.exclusive) @@ -29,8 +29,8 @@ function record(options) { transaction.finalizeNameFromUri(options.url, options.code) transaction.queueTime = options.queueTime - segment.markAsWeb(options.url) - recordWeb(segment, options.transaction.name) + segment.markAsWeb(transaction) + recordWeb(segment, options.transaction.name, options.transaction) } test('when recording queueTime', async (t) => { diff --git a/test/unit/shim/datastore-shim.test.js b/test/unit/shim/datastore-shim.test.js index 42388805a4..344367a5af 100644 --- a/test/unit/shim/datastore-shim.test.js +++ b/test/unit/shim/datastore-shim.test.js @@ -33,8 +33,13 @@ test('DatastoreShim', async function (t) { return agent.tracer.getSegment() }, withNested: function () { + const transaction = agent.tracer.getTransaction() const segment = agent.tracer.getSegment() - segment.add('ChildSegment') + segment.add({ + config: agent.config, + name: 'ChildSegment', + root: transaction.trace.root + }) return segment } @@ -323,11 +328,10 @@ test('DatastoreShim', async function (t) { name: 'getActiveSegment' }) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = agent.tracer.getSegment() const segment = wrappable.getActiveSegment() assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'getActiveSegment') assert.equal(agent.tracer.getSegment(), startingSegment) end() @@ -344,11 +348,10 @@ test('DatastoreShim', async function (t) { name: 'getActiveSegment' }) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = agent.tracer.getSegment() const segment = wrappable.getActiveSegment() assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'Datastore/operation/Cassandra/getActiveSegment') assert.equal(agent.tracer.getSegment(), startingSegment) end() @@ -362,11 +365,10 @@ test('DatastoreShim', async function (t) { const { agent, shim, wrappable } = t.nr shim.recordOperation(wrappable, 'getActiveSegment', { name: 'getActiveSegment' }) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = agent.tracer.getSegment() const segment = wrappable.getActiveSegment() assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'Datastore/operation/Cassandra/getActiveSegment') assert.equal(agent.tracer.getSegment(), startingSegment) end() @@ -379,11 +381,10 @@ test('DatastoreShim', async function (t) { shim.recordOperation(wrappable, 'withNested', () => { return new OperationSpec({ name: 'test', opaque: false }) }) - helper.runInTransaction(agent, (tx) => { + helper.runInTransaction(agent, () => { const startingSegment = agent.tracer.getSegment() const segment = wrappable.withNested() assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'Datastore/operation/Cassandra/test') assert.equal(segment.children.length, 1) const [childSegment] = segment.children @@ -397,11 +398,10 @@ test('DatastoreShim', async function (t) { shim.recordOperation(wrappable, 'withNested', () => { return new OperationSpec({ name: 'test', opaque: true }) }) - helper.runInTransaction(agent, (tx) => { + helper.runInTransaction(agent, () => { const startingSegment = agent.tracer.getSegment() const segment = wrappable.withNested() assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'Datastore/operation/Cassandra/test') assert.equal(segment.children.length, 0) end() @@ -646,11 +646,10 @@ test('DatastoreShim', async function (t) { }) ) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = agent.tracer.getSegment() const segment = wrappable.getActiveSegment(query) assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'getActiveSegment') assert.equal(agent.tracer.getSegment(), startingSegment) end() @@ -666,11 +665,10 @@ test('DatastoreShim', async function (t) { new QuerySpec({ query: shim.FIRST, record: true }) ) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = agent.tracer.getSegment() const segment = wrappable.getActiveSegment(query) assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'Datastore/statement/Cassandra/my_table/select') assert.equal(agent.tracer.getSegment(), startingSegment) end() @@ -683,11 +681,10 @@ test('DatastoreShim', async function (t) { const { agent, shim, wrappable } = t.nr shim.recordQuery(wrappable, 'getActiveSegment', new QuerySpec({ query: shim.FIRST })) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = agent.tracer.getSegment() const segment = wrappable.getActiveSegment(query) assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'Datastore/statement/Cassandra/my_table/select') assert.equal(agent.tracer.getSegment(), startingSegment) end() @@ -841,11 +838,10 @@ test('DatastoreShim', async function (t) { const { agent, shim, wrappable } = t.nr shim.recordBatchQuery(wrappable, 'getActiveSegment', new QuerySpec({ query: shim.FIRST })) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = agent.tracer.getSegment() const segment = wrappable.getActiveSegment(query) assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'Datastore/statement/Cassandra/my_table/select/batch') assert.equal(agent.tracer.getSegment(), startingSegment) end() diff --git a/test/unit/shim/message-shim.test.js b/test/unit/shim/message-shim.test.js index 00ad948b9e..e81538540b 100644 --- a/test/unit/shim/message-shim.test.js +++ b/test/unit/shim/message-shim.test.js @@ -42,8 +42,13 @@ test('MessageShim', async function (t) { }, sendMessages: function () {}, withNested: function () { + const transaction = agent.tracer.getTransaction() const segment = agent.tracer.getSegment() - segment.add('ChildSegment') + segment.add({ + config: agent.config, + name: 'ChildSegment', + root: transaction.trace.root + }) return segment } @@ -177,11 +182,10 @@ test('MessageShim', async function (t) { return new MessageSpec({ destinationName: 'foobar' }) }) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = agent.tracer.getSegment() const segment = wrappable.getActiveSegment() assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Produce/Named/foobar') assert.equal(agent.tracer.getSegment(), startingSegment) end() @@ -325,9 +329,8 @@ test('MessageShim', async function (t) { return new MessageSpec({ destinationName: 'foobar', opaque: false }) }) - helper.runInTransaction(agent, (tx) => { + helper.runInTransaction(agent, () => { const segment = wrappable.withNested() - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Produce/Named/foobar') assert.equal(segment.children.length, 1) @@ -343,9 +346,8 @@ test('MessageShim', async function (t) { return new MessageSpec({ destinationName: 'foobar', opaque: true }) }) - helper.runInTransaction(agent, (tx) => { + helper.runInTransaction(agent, () => { const segment = wrappable.withNested() - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Produce/Named/foobar') assert.equal(segment.children.length, 0) @@ -497,11 +499,10 @@ test('MessageShim', async function (t) { return new MessageSpec({ destinationName: 'foobar' }) }) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = agent.tracer.getSegment() const segment = wrappable.getActiveSegment() assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Consume/Named/foobar') assert.equal(agent.tracer.getSegment(), startingSegment) end() @@ -526,11 +527,10 @@ test('MessageShim', async function (t) { destinationType: shim.EXCHANGE }) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = agent.tracer.getSegment() const segment = wrappable.getActiveSegment('fizzbang') assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Consume/Named/fizzbang') assert.equal(agent.tracer.getSegment(), startingSegment) end() @@ -623,9 +623,8 @@ test('MessageShim', async function (t) { return new MessageSpec({ destinationName: 'foobar', opaque: false }) }) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const segment = wrappable.withNested() - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Consume/Named/foobar') assert.equal(segment.children.length, 1) @@ -641,9 +640,8 @@ test('MessageShim', async function (t) { return new MessageSpec({ destinationName: 'foobar', opaque: true }) }) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const segment = wrappable.withNested() - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Consume/Named/foobar') assert.equal(segment.children.length, 0) end() @@ -721,11 +719,10 @@ test('MessageShim', async function (t) { const { agent, shim, wrappable } = t.nr shim.recordPurgeQueue(wrappable, 'getActiveSegment', new MessageSpec({ queue: shim.FIRST })) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = agent.tracer.getSegment() const segment = wrappable.getActiveSegment('foobar') assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'MessageBroker/RabbitMQ/Queue/Purge/Named/foobar') assert.equal(agent.tracer.getSegment(), startingSegment) end() @@ -948,7 +945,8 @@ test('MessageShim', async function (t) { const parent = wrapped('my.queue', function consumer() { const segment = shim.getSegment() assert.notEqual(segment.name, 'Callback: consumer') - assert.equal(segment.transaction.type, 'message') + const transaction = shim.tracer.getTransaction() + assert.equal(transaction.type, 'message') end() }) @@ -958,7 +956,7 @@ test('MessageShim', async function (t) { await t.test('should end the transaction immediately if not handled', function (t, end) { const { shim, wrapped } = t.nr wrapped('my.queue', function consumer() { - const tx = shim.getSegment().transaction + const tx = shim.tracer.getTransaction() assert.equal(tx.isActive(), true) setTimeout(function () { assert.equal(tx.isActive(), false) @@ -974,7 +972,7 @@ test('MessageShim', async function (t) { } wrapped('my.queue', function consumer() { - const tx = shim.getSegment().transaction + const tx = shim.tracer.getTransaction() assert.equal(tx.isActive(), true) return new Promise(function (resolve) { @@ -1017,7 +1015,7 @@ test('MessageShim', async function (t) { const api = new API(agent) wrapped('my.queue', function consumer() { - const tx = shim.getSegment().transaction + const tx = shim.tracer.getTransaction() const handle = api.getTransaction() assert.equal(tx.isActive(), true) @@ -1045,8 +1043,7 @@ test('MessageShim', async function (t) { await t.test('should add agent attributes (e.g. routing key)', function (t, end) { const { shim, wrapped } = t.nr wrapped('my.queue', function consumer() { - const segment = shim.getSegment() - const tx = segment.transaction + const tx = shim.tracer.getTransaction() const traceParams = tx.trace.attributes.get(DESTINATIONS.TRANS_TRACE) assert.equal(traceParams['message.routingKey'], 'routing.key') @@ -1184,7 +1181,7 @@ test('MessageShim', async function (t) { } wrapped('my.queue', function consumer() { - const tx = shim.getSegment().transaction + const tx = shim.tracer.getTransaction() assert.equal(tx.incomingCatId, '9876#id') assert.equal(tx.referringTransactionGuid, 'trans id') @@ -1229,12 +1226,13 @@ test('MessageShim', async function (t) { await t.test('should still start a new transaction in the consumer', function (t, end) { const { agent, shim, wrapped } = t.nr - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const parent = wrapped('my.queue', function consumer() { + const childTx = shim.tracer.getTransaction() const segment = shim.getSegment() assert.notEqual(segment.name, 'Callback: consumer') - assert.ok(segment.transaction.id) - assert.notEqual(segment.transaction.id, parent.transaction.id) + assert.ok(childTx.id) + assert.notEqual(tx.id, childTx.id) end() }) assert.ok(parent) diff --git a/test/unit/shim/shim.test.js b/test/unit/shim/shim.test.js index 71238d557d..aab0645c28 100644 --- a/test/unit/shim/shim.test.js +++ b/test/unit/shim/shim.test.js @@ -20,6 +20,8 @@ const { const promiseResolvers = require('../../lib/promise-resolvers') const { tspl } = require('@matteo.collina/tspl') const tempOverrideUncaught = require('../../lib/temp-override-uncaught') +const Transaction = require('../../../lib/transaction') +const TraceSegment = require('../../../lib/transaction/trace/segment') test('Shim', async function (t) { function beforeEach(ctx) { @@ -362,21 +364,12 @@ test('Shim', async function (t) { t.beforeEach(function (ctx) { beforeEach(ctx) - ctx.nr.segment = { - started: false, - touched: false, - probed: false, - start: function () { - this.started = true - }, - touch: function () { - this.touched = true - }, - probe: function () { - this.probed = true - } - } - + const transaction = new Transaction(ctx.nr.agent) + ctx.nr.segment = new TraceSegment({ + config: ctx.nr.agent.config, + name: 'test', + root: transaction.trace.root + }) ctx.nr.startingSegment = ctx.nr.tracer.getSegment() }) @@ -446,9 +439,9 @@ test('Shim', async function (t) { // no segment is passed in. To get around this we set the // active segment to an object known not to be null then do the // wrapping. - tracer.setSegment(segment) + tracer.setSegment({ segment }) const wrapped = shim.bindSegment(wrappable.getActiveSegment) - tracer.setSegment(startingSegment) + tracer.setSegment({ segment: startingSegment }) assert.equal(wrapped(), segment) assert.equal(tracer.getSegment(), startingSegment) @@ -459,8 +452,8 @@ test('Shim', async function (t) { shim.bindSegment(wrappable, 'getActiveSegment', segment) wrappable.getActiveSegment() - assert.equal(segment.started, false) - assert.equal(segment.touched, false) + assert.equal(segment.timer.state, 1) + assert.equal(segment.timer.touched, false) }) await t.test('should start and touch the segment if `full` is `true`', function (t) { @@ -468,13 +461,13 @@ test('Shim', async function (t) { shim.bindSegment(wrappable, 'getActiveSegment', segment, true) wrappable.getActiveSegment() - assert.equal(segment.started, true) - assert.equal(segment.touched, true) + assert.equal(segment.timer.state, 2) + assert.equal(segment.timer.touched, true) }) await t.test('should default to the current segment', function (t) { const { tracer, segment, shim, wrappable } = t.nr - tracer.setSegment(segment) + tracer.setSegment({ segment }) shim.bindSegment(wrappable, 'getActiveSegment') const activeSegment = wrappable.getActiveSegment() assert.equal(activeSegment, segment) @@ -886,13 +879,12 @@ test('Shim', async function (t) { return new RecorderSpec({ name: 'internal test segment', internal: true }) }) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = tracer.getSegment() startingSegment.internal = true - startingSegment.shim = shim + startingSegment.shimId = shim.id const segment = wrappable.getActiveSegment() assert.equal(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'ROOT') assert.equal(tracer.getSegment(), startingSegment) end() @@ -948,7 +940,7 @@ test('Shim', async function (t) { 'should call after hook on record when function is done executing', function (t, end) { const { agent, shim } = t.nr - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { function testAfter() { return 'result' } @@ -957,13 +949,14 @@ test('Shim', async function (t) { name: 'test segment', callback: shim.LAST, after(args) { - assert.equal(Object.keys(args).length, 6, 'should have 6 args to after hook') - const { fn, name, error, result, segment } = args + assert.equal(Object.keys(args).length, 7, 'should have 7 args to after hook') + const { fn, name, error, result, segment, transaction } = args assert.equal(segment.name, 'test segment') assert.equal(error, undefined) assert.deepEqual(fn, testAfter) assert.equal(name, testAfter.name) assert.equal(result, 'result') + assert.equal(tx.id, transaction.id) } }) }) @@ -980,7 +973,7 @@ test('Shim', async function (t) { function (t, end) { const { agent, shim } = t.nr const err = new Error('test err') - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { function testAfter() { throw err } @@ -989,13 +982,14 @@ test('Shim', async function (t) { name: 'test segment', callback: shim.LAST, after(args) { - assert.equal(Object.keys(args).length, 6, 'should have 6 args to after hook') - const { fn, name, error, result, segment } = args + assert.equal(Object.keys(args).length, 7, 'should have 7 args to after hook') + const { fn, name, error, result, segment, transaction } = args assert.equal(segment.name, 'test segment') assert.deepEqual(error, err) assert.equal(result, undefined) assert.deepEqual(fn, testAfter) assert.equal(name, testAfter.name) + assert.equal(tx.id, transaction.id) } }) }) @@ -1121,17 +1115,17 @@ test('Shim', async function (t) { return new RecorderSpec({ name: 'test segment', stream: 'foobar' }) }) - helper.runInTransaction(agent, function () { - const ret = wrapped() - assert.equal(ret, stream) - }) - stream.on('foobar', function () { const emitSegment = shim.getSegment() assert.equal(emitSegment.parent, stream.segment) end() }) - stream.emit('foobar') + + helper.runInTransaction(agent, function () { + const ret = wrapped() + assert.equal(ret, stream) + stream.emit('foobar') + }) }) await t.test('should create an event segment if an event name is given', function (t) { @@ -1143,28 +1137,27 @@ test('Shim', async function (t) { helper.runInTransaction(agent, function () { const ret = wrapped() assert.equal(ret, stream) - }) + // Emit the event and check the segment name. + assert.equal(stream.segment.children.length, 0) + stream.emit('foobar') + assert.equal(stream.segment.children.length, 1) - // Emit the event and check the segment name. - assert.equal(stream.segment.children.length, 0) - stream.emit('foobar') - assert.equal(stream.segment.children.length, 1) + const [eventSegment] = stream.segment.children + assert.match(eventSegment.name, /Event callback: foobar/) + assert.equal(eventSegment.getAttributes().count, 1) - const [eventSegment] = stream.segment.children - assert.match(eventSegment.name, /Event callback: foobar/) - assert.equal(eventSegment.getAttributes().count, 1) + // Emit it again and see if the name updated. + stream.emit('foobar') + assert.equal(stream.segment.children.length, 1) + assert.equal(stream.segment.children[0], eventSegment) + assert.equal(eventSegment.getAttributes().count, 2) - // Emit it again and see if the name updated. - stream.emit('foobar') - assert.equal(stream.segment.children.length, 1) - assert.equal(stream.segment.children[0], eventSegment) - assert.equal(eventSegment.getAttributes().count, 2) - - // Emit it once more and see if the name updated again. - stream.emit('foobar') - assert.equal(stream.segment.children.length, 1) - assert.equal(stream.segment.children[0], eventSegment) - assert.equal(eventSegment.getAttributes().count, 3) + // Emit it once more and see if the name updated again. + stream.emit('foobar') + assert.equal(stream.segment.children.length, 1) + assert.equal(stream.segment.children[0], eventSegment) + assert.equal(eventSegment.getAttributes().count, 3) + }) }) }) @@ -1333,7 +1326,7 @@ test('Shim', async function (t) { name: segmentName, promise: true, after(args) { - plan.equal(Object.keys(args).length, 6, 'should have 6 args to after hook') + plan.equal(Object.keys(args).length, 7, 'should have 7 args to after hook') const { fn, name, error, result, segment } = args plan.deepEqual(fn, toWrap) plan.equal(name, toWrap.name) @@ -1366,7 +1359,7 @@ test('Shim', async function (t) { name: segmentName, promise: true, after(args) { - plan.equal(Object.keys(args).length, 5, 'should have 6 args to after hook') + plan.equal(Object.keys(args).length, 6, 'should have 6 args to after hook') const { fn, name, error, segment } = args plan.deepEqual(fn, toWrap) plan.equal(name, toWrap.name) @@ -1461,11 +1454,10 @@ test('Shim', async function (t) { return new RecorderSpec({ name: 'test segment' }) }) - helper.runInTransaction(agent, function (tx) { + helper.runInTransaction(agent, function () { const startingSegment = tracer.getSegment() const segment = wrappable.getActiveSegment() assert.notEqual(segment, startingSegment) - assert.equal(segment.transaction, tx) assert.equal(segment.name, 'test segment') assert.equal(tracer.getSegment(), startingSegment) end() @@ -1900,7 +1892,12 @@ test('Shim', async function (t) { await t.test('#getSegment', async function (t) { t.beforeEach(function (ctx) { beforeEach(ctx) - ctx.nr.segment = { probe: function () {} } + const transaction = new Transaction(ctx.nr.agent) + ctx.nr.segment = new TraceSegment({ + config: ctx.nr.agent.config, + name: 'test', + root: transaction.trace.root + }) }) t.afterEach(afterEach) @@ -1912,7 +1909,7 @@ test('Shim', async function (t) { await t.test('should return the current segment if the function is not bound', function (t) { const { tracer, segment, shim } = t.nr - tracer.setSegment(segment) + tracer.setSegment({ segment }) assert.equal( shim.getSegment(function () {}), segment @@ -1921,7 +1918,7 @@ test('Shim', async function (t) { await t.test('should return the current segment if no object is provided', function (t) { const { tracer, segment, shim } = t.nr - tracer.setSegment(segment) + tracer.setSegment({ segment }) assert.equal(shim.getSegment(), segment) }) }) @@ -1929,22 +1926,21 @@ test('Shim', async function (t) { await t.test('#getActiveSegment', async function (t) { t.beforeEach(function (ctx) { beforeEach(ctx) - ctx.nr.segment = { - probe: function () {}, - transaction: { - active: true, - isActive: function () { - return this.active - } - } - } + const transaction = new Transaction(ctx.nr.agent) + ctx.nr.segment = new TraceSegment({ + config: ctx.nr.agent.config, + name: 'test', + root: transaction.trace.root + }) + ctx.nr.transaction = transaction }) t.afterEach(afterEach) await t.test( 'should return the segment a function is bound to when transaction is active', function (t) { - const { segment, shim } = t.nr + const { segment, shim, transaction, tracer } = t.nr + tracer.setSegment({ transaction }) const bound = shim.bindSegment(function () {}, segment) assert.equal(shim.getActiveSegment(bound), segment) } @@ -1953,8 +1949,8 @@ test('Shim', async function (t) { await t.test( 'should return the current segment if the function is not bound when transaction is active', function (t) { - const { tracer, segment, shim } = t.nr - tracer.setSegment(segment) + const { segment, shim, tracer, transaction } = t.nr + tracer.setSegment({ segment, transaction }) assert.equal( shim.getActiveSegment(function () {}), segment @@ -1965,8 +1961,8 @@ test('Shim', async function (t) { await t.test( 'should return the current segment if no object is provided when transaction is active', function (t) { - const { tracer, segment, shim } = t.nr - tracer.setSegment(segment) + const { segment, shim, tracer, transaction } = t.nr + tracer.setSegment({ segment, transaction }) assert.equal(shim.getActiveSegment(), segment) } ) @@ -1974,9 +1970,10 @@ test('Shim', async function (t) { await t.test( 'should return null for a bound function when transaction is not active', function (t) { - const { segment, shim } = t.nr - segment.transaction.active = false - const bound = shim.bindSegment(function () {}, segment) + const { segment, shim, transaction, tracer } = t.nr + transaction.timer.state = 3 + tracer.setSegment({ transaction }) + const bound = shim.bindSegment(function () {}, { segment }) assert.equal(shim.getActiveSegment(bound), null) } ) @@ -1984,9 +1981,9 @@ test('Shim', async function (t) { await t.test( 'should return null if the function is not bound when transaction is not active', function (t) { - const { tracer, segment, shim } = t.nr - segment.transaction.active = false - tracer.setSegment(segment) + const { tracer, segment, shim, transaction } = t.nr + transaction.timer.state = 3 + tracer.setSegment({ segment, transaction }) assert.equal( shim.getActiveSegment(function () {}), null @@ -1997,9 +1994,9 @@ test('Shim', async function (t) { await t.test( 'should return null if no object is provided when transaction is not active', function (t) { - const { tracer, segment, shim } = t.nr - segment.transaction.active = false - tracer.setSegment(segment) + const { tracer, segment, shim, transaction } = t.nr + transaction.timer.state = 3 + tracer.setSegment({ segment, transaction }) assert.equal(shim.getActiveSegment(), null) } ) @@ -2019,7 +2016,7 @@ test('Shim', async function (t) { await t.test('should default to the current segment', function (t) { const { tracer, shim, wrappable } = t.nr const segment = { probe: function () {} } - tracer.setSegment(segment) + tracer.setSegment({ segment }) shim.storeSegment(wrappable) assert.equal(shim.getSegment(wrappable), segment) }) @@ -2111,7 +2108,7 @@ test('Shim', async function (t) { helper.runInTransaction(agent, function () { const args = [wrappable.getActiveSegment] const segment = wrappable.getActiveSegment() - const parent = shim.createSegment('test segment') + const parent = shim.createSegment({ name: 'test segment', parent: segment }) shim.bindCallbackSegment({}, args, shim.LAST, parent) const cbSegment = args[0]() @@ -2124,9 +2121,9 @@ test('Shim', async function (t) { await t.test('should make the `parentSegment` translucent after running', function (t, end) { const { agent, shim, wrappable } = t.nr - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const args = [wrappable.getActiveSegment] - const parent = shim.createSegment('test segment') + const parent = shim.createSegment({ name: 'test segment', parent: tx.trace.root }) parent.opaque = true shim.bindCallbackSegment({}, args, shim.LAST, parent) const cbSegment = args[0]() @@ -2177,20 +2174,12 @@ test('Shim', async function (t) { await t.test('#applySegment', async function (t) { t.beforeEach(function (ctx) { beforeEach(ctx) - ctx.nr.segment = { - name: 'segment', - started: false, - touched: false, - start: function () { - this.started = true - }, - touch: function () { - this.touched = true - }, - probe: function () { - this.probed = true - } - } + const transaction = new Transaction(ctx.nr.agent) + ctx.nr.segment = new TraceSegment({ + config: ctx.nr.agent.config, + name: 'test', + root: transaction.trace.root + }) }) t.afterEach(afterEach) @@ -2238,25 +2227,25 @@ test('Shim', async function (t) { await t.test('should make the segment active for the duration of execution', function (t) { const { tracer, segment, shim, wrappable } = t.nr const prevSegment = { name: 'prevSegment', probe: function () {} } - tracer.setSegment(prevSegment) + tracer.setSegment({ segment: prevSegment }) const activeSegment = shim.applySegment(wrappable.getActiveSegment, segment) assert.equal(tracer.getSegment(), prevSegment) assert.equal(activeSegment, segment) - assert.equal(segment.touched, false) - assert.equal(segment.started, false) + assert.equal(segment.timer.touched, false) + assert.equal(segment.timer.state, 1) }) await t.test('should start and touch the segment if `full` is `true`', function (t) { const { segment, shim, wrappable } = t.nr shim.applySegment(wrappable.getActiveSegment, segment, true) - assert.equal(segment.touched, true) - assert.equal(segment.started, true) + assert.equal(segment.timer.touched, true) + assert.equal(segment.timer.state, 2) }) await t.test('should not change the active segment if `segment` is `null`', function (t) { const { tracer, segment, shim, wrappable } = t.nr - tracer.setSegment(segment) + tracer.setSegment({ segment }) let activeSegment = null assert.doesNotThrow(function () { activeSegment = shim.applySegment(wrappable.getActiveSegment, null) @@ -2300,7 +2289,7 @@ test('Shim', async function (t) { throw new Error('test error') } const prevSegment = { name: 'prevSegment', probe: function () {} } - tracer.setSegment(prevSegment) + tracer.setSegment({ segment: prevSegment }) assert.throws(function () { shim.applySegment(func, segment) @@ -2320,7 +2309,7 @@ test('Shim', async function (t) { shim.applySegment(func, segment, true) }, 'Error: test error') - assert.equal(segment.touched, true) + assert.equal(segment.timer.touched, true) } ) }) @@ -2330,8 +2319,8 @@ test('Shim', async function (t) { t.afterEach(afterEach) await t.test('should create a segment with the correct name', function (t, end) { const { agent, shim } = t.nr - helper.runInTransaction(agent, function () { - const segment = shim.createSegment('foobar') + helper.runInTransaction(agent, function (tx) { + const segment = shim.createSegment({ name: 'foobar', parent: tx.trace.root }) assert.equal(segment.name, 'foobar') end() }) @@ -2339,8 +2328,8 @@ test('Shim', async function (t) { await t.test('should allow `recorder` to be omitted', function (t, end) { const { agent, shim } = t.nr - helper.runInTransaction(agent, function () { - const parent = shim.createSegment('parent') + helper.runInTransaction(agent, function (tx) { + const parent = shim.createSegment({ name: 'parent', parent: tx.trace.root }) const child = shim.createSegment('child', parent) assert.equal(child.name, 'child') compareSegments(parent, [child]) @@ -2350,8 +2339,8 @@ test('Shim', async function (t) { await t.test('should allow `recorder` to be null', function (t, end) { const { agent, shim } = t.nr - helper.runInTransaction(agent, function () { - const parent = shim.createSegment('parent') + helper.runInTransaction(agent, function (tx) { + const parent = shim.createSegment('parent', tx.trace.root) const child = shim.createSegment('child', null, parent) assert.equal(child.name, 'child') compareSegments(parent, [child]) @@ -2361,8 +2350,8 @@ test('Shim', async function (t) { await t.test('should not create children for opaque segments', function (t, end) { const { agent, shim } = t.nr - helper.runInTransaction(agent, function () { - const parent = shim.createSegment('parent') + helper.runInTransaction(agent, function (tx) { + const parent = shim.createSegment('parent', tx.trace.root) parent.opaque = true const child = shim.createSegment('child', parent) assert.equal(child.name, 'parent') @@ -2373,8 +2362,8 @@ test('Shim', async function (t) { await t.test('should not modify returned parent for opaque segments', (t, end) => { const { agent, shim } = t.nr - helper.runInTransaction(agent, () => { - const parent = shim.createSegment('parent') + helper.runInTransaction(agent, (tx) => { + const parent = shim.createSegment('parent', tx.trace.root) parent.opaque = true parent.internal = true @@ -2391,7 +2380,7 @@ test('Shim', async function (t) { const { agent, shim } = t.nr helper.runInTransaction(agent, function () { const parent = shim.getSegment() - const child = shim.createSegment('child') + const child = shim.createSegment('child', parent) compareSegments(parent, [child]) end() }) @@ -2399,14 +2388,12 @@ test('Shim', async function (t) { await t.test('should not modify returned parent for opaque segments', (t, end) => { const { agent, shim } = t.nr - helper.runInTransaction(agent, () => { - const parent = shim.createSegment('parent') + helper.runInTransaction(agent, (tx) => { + const parent = shim.createSegment({ name: 'parent', parent: tx.trace.root }) parent.opaque = true parent.internal = true - shim.setActiveSegment(parent) - - const child = shim.createSegment('child') + const child = shim.createSegment('child', parent) assert.equal(child, parent) assert.equal(parent.opaque, true) @@ -2417,8 +2404,8 @@ test('Shim', async function (t) { await t.test('should work with all parameters in an object', function (t, end) { const { agent, shim } = t.nr - helper.runInTransaction(agent, function () { - const parent = shim.createSegment('parent') + helper.runInTransaction(agent, function (tx) { + const parent = shim.createSegment('parent', tx.trace.root) const child = shim.createSegment({ name: 'child', parent }) assert.equal(child.name, 'child') compareSegments(parent, [child]) @@ -2443,8 +2430,8 @@ test('Shim', async function (t) { agent.config.attributes.exclude = ['ignore_me', 'host', 'port_path_or_id', 'database_name'] agent.config.emit('attributes.exclude') agent.config.attributes.enabled = true - helper.runInTransaction(agent, function () { - ctx.nr.segment = shim.createSegment({ name: 'child', parameters }) + helper.runInTransaction(agent, function (tx) { + ctx.nr.segment = shim.createSegment({ name: 'child', parameters, parent: tx.trace.root }) }) ctx.nr.parameters = parameters }) @@ -2482,8 +2469,8 @@ test('Shim', async function (t) { const { agent, parameters, shim } = t.nr let segment agent.config.attributes.enabled = false - helper.runInTransaction(agent, function () { - segment = shim.createSegment({ name: 'child', parameters }) + helper.runInTransaction(agent, function (tx) { + segment = shim.createSegment({ name: 'child', parameters, parent: tx.trace.root }) }) assert.ok(segment.attributes) const attributes = segment.getAttributes() diff --git a/test/unit/shim/transaction-shim.test.js b/test/unit/shim/transaction-shim.test.js index 06766ff21e..e6cd12122c 100644 --- a/test/unit/shim/transaction-shim.test.js +++ b/test/unit/shim/transaction-shim.test.js @@ -63,8 +63,8 @@ test('TransactionShim', async function (t) { return 'fiz' }, anony: function () {}, - getActiveSegment: function () { - return agent.tracer.getSegment() + getActiveTransaction: function () { + return agent.tracer.getTransaction() } } @@ -202,20 +202,20 @@ test('TransactionShim', async function (t) { const { shim, wrappable } = t.nr shim.bindCreateTransaction( wrappable, - 'getActiveSegment', + 'getActiveTransaction', new TransactionSpec({ type: shim.WEB }) ) - const segment = wrappable.getActiveSegment() - assert.equal(segment.transaction.type, shim.WEB) + const tx = wrappable.getActiveTransaction() + assert.equal(tx.type, shim.WEB) - shim.unwrap(wrappable, 'getActiveSegment') + shim.unwrap(wrappable, 'getActiveTransaction') shim.bindCreateTransaction( wrappable, - 'getActiveSegment', + 'getActiveTransaction', new TransactionSpec({ type: shim.BG }) ) - const bgSegment = wrappable.getActiveSegment() - assert.equal(bgSegment.transaction.type, shim.BG) + const bgTx = wrappable.getActiveTransaction() + assert.equal(bgTx.type, shim.BG) }) await t.test('should not create a nested transaction when `spec.nest` is false', function (t) { @@ -226,11 +226,11 @@ test('TransactionShim', async function (t) { let bgCalled = false const bg = shim.bindCreateTransaction(function () { bgCalled = true - bgTx = shim.getSegment().transaction + bgTx = shim.tracer.getTransaction() }, new TransactionSpec({ type: shim.BG })) const web = shim.bindCreateTransaction(function () { webCalled = true - webTx = shim.getSegment().transaction + webTx = shim.tracer.getTransaction() bg() }, new TransactionSpec({ type: shim.WEB })) @@ -266,14 +266,14 @@ test('TransactionShim', async function (t) { const { shim } = ctx.nr ctx.nr.transactions = [] ctx.nr.web = shim.bindCreateTransaction(function (cb) { - ctx.nr.transactions.push(shim.getSegment().transaction) + ctx.nr.transactions.push(shim.tracer.getTransaction()) if (cb) { cb() } }, new TransactionSpec({ type: shim.WEB, nest: true })) ctx.nr.bg = shim.bindCreateTransaction(function (cb) { - ctx.nr.transactions.push(shim.getSegment().transaction) + ctx.nr.transactions.push(shim.tracer.getTransaction()) if (cb) { cb() } @@ -441,7 +441,7 @@ test('TransactionShim', async function (t) { assert.ok(!segment.catTransaction) assert.ok(!segment.getAttributes().transaction_guid) - shim.handleMqTracingHeaders(headers, segment) + shim.handleMqTracingHeaders(headers, segment, tx) assert.ok(!tx.incomingCatId) assert.ok(!tx.referringTransactionGuid) @@ -465,7 +465,7 @@ test('TransactionShim', async function (t) { assert.ok(!segment.catTransaction) assert.ok(!segment.getAttributes().transaction_guid) - shim.handleMqTracingHeaders(headers, segment) + shim.handleMqTracingHeaders(headers, segment, tx) assert.ok(!tx.incomingCatId) assert.ok(!tx.referringTransactionGuid) @@ -488,7 +488,7 @@ test('TransactionShim', async function (t) { assert.ok(!segment.getAttributes().transaction_guid) assert.doesNotThrow(function () { - shim.handleMqTracingHeaders(null, segment) + shim.handleMqTracingHeaders(null, segment, tx) }) assert.ok(!tx.incomingCatId) @@ -516,7 +516,7 @@ test('TransactionShim', async function (t) { helper.runInTransaction(agent, shim.BG, function (tx2) { assert.notEqual(tx2, tx) - shim.handleMqTracingHeaders(headers, segment) + shim.handleMqTracingHeaders(headers, segment, tx) }) assert.equal(tx.incomingCatId, '9876#id') @@ -541,7 +541,7 @@ test('TransactionShim', async function (t) { assert.ok(!tx.tripId) assert.ok(!tx.referringPathHash) - shim.handleMqTracingHeaders(headers) + shim.handleMqTracingHeaders(headers, null, tx) assert.equal(tx.incomingCatId, '9876#id') assert.equal(tx.referringTransactionGuid, 'trans id') @@ -568,7 +568,7 @@ test('TransactionShim', async function (t) { helper.runInTransaction(agent, shim.BG, function (tx2) { assert.notEqual(tx2, tx) - shim.handleMqTracingHeaders(headers, segment) + shim.handleMqTracingHeaders(headers, segment, tx) }) assert.equal(tx.incomingCatId, '9876#id') @@ -592,7 +592,7 @@ test('TransactionShim', async function (t) { helper.runInTransaction(agent, function (tx) { const headers = { traceparent, tracestate } const segment = shim.getSegment() - shim.handleMqTracingHeaders(headers, segment) + shim.handleMqTracingHeaders(headers, segment, tx) const outboundHeaders = {} tx.insertDistributedTraceHeaders(outboundHeaders) @@ -615,7 +615,7 @@ test('TransactionShim', async function (t) { helper.runInTransaction(agent, function (tx) { const headers = { traceparent } const segment = shim.getSegment() - shim.handleMqTracingHeaders(headers, segment) + shim.handleMqTracingHeaders(headers, segment, tx) const outboundHeaders = {} tx.insertDistributedTraceHeaders(outboundHeaders) @@ -638,7 +638,7 @@ test('TransactionShim', async function (t) { helper.runInTransaction(agent, function (tx) { const headers = { traceparent, tracestate } const segment = shim.getSegment() - shim.handleMqTracingHeaders(headers, segment) + shim.handleMqTracingHeaders(headers, segment, tx) const outboundHeaders = {} tx.insertDistributedTraceHeaders(outboundHeaders) @@ -660,7 +660,7 @@ test('TransactionShim', async function (t) { helper.runInTransaction(agent, function (tx) { const headers = { traceparent, tracestate } const segment = shim.getSegment() - shim.handleMqTracingHeaders(headers, segment) + shim.handleMqTracingHeaders(headers, segment, tx) const outboundHeaders = {} tx.insertDistributedTraceHeaders(outboundHeaders) @@ -687,7 +687,7 @@ test('TransactionShim', async function (t) { helper.runInTransaction(agent, shim.BG, function (tx2) { assert.notEqual(tx2, tx) - shim.handleMqTracingHeaders(headers, segment) + shim.handleMqTracingHeaders(headers, segment, tx2) }) assert.equal(segment.catId, '6789#app') @@ -702,7 +702,7 @@ test('TransactionShim', async function (t) { 'should attach the CAT info to current segment if not provided - DT disabled, app data is provided', function (t, end) { const { agent, shim } = t.nr - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const headers = createCATHeaders(agent.config) const segment = shim.getSegment() delete headers['X-NewRelic-Id'] @@ -712,7 +712,7 @@ test('TransactionShim', async function (t) { assert.ok(!segment.catTransaction) assert.ok(!segment.getAttributes().transaction_guid) - shim.handleMqTracingHeaders(headers) + shim.handleMqTracingHeaders(headers, null, tx) assert.equal(segment.catId, '6789#app') assert.equal(segment.catTransaction, 'app data transaction name') @@ -738,7 +738,7 @@ test('TransactionShim', async function (t) { helper.runInTransaction(agent, shim.BG, function (tx2) { assert.notEqual(tx2, tx) - shim.handleMqTracingHeaders(headers, segment) + shim.handleMqTracingHeaders(headers, segment, tx2) }) assert.equal(segment.catId, '6789#app') @@ -753,7 +753,7 @@ test('TransactionShim', async function (t) { 'should not attach any CAT data to the segment, app data is for an untrusted application', function (t, end) { const { agent, shim } = t.nr - helper.runInTransaction(agent, function () { + helper.runInTransaction(agent, function (tx) { const headers = createCATHeaders(agent.config) const segment = shim.getSegment() delete headers['X-NewRelic-Id'] @@ -764,7 +764,7 @@ test('TransactionShim', async function (t) { assert.ok(!segment.catTransaction) assert.ok(!segment.getAttributes().transaction_guid) - shim.handleMqTracingHeaders(headers) + shim.handleMqTracingHeaders(headers, null, tx) assert.ok(!segment.catId) assert.ok(!segment.catTransaction) diff --git a/test/unit/shim/webframework-shim.test.js b/test/unit/shim/webframework-shim.test.js index ec1fe6f1ee..20696044ea 100644 --- a/test/unit/shim/webframework-shim.test.js +++ b/test/unit/shim/webframework-shim.test.js @@ -625,31 +625,6 @@ test('WebFrameworkShim', async function (t) { } }) - await t.test('should reinstate its own context', function (t, end) { - const { agent, req, shim, txInfo, wrappable } = t.nr - testType(shim.MIDDLEWARE, 'Nodejs/Middleware/Restify/getActiveSegment') - - function testType(type, expectedName) { - const wrapped = shim.recordMiddleware( - wrappable.getActiveSegment, - new MiddlewareSpec({ - type: type, - route: '' - }) - ) - const tx = helper.runInTransaction(agent, function (_tx) { - return _tx - }) - txInfo.transaction = tx - txInfo.segmentStack.push(tx.trace.root) - - const segment = wrapped(req) - - assert.equal(segment.name, expectedName) - end() - } - }) - await t.test('should capture route parameters when high_security is off', function (t, end) { const { agent, req, shim, txInfo, wrappable } = t.nr agent.config.high_security = false diff --git a/test/unit/shimmer.test.js b/test/unit/shimmer.test.js index ec1600628b..23fa04d8ba 100644 --- a/test/unit/shimmer.test.js +++ b/test/unit/shimmer.test.js @@ -440,13 +440,14 @@ test('shimmer', async function (t) { transactions[i] = current ids[i] = current.id + const ctx = agent.tracer.getContext() process.nextTick( agent.tracer.bindFunction(function bindFunctionCb() { const lookup = agent.getTransaction() plan.equal(lookup, current) synchronizer.emit('inner', lookup, i) - }) + }, ctx) ) }) wrapped() @@ -479,13 +480,14 @@ test('shimmer', async function (t) { transactions[i] = current ids[i] = current.id + const ctx = agent.tracer.getContext() setTimeout( agent.tracer.bindFunction(function bindFunctionCb() { const lookup = agent.getTransaction() plan.equal(lookup, current) synchronizer.emit('inner', lookup, i) - }), + }, ctx), 1 ) }) @@ -524,6 +526,7 @@ test('shimmer', async function (t) { transactions[j] = current ids[j] = id + const ctx = agent.tracer.getContext() eventer.on( name, agent.tracer.bindFunction(function bindFunctionCb() { @@ -532,7 +535,7 @@ test('shimmer', async function (t) { plan.equal(lookup.id, id) eventer.emit('inner', lookup, j) - }) + }, ctx) ) eventer.emit(name) @@ -604,11 +607,12 @@ test('shimmer', async function (t) { verify(j, 'createTicker', current) + const ctx = agent.tracer.getContext() process.nextTick( agent.tracer.bindFunction(function bindFunctionCb() { verify(j, 'nextTick', current) createTimer(current, j) - }) + }, ctx) ) }) } diff --git a/test/unit/spans/span-event-aggregator.test.js b/test/unit/spans/span-event-aggregator.test.js index 6d5da42061..c506739326 100644 --- a/test/unit/spans/span-event-aggregator.test.js +++ b/test/unit/spans/span-event-aggregator.test.js @@ -60,7 +60,7 @@ test('SpanAggregator', async (t) => { assert.equal(spanEventAggregator.length, 0) - spanEventAggregator.addSegment(segment, 'p') + spanEventAggregator.addSegment({ segment, transaction: tx, parentId: 'p' }) assert.equal(spanEventAggregator.length, 1) const event = spanEventAggregator.getEvents()[0] @@ -84,7 +84,7 @@ test('SpanAggregator', async (t) => { const segment = agent.tracer.getSegment() assert.equal(spanEventAggregator.length, 0) - spanEventAggregator.addSegment(segment) + spanEventAggregator.addSegment({ segment, transaction: tx }) assert.equal(spanEventAggregator.length, 1) const event = spanEventAggregator.getEvents()[0] @@ -134,20 +134,20 @@ test('SpanAggregator', async (t) => { assert.equal(spanEventAggregator.seen, 0) // First segment is added regardless of priority. - assert.equal(spanEventAggregator.addSegment(segment), true) + assert.equal(spanEventAggregator.addSegment({ segment, transaction: tx }), true) assert.equal(spanEventAggregator.length, 1) assert.equal(spanEventAggregator.seen, 1) // Higher priority should be added. tx.priority = 100 - assert.equal(spanEventAggregator.addSegment(segment), true) + assert.equal(spanEventAggregator.addSegment({ segment, transaction: tx }), true) assert.equal(spanEventAggregator.length, 1) assert.equal(spanEventAggregator.seen, 2) const event1 = spanEventAggregator.getEvents()[0] // Lower priority should not be added. tx.priority = 1 - assert.equal(spanEventAggregator.addSegment(segment), false) + assert.equal(spanEventAggregator.addSegment({ segment, transaction: tx }), false) assert.equal(spanEventAggregator.length, 1) assert.equal(spanEventAggregator.seen, 3) const event2 = spanEventAggregator.getEvents()[0] @@ -173,7 +173,7 @@ test('SpanAggregator', async (t) => { setTimeout(() => { const segment = agent.tracer.getSegment() - spanEventAggregator.addSegment(segment) + spanEventAggregator.addSegment({ segment, transaction: tx }) const payload = spanEventAggregator._toPayloadSync() diff --git a/test/unit/spans/span-event.test.js b/test/unit/spans/span-event.test.js index 2629bf42e8..33fd151c4d 100644 --- a/test/unit/spans/span-event.test.js +++ b/test/unit/spans/span-event.test.js @@ -70,7 +70,7 @@ test('fromSegment()', async (t) => { const spanContext = segment.getSpanContext() spanContext.addCustomAttribute('Span Lee', 'no prize') - const span = SpanEvent.fromSegment(segment, 'parent') + const span = SpanEvent.fromSegment(segment, transaction, 'parent') // Should have all the normal properties. assert.ok(span) @@ -136,7 +136,7 @@ test('fromSegment()', async (t) => { res.resume() res.on('end', () => { const segment = agent.tracer.getTransaction().trace.root.children[0] - const span = SpanEvent.fromSegment(segment, 'parent') + const span = SpanEvent.fromSegment(segment, transaction, 'parent') // Should have all the normal properties. assert.ok(span) @@ -238,7 +238,7 @@ test('fromSegment()', async (t) => { dsConn.myDbOp(longQuery, () => { transaction.end() const segment = transaction.trace.root.children[0] - const span = SpanEvent.fromSegment(segment, 'parent') + const span = SpanEvent.fromSegment(segment, transaction, 'parent') // Should have all the normal properties. assert.ok(span) @@ -303,7 +303,7 @@ test('fromSegment()', async (t) => { setTimeout(() => { const segment = agent.tracer.getSegment() - const span = SpanEvent.fromSegment(segment, 'parent') + const span = SpanEvent.fromSegment(segment, transaction, 'parent') const serializedSpan = span.toJSON() const [intrinsics] = serializedSpan @@ -336,7 +336,7 @@ test('fromSegment()', async (t) => { spanContext.addIntrinsicAttribute('intrinsic.1', 1) spanContext.addIntrinsicAttribute('intrinsic.2', 2) - const span = SpanEvent.fromSegment(segment, 'parent') + const span = SpanEvent.fromSegment(segment, transaction, 'parent') const serializedSpan = span.toJSON() const [intrinsics] = serializedSpan @@ -360,7 +360,7 @@ test('fromSegment()', async (t) => { const segment = transaction.trace.root.children[0] assert.ok(segment.name.startsWith('Truncated')) - const span = SpanEvent.fromSegment(segment) + const span = SpanEvent.fromSegment(segment, transaction) assert.ok(span) assert.ok(span instanceof SpanEvent) assert.ok(span instanceof SpanEvent.HttpSpanEvent) @@ -374,12 +374,12 @@ test('fromSegment()', async (t) => { await t.test('should handle truncated datastore spans', (t, end) => { const { agent } = t.nr helper.runInTransaction(agent, (transaction) => { - const segment = transaction.trace.root.add('Datastore/operation/something') + const segment = transaction.trace.add('Datastore/operation/something') transaction.end() // end before segment to trigger truncate assert.ok(segment.name.startsWith('Truncated')) - const span = SpanEvent.fromSegment(segment) + const span = SpanEvent.fromSegment(segment, transaction) assert.ok(span) assert.ok(span instanceof SpanEvent) assert.ok(span instanceof SpanEvent.DatastoreSpanEvent) diff --git a/test/unit/spans/streaming-span-event.test.js b/test/unit/spans/streaming-span-event.test.js index 4c63ce144c..3e92aaa3cc 100644 --- a/test/unit/spans/streaming-span-event.test.js +++ b/test/unit/spans/streaming-span-event.test.js @@ -66,7 +66,7 @@ test('fromSegment()', async (t) => { segment.addSpanAttribute('host', 'my-host') segment.addSpanAttribute('port', 22) - const span = StreamingSpanEvent.fromSegment(segment, 'parent') + const span = StreamingSpanEvent.fromSegment(segment, transaction, 'parent') // Should have all the normal properties. assert.ok(span) @@ -131,7 +131,7 @@ test('fromSegment()', async (t) => { res.resume() res.on('end', () => { const segment = agent.tracer.getTransaction().trace.root.children[0] - const span = StreamingSpanEvent.fromSegment(segment, 'parent') + const span = StreamingSpanEvent.fromSegment(segment, transaction, 'parent') // Should have all the normal properties. assert.ok(span) @@ -238,7 +238,7 @@ test('fromSegment()', async (t) => { dsConn.myDbOp(longQuery, () => { transaction.end() const segment = transaction.trace.root.children[0] - const span = StreamingSpanEvent.fromSegment(segment, 'parent') + const span = StreamingSpanEvent.fromSegment(segment, transaction, 'parent') // Should have all the normal properties. assert.ok(span) @@ -329,7 +329,7 @@ test('fromSegment()', async (t) => { const spanContext = agent.tracer.getSpanContext() spanContext.addCustomAttribute('customKey', 'customValue') - const span = StreamingSpanEvent.fromSegment(segment, 'parent') + const span = StreamingSpanEvent.fromSegment(segment, transaction, 'parent') const serializedSpan = span.toStreamingFormat() const { @@ -366,7 +366,7 @@ test('fromSegment()', async (t) => { spanContext.addIntrinsicAttribute('intrinsic.1', 1) spanContext.addIntrinsicAttribute('intrinsic.2', 2) - const span = StreamingSpanEvent.fromSegment(segment, 'parent') + const span = StreamingSpanEvent.fromSegment(segment, transaction, 'parent') const serializedSpan = span.toStreamingFormat() const { intrinsics } = serializedSpan @@ -390,7 +390,7 @@ test('fromSegment()', async (t) => { const segment = transaction.trace.root.children[0] assert.ok(segment.name.startsWith('Truncated')) - const span = StreamingSpanEvent.fromSegment(segment) + const span = StreamingSpanEvent.fromSegment(segment, transaction) assert.ok(span) assert.ok(span instanceof StreamingSpanEvent) @@ -407,12 +407,12 @@ test('fromSegment()', async (t) => { await t.test('should handle truncated datastore spans', (t, end) => { const { agent } = t.nr helper.runInTransaction(agent, (transaction) => { - const segment = transaction.trace.root.add('Datastore/operation/something') + const segment = transaction.trace.add('Datastore/operation/something') transaction.end() // end before segment to trigger truncate assert.ok(segment.name.startsWith('Truncated')) - const span = StreamingSpanEvent.fromSegment(segment) + const span = StreamingSpanEvent.fromSegment(segment, transaction) assert.ok(span) assert.ok(span instanceof StreamingSpanEvent) diff --git a/test/unit/transaction.test.js b/test/unit/transaction.test.js index 8a643a45d0..5f34a1dc09 100644 --- a/test/unit/transaction.test.js +++ b/test/unit/transaction.test.js @@ -1142,11 +1142,11 @@ test('_createDistributedTracePayload', async (t) => { await t.test('adds the current span id as the parent span id', (t) => { const { agent, txn, tracer } = t.nr agent.config.span_events.enabled = true - tracer.setSegment(txn.trace.root) + tracer.setSegment({ segment: txn.trace.root, transaction: txn }) txn.sampled = true const payload = JSON.parse(txn._createDistributedTracePayload().text()) assert.equal(payload.d.id, txn.trace.root.id) - tracer.setSegment(null) + tracer.setSegment({ segment: null, transaction: null }) agent.config.span_events.enabled = false }) @@ -1155,10 +1155,10 @@ test('_createDistributedTracePayload', async (t) => { agent.config.span_events.enabled = true txn._calculatePriority() txn.sampled = false - tracer.setSegment(txn.trace.root) + tracer.setSegment({ segment: txn.trace.root, transaction: txn }) const payload = JSON.parse(txn._createDistributedTracePayload().text()) assert.equal(payload.d.id, undefined) - tracer.setSegment(null) + tracer.setSegment({ segment: null, transaction: null }) agent.config.span_events.enabled = false }) @@ -1458,7 +1458,7 @@ test('insertDistributedTraceHeaders', async (t) => { const txn = new Transaction(agent) - tracer.setSegment(txn.trace.root) + tracer.setSegment({ transaction: txn, segment: txn.trace.root }) const outboundHeaders = createHeadersAndInsertTrace(txn) const traceparent = outboundHeaders.traceparent @@ -1485,7 +1485,7 @@ test('insertDistributedTraceHeaders', async (t) => { const txn = new Transaction(agent) const lowercaseHexRegex = /^[a-f0-9]+/ - tracer.setSegment(txn.trace.root) + tracer.setSegment({ transaction: txn, segment: txn.trace.root }) const outboundHeaders = createHeadersAndInsertTrace(txn) const traceparent = outboundHeaders.traceparent @@ -1504,7 +1504,7 @@ test('insertDistributedTraceHeaders', async (t) => { const txn = new Transaction(agent) - tracer.setSegment(txn.trace.root) + tracer.setSegment({ transaction: txn, segment: txn.trace.root }) txn.sampled = true const outboundHeaders = createHeadersAndInsertTrace(txn) @@ -1526,7 +1526,7 @@ test('insertDistributedTraceHeaders', async (t) => { txn.acceptTraceContextPayload(traceparent, tracestate) - tracer.setSegment(txn.trace.root) + tracer.setSegment({ transaction: txn, segment: txn.trace.root }) const outboundHeaders = createHeadersAndInsertTrace(txn) const traceparentParts = outboundHeaders.traceparent.split('-') @@ -2027,7 +2027,7 @@ test('when being named with finalizeName', async (t) => { }) function setupNameState(transaction) { - transaction.baseSegment = transaction.trace.root.add('basesegment') + transaction.baseSegment = transaction.trace.add('basesegment') transaction.nameState.setPrefix('Restify') transaction.nameState.setVerb('COOL') transaction.nameState.setDelimiter('/') @@ -2053,8 +2053,12 @@ function createHeadersAndInsertTrace(transaction) { } function addSegmentInContext(tracer, transaction, name) { - const segment = new Segment(transaction, name) - tracer.setSegment(segment) + const segment = new Segment({ + config: transaction.agent.config, + name, + root: transaction.trace.root + }) + tracer.setSegment({ transaction, segment }) return segment } diff --git a/test/unit/transaction/trace/index.test.js b/test/unit/transaction/trace/index.test.js index b3641a0cd9..46316f3236 100644 --- a/test/unit/transaction/trace/index.test.js +++ b/test/unit/transaction/trace/index.test.js @@ -113,7 +113,7 @@ test('Trace', async (t) => { const trace = transaction.trace const child1 = (transaction.baseSegment = trace.add('test')) child1.start() - const child2 = child1.add('nested') + const child2 = trace.add('nested', null, child1) child2.start() child1.end() child2.end() @@ -171,7 +171,7 @@ test('Trace', async (t) => { const trace = transaction.trace const child1 = trace.add('test') child1.start() - const child2 = child1.add('nested') + const child2 = trace.add('nested', null, child1) child2.start() child1.end() child2.end() @@ -192,7 +192,7 @@ test('Trace', async (t) => { const trace = transaction.trace const child1 = trace.add('test') child1.start() - const child2 = child1.add('nested') + const child2 = trace.add('nested', null, child1) child2.start() child1.end() child2.end() @@ -203,30 +203,6 @@ test('Trace', async (t) => { assert.equal(events.length, 0) }) - await t.test('should not generate span events on end if transaction is not sampled', (t) => { - const { agent } = t.nr - agent.config.span_events.enabled = true - agent.config.distributed_tracing.enabled = false - - const transaction = new Transaction(agent) - - const trace = transaction.trace - const child1 = trace.add('test') - child1.start() - const child2 = child1.add('nested') - child2.start() - child1.end() - child2.end() - trace.root.end() - - transaction.priority = 0 - transaction.sampled = false - transaction.end() - - const events = agent.spanEventAggregator.getEvents() - assert.equal(events.length, 0) - }) - await t.test('parent.* attributes should be present on generated spans', (t) => { const { agent } = t.nr // Setup DT @@ -258,6 +234,7 @@ test('Trace', async (t) => { // Create at least one segment const trace = new Trace(transaction) const child = (transaction.baseSegment = trace.add('test')) + child.start() child.end() @@ -299,6 +276,7 @@ test('Trace', async (t) => { // add a child segment const child = (transaction.baseSegment = trace.add('test')) + child.start() child.end() @@ -500,39 +478,83 @@ test('when inserting segments', async (t) => { }) await t.test('should report total time', (t) => { - const { trace } = t.nr + const { agent, trace } = t.nr + const root = trace.root trace.setDurationInMillis(40, 0) const child = trace.add('Custom/Test18/Child1') + child.setDurationInMillis(27, 0) - let seg = child.add('UnitTest') + let seg = child.add({ + config: agent.config, + name: 'UnitTest', + collect: true, + root + }) seg.setDurationInMillis(9, 1) - seg = child.add('UnitTest1') + seg = child.add({ + config: agent.config, + name: 'UnitTest1', + collect: true, + root + }) seg.setDurationInMillis(13, 1) - seg = child.add('UnitTest2') + seg = child.add({ + config: agent.config, + name: 'UnitTest2', + collect: true, + root + }) seg.setDurationInMillis(9, 16) - seg = child.add('UnitTest2') + seg = child.add({ + config: agent.config, + name: 'UnitTest2', + collect: true, + root + }) seg.setDurationInMillis(14, 16) assert.equal(trace.getTotalTimeDurationInMillis(), 48) }) await t.test('should report total time on branched traces', (t) => { - const { trace } = t.nr + const { trace, agent } = t.nr + const root = trace.root trace.setDurationInMillis(40, 0) const child = trace.add('Custom/Test18/Child1') child.setDurationInMillis(27, 0) - const seg1 = child.add('UnitTest') + const seg1 = child.add({ + config: agent.config, + name: 'UnitTest', + collect: true, + root + }) seg1.setDurationInMillis(9, 1) - let seg = child.add('UnitTest1') + let seg = child.add({ + config: agent.config, + name: 'UnitTest1', + collect: true, + root + }) seg.setDurationInMillis(13, 1) - seg = seg1.add('UnitTest2') + seg = seg1.add({ + config: agent.config, + name: 'UnitTest2', + collect: true, + root + }) seg.setDurationInMillis(9, 16) - seg = seg1.add('UnitTest2') + seg = seg1.add({ + config: agent.config, + name: 'UnitTest2', + collect: true, + root + }) seg.setDurationInMillis(14, 16) assert.equal(trace.getTotalTimeDurationInMillis(), 48) }) await t.test('should report the expected trees for trees with uncollected segments', (t) => { - const { trace } = t.nr + const { agent, trace } = t.nr + const root = trace.root const expectedTrace = [ 0, 27, @@ -561,19 +583,51 @@ test('when inserting segments', async (t) => { trace.setDurationInMillis(40, 0) const child = trace.add('Root') + child.setDurationInMillis(27, 0) - const seg1 = child.add('first') + const seg1 = child.add({ + config: agent.config, + name: 'first', + collect: true, + root + }) + seg1.setDurationInMillis(9, 1) - const seg2 = child.add('second') + const seg2 = child.add({ + config: agent.config, + name: 'second', + collect: true, + root + }) seg2.setDurationInMillis(13, 1) - let seg = seg1.add('first-first') + let seg = seg1.add({ + config: agent.config, + name: 'first-first', + collect: true, + root + }) seg.setDurationInMillis(9, 16) - seg = seg1.add('first-second') + seg = seg1.add({ + config: agent.config, + name: 'first-second', + collect: true, + root + }) seg.setDurationInMillis(14, 16) seg._collect = false - seg = seg2.add('second-first') + seg = seg2.add({ + config: agent.config, + name: 'second-first', + collect: true, + root + }) seg.setDurationInMillis(9, 16) - seg = seg2.add('second-second') + seg = seg2.add({ + config: agent.config, + name: 'second-second', + collect: true, + root + }) seg.setDurationInMillis(9, 16) trace.end() @@ -582,7 +636,7 @@ test('when inserting segments', async (t) => { }) await t.test('should report the expected trees for branched trees', (t) => { - const { trace } = t.nr + const { agent, trace } = t.nr const expectedTrace = [ 0, 27, @@ -613,18 +667,50 @@ test('when inserting segments', async (t) => { ] trace.setDurationInMillis(40, 0) const child = trace.add('Root') + const root = trace.root + child.setDurationInMillis(27, 0) - const seg1 = child.add('first') + const seg1 = child.add({ + config: agent.config, + name: 'first', + collect: true, + root + }) seg1.setDurationInMillis(9, 1) - const seg2 = child.add('second') + const seg2 = child.add({ + config: agent.config, + name: 'second', + collect: true, + root + }) seg2.setDurationInMillis(13, 1) - let seg = seg1.add('first-first') + let seg = seg1.add({ + config: agent.config, + name: 'first-first', + collect: true, + root + }) seg.setDurationInMillis(9, 16) - seg = seg1.add('first-second') + seg = seg1.add({ + config: agent.config, + name: 'first-second', + collect: true, + root + }) seg.setDurationInMillis(14, 16) - seg = seg2.add('second-first') + seg = seg2.add({ + config: agent.config, + name: 'second-first', + collect: true, + root + }) seg.setDurationInMillis(9, 16) - seg = seg2.add('second-second') + seg = seg2.add({ + config: agent.config, + name: 'second-second', + collect: true, + root + }) seg.setDurationInMillis(9, 16) trace.end() @@ -657,34 +743,56 @@ test('when inserting segments', async (t) => { // add another that starts within the first range but that extends beyond const child3 = trace.add('Custom/Test19/Child3') + child3.setDurationInMillis(22, now + 11) // add a final child that's entirely disjoint const child4 = trace.add('Custom/Test19/Child4') + child4.setDurationInMillis(4, now + 35) assert.equal(trace.getExclusiveDurationInMillis(), 5) }) await t.test('should accurately sum overlapping subtrees', (t) => { - const { trace } = t.nr + const { agent, trace } = t.nr trace.setDurationInMillis(42) const now = Date.now() + const root = trace.root // create a long child on its own const child1 = trace.add('Custom/Test20/Child1') + child1.setDurationInMillis(33, now) // add another, short child as a sibling - const child2 = child1.add('Custom/Test20/Child2') + const child2 = child1.add({ + config: agent.config, + name: 'Custom/Test20/Child2', + collect: true, + root + }) + child2.setDurationInMillis(5, now) // add two disjoint children of the second segment encompassed by the first segment - const child3 = child2.add('Custom/Test20/Child3') + const child3 = child2.add({ + config: agent.config, + name: 'Custom/Test20/Child3', + collect: true, + root + }) + child3.setDurationInMillis(11, now) - const child4 = child2.add('Custom/Test20/Child3') + const child4 = child2.add({ + config: agent.config, + name: 'Custom/Test20/Child3', + collect: true, + root + }) + child4.setDurationInMillis(11, now + 16) assert.equal(trace.getExclusiveDurationInMillis(), 9) @@ -923,9 +1031,14 @@ test('infinite tracing', async (t) => { function addTwoSegments(transaction) { const trace = transaction.trace + const root = trace.root const child1 = (transaction.baseSegment = trace.add('test')) child1.start() - const child2 = child1.add('nested') + const child2 = child1.add({ + config: transaction.agent.config, + name: 'nested', + root + }) child2.start() child1.end() child2.end() @@ -947,6 +1060,7 @@ async function makeTrace(agent) { transaction.timer.setDurationInMillis(DURATION) const trace = transaction.trace + const root = trace.root // promisifying `trace.generateJSON` so tests do not have to call done // and instead use async/await @@ -955,16 +1069,31 @@ async function makeTrace(agent) { assert.ok(start > 0, "root segment's start time") trace.setDurationInMillis(DURATION, 0) - const web = trace.root.add(URL) + const web = trace.root.add({ + config: agent.config, + name: URL, + collect: true, + root + }) transaction.baseSegment = web transaction.finalizeNameFromUri(URL, 200) // top-level element will share a duration with the quasi-ROOT node web.setDurationInMillis(DURATION, 0) - const db = web.add('Database/statement/AntiSQL/select/getSome') + const db = web.add({ + config: agent.config, + name: 'Database/statement/AntiSQL/select/getSome', + collect: true, + root + }) db.setDurationInMillis(14, 3) - const memcache = web.add('Datastore/operation/Memcache/lookup') + const memcache = web.add({ + config: agent.config, + name: 'Datastore/operation/Memcache/lookup', + collect: true, + root + }) memcache.setDurationInMillis(20, 8) trace.end() diff --git a/test/unit/transaction/trace/segment.test.js b/test/unit/transaction/trace/segment.test.js index a56a4444a6..c8bcf63e6c 100644 --- a/test/unit/transaction/trace/segment.test.js +++ b/test/unit/transaction/trace/segment.test.js @@ -16,6 +16,7 @@ const Transaction = require('../../../../lib/transaction') function beforeEach(ctx) { ctx.nr = {} ctx.nr.agent = helper.loadMockedAgent() + ctx.nr.agent.config.logging.diagnostics = true } function afterEach(ctx) { @@ -26,100 +27,114 @@ test('TraceSegment', async (t) => { t.beforeEach(beforeEach) t.afterEach(afterEach) - await t.test('should be bound to a Trace', (t) => { - const { agent } = t.nr - let segment = null - const trans = new Transaction(agent) - assert.throws(function noTrace() { - segment = new TraceSegment(null, 'UnitTest') - }) - assert.equal(segment, null) - - const success = new TraceSegment(trans, 'UnitTest') - assert.equal(success.transaction, trans) - trans.end() - }) - await t.test('should not add new children when marked as opaque', (t) => { const { agent } = t.nr const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') + const root = trans.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'UnitTest', + collect: true, + root + }) assert.ok(!segment.opaque) segment.opaque = true - segment.add('child') + segment.add({ + config: agent.config, + name: 'child', + collect: true, + root + }) assert.equal(segment.children.length, 0) segment.opaque = false - segment.add('child') - assert.equal(segment.children.length, 1) - trans.end() - }) - - await t.test('should call an optional callback function', (t, end) => { - const { agent } = t.nr - const trans = new Transaction(agent) - assert.doesNotThrow(function noCallback() { - new TraceSegment(trans, 'UnitTest') // eslint-disable-line no-new - }) - const working = new TraceSegment(trans, 'UnitTest', function () { - end() + segment.add({ + config: agent.config, + name: 'child', + collect: true, + root }) - working.end() + assert.equal(segment.children.length, 1) trans.end() }) await t.test('has a name', (t) => { const { agent } = t.nr const trans = new Transaction(agent) - const success = new TraceSegment(trans, 'UnitTest') + const root = trans.trace.root + const success = new TraceSegment({ + config: agent.config, + name: 'UnitTest', + collect: true, + root + }) assert.equal(success.name, 'UnitTest') }) await t.test('is created with no children', (t) => { const { agent } = t.nr const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') + const root = trans.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'UnitTest', + collect: true, + root + }) assert.equal(segment.children.length, 0) }) await t.test('has a timer', (t) => { const { agent } = t.nr const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') + const root = trans.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'UnitTest', + collect: true, + root + }) assert.ok(segment.timer) }) await t.test('does not start its timer on creation', (t) => { const { agent } = t.nr const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') + const root = trans.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'UnitTest', + collect: true, + root + }) assert.equal(segment.timer.isRunning(), false) }) await t.test('allows the timer to be updated without ending it', (t) => { const { agent } = t.nr const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') + const root = trans.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'UnitTest', + collect: true, + root + }) segment.start() segment.touch() assert.equal(segment.timer.isRunning(), true) assert.ok(segment.getDurationInMillis() > 0) }) - await t.test('accepts a callback that records metrics for this segment', (t, end) => { - const { agent } = t.nr - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'Test', (insider) => { - assert.equal(insider, segment) - end() - }) - segment.end() - trans.end() - }) - await t.test('should return the segment id when dt and spans are enabled', (t) => { const { agent } = t.nr const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'Test') + const root = trans.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'Test', + collect: true, + root + }) agent.config.distributed_tracing.enabled = true agent.config.span_events.enabled = true assert.equal(segment.getSpanId(), segment.id) @@ -128,18 +143,30 @@ test('TraceSegment', async (t) => { await t.test('should return null when dt is disabled', (t) => { const { agent } = t.nr const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'Test') + const root = trans.trace.root agent.config.distributed_tracing.enabled = false agent.config.span_events.enabled = true + const segment = new TraceSegment({ + config: agent.config, + name: 'Test', + collect: true, + root + }) assert.equal(segment.getSpanId(), null) }) await t.test('should return null when spans are disabled', (t) => { const { agent } = t.nr const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'Test') + const root = trans.trace.root agent.config.distributed_tracing.enabled = true agent.config.span_events.enabled = false + const segment = new TraceSegment({ + config: agent.config, + name: 'Test', + collect: true, + root + }) assert.ok(segment.getSpanId() === null) }) @@ -147,7 +174,13 @@ test('TraceSegment', async (t) => { const { agent } = t.nr const trans = new Transaction(agent) const trace = trans.trace - const segment = new TraceSegment(trans, 'Test') + const root = trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'Test', + collect: true, + root + }) segment.setDurationInMillis(10, 0) @@ -159,43 +192,16 @@ test('TraceSegment', async (t) => { }, 10) }) - await t.test('properly tracks the number of active or harvested segments', (t, end) => { - const { agent } = t.nr - assert.equal(agent.activeTransactions, 0) - assert.equal(agent.totalActiveSegments, 0) - assert.equal(agent.segmentsCreatedInHarvest, 0) - - const tx = new Transaction(agent) - assert.equal(agent.totalActiveSegments, 1) - assert.equal(agent.segmentsCreatedInHarvest, 1) - assert.equal(tx.numSegments, 1) - assert.equal(agent.activeTransactions, 1) - - const segment = new TraceSegment(tx, 'Test') // eslint-disable-line no-unused-vars - assert.equal(agent.totalActiveSegments, 2) - assert.equal(agent.segmentsCreatedInHarvest, 2) - assert.equal(tx.numSegments, 2) - tx.end() - - assert.equal(agent.activeTransactions, 0) - - setTimeout(function () { - assert.equal(agent.totalActiveSegments, 0) - assert.equal(agent.segmentsClearedInHarvest, 2) - - agent.forceHarvestAll(() => { - assert.equal(agent.totalActiveSegments, 0) - assert.equal(agent.segmentsClearedInHarvest, 0) - assert.equal(agent.segmentsCreatedInHarvest, 0) - end() - }) - }, 10) - }) - await t.test('toJSON should not modify attributes', (t) => { const { agent } = t.nr const transaction = new Transaction(agent) - const segment = new TraceSegment(transaction, 'TestSegment') + const root = transaction.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'TestSegment', + collect: true, + root + }) segment.toJSON() assert.deepEqual(segment.getAttributes(), {}) }) @@ -203,7 +209,13 @@ test('TraceSegment', async (t) => { await t.test('when ended stops its timer', (t) => { const { agent } = t.nr const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') + const root = trans.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'UnitTest', + collect: true, + root + }) segment.end() assert.equal(segment.timer.isRunning(), false) }) @@ -232,7 +244,13 @@ test('TraceSegment', async (t) => { await t.test('#finalize should add nr_exclusive_duration_millis attribute', (t) => { const { agent } = t.nr const transaction = new Transaction(agent) - const segment = new TraceSegment(transaction, 'TestSegment') + const root = transaction.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'TestSegment', + collect: true, + root + }) segment._setExclusiveDurationInMillis(1) @@ -248,14 +266,18 @@ test('TraceSegment', async (t) => { const segmentName = 'TestSegment' const transaction = new Transaction(agent) - const segment = new TraceSegment(transaction, segmentName) + const root = transaction.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: segmentName, + collect: true, + root + }) // Force truncation sinon.stub(segment.timer, 'softEnd').returns(true) sinon.stub(segment.timer, 'endsAfter').returns(true) - const root = transaction.trace.root - // Make root duration calculation predictable root.timer.start = 1000 segment.timer.start = 1001 @@ -277,11 +299,17 @@ test('with children created from URLs', async (t) => { const transaction = new Transaction(ctx.nr.agent) const trace = transaction.trace + const root = transaction.trace.root const segment = trace.add('UnitTest') const url = '/test?test1=value1&test2&test3=50&test4=' - const webChild = segment.add(url) + const webChild = segment.add({ + config: ctx.nr.agent, + name: url, + collect: true, + root + }) transaction.baseSegment = webChild transaction.finalizeNameFromUri(url, 200) @@ -346,6 +374,7 @@ test('with parameters parsed out by framework', async (t) => { const transaction = new Transaction(ctx.nr.agent) const trace = transaction.trace + const root = trace.root trace.mer = 6 const segment = trace.add('UnitTest') @@ -358,7 +387,12 @@ test('with parameters parsed out by framework', async (t) => { params[1] = 'another' params.test3 = '50' - const webChild = segment.add(url) + const webChild = segment.add({ + config: ctx.nr.agent.config, + name: url, + collect: true, + root + }) transaction.trace.attributes.addAttributes(DESTINATIONS.TRANS_SCOPE, params) transaction.baseSegment = webChild transaction.finalizeNameFromUri(url, 200) @@ -419,10 +453,17 @@ test('with attributes.enabled set to false', async (t) => { const transaction = new Transaction(ctx.nr.agent) const trace = transaction.trace - const segment = new TraceSegment(transaction, 'UnitTest') + const root = trace.root + const segment = trace.add('UnitTest') const url = '/test?test1=value1&test2&test3=50&test4=' - const webChild = segment.add(url) + const webChild = segment.add({ + config: ctx.nr.agent.config, + name: url, + collect: true, + + root + }) webChild.addAttribute('test', 'non-null value') transaction.baseSegment = webChild transaction.finalizeNameFromUri(url, 200) @@ -463,14 +504,21 @@ test('with attributes.enabled set', async (t) => { const transaction = new Transaction(ctx.nr.agent) const trace = transaction.trace + const root = trace.root const segment = trace.add('UnitTest') const url = '/test?test1=value1&test2&test3=50&test4=' - const webChild = segment.add(url) + const webChild = segment.add({ + config: ctx.nr.agent.config, + name: url, + collect: true, + + root + }) transaction.baseSegment = webChild transaction.finalizeNameFromUri(url, 200) - webChild.markAsWeb(url) + webChild.markAsWeb(transaction) trace.setDurationInMillis(1, 0) webChild.setDurationInMillis(1, 0) @@ -528,13 +576,23 @@ test('with attributes.enabled set', async (t) => { test('when serialized', async (t) => { t.beforeEach((ctx) => { const agent = helper.loadMockedAgent() - const trans = new Transaction(agent) - const segment = new TraceSegment(trans, 'UnitTest') + const transaction = new Transaction(agent) + const root = transaction.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'UnitTest', + collect: true, + root + }) + ctx.nr = { - agent, + agent: agent, segment, - trans + transaction, + root } + + ctx.nr.agent.config.logging.diagnostics = true }) t.afterEach((ctx) => { @@ -559,10 +617,15 @@ test('when serialized', async (t) => { }) await t.test('should not cause a stack overflow', { timeout: 30000 }, (t) => { - const { segment, trans } = t.nr + const { segment, agent, root } = t.nr let parent = segment for (let i = 0; i < 9000; ++i) { - const child = new TraceSegment(trans, 'Child ' + i) + const child = new TraceSegment({ + config: agent.config, + name: 'Child ' + i, + collect: true, + root + }) parent.children.push(child) parent = child } @@ -581,7 +644,13 @@ test('getSpanContext', async (t) => { } }) const transaction = new Transaction(agent) - const segment = new TraceSegment(transaction, 'UnitTest') + const root = transaction.trace.root + const segment = new TraceSegment({ + config: agent.config, + name: 'UnitTest', + collect: true, + root + }) ctx.nr = { agent, segment, @@ -605,15 +674,27 @@ test('getSpanContext', async (t) => { }) await t.test('should not create a new context when empty and DT disabled', (t) => { - const { agent, segment } = t.nr + const { agent, transaction } = t.nr agent.config.distributed_tracing.enabled = false + const segment = new TraceSegment({ + config: agent.config, + name: 'UnitTest', + collect: true, + root: transaction.trace.root + }) const spanContext = segment.getSpanContext() assert.ok(!spanContext) }) await t.test('should not create a new context when empty and Spans disabled', (t) => { - const { agent, segment } = t.nr + const { agent, transaction } = t.nr agent.config.span_events.enabled = false + const segment = new TraceSegment({ + config: agent.config, + name: 'UnitTest', + collect: true, + root: transaction.trace.root + }) const spanContext = segment.getSpanContext() assert.ok(!spanContext) }) diff --git a/test/unit/transaction/tracer.test.js b/test/unit/transaction/tracer.test.js index bbc6ea2465..a2ecbd101f 100644 --- a/test/unit/transaction/tracer.test.js +++ b/test/unit/transaction/tracer.test.js @@ -8,6 +8,7 @@ const assert = require('node:assert') const test = require('node:test') const helper = require('../../lib/agent_helper') const Segment = require('../../../lib/transaction/trace/segment') +const Transaction = require('../../../lib/transaction') const notRunningStates = ['stopped', 'stopping', 'errored'] function beforeEach(ctx) { @@ -118,7 +119,10 @@ test('Tracer', async function (t) { } assert.throws(() => { - const fn = tracer.bindFunction(wrapMe, new Segment(trans, 'name')) + const segment = new Segment({ name: 'name', isRoot: false, root: trans.trace.root }) + let context = tracer.getContext() + context = context.enterSegment({ segment }) + const fn = tracer.bindFunction(wrapMe, context) fn() }, /Error: FIREBOMB/) end() @@ -142,4 +146,89 @@ test('Tracer', async function (t) { } ) }) + + await t.test('Optional callback', async (t) => { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await t.test('should call an optional callback function', (t, end) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const trace = trans.trace + + assert.doesNotThrow(function noCallback() { + trace.add('UnitTest', null, null) + }) + + const working = trace.add( + 'UnitTest', + function () { + end() + }, + null, + false, + function () {} + ) + + working.end() + trans.end() + }) + + await t.test('accepts a callback that records metrics for this segment', (t, end) => { + const { agent } = t.nr + const trans = new Transaction(agent) + const trace = trans.trace + + const segment = trace.add( + 'Test', + (insider) => { + assert.equal(insider, segment) + end() + }, + null, + false, + function () {} + ) + segment.end() + trans.end() + }) + }) + + await t.test('increment segments', async (t) => { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + await t.test('properly tracks the number of active or harvested segments', (t, end) => { + const { agent, tracer } = t.nr + assert.equal(agent.activeTransactions, 0) + assert.equal(agent.totalActiveSegments, 0) + assert.equal(agent.segmentsCreatedInHarvest, 0) + + const tx = new Transaction(agent) + tracer.setSegment({ transaction: tx, segment: tx.trace.root }) + assert.equal(agent.totalActiveSegments, 1) + assert.equal(agent.segmentsCreatedInHarvest, 1) + assert.equal(tx.numSegments, 1) + assert.equal(agent.activeTransactions, 1) + + tracer.createSegment({ name: 'Test', parent: tx.trace.root, transaction: tx }) + assert.equal(agent.totalActiveSegments, 2) + assert.equal(agent.segmentsCreatedInHarvest, 2) + assert.equal(tx.numSegments, 2) + tx.end() + + assert.equal(agent.activeTransactions, 0) + + setTimeout(function () { + assert.equal(agent.totalActiveSegments, 0) + assert.equal(agent.segmentsClearedInHarvest, 2) + + agent.forceHarvestAll(() => { + assert.equal(agent.totalActiveSegments, 0) + assert.equal(agent.segmentsClearedInHarvest, 0) + assert.equal(agent.segmentsCreatedInHarvest, 0) + end() + }) + }, 10) + }) + }) }) diff --git a/test/versioned-external/external-repos.js b/test/versioned-external/external-repos.js index 396200a2f9..f2c70b6260 100644 --- a/test/versioned-external/external-repos.js +++ b/test/versioned-external/external-repos.js @@ -15,7 +15,7 @@ const repos = [ { name: 'apollo-server', repository: 'https://github.com/newrelic/newrelic-node-apollo-server-plugin.git', - branch: 'main', + branch: 'remove-transaction-from-segment', additionalFiles: [ 'tests/agent-testing.js', 'tests/create-apollo-server-setup.js', diff --git a/test/versioned/amqplib-esm/issue-2663.test.mjs b/test/versioned/amqplib-esm/issue-2663.test.mjs index 0e790198a0..e0ef1c30f4 100644 --- a/test/versioned/amqplib-esm/issue-2663.test.mjs +++ b/test/versioned/amqplib-esm/issue-2663.test.mjs @@ -46,7 +46,7 @@ test('esm import does instrumentation', async () => { const body = msg.content.toString('utf8') assert.equal(body, 'hello', 'should receive expected body') - amqpUtils.verifyTransaction(tx, 'get') + amqpUtils.verifyTransaction(agent, tx, 'get') channel.ack(msg) tx.end() amqpUtils.verifyGet({ diff --git a/test/versioned/amqplib/amqp-utils.js b/test/versioned/amqplib/amqp-utils.js index f9cc6228ce..c0ac47fcce 100644 --- a/test/versioned/amqplib/amqp-utils.js +++ b/test/versioned/amqplib/amqp-utils.js @@ -277,11 +277,9 @@ function verifyPurge(tx) { assertMetrics(tx.metrics, [[{ name: 'MessageBroker/RabbitMQ/Queue/Purge/Temp' }]], false, false) } -function verifyTransaction(tx, msg) { - const seg = tx.agent.tracer.getSegment() - if (seg) { - assert.equal(seg.transaction.id, tx.id, 'should have correct transaction in ' + msg) - } +function verifyTransaction(agent, tx, msg) { + const transaction = agent.getTransaction() + assert.equal(transaction.id, tx.id, 'should have correct transaction in ' + msg) } function getChannel(amqplib, cb) { diff --git a/test/versioned/amqplib/callback.test.js b/test/versioned/amqplib/callback.test.js index d0f3a9ab86..9d1e7e87f6 100644 --- a/test/versioned/amqplib/callback.test.js +++ b/test/versioned/amqplib/callback.test.js @@ -113,16 +113,16 @@ test('amqplib callback instrumentation', async function (t) { assert.ok(agent.tracer.getSegment(), 'should start in transaction') channel.assertExchange(exchange, 'fanout', null, function (err) { assert.ok(!err, 'should not error asserting exchange') - amqpUtils.verifyTransaction(tx, 'assertExchange') + amqpUtils.verifyTransaction(agent, tx, 'assertExchange') channel.assertQueue('', { exclusive: true }, function (err, result) { assert.ok(!err, 'should not error asserting queue') - amqpUtils.verifyTransaction(tx, 'assertQueue') + amqpUtils.verifyTransaction(agent, tx, 'assertQueue') const queueName = result.queue channel.bindQueue(queueName, exchange, '', null, function (err) { assert.ok(!err, 'should not error binding queue') - amqpUtils.verifyTransaction(tx, 'bindQueue') + amqpUtils.verifyTransaction(agent, tx, 'bindQueue') channel.publish(exchange, '', Buffer.from('hello')) setImmediate(function () { tx.end() @@ -145,16 +145,16 @@ test('amqplib callback instrumentation', async function (t) { helper.runInTransaction(agent, function (tx) { channel.assertExchange(exchange, 'direct', null, function (err) { assert.ok(!err, 'should not error asserting exchange') - amqpUtils.verifyTransaction(tx, 'assertExchange') + amqpUtils.verifyTransaction(agent, tx, 'assertExchange') channel.assertQueue('', { exclusive: true }, function (err, result) { assert.ok(!err, 'should not error asserting queue') - amqpUtils.verifyTransaction(tx, 'assertQueue') + amqpUtils.verifyTransaction(agent, tx, 'assertQueue') const queueName = result.queue channel.bindQueue(queueName, exchange, 'key1', null, function (err) { assert.ok(!err, 'should not error binding queue') - amqpUtils.verifyTransaction(tx, 'bindQueue') + amqpUtils.verifyTransaction(agent, tx, 'bindQueue') channel.publish(exchange, 'key1', Buffer.from('hello')) setImmediate(function () { tx.end() @@ -178,16 +178,16 @@ test('amqplib callback instrumentation', async function (t) { helper.runInTransaction(agent, function (tx) { channel.assertExchange(exchange, 'direct', null, function (err) { assert.ok(!err, 'should not error asserting exchange') - amqpUtils.verifyTransaction(tx, 'assertExchange') + amqpUtils.verifyTransaction(agent, tx, 'assertExchange') channel.assertQueue('', { exclusive: true }, function (err, result) { assert.ok(!err, 'should not error asserting queue') - amqpUtils.verifyTransaction(tx, 'assertQueue') + amqpUtils.verifyTransaction(agent, tx, 'assertQueue') queueName = result.queue channel.bindQueue(queueName, exchange, 'key1', null, function (err) { assert.ok(!err, 'should not error binding queue') - amqpUtils.verifyTransaction(tx, 'bindQueue') + amqpUtils.verifyTransaction(agent, tx, 'bindQueue') channel.purgeQueue(queueName, function (err) { assert.ok(!err, 'should not error purging queue') setImmediate(function () { @@ -221,7 +221,7 @@ test('amqplib callback instrumentation', async function (t) { assert.ok(!err, 'should not cause an error') assert.ok(msg, 'should receive a message') - amqpUtils.verifyTransaction(tx, 'get') + amqpUtils.verifyTransaction(agent, tx, 'get') const body = msg.content.toString('utf8') assert.equal(body, 'hello', 'should receive expected body') @@ -266,7 +266,7 @@ test('amqplib callback instrumentation', async function (t) { assert.ok(!err, 'should not cause an error') assert.ok(msg, 'should receive a message') - amqpUtils.verifyTransaction(tx, 'get') + amqpUtils.verifyTransaction(agent, tx, 'get') const body = msg.content.toString('utf8') assert.equal(body, 'hello', 'should receive expected body') @@ -321,7 +321,7 @@ test('amqplib callback instrumentation', async function (t) { }) helper.runInTransaction(agent, function (tx) { produceTx = tx - amqpUtils.verifyTransaction(tx, 'consume') + amqpUtils.verifyTransaction(agent, tx, 'consume') channel.publish(exchange, 'consume-tx-key', Buffer.from('hello')) }) }) @@ -373,7 +373,7 @@ test('amqplib callback instrumentation', async function (t) { helper.runInTransaction(agent, function (tx) { produceTx = tx assert.ok(!err, 'should not error subscribing consumer') - amqpUtils.verifyTransaction(tx, 'consume') + amqpUtils.verifyTransaction(agent, tx, 'consume') channel.publish(exchange, 'consume-tx-key', Buffer.from('hello')) }) diff --git a/test/versioned/amqplib/promises.test.js b/test/versioned/amqplib/promises.test.js index 1314e80582..7999e04a6d 100644 --- a/test/versioned/amqplib/promises.test.js +++ b/test/versioned/amqplib/promises.test.js @@ -95,12 +95,12 @@ test('amqplib promise instrumentation', async function (t) { await helper.runInTransaction(agent, async function (tx) { assert.ok(agent.tracer.getSegment(), 'should start in transaction') await channel.assertExchange(amqpUtils.FANOUT_EXCHANGE, 'fanout') - amqpUtils.verifyTransaction(tx, 'assertExchange') + amqpUtils.verifyTransaction(agent, tx, 'assertExchange') const result = await channel.assertQueue('', { exclusive: true }) - amqpUtils.verifyTransaction(tx, 'assertQueue') + amqpUtils.verifyTransaction(agent, tx, 'assertQueue') const queueName = result.queue await channel.bindQueue(queueName, amqpUtils.FANOUT_EXCHANGE) - amqpUtils.verifyTransaction(tx, 'bindQueue') + amqpUtils.verifyTransaction(agent, tx, 'bindQueue') channel.publish(amqpUtils.FANOUT_EXCHANGE, '', Buffer.from('hello')) tx.end() amqpUtils.verifyProduce(tx, amqpUtils.FANOUT_EXCHANGE) @@ -111,12 +111,12 @@ test('amqplib promise instrumentation', async function (t) { const { agent, channel } = t.nr await helper.runInTransaction(agent, async function (tx) { await channel.assertExchange(amqpUtils.DIRECT_EXCHANGE, 'direct') - amqpUtils.verifyTransaction(tx, 'assertExchange') + amqpUtils.verifyTransaction(agent, tx, 'assertExchange') const result = await channel.assertQueue('', { exclusive: true }) - amqpUtils.verifyTransaction(tx, 'assertQueue') + amqpUtils.verifyTransaction(agent, tx, 'assertQueue') const queueName = result.queue await channel.bindQueue(queueName, amqpUtils.DIRECT_EXCHANGE, 'key1') - amqpUtils.verifyTransaction(tx, 'bindQueue') + amqpUtils.verifyTransaction(agent, tx, 'bindQueue') channel.publish(amqpUtils.DIRECT_EXCHANGE, 'key1', Buffer.from('hello')) tx.end() amqpUtils.verifyProduce(tx, amqpUtils.DIRECT_EXCHANGE, 'key1') @@ -128,14 +128,14 @@ test('amqplib promise instrumentation', async function (t) { await helper.runInTransaction(agent, async function (tx) { await channel.assertExchange(amqpUtils.DIRECT_EXCHANGE, 'direct') - amqpUtils.verifyTransaction(tx, 'assertExchange') + amqpUtils.verifyTransaction(agent, tx, 'assertExchange') const result = await channel.assertQueue('', { exclusive: true }) - amqpUtils.verifyTransaction(tx, 'assertQueue') + amqpUtils.verifyTransaction(agent, tx, 'assertQueue') const queueName = result.queue await channel.bindQueue(queueName, amqpUtils.DIRECT_EXCHANGE, 'key1') - amqpUtils.verifyTransaction(tx, 'bindQueue') + amqpUtils.verifyTransaction(agent, tx, 'bindQueue') await channel.purgeQueue(queueName) - amqpUtils.verifyTransaction(tx, 'purgeQueue') + amqpUtils.verifyTransaction(agent, tx, 'purgeQueue') tx.end() amqpUtils.verifyPurge(tx) }) @@ -155,7 +155,7 @@ test('amqplib promise instrumentation', async function (t) { const body = msg.content.toString('utf8') assert.equal(body, 'hello', 'should receive expected body') - amqpUtils.verifyTransaction(tx, 'get') + amqpUtils.verifyTransaction(agent, tx, 'get') channel.ack(msg) tx.end() amqpUtils.verifyGet({ @@ -184,7 +184,7 @@ test('amqplib promise instrumentation', async function (t) { const body = msg.content.toString('utf8') assert.equal(body, 'hello', 'should receive expected body') - amqpUtils.verifyTransaction(tx, 'get') + amqpUtils.verifyTransaction(agent, tx, 'get') channel.ack(msg) tx.end() amqpUtils.verifyGet({ @@ -223,7 +223,7 @@ test('amqplib promise instrumentation', async function (t) { }) await helper.runInTransaction(agent, async function (tx) { publishTx = tx - amqpUtils.verifyTransaction(tx, 'consume') + amqpUtils.verifyTransaction(agent, tx, 'consume') channel.publish(exchange, 'consume-tx-key', Buffer.from('hello')) }) await promise @@ -262,7 +262,7 @@ test('amqplib promise instrumentation', async function (t) { }) await helper.runInTransaction(agent, async function (tx) { publishTx = tx - amqpUtils.verifyTransaction(tx, 'consume') + amqpUtils.verifyTransaction(agent, tx, 'consume') channel.publish(exchange, 'consume-tx-key', Buffer.from('hello')) }) await promise diff --git a/test/versioned/cassandra-driver/query.test.js b/test/versioned/cassandra-driver/query.test.js index 688809d8b9..3022d70fe2 100644 --- a/test/versioned/cassandra-driver/query.test.js +++ b/test/versioned/cassandra-driver/query.test.js @@ -137,6 +137,7 @@ test('executeBatch - promise style', (t, end) => { client .batch(insArr, { hints: hints }) .then(() => { + assert.ok(agent.getTransaction(), 'transaction still should be visible') client .execute(selQuery) .then((result) => { diff --git a/test/versioned/koa/koa.tap.js b/test/versioned/koa/koa.tap.js index 9f2844fcce..de00947b94 100644 --- a/test/versioned/koa/koa.tap.js +++ b/test/versioned/koa/koa.tap.js @@ -212,9 +212,10 @@ tap.test('Koa instrumentation', (t) => { const { agent, app, testShim } = t.context app.use(function one(ctx, next) { - testShim.createSegment('testSegment') + const parent = agent.tracer.getSegment() + testShim.createSegment({ name: 'testSegment', parent }) return next().then(function () { - testShim.createSegment('nestedSegment') + testShim.createSegment({ name: 'nestedSegment', parent }) }) }) app.use(function two(ctx, next) { diff --git a/test/versioned/pg-esm/pg.common.mjs b/test/versioned/pg-esm/pg.common.mjs index aa52a699ac..07ff9798ad 100644 --- a/test/versioned/pg-esm/pg.common.mjs +++ b/test/versioned/pg-esm/pg.common.mjs @@ -64,14 +64,13 @@ export default function runTests(name, clientFactory) { return pg } - function verify(t, segment, selectTable) { - verifyMetrics(t, segment, selectTable) - verifyTrace(t, segment, selectTable) - verifyInstanceParameters(t, segment) + function verify(t, transaction, selectTable) { + verifyMetrics(t, transaction, selectTable) + verifyTrace(t, transaction, selectTable) + verifyInstanceParameters(t, transaction) } - function verifyMetrics(t, segment, selectTable) { - const transaction = segment.transaction + function verifyMetrics(t, transaction, selectTable) { const agent = transaction.agent selectTable = selectTable || TABLE t.equal(Object.keys(transaction.metrics.scoped).length, 0, 'should not have any scoped metrics') @@ -115,8 +114,7 @@ export default function runTests(name, clientFactory) { ) } - function verifyTrace(t, segment, selectTable) { - const transaction = segment.transaction + function verifyTrace(t, transaction, selectTable) { selectTable = selectTable || TABLE const trace = transaction.trace @@ -146,8 +144,7 @@ export default function runTests(name, clientFactory) { t.ok(getSegment.timer.hrDuration, 'trace segment should have ended') } - function verifyInstanceParameters(t, segment) { - const transaction = segment.transaction + function verifyInstanceParameters(t, transaction) { const agent = transaction.agent const trace = transaction.trace @@ -259,7 +256,7 @@ export default function runTests(name, clientFactory) { t.equal(value.rows[0][COL], colVal, 'Postgres client should still work') transaction.end() - verify(t, agent.tracer.getSegment()) + verify(t, transaction) t.end() }) }) @@ -304,7 +301,7 @@ export default function runTests(name, clientFactory) { t.ok(agent.getTransaction(), 'transaction should still still be visible') t.equal(selectResults.rows[0][COL], colVal, 'Postgres client should still work') transaction.end() - verify(t, agent.tracer.getSegment()) + verify(t, transaction) t.end() } catch (err) { t.error(err) @@ -342,14 +339,11 @@ export default function runTests(name, clientFactory) { }) pgQuery.on('end', () => { - t.ok(agent.getTransaction(), 'transaction should still be visible') + const finalTx = agent.getTransaction() + t.ok(finalTx, 'transaction should still be visible') transaction.end() - const segment = agent.tracer.getSegment() - - const finalTx = segment.transaction - const metrics = finalTx.metrics.getMetric('Datastore/operation/Postgres/select') t.ok( @@ -386,13 +380,10 @@ export default function runTests(name, clientFactory) { const selectResults = await client.query(selQuery) - t.ok(agent.getTransaction(), 'transaction should still still be visible') t.ok(selectResults, 'Postgres client should still work') + const finalTx = agent.getTransaction() + t.ok(finalTx, 'transaction should still be visible') transaction.end() - const segment = agent.tracer.getSegment() - - const finalTx = segment.transaction - const metrics = finalTx.metrics.getMetric('Datastore/operation/Postgres/select') t.ok( @@ -432,13 +423,10 @@ export default function runTests(name, clientFactory) { return t.end() } - t.ok(agent.getTransaction(), 'transaction should still be visible') t.ok(ok, 'everything should be peachy after setting') - + const finalTx = agent.getTransaction() + t.ok(finalTx, 'transaction should still be visible') transaction.end() - const segment = agent.tracer.getSegment() - - const finalTx = segment.transaction const metrics = finalTx.metrics.getMetric('Datastore/operation/Postgres/select') @@ -487,7 +475,7 @@ export default function runTests(name, clientFactory) { transaction.end() pool.end() - verify(t, agent.tracer.getSegment()) + verify(t, transaction) }) }) }) @@ -539,7 +527,7 @@ export default function runTests(name, clientFactory) { } done(true) - verify(t, agent.tracer.getSegment()) + verify(t, transaction) }) }) }) diff --git a/test/versioned/pg/pg.common.js b/test/versioned/pg/pg.common.js index d29dfd301b..1d769b25d9 100644 --- a/test/versioned/pg/pg.common.js +++ b/test/versioned/pg/pg.common.js @@ -66,14 +66,13 @@ module.exports = function runTests(name, clientFactory) { setupClient.end() } - function verify(t, segment, selectTable) { - verifyMetrics(t, segment, selectTable) - verifyTrace(t, segment, selectTable) - verifyInstanceParameters(t, segment) + function verify(t, transaction, selectTable) { + verifyMetrics(t, transaction, selectTable) + verifyTrace(t, transaction, selectTable) + verifyInstanceParameters(t, transaction) } - function verifyMetrics(t, segment, selectTable) { - const transaction = segment.transaction + function verifyMetrics(t, transaction, selectTable) { const agent = transaction.agent selectTable = selectTable || TABLE t.equal(Object.keys(transaction.metrics.scoped).length, 0, 'should not have any scoped metrics') @@ -117,8 +116,7 @@ module.exports = function runTests(name, clientFactory) { ) } - function verifyTrace(t, segment, selectTable) { - const transaction = segment.transaction + function verifyTrace(t, transaction, selectTable) { selectTable = selectTable || TABLE const trace = transaction.trace @@ -148,8 +146,7 @@ module.exports = function runTests(name, clientFactory) { t.ok(getSegment.timer.hrDuration, 'trace segment should have ended') } - function verifyInstanceParameters(t, segment) { - const transaction = segment.transaction + function verifyInstanceParameters(t, transaction) { const agent = transaction.agent const trace = transaction.trace @@ -259,7 +256,7 @@ module.exports = function runTests(name, clientFactory) { t.equal(value.rows[0][COL], colVal, 'Postgres client should still work') transaction.end() - verify(t, agent.tracer.getSegment()) + verify(t, transaction) t.end() }) }) @@ -304,7 +301,7 @@ module.exports = function runTests(name, clientFactory) { t.ok(agent.getTransaction(), 'transaction should still still be visible') t.equal(selectResults.rows[0][COL], colVal, 'Postgres client should still work') transaction.end() - verify(t, agent.tracer.getSegment()) + verify(t, transaction) t.end() } catch (err) { t.error(err) @@ -342,14 +339,11 @@ module.exports = function runTests(name, clientFactory) { }) pgQuery.on('end', () => { - t.ok(agent.getTransaction(), 'transaction should still be visible') + const finalTx = agent.getTransaction() + t.ok(finalTx, 'transaction should still be visible') transaction.end() - const segment = agent.tracer.getSegment() - - const finalTx = segment.transaction - const metrics = finalTx.metrics.getMetric('Datastore/operation/Postgres/select') t.ok( @@ -386,12 +380,10 @@ module.exports = function runTests(name, clientFactory) { const selectResults = await client.query(selQuery) - t.ok(agent.getTransaction(), 'transaction should still still be visible') + const finalTx = agent.getTransaction() + t.ok(finalTx, 'transaction should still be visible') t.ok(selectResults, 'Postgres client should still work') transaction.end() - const segment = agent.tracer.getSegment() - - const finalTx = segment.transaction const metrics = finalTx.metrics.getMetric('Datastore/operation/Postgres/select') @@ -432,13 +424,11 @@ module.exports = function runTests(name, clientFactory) { return t.end() } - t.ok(agent.getTransaction(), 'transaction should still be visible') + const finalTx = agent.getTransaction() + t.ok(finalTx, 'transaction should still be visible') t.ok(ok, 'everything should be peachy after setting') transaction.end() - const segment = agent.tracer.getSegment() - - const finalTx = segment.transaction const metrics = finalTx.metrics.getMetric('Datastore/operation/Postgres/select') @@ -487,7 +477,7 @@ module.exports = function runTests(name, clientFactory) { transaction.end() pool.end() - verify(t, agent.tracer.getSegment()) + verify(t, transaction) }) }) }) @@ -539,7 +529,7 @@ module.exports = function runTests(name, clientFactory) { } done(true) - verify(t, agent.tracer.getSegment()) + verify(t, transaction) }) }) }) diff --git a/test/versioned/when/legacy-promise-segments.js b/test/versioned/when/legacy-promise-segments.js index 3edc160bc6..4d957985e9 100644 --- a/test/versioned/when/legacy-promise-segments.js +++ b/test/versioned/when/legacy-promise-segments.js @@ -17,8 +17,14 @@ function runTests(t, agent, Promise) { // simulates a function that returns a promise and has a segment created for itself function doSomeWork(segmentName, shouldReject) { const tracer = agent.tracer - const segment = tracer.createSegment(segmentName) - return tracer.bindFunction(actualWork, segment)() + const ctx = tracer.getContext() + const segment = tracer.createSegment({ + name: segmentName, + parent: ctx.segment, + transaction: ctx.transaction + }) + const newCtx = ctx.enterSegment({ segment }) + return tracer.bindFunction(actualWork, newCtx)() function actualWork() { segment.touch() return new Promise(function startSomeWork(resolve, reject) { @@ -55,10 +61,16 @@ function segmentsEnabledTests(t, agent, Promise, doSomeWork) { helper.runInTransaction(agent, function transactionWrapper(transaction) { doSomeWork('doSomeWork').then(function () { - const childSegment = tracer.createSegment('someChildSegment') + const ctx = agent.tracer.getContext() + const childSegment = tracer.createSegment({ + name: 'someChildSegment', + parent: ctx.segment, + transaction + }) + const newCtx = ctx.enterSegment({ segment: childSegment }) // touch the segment, so that it is not truncated childSegment.touch() - tracer.bindFunction(function () {}, childSegment) + tracer.bindFunction(function () {}, newCtx) process.nextTick(transaction.end.bind(transaction)) }) }) @@ -83,7 +95,8 @@ function segmentsEnabledTests(t, agent, Promise, doSomeWork) { return doSomeWork('doWork2') }) .then(function secondThen() { - const s = tracer.createSegment('secondThen') + const ctx = agent.tracer.getContext() + const s = tracer.createSegment({ name: 'secondThen', parent: ctx.segment, transaction }) s.start() s.end() process.nextTick(transaction.end.bind(transaction)) @@ -161,12 +174,14 @@ function segmentsEnabledTests(t, agent, Promise, doSomeWork) { return doSomeWork('doWork2', true) }) .then(function secondThen() { - const s = tracer.createSegment('secondThen') + const ctx = agent.tracer.getContext() + const s = tracer.createSegment({ name: 'secondThen', parent: ctx.segment, transaction }) s.start() s.end() }) .catch(function catchHandler() { - const s = tracer.createSegment('catchHandler') + const ctx = agent.tracer.getContext() + const s = tracer.createSegment({ name: 'catchHandler', parent: ctx.segment, transaction }) s.start() s.end() process.nextTick(transaction.end.bind(transaction)) @@ -195,8 +210,10 @@ function segmentsEnabledTests(t, agent, Promise, doSomeWork) { resolve = r }) - const segment = tracer.createSegment('doSomeWork') - resolve = tracer.bindFunction(resolve, segment) + const ctx = agent.tracer.getContext() + const segment = tracer.createSegment({ name: 'doSomeWork', parent: ctx.segment, transaction }) + const newCtx = ctx.enterSegment({ segment }) + resolve = tracer.bindFunction(resolve, newCtx) p.then(function myThen() { segment.touch() @@ -226,10 +243,16 @@ function segmentsDisabledTests(t, agent, Promise, doSomeWork) { helper.runInTransaction(agent, function transactionWrapper(transaction) { doSomeWork('doSomeWork').then(function () { - const childSegment = tracer.createSegment('someChildSegment') + const ctx = agent.tracer.getContext() + const childSegment = tracer.createSegment({ + name: 'someChildSegment', + parent: ctx.segment, + transaction + }) + const newCtx = ctx.enterSegment({ segment: childSegment }) // touch the segment, so that it is not truncated childSegment.touch() - tracer.bindFunction(function () {}, childSegment) + tracer.bindFunction(function () {}, newCtx) process.nextTick(transaction.end.bind(transaction)) }) }) @@ -343,8 +366,10 @@ function segmentsDisabledTests(t, agent, Promise, doSomeWork) { resolve = r }) - const segment = tracer.createSegment('doSomeWork') - resolve = tracer.bindFunction(resolve, segment) + const ctx = agent.tracer.getContext() + const segment = tracer.createSegment({ name: 'doSomeWork', parent: ctx.segment, transaction }) + const newCtx = ctx.enterSegment({ segment }) + resolve = tracer.bindFunction(resolve, newCtx) p.then(function myThen() { segment.touch()