newrelic 13.2.0 → 13.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/NEWS.md +44 -0
  2. package/THIRD_PARTY_NOTICES.md +219 -8
  3. package/api.js +1 -1
  4. package/esm-loader.mjs +6 -1
  5. package/lib/instrumentation/@azure/functions.js +8 -46
  6. package/lib/instrumentation/core/http-outbound.js +26 -10
  7. package/lib/instrumentation/core/http.js +20 -118
  8. package/lib/instrumentations.js +0 -14
  9. package/lib/otel/logs/index.js +80 -0
  10. package/lib/otel/metrics/index.js +119 -0
  11. package/lib/otel/setup-signal.js +37 -0
  12. package/lib/otel/setup.js +15 -25
  13. package/lib/otel/{attr-reconciler.js → traces/attr-reconciler.js} +2 -2
  14. package/lib/otel/{exception-mapping.js → traces/exception-mapping.js} +1 -1
  15. package/lib/otel/traces/index.js +40 -0
  16. package/lib/otel/{rules.js → traces/rules.js} +1 -1
  17. package/lib/otel/{segment-synthesis.js → traces/segment-synthesis.js} +5 -4
  18. package/lib/otel/{segments → traces/segments}/consumer.js +4 -4
  19. package/lib/otel/{segments → traces/segments}/database.js +6 -6
  20. package/lib/otel/{segments → traces/segments}/http-external.js +11 -8
  21. package/lib/otel/traces/segments/index.js +22 -0
  22. package/lib/otel/{segments → traces/segments}/internal.js +1 -1
  23. package/lib/otel/{segments → traces/segments}/producer.js +2 -2
  24. package/lib/otel/{segments → traces/segments}/server.js +4 -4
  25. package/lib/otel/{segments → traces/segments}/utils.js +1 -1
  26. package/lib/otel/{span-processor.js → traces/span-processor.js} +13 -19
  27. package/lib/otel/{transformation-rules.json → traces/transformation-rules.json} +3 -2
  28. package/lib/otel/{utils.js → traces/utils.js} +2 -1
  29. package/lib/serverless/aws-lambda.js +7 -68
  30. package/lib/shim/webframework-shim/middleware.js +1 -1
  31. package/lib/shimmer.js +2 -2
  32. package/lib/subscriber-configs.js +2 -1
  33. package/lib/subscribers/base.js +30 -0
  34. package/lib/subscribers/elasticsearch/config.js +1 -0
  35. package/lib/subscribers/mcp-sdk/client-request.js +66 -0
  36. package/lib/subscribers/mcp-sdk/config.js +13 -75
  37. package/lib/subscribers/openai/base.js +21 -0
  38. package/lib/subscribers/openai/chat-responses.js +15 -0
  39. package/lib/subscribers/openai/chat.js +85 -0
  40. package/lib/subscribers/openai/client.js +31 -0
  41. package/lib/subscribers/openai/config.js +85 -0
  42. package/lib/subscribers/openai/embeddings.js +54 -0
  43. package/lib/subscribers/openai/utils.js +349 -0
  44. package/lib/transaction/index.js +157 -56
  45. package/lib/transaction/trace/segment.js +5 -2
  46. package/lib/util/urltils.js +158 -192
  47. package/lib/utilization/common.js +0 -6
  48. package/package.json +3 -4
  49. package/lib/instrumentation/openai.js +0 -482
  50. package/lib/otel/logs/bootstrap-logs.js +0 -84
  51. package/lib/otel/metrics/bootstrap-metrics.js +0 -117
  52. package/lib/otel/segments/index.js +0 -22
  53. package/lib/patch-module.js +0 -70
  54. package/lib/subscribers/mcp-sdk/client-prompt.js +0 -23
  55. package/lib/subscribers/mcp-sdk/client-resource.js +0 -24
  56. package/lib/subscribers/mcp-sdk/client-tool.js +0 -23
  57. package/lib/subscribers/mcp-sdk/client.js +0 -29
  58. /package/lib/otel/{constants.js → traces/constants.js} +0 -0
  59. /package/lib/otel/{sampler.js → traces/sampler.js} +0 -0
@@ -10,14 +10,11 @@ const logger = require('../../logger').child({ component: 'http' })
10
10
  const recordWeb = require('../../metrics/recorders/http')
11
11
  const cat = require('../../util/cat')
12
12
  const instrumentOutbound = require('./http-outbound')
13
- const url = require('url')
14
13
  const urltils = require('../../util/urltils')
15
- const headerAttributes = require('../../header-attributes')
16
14
  const headerProcessing = require('../../header-processing')
17
15
  const synthetics = require('../../synthetics')
18
16
 
19
17
  const NAMES = require('../../metrics/names')
20
- const DESTS = require('../../config/attribute-filter').DESTINATIONS
21
18
  const symbols = require('../../symbols')
22
19
 
23
20
  // For incoming requests this instrumentation functions by wrapping
@@ -41,14 +38,13 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
41
38
 
42
39
  transaction.nameState.setPrefix(NAMES.NODEJS.PREFIX)
43
40
  transaction.nameState.setDelimiter(NAMES.ACTION_DELIMITER)
41
+ if (request.method != null) {
42
+ transaction.nameState.setVerb(request.method)
43
+ }
44
44
 
45
45
  // Store the transaction information on the request and response.
46
46
  const txInfo = storeTxInfo(transaction, request, response)
47
47
 
48
- if (request) {
49
- initializeRequest(transaction, request)
50
- }
51
-
52
48
  // Create the transaction segment using the request URL for now. Once a
53
49
  // better name can be determined this segment will be renamed to that.
54
50
  const segment = tracer.createSegment({
@@ -58,10 +54,7 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
58
54
  transaction
59
55
  })
60
56
  segment.start()
61
-
62
- if (request.method != null) {
63
- segment.addSpanAttribute('request.method', request.method)
64
- }
57
+ transaction.baseSegment = segment
65
58
 
66
59
  if (txInfo) {
67
60
  // Seed segment stack to enable parenting logic leveraged by
@@ -69,8 +62,16 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
69
62
  txInfo.segmentStack.push(segment)
70
63
  }
71
64
 
72
- transaction.type = 'web'
73
- transaction.baseSegment = segment
65
+ let absoluteUrl = null
66
+ // handle a request that is to a proxy url
67
+ if (request?.url.startsWith('http://') || request?.url.startsWith('https://')) {
68
+ absoluteUrl = request.url
69
+ // attempt to construct the full URL with known attributes
70
+ } else {
71
+ absoluteUrl = `${transport}://${request.headers.host || 'localhost'}${request.url}`
72
+ }
73
+ const port = parsePort(this)
74
+ transaction.initializeWeb({ absoluteUrl, method: request.method, port, headers: request.headers, transport })
74
75
 
75
76
  /* Needed for Connect and Express middleware that monkeypatch request
76
77
  * and response via listeners.
@@ -78,40 +79,8 @@ function wrapEmitWithTransaction(agent, emit, isHTTPS) {
78
79
  tracer.bindEmitter(request, segment)
79
80
  tracer.bindEmitter(response, segment)
80
81
 
81
- // the error tracer needs a URL for tracing, even though naming overwrites
82
-
83
- transaction.parsedUrl = url.parse(request.url, true)
84
- transaction.url = urltils.obfuscatePath(agent.config, transaction.parsedUrl.pathname)
85
- transaction.verb = request.method
86
-
87
- // URL is sent as an agent attribute with transaction events
88
- transaction.trace.attributes.addAttribute(
89
- DESTS.TRANS_EVENT | DESTS.ERROR_EVENT,
90
- 'request.uri',
91
- transaction.url
92
- )
93
-
94
- segment.addSpanAttribute('request.uri', transaction.url)
95
-
96
- transaction.port = parsePort(this)
97
- // need to set any config-driven names early for RUM
98
- logger.trace(
99
- { url: request.url, transaction: transaction.id },
100
- 'Applying user naming rules for RUM.'
101
- )
102
- transaction.applyUserNamingRules(request.url)
103
-
104
- const queueTimeStamp = headerProcessing.getQueueTime(logger, request.headers)
105
- if (queueTimeStamp) {
106
- transaction.queueTime = Date.now() - queueTimeStamp
107
- }
108
-
109
- synthetics.assignHeadersToTransaction(agent.config, transaction, request.headers)
110
-
111
- maybeAddDtCatHeaders({ transaction, request, transport, agent })
112
-
113
- response.once('finish', instrumentedFinish.bind(response, segment, transaction))
114
- response.once('close', instrumentedFinish.bind(response, segment, transaction))
82
+ response.once('finish', instrumentedFinish.bind(response, transaction))
83
+ response.once('close', instrumentedFinish.bind(response, transaction))
115
84
 
116
85
  const newContext = context.enterSegment({ segment })
117
86
  return tracer.bindFunction(emit, newContext).apply(this, arguments)
@@ -136,76 +105,19 @@ function parsePort(server) {
136
105
  return serverPort
137
106
  }
138
107
 
139
- function maybeAddDtCatHeaders({ agent, request, transaction, transport }) {
140
- if (agent.config.distributed_tracing.enabled) {
141
- // Node http headers are automatically lowercase
142
- transaction.acceptDistributedTraceHeaders(transport, request.headers)
143
- } else if (agent.config.cross_application_tracer.enabled) {
144
- const { id, transactionId } = cat.extractCatHeaders(request.headers)
145
- const { externalId, externalTransaction } = cat.parseCatData(
146
- id,
147
- transactionId,
148
- agent.config.encoding_key
149
- )
150
- cat.assignCatToTransaction(externalId, externalTransaction, transaction)
151
- }
152
- }
153
-
154
108
  /**
155
109
  * Adds instrumentation to response on finish/close.
156
110
  * It will add `http.statusCode`, `http.statusText`
157
111
  * to the transaction trace and span.
158
112
  * It will also assign the response headers to the transaction
159
113
  *
160
- * @param {TraceSegment} segment active segment
161
114
  * @param {Transaction} transaction active transaction
162
115
  */
163
- function instrumentedFinish(segment, transaction) {
116
+ function instrumentedFinish(transaction) {
164
117
  // Remove listeners so this doesn't get called twice.
165
118
  this.removeListener('finish', instrumentedFinish)
166
119
  this.removeListener('close', instrumentedFinish)
167
-
168
- // Naming must happen before the segment and transaction are ended,
169
- // because metrics recording depends on naming's side effects.
170
-
171
- transaction.finalizeNameFromUri(transaction.parsedUrl, this.statusCode)
172
-
173
- if (this) {
174
- const { statusCode, statusMessage } = this
175
-
176
- if (statusCode != null) {
177
- const responseCode = String(statusCode)
178
-
179
- if (/^\d+$/.test(responseCode)) {
180
- transaction.trace.attributes.addAttribute(
181
- DESTS.TRANS_COMMON,
182
- 'http.statusCode',
183
- responseCode
184
- )
185
-
186
- segment.addSpanAttribute('http.statusCode', responseCode)
187
- }
188
- }
189
-
190
- if (statusMessage !== undefined) {
191
- transaction.trace.attributes.addAttribute(
192
- DESTS.TRANS_COMMON,
193
- 'http.statusText',
194
- statusMessage
195
- )
196
-
197
- segment.addSpanAttribute('http.statusText', statusMessage)
198
- }
199
-
200
- const headers = this.getHeaders()
201
- if (headers) {
202
- headerAttributes.collectResponseHeaders(headers, transaction)
203
- }
204
- }
205
-
206
- // And we are done! End the segment and transaction.
207
- segment.end()
208
- transaction.end()
120
+ transaction.finalizeWeb({ end: true, statusCode: this.statusCode, statusMessage: this.statusMessage, headers: this.getHeaders() })
209
121
  }
210
122
 
211
123
  function storeTxInfo(transaction, request, response) {
@@ -227,15 +139,6 @@ function storeTxInfo(transaction, request, response) {
227
139
  return txInfo
228
140
  }
229
141
 
230
- function initializeRequest(transaction, request) {
231
- headerAttributes.collectRequestHeaders(request.headers, transaction)
232
-
233
- if (request.method != null) {
234
- transaction.trace.attributes.addAttribute(DESTS.TRANS_COMMON, 'request.method', request.method)
235
- transaction.nameState.setVerb(request.method)
236
- }
237
- }
238
-
239
142
  function wrapResponseEnd(agent, proto) {
240
143
  const tracer = agent.tracer
241
144
 
@@ -419,11 +322,10 @@ function wrapRequest(agent, request) {
419
322
  const transaction = agent.tracer.getTransaction()
420
323
  if (!transaction || internalOnly) {
421
324
  if (!internalOnly && logger.traceEnabled()) {
422
- const logOpts = typeof options === 'string' ? url.parse(options) : options
423
325
  logger.trace(
424
326
  'No transaction, not recording external to %s:%s',
425
- logOpts.hostname || logOpts.host,
426
- logOpts.port
327
+ options?.hostname || options?.host,
328
+ options?.port
427
329
  )
428
330
  }
429
331
  return request.apply(this, reqArgs)
@@ -35,7 +35,6 @@ module.exports = function instrumentations() {
35
35
  mongodb: { type: InstrumentationDescriptor.TYPE_DATASTORE },
36
36
  mysql: { module: './instrumentation/mysql' },
37
37
  next: { module: './instrumentation/nextjs' },
38
- openai: { type: InstrumentationDescriptor.TYPE_GENERIC },
39
38
  pg: { type: InstrumentationDescriptor.TYPE_DATASTORE },
40
39
  q: { type: null },
41
40
  redis: { type: InstrumentationDescriptor.TYPE_DATASTORE },
@@ -59,18 +58,5 @@ module.exports = function instrumentations() {
59
58
  knex: { type: InstrumentationDescriptor.TYPE_TRACKING },
60
59
  loglevel: { type: InstrumentationDescriptor.TYPE_TRACKING },
61
60
  npmlog: { type: InstrumentationDescriptor.TYPE_TRACKING },
62
-
63
- /**
64
- * The modules below are listed here is a temporary solution to maintaining
65
- * the Supportability/Features/onRequire/<module> metrics for libraries
66
- * that have been migrated to use tracing chanel instrumentation.
67
- * Once orchestrion can emit the package version, these can be removed.
68
- * {@link https://github.com/newrelic/node-newrelic/issues/3308 Github Issue}
69
- */
70
- '@elastic/elasticsearch': { type: InstrumentationDescriptor.TYPE_TRACKING },
71
- '@modelcontextprotocol/sdk/client/index.js': { type: InstrumentationDescriptor.TYPE_TRACKING },
72
- '@opensearch-project/opensearch': { type: InstrumentationDescriptor.TYPE_TRACKING },
73
- ioredis: { type: InstrumentationDescriptor.TYPE_TRACKING },
74
- pino: { type: InstrumentationDescriptor.TYPE_TRACKING }
75
61
  }
76
62
  }
@@ -0,0 +1,80 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const logsApi = require('@opentelemetry/api-logs')
9
+ const logsSdk = require('@opentelemetry/sdk-logs')
10
+ const {
11
+ isApplicationLoggingEnabled,
12
+ isLogForwardingEnabled,
13
+ isMetricsEnabled,
14
+ incrementLoggingLinesMetrics
15
+ } = require('#agentlib/util/application-logging.js')
16
+
17
+ const defaultLogger = require('../../logger').child({ component: 'opentelemetry-metrics' })
18
+ const SetupSignal = require('../setup-signal.js')
19
+ const NewRelicLoggerProvider = require('./proxying-provider.js')
20
+ const NoOpExporter = require('./no-op-exporter.js')
21
+ const normalizeTimestamp = require('./normalize-timestamp.js')
22
+ const severityToString = require('./severity-to-string.js')
23
+
24
+ class SetupLogs extends SetupSignal {
25
+ constructor({ agent, logger = defaultLogger } = {}) {
26
+ super({ agent, logger })
27
+
28
+ if (isApplicationLoggingEnabled(agent.config) === false) {
29
+ logger.info('application logging disabled, skipping otel logs setup')
30
+ return
31
+ }
32
+
33
+ agent.metrics
34
+ .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Logs')
35
+ .incrementCallCount()
36
+
37
+ const exporter = new NoOpExporter()
38
+ const processor = new logsSdk.BatchLogRecordProcessor(exporter)
39
+ const otelProvider = new logsSdk.LoggerProvider({
40
+ processors: [processor]
41
+ })
42
+ const provider = new NewRelicLoggerProvider({
43
+ agent,
44
+ provider: otelProvider,
45
+ emitHandler: nrEmitHandler
46
+ })
47
+ logsApi.logs.setGlobalLoggerProvider(provider)
48
+
49
+ function nrEmitHandler(record) {
50
+ const level = severityToString(record.severityNumber ?? 0)
51
+ if (isMetricsEnabled(agent.config) === true) {
52
+ incrementLoggingLinesMetrics(level, agent.metrics)
53
+ }
54
+
55
+ // TODO: if we decide to support local decorating, implement it here
56
+
57
+ if (isLogForwardingEnabled(agent.config, agent) === true) {
58
+ const meta = agent.getLinkingMetadata(true)
59
+ const timestamp = normalizeTimestamp(record.timestamp)
60
+ const logData = {
61
+ message: record.body,
62
+ level,
63
+ timestamp,
64
+ ...record.attributes,
65
+ ...meta
66
+ }
67
+
68
+ agent.logs.add(logData)
69
+ }
70
+ }
71
+
72
+ agent.emit('otelLogsBootstrapped')
73
+ }
74
+
75
+ teardown() {
76
+ logsApi.logs.disable()
77
+ }
78
+ }
79
+
80
+ module.exports = SetupLogs
@@ -0,0 +1,119 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const { resourceFromAttributes } = require('@opentelemetry/resources')
9
+ const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-proto')
10
+ const {
11
+ AggregationTemporality,
12
+ InMemoryMetricExporter,
13
+ MeterProvider,
14
+ PeriodicExportingMetricReader
15
+ } = require('@opentelemetry/sdk-metrics')
16
+
17
+ const defaultLogger = require('../../logger').child({ component: 'opentelemetry-metrics' })
18
+ const SetupSignal = require('../setup-signal.js')
19
+ const ProxyingExporter = require('./proxying-exporter.js')
20
+
21
+ class SetupMetrics extends SetupSignal {
22
+ constructor({ agent, logger = defaultLogger } = {}) {
23
+ super({ agent, logger })
24
+
25
+ const { config } = agent
26
+ const exportInterval = config.opentelemetry_bridge.metrics.exportInterval
27
+ const exportTimeout = config.opentelemetry_bridge.metrics.exportTimeout
28
+
29
+ const resource = resourceFromAttributes({ })
30
+ const memExporter = new InMemoryMetricExporter(AggregationTemporality.DELTA)
31
+ const proxyExporter = new ProxyingExporter({ exporter: memExporter })
32
+ const reader = new PeriodicExportingMetricReader({
33
+ exporter: proxyExporter,
34
+ exportIntervalMillis: exportInterval,
35
+ exportTimeoutMillis: exportTimeout
36
+ })
37
+ const provider = new MeterProvider({
38
+ readers: [reader],
39
+ resource
40
+ })
41
+
42
+ const getMeter = provider.getMeter
43
+ provider.getMeter = function nrGetMeter(...args) {
44
+ agent.metrics
45
+ .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Metrics/getMeter')
46
+ .incrementCallCount()
47
+
48
+ const meter = getMeter.apply(provider, args)
49
+ const proto = Object.getPrototypeOf(meter)
50
+ const methods = Object.getOwnPropertyNames(proto).filter((name) => name.startsWith('create'))
51
+ const originals = {}
52
+ for (const method of methods) {
53
+ originals[method] = meter[method]
54
+ // As of 2025-06-17:
55
+ // + createGauge
56
+ // + createHistogram
57
+ // + createCounter
58
+ // + createUpDownCounter
59
+ // + createObservableGauge
60
+ // + createObservableCounter
61
+ // + createObservableUpDownCounter
62
+ meter[method] = function nrWrappedMethod(...args) {
63
+ agent.metrics
64
+ .getOrCreateMetric(`Supportability/Nodejs/OpenTelemetryBridge/Metrics/meter/${method}`)
65
+ .incrementCallCount()
66
+ return originals[method].apply(meter, args)
67
+ }
68
+ }
69
+
70
+ return meter
71
+ }
72
+
73
+ this.coreApi.metrics.setGlobalMeterProvider(provider)
74
+
75
+ agent.metrics
76
+ .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Metrics')
77
+ .incrementCallCount()
78
+
79
+ // We need access to `agent.config.entity_guid` in order to attach metrics
80
+ // to the correct instrumentation entity. But that value is not available
81
+ // until either at least the first firing of `agent.config.on('change')`, or
82
+ // the `agent.on('started')` event. Which means that we can't finalize the
83
+ // metrics client configuration until after the `started` event.
84
+ agent.on('started', postReady)
85
+ function postReady() {
86
+ agent.removeListener('started', postReady)
87
+
88
+ reader.collect().then(({ resourceMetrics: collectedMetrics }) => {
89
+ proxyExporter.exporter = new OTLPMetricExporter({
90
+ url: `https://${config.host}:${config.port}/v1/metrics`,
91
+ headers: {
92
+ 'api-key': config.license_key
93
+ },
94
+ temporalityPreference: AggregationTemporality.DELTA
95
+ })
96
+
97
+ const resource = resourceFromAttributes({ 'entity.guid': config.entity_guid })
98
+ // Assigning the resource after having received the `entity.guid` from
99
+ // the server is a key detail of this implementation. Unfortunately,
100
+ // we don't have real public access to the object that retains the
101
+ // resource reference. If upstream ever hides this from us, we'll be
102
+ // in a bit of a bind.
103
+ provider._sharedState.resource = resource
104
+
105
+ // Attempt to ship any metrics recorded prior to the `started` event.
106
+ collectedMetrics.resource = resource
107
+ proxyExporter.exporter.export(collectedMetrics, () => {})
108
+
109
+ agent.emit('otelMetricsBootstrapped')
110
+ })
111
+ }
112
+ }
113
+
114
+ teardown() {
115
+ this.coreApi.metrics.disable()
116
+ }
117
+ }
118
+
119
+ module.exports = SetupMetrics
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const defaultLogger = require('#agentlib/logger.js').child({ component: 'opentelemetry-bridge' })
9
+
10
+ /**
11
+ * SetupSignal is an interface to standardize how we bootstrap OpenTelemetry
12
+ * observability signals within the OTEL bridge. Subclasses are expected to
13
+ * implement their setup in the constructor, and to provide a `.teardown`
14
+ * method that properly cleans up any created resources (i.e. unblocks the
15
+ * parent singleton managed by the OTEL packages from being garbage collected).
16
+ */
17
+ class SetupSignal {
18
+ agent
19
+ logger
20
+
21
+ coreApi = require('@opentelemetry/api')
22
+
23
+ constructor({ agent, logger = defaultLogger } = {}) {
24
+ if (Object.prototype.toString.call(agent) !== '[object Agent]') {
25
+ throw Error('must provide an instance of the New Relic agent')
26
+ }
27
+
28
+ this.agent = agent
29
+ this.logger = logger
30
+ }
31
+
32
+ teardown() {
33
+ this.logger.warn('teardown method is not implemented')
34
+ }
35
+ }
36
+
37
+ module.exports = SetupSignal
package/lib/otel/setup.js CHANGED
@@ -6,18 +6,17 @@
6
6
  'use strict'
7
7
 
8
8
  const opentelemetry = require('@opentelemetry/api')
9
- const logsApi = require('@opentelemetry/api-logs')
10
- const { BasicTracerProvider } = require('@opentelemetry/sdk-trace-base')
11
9
 
12
- const NrSpanProcessor = require('./span-processor')
13
- const NrSampler = require('./sampler')
10
+ const SetupLogs = require('./logs/index.js')
11
+ const SetupMetrics = require('./metrics/index.js')
12
+ const SetupTraces = require('./traces/index.js')
14
13
  const ContextManager = require('./context-manager')
15
14
  const TracePropagator = require('./trace-propagator')
16
15
  const defaultLogger = require('../logger').child({ component: 'opentelemetry-bridge' })
17
16
  const createOtelLogger = require('./logger')
18
17
  const interceptSpanKey = require('./span-key-interceptor')
19
- const bootstrapMetrics = require('./metrics/bootstrap-metrics')
20
- const bootstrapLogs = require('./logs/bootstrap-logs')
18
+
19
+ const signals = []
21
20
 
22
21
  function setupOtel(agent, logger = defaultLogger) {
23
22
  if (agent.config.opentelemetry_bridge.enabled !== true) {
@@ -38,19 +37,22 @@ function setupOtel(agent, logger = defaultLogger) {
38
37
  opentelemetry.propagation.setGlobalPropagator(new TracePropagator(agent))
39
38
 
40
39
  if (agent.config.opentelemetry_bridge.traces.enabled === true) {
41
- bootstrapTraces(agent)
40
+ const signal = new SetupTraces({ agent })
41
+ signals.push(signal)
42
42
  } else {
43
43
  logger.debug('`opentelemetry_bridge.traces` is not enabled, skipping')
44
44
  }
45
45
 
46
46
  if (agent.config.opentelemetry_bridge.metrics.enabled === true) {
47
- bootstrapMetrics(agent)
47
+ const signal = new SetupMetrics({ agent })
48
+ signals.push(signal)
48
49
  } else {
49
50
  logger.debug('`opentelemetry_bridge.metrics` is not enabled, skipping')
50
51
  }
51
52
 
52
53
  if (agent.config.opentelemetry_bridge.logs.enabled === true) {
53
- bootstrapLogs({ agent })
54
+ const signal = new SetupLogs({ agent })
55
+ signals.push(signal)
54
56
  } else {
55
57
  logger.debug('`opentelemetry_bridge.logs` is not enabled, skipping')
56
58
  }
@@ -60,30 +62,18 @@ function setupOtel(agent, logger = defaultLogger) {
60
62
  .incrementCallCount()
61
63
  }
62
64
 
63
- function bootstrapTraces(agent) {
64
- opentelemetry.trace.setGlobalTracerProvider(new BasicTracerProvider({
65
- sampler: new NrSampler(),
66
- spanProcessors: [new NrSpanProcessor(agent)],
67
- generalLimits: {
68
- attributeValueLengthLimit: 4095
69
- }
70
- }))
71
-
72
- agent.metrics
73
- .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Traces')
74
- .incrementCallCount()
75
- }
76
-
77
65
  function teardownOtel(agent) {
78
66
  if (agent?.config?.opentelemetry_bridge?.enabled !== true) {
79
67
  return
80
68
  }
81
69
 
82
- opentelemetry.trace.disable()
70
+ for (const signal of signals) {
71
+ signal.teardown()
72
+ }
73
+
83
74
  opentelemetry.context.disable()
84
75
  opentelemetry.propagation.disable()
85
76
  opentelemetry.diag.disable()
86
- logsApi.logs.disable()
87
77
  }
88
78
 
89
79
  module.exports = {
@@ -5,8 +5,8 @@
5
5
 
6
6
  'use strict'
7
7
 
8
- const urltils = require('../util/urltils')
9
- const constants = require('./constants')
8
+ const urltils = require('#agentlib/util/urltils.js')
9
+ const constants = require('./constants.js')
10
10
 
11
11
  const hostKeys = [
12
12
  constants.ATTR_NET_HOST_NAME,
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  'use strict'
7
- const constants = require('./constants')
7
+ const constants = require('./constants.js')
8
8
  const attrMappings = {
9
9
  msg: {
10
10
  attrs: [constants.EXCEPTION_MESSAGE, constants.EXCEPTION_TYPE]
@@ -0,0 +1,40 @@
1
+ /*
2
+ * Copyright 2025 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ 'use strict'
7
+
8
+ const { BasicTracerProvider } = require('@opentelemetry/sdk-trace-base')
9
+
10
+ const defaultLogger = require('../../logger').child({ component: 'opentelemetry-traces' })
11
+ const SetupSignal = require('../setup-signal.js')
12
+ const NrSampler = require('./sampler.js')
13
+ const NrSpanProcessor = require('./span-processor.js')
14
+
15
+ class SetupTraces extends SetupSignal {
16
+ constructor({ agent, logger = defaultLogger } = {}) {
17
+ super({ agent, logger })
18
+
19
+ const sampler = new NrSampler()
20
+ const spanProcessor = new NrSpanProcessor(agent)
21
+ const traceProvider = new BasicTracerProvider({
22
+ sampler,
23
+ spanProcessors: [spanProcessor],
24
+ generalLimits: {
25
+ attributeValueLengthLimit: 4_095
26
+ }
27
+ })
28
+ this.coreApi.trace.setGlobalTracerProvider(traceProvider)
29
+
30
+ agent.metrics
31
+ .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Traces')
32
+ .incrementCallCount()
33
+ }
34
+
35
+ teardown() {
36
+ this.coreApi.trace.disable()
37
+ }
38
+ }
39
+
40
+ module.exports = SetupTraces
@@ -6,7 +6,7 @@
6
6
  'use strict'
7
7
 
8
8
  const { SpanKind } = require('@opentelemetry/api')
9
- const srcJson = require('./transformation-rules')
9
+ const srcJson = require('./transformation-rules.json')
10
10
 
11
11
  /**
12
12
  * Mapping rules are stored, and provided to the agent, as JSON. We call these
@@ -4,8 +4,9 @@
4
4
  */
5
5
 
6
6
  'use strict'
7
- const { RulesEngine } = require('./rules')
8
- const defaultLogger = require('../logger').child({ component: 'segment-synthesizer' })
7
+
8
+ const defaultLogger = require('#agentlib/logger.js').child({ component: 'segment-synthesizer' })
9
+ const { RulesEngine } = require('./rules.js')
9
10
  const {
10
11
  createConsumerSegment,
11
12
  createDbSegment,
@@ -13,7 +14,7 @@ const {
13
14
  createInternalSegment,
14
15
  createProducerSegment,
15
16
  createServerSegment
16
- } = require('./segments')
17
+ } = require('./segments/index.js')
17
18
 
18
19
  class SegmentSynthesizer {
19
20
  constructor(agent, { logger = defaultLogger } = {}) {
@@ -43,7 +44,7 @@ class SegmentSynthesizer {
43
44
  }
44
45
 
45
46
  case 'external': {
46
- return createHttpExternalSegment(this.agent, otelSpan, rule)
47
+ return createHttpExternalSegment(this.agent, otelSpan, rule, this.logger)
47
48
  }
48
49
 
49
50
  case 'internal': {
@@ -7,10 +7,10 @@
7
7
 
8
8
  module.exports = createConsumerSegment
9
9
 
10
- const Transaction = require('../../transaction/')
11
- const recorder = require('../../metrics/recorders/message-transaction')
12
- const { propagateTraceContext } = require('./utils')
13
- const createInternalSegment = require('./internal')
10
+ const Transaction = require('#agentlib/transaction/index.js')
11
+ const recorder = require('#agentlib/metrics/recorders/message-transaction.js')
12
+ const { propagateTraceContext } = require('./utils.js')
13
+ const createInternalSegment = require('./internal.js')
14
14
 
15
15
  function createConsumerSegment(agent, otelSpan, rule) {
16
16
  const activeTx = agent.tracer.getTransaction()