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
@@ -1,482 +0,0 @@
1
- /*
2
- * Copyright 2023 New Relic Corporation. All rights reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- 'use strict'
7
- const { openAiHeaders, openAiApiKey } = require('../../lib/symbols')
8
- const {
9
- LlmChatCompletionMessage,
10
- LlmChatCompletionSummary,
11
- LlmEmbedding,
12
- LlmErrorMessage
13
- } = require('../../lib/llm-events/openai')
14
- const { RecorderSpec } = require('../../lib/shim/specs')
15
- const { extractLlmContext } = require('../util/llm-utils')
16
-
17
- const MIN_VERSION = '4.0.0'
18
- const MIN_STREAM_VERSION = '4.12.2'
19
- const { AI } = require('../../lib/metrics/names')
20
- const { OPENAI } = AI
21
- const semver = require('semver')
22
- const { DESTINATIONS } = require('../config/attribute-filter')
23
-
24
- let TRACKING_METRIC = OPENAI.TRACKING_PREFIX
25
-
26
- /**
27
- * Parses the response from OpenAI and extracts the message content and role.
28
- *
29
- * @param {object} response The OpenAI SDK response object
30
- * @returns {{ content: string, role: string }} the message object with fields `content` and `role`
31
- */
32
- function getMessageFromResponse(response) {
33
- let content
34
- let role
35
- if (response?.object === 'response') {
36
- content = response?.output?.[0]?.content?.[0]?.text
37
- role = response?.output?.[0]?.role
38
- } else {
39
- content = response?.choices?.[0]?.message?.content
40
- role = response?.choices?.[0]?.message?.role
41
- }
42
-
43
- return { content, role }
44
- }
45
-
46
- /**
47
- * Parses all messages from the OpenAI request object.
48
- *
49
- * @param {object} request The OpenAI SDK request object
50
- * @param {Shim} shim instance of shim
51
- * @returns {Array<object>} an array of message objects with fields `content` and `role`
52
- */
53
- function getMessagesFromRequest(request, shim) {
54
- // There are a few different ways to pass messages to OpenAI SDK.
55
- //
56
- // For langchain and `chat.completions.create`, messages are passed
57
- // as an array of objects with `content` and `role` properties
58
- // to the `messages` field of the request.
59
- //
60
- // For `responses.create`, messages are passed as an array of objects
61
- // with `content` and `role` properties OR as a single string (implied
62
- // to be a user message) to the `input` field of the request.
63
- let messages = []
64
-
65
- if (Array.isArray(request?.input)) {
66
- // Handle array of input messages
67
- messages = request.input.filter((msg) => msg?.content && msg?.role)
68
- } else if (typeof request?.input === 'string') {
69
- // Handle single string input as a user message
70
- messages = [{ content: request.input, role: 'user' }]
71
- } else if (Array.isArray(request?.messages)) {
72
- // Handle array of messages
73
- messages = request.messages.filter((msg) => msg?.content && msg?.role)
74
- } else {
75
- shim.logger.warn('No valid messages found in OpenAI request object.')
76
- }
77
-
78
- return messages
79
- }
80
-
81
- /**
82
- * Checks if we should skip instrumentation.
83
- * Currently, it checks if `ai_monitoring.enabled` is true
84
- * and the package version >= 4.0.0
85
- *
86
- * @param {object} config agent config
87
- * @param {Shim} shim instance of shim
88
- * @returns {boolean} flag if instrumentation should be skipped
89
- */
90
- function shouldSkipInstrumentation(config, shim) {
91
- if (config?.ai_monitoring?.enabled !== true) {
92
- shim.logger.debug('config.ai_monitoring.enabled is set to false.')
93
- return true
94
- }
95
-
96
- const { pkgVersion } = shim
97
- return semver.lt(pkgVersion, MIN_VERSION)
98
- }
99
-
100
- /**
101
- * Adds apiKey and response headers to the active segment
102
- * on symbols
103
- *
104
- * @param {object} params input params
105
- * @param {Shim} params.shim instance of shim
106
- * @param {object} params.result from openai request
107
- * @param {string} params.apiKey api key from openai client
108
- */
109
- function decorateSegment({ shim, result, apiKey }) {
110
- const segment = shim.getActiveSegment()
111
-
112
- if (segment) {
113
- segment[openAiApiKey] = apiKey
114
-
115
- // If the result is an error, which is an OpenAI client error, then
116
- // the headers are provided via a proxy attached to `result.headers`.
117
- // Otherwise, result is a typical response-like object.
118
- const headers = result?.response?.headers
119
- ? Object.fromEntries(result.response.headers)
120
- : { ...result?.headers }
121
- segment[openAiHeaders] = headers
122
- }
123
- }
124
-
125
- /**
126
- * Enqueues a LLM event to the custom event aggregator
127
- *
128
- * @param {object} params input params
129
- * @param {Agent} params.agent NR agent instance
130
- * @param {string} params.type LLM event type
131
- * @param {object} params.msg LLM event
132
- */
133
- function recordEvent({ agent, type, msg }) {
134
- const llmContext = extractLlmContext(agent)
135
-
136
- agent.customEventAggregator.add([
137
- { type, timestamp: Date.now() },
138
- Object.assign({}, msg, llmContext)
139
- ])
140
- }
141
-
142
- /**
143
- * Increments the tracking metric and sets the llm attribute on transactions
144
- *
145
- * @param {object} params input params
146
- * @param {Agent} params.agent NR agent instance
147
- * @param {Transaction} params.transaction active transaction
148
- */
149
- function addLlmMeta({ agent, transaction }) {
150
- agent.metrics.getOrCreateMetric(TRACKING_METRIC).incrementCallCount()
151
- transaction.trace.attributes.addAttribute(DESTINATIONS.TRANS_EVENT, 'llm', true)
152
- }
153
-
154
- /**
155
- * Generates LlmChatCompletionSummary for a chat completion creation.
156
- * Also iterates over both input messages and the first response message
157
- * and creates LlmChatCompletionMessage.
158
- *
159
- * Also assigns relevant ids by response id for LlmFeedbackEvent creation
160
- *
161
- * @param {object} params input params
162
- * @param {Agent} params.agent NR agent instance
163
- * @param {Shim} params.shim the current shim instance
164
- * @param {TraceSegment} params.segment active segment from chat completion
165
- * @param {object} params.request chat completion params
166
- * @param {object} params.response chat completion response
167
- * @param {boolean} [params.err] err if it exists
168
- * @param {Transaction} params.transaction active transaction
169
- */
170
- function recordChatCompletionMessages({
171
- agent,
172
- shim,
173
- segment,
174
- request,
175
- response,
176
- err,
177
- transaction
178
- }) {
179
- if (shouldSkipInstrumentation(agent.config, shim) === true) {
180
- shim.logger.debug('skipping sending of ai data')
181
- return
182
- }
183
-
184
- if (!response) {
185
- // If we get an error, it is possible that `response = null`.
186
- // In that case, we define it to be an empty object.
187
- response = {}
188
- }
189
-
190
- response.headers = segment[openAiHeaders]
191
- // explicitly end segment to consistent duration
192
- // for both LLM events and the segment
193
- segment.end()
194
- const completionSummary = new LlmChatCompletionSummary({
195
- agent,
196
- segment,
197
- transaction,
198
- request,
199
- response,
200
- withError: err != null
201
- })
202
-
203
- // Note: langchain still expects a message event even
204
- // when the response is empty, so we will not filter
205
- // the response message.
206
- const messages = [
207
- ...getMessagesFromRequest(request, shim),
208
- getMessageFromResponse(response)
209
- ]
210
-
211
- messages.forEach((message, index) => {
212
- const completionMsg = new LlmChatCompletionMessage({
213
- agent,
214
- segment,
215
- transaction,
216
- request,
217
- response,
218
- index,
219
- completionId: completionSummary.id,
220
- message
221
- })
222
-
223
- recordEvent({ agent, type: 'LlmChatCompletionMessage', msg: completionMsg })
224
- })
225
-
226
- recordEvent({ agent, type: 'LlmChatCompletionSummary', msg: completionSummary })
227
-
228
- if (err) {
229
- const llmError = new LlmErrorMessage({ cause: err, summary: completionSummary, response })
230
- agent.errors.add(transaction, err, llmError)
231
- }
232
-
233
- delete response.headers
234
- }
235
-
236
- /**
237
- * `chat.completions.create` can return a stream once promise resolves.
238
- * This wraps the iterator which is a generator function.
239
- * We will call the original iterator, intercept chunks and yield
240
- * to the original. On complete, we will construct the new message object
241
- * with what we have seen in the stream and create the chat completion
242
- * messages.
243
- * @param {object} params input params
244
- * @param {Agent} params.agent NR agent instance
245
- * @param {Shim} params.shim the current shim instance
246
- * @param {object} params.request chat completion params
247
- * @param {object} params.response chat completion response
248
- * @param {TraceSegment} params.segment active segment from chat completion
249
- * @param {Transaction} params.transaction active transaction
250
- * @param {Error} params.err error if it exists
251
- */
252
- function instrumentStream({ agent, shim, request, response, segment, transaction, err = null }) {
253
- if (!agent.config.ai_monitoring.streaming.enabled) {
254
- shim.logger.warn(
255
- '`ai_monitoring.streaming.enabled` is set to `false`, stream will not be instrumented.'
256
- )
257
- agent.metrics.getOrCreateMetric(AI.STREAMING_DISABLED).incrementCallCount()
258
- return
259
- }
260
-
261
- if (err) {
262
- // If there is an error already e.g. APIConnectionError,
263
- // the iterator will not be called, so we have to
264
- // record the chat completion messages with the error now.
265
- segment.touch()
266
- recordChatCompletionMessages({
267
- agent,
268
- shim,
269
- segment,
270
- transaction,
271
- request,
272
- response,
273
- err
274
- })
275
- return
276
- }
277
-
278
- shim.wrap(response, 'iterator', function wrapIterator(shim, orig) {
279
- return async function * wrappedIterator() {
280
- let content = ''
281
- let role = ''
282
- let chunk
283
- try {
284
- const iterator = orig.apply(this, arguments)
285
-
286
- for await (chunk of iterator) {
287
- if (chunk.choices?.[0]?.delta?.role) {
288
- role = chunk.choices[0].delta.role
289
- }
290
-
291
- content += chunk.choices?.[0]?.delta?.content ?? ''
292
- yield chunk
293
- }
294
- } catch (streamErr) {
295
- err = streamErr
296
- throw err
297
- } finally {
298
- if (chunk?.choices) {
299
- chunk.choices[0].message = { role, content }
300
- } else if (chunk?.response) {
301
- chunk = chunk.response
302
- }
303
-
304
- // update segment duration since we want to extend the time it took to
305
- // handle the stream
306
- segment.touch()
307
-
308
- recordChatCompletionMessages({
309
- agent: shim.agent,
310
- shim,
311
- segment,
312
- transaction,
313
- request,
314
- response: chunk,
315
- err
316
- })
317
- }
318
- }
319
- })
320
- }
321
-
322
- module.exports = function initialize(agent, openai, moduleName, shim) {
323
- if (shouldSkipInstrumentation(agent.config, shim)) {
324
- shim.logger.debug(
325
- `${moduleName} instrumentation support is for versions >=${MIN_VERSION}. Skipping instrumentation.`
326
- )
327
- return
328
- }
329
-
330
- // Update the tracking metric name with the version of the library
331
- // being instrumented. We do not have access to the version when
332
- // initially declaring the variable.
333
- TRACKING_METRIC = `${TRACKING_METRIC}/${shim.pkgVersion}`
334
-
335
- // openai@^5.0.0 uses a different prototype structure
336
- const openaiClient = semver.gte(shim.pkgVersion, '5.0.0') ? openai.OpenAI : openai
337
-
338
- // Instrumentation is only done to get the response headers and attach
339
- // to the active segment as openai hides the headers from the functions we are
340
- // trying to instrument
341
- shim.wrap(openaiClient.prototype, 'makeRequest', function wrapRequest(shim, makeRequest) {
342
- return function wrappedRequest() {
343
- const apiKey = this.apiKey
344
- const request = makeRequest.apply(this, arguments)
345
- request.then(
346
- (result) => {
347
- // add headers on resolve
348
- decorateSegment({ shim, result, apiKey })
349
- },
350
- (result) => {
351
- // add headers on reject
352
- decorateSegment({ shim, result, apiKey })
353
- }
354
- )
355
- return request
356
- }
357
- })
358
-
359
- // Instruments chat completion creation
360
- // and creates the LLM events
361
- shim.record(
362
- openaiClient.Chat.Completions.prototype,
363
- 'create',
364
- function wrapCreate(shim, create, name, args) {
365
- const [request] = args
366
- if (request.stream && semver.lt(shim.pkgVersion, MIN_STREAM_VERSION)) {
367
- shim.logger.warn(
368
- `Instrumenting chat completion streams is only supported with openai version ${MIN_STREAM_VERSION}+.`
369
- )
370
- return
371
- }
372
-
373
- return new RecorderSpec({
374
- name: OPENAI.COMPLETION,
375
- promise: true,
376
- after({ error: err, result: response, segment, transaction }) {
377
- if (request.stream) {
378
- instrumentStream({ agent, shim, request, response, segment, transaction, err })
379
- } else {
380
- recordChatCompletionMessages({
381
- agent,
382
- shim,
383
- segment,
384
- transaction,
385
- request,
386
- response,
387
- err
388
- })
389
- }
390
-
391
- addLlmMeta({ agent, transaction })
392
- }
393
- })
394
- }
395
- )
396
-
397
- // New API introduced in openai@4.87.0
398
- // Also instruments chat completion creation
399
- // and creates the LLM events
400
- if (semver.gte(shim.pkgVersion, '4.87.0')) {
401
- shim.record(openaiClient.Responses.prototype, 'create', function wrapCreate(shim, create, name, args) {
402
- const [request] = args
403
-
404
- return new RecorderSpec({
405
- name: OPENAI.COMPLETION,
406
- promise: true,
407
- after({ error: err, result: response, segment, transaction }) {
408
- if (request.stream) {
409
- instrumentStream({ agent, shim, request, response, segment, transaction, err })
410
- } else {
411
- recordChatCompletionMessages({
412
- agent,
413
- shim,
414
- segment,
415
- transaction,
416
- request,
417
- response,
418
- err
419
- })
420
- }
421
-
422
- addLlmMeta({ agent, transaction })
423
- }
424
- })
425
- })
426
- }
427
-
428
- // Instruments embedding creation
429
- // and creates LlmEmbedding event
430
- shim.record(
431
- openaiClient.Embeddings.prototype,
432
- 'create',
433
- function wrapEmbeddingCreate(shim, embeddingCreate, name, args) {
434
- const [request] = args
435
- return new RecorderSpec({
436
- name: OPENAI.EMBEDDING,
437
- promise: true,
438
- after({ error: err, result: response, segment, transaction }) {
439
- addLlmMeta({ agent, transaction })
440
-
441
- if (!response) {
442
- // If we get an error, it is possible that `response = null`.
443
- // In that case, we define it to be an empty object.
444
- response = {}
445
- }
446
-
447
- segment.end()
448
- if (shouldSkipInstrumentation(shim.agent.config, shim) === true) {
449
- // We need this check inside the wrapper because it is possible for monitoring
450
- // to be disabled at the account level. In such a case, the value is set
451
- // after the instrumentation has been initialized.
452
- shim.logger.debug('skipping sending of ai data')
453
- return
454
- }
455
-
456
- response.headers = segment[openAiHeaders]
457
- // explicitly end segment to get consistent duration
458
- // for both LLM events and the segment
459
-
460
- const embedding = new LlmEmbedding({
461
- agent,
462
- segment,
463
- transaction,
464
- request,
465
- response,
466
- withError: err != null
467
- })
468
-
469
- recordEvent({ agent, type: 'LlmEmbedding', msg: embedding })
470
-
471
- if (err) {
472
- const llmError = new LlmErrorMessage({ cause: err, embedding, response })
473
- shim.agent.errors.add(transaction, err, llmError)
474
- }
475
-
476
- // cleanup keys on response before returning to user code
477
- delete response.headers
478
- }
479
- })
480
- }
481
- )
482
- }
@@ -1,84 +0,0 @@
1
- /*
2
- * Copyright 2025 New Relic Corporation. All rights reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- 'use strict'
7
-
8
- module.exports = bootstrapOtelLogs
9
-
10
- const logsApi = require('@opentelemetry/api-logs')
11
- const logsSdk = require('@opentelemetry/sdk-logs')
12
- const defaultLogger = require('#agentlib/logger.js').child({ component: 'otel-logs' })
13
- const {
14
- isApplicationLoggingEnabled,
15
- isLogForwardingEnabled,
16
- isMetricsEnabled,
17
- incrementLoggingLinesMetrics
18
- } = require('#agentlib/util/application-logging.js')
19
-
20
- const NewRelicLoggerProvider = require('./proxying-provider.js')
21
- const NoOpExporter = require('./no-op-exporter.js')
22
- const normalizeTimestamp = require('./normalize-timestamp.js')
23
- const severityToString = require('./severity-to-string.js')
24
-
25
- /**
26
- * Sets up the OTEL logging system and instruments it in such a fashion
27
- * that logs are shipped to New Relic when logs forwarding is enabled.
28
- *
29
- * The API, as of 2025-07, is in development. We should expect changes that
30
- * will require significant refactoring here. For example, we might need to
31
- * accept instances of various components, e.g. record processors, in order
32
- * for customer apps to work as they expect.
33
- *
34
- * @param {object} params Factory function parameters.
35
- * @param {Agent} params.agent The Node.js agent instance.
36
- * @param {object} [params.logger] An agent logger instance.
37
- */
38
- function bootstrapOtelLogs({ agent, logger = defaultLogger } = {}) {
39
- if (isApplicationLoggingEnabled(agent.config) === false) {
40
- logger.info('application logging disabled, skipping otel logs setup')
41
- return
42
- }
43
-
44
- agent.metrics
45
- .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Logs')
46
- .incrementCallCount()
47
-
48
- const exporter = new NoOpExporter()
49
- const processor = new logsSdk.BatchLogRecordProcessor(exporter)
50
- const otelProvider = new logsSdk.LoggerProvider({
51
- processors: [processor]
52
- })
53
- const provider = new NewRelicLoggerProvider({
54
- agent,
55
- provider: otelProvider,
56
- emitHandler: nrEmitHandler
57
- })
58
- logsApi.logs.setGlobalLoggerProvider(provider)
59
-
60
- function nrEmitHandler(record) {
61
- const level = severityToString(record.severityNumber ?? 0)
62
- if (isMetricsEnabled(agent.config) === true) {
63
- incrementLoggingLinesMetrics(level, agent.metrics)
64
- }
65
-
66
- // TODO: if we decide to support local decorating, implement it here
67
-
68
- if (isLogForwardingEnabled(agent.config, agent) === true) {
69
- const meta = agent.getLinkingMetadata(true)
70
- const timestamp = normalizeTimestamp(record.timestamp)
71
- const logData = {
72
- message: record.body,
73
- level,
74
- timestamp,
75
- ...record.attributes,
76
- ...meta
77
- }
78
-
79
- agent.logs.add(logData)
80
- }
81
- }
82
-
83
- agent.emit('otelLogsBootstrapped')
84
- }
@@ -1,117 +0,0 @@
1
- /*
2
- * Copyright 2025 New Relic Corporation. All rights reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- 'use strict'
7
-
8
- module.exports = bootstrapOtelMetrics
9
-
10
- const { metrics } = require('@opentelemetry/api')
11
- const { resourceFromAttributes } = require('@opentelemetry/resources')
12
- const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-proto')
13
- const {
14
- AggregationTemporality,
15
- InMemoryMetricExporter,
16
- MeterProvider,
17
- PeriodicExportingMetricReader
18
- } = require('@opentelemetry/sdk-metrics')
19
-
20
- const ProxyingExporter = require('./proxying-exporter.js')
21
-
22
- /**
23
- * Configures the OpenTelemetry metrics API client to send metrics data
24
- * to New Relic.
25
- *
26
- * @param {Agent} agent The Node.js agent instance.
27
- * @fires Agent#otelMetricsBootstrapped
28
- */
29
- function bootstrapOtelMetrics(agent) {
30
- const { config } = agent
31
- const exportInterval = config.opentelemetry_bridge.metrics.exportInterval
32
- const exportTimeout = config.opentelemetry_bridge.metrics.exportTimeout
33
-
34
- const resource = resourceFromAttributes({ })
35
- const memExporter = new InMemoryMetricExporter(AggregationTemporality.DELTA)
36
- const proxyExporter = new ProxyingExporter({ exporter: memExporter })
37
- const reader = new PeriodicExportingMetricReader({
38
- exporter: proxyExporter,
39
- exportIntervalMillis: exportInterval,
40
- exportTimeoutMillis: exportTimeout
41
- })
42
- const provider = new MeterProvider({
43
- readers: [reader],
44
- resource
45
- })
46
-
47
- const getMeter = provider.getMeter
48
- provider.getMeter = function nrGetMeter(...args) {
49
- agent.metrics
50
- .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Metrics/getMeter')
51
- .incrementCallCount()
52
-
53
- const meter = getMeter.apply(provider, args)
54
- const proto = Object.getPrototypeOf(meter)
55
- const methods = Object.getOwnPropertyNames(proto).filter((name) => name.startsWith('create'))
56
- const originals = {}
57
- for (const method of methods) {
58
- originals[method] = meter[method]
59
- // As of 2025-06-17:
60
- // + createGauge
61
- // + createHistogram
62
- // + createCounter
63
- // + createUpDownCounter
64
- // + createObservableGauge
65
- // + createObservableCounter
66
- // + createObservableUpDownCounter
67
- meter[method] = function nrWrappedMethod(...args) {
68
- agent.metrics
69
- .getOrCreateMetric(`Supportability/Nodejs/OpenTelemetryBridge/Metrics/meter/${method}`)
70
- .incrementCallCount()
71
- return originals[method].apply(meter, args)
72
- }
73
- }
74
-
75
- return meter
76
- }
77
-
78
- metrics.setGlobalMeterProvider(provider)
79
-
80
- agent.metrics
81
- .getOrCreateMetric('Supportability/Nodejs/OpenTelemetryBridge/Metrics')
82
- .incrementCallCount()
83
-
84
- // We need access to `agent.config.entity_guid` in order to attach metrics
85
- // to the correct instrumentation entity. But that value is not available
86
- // until either at least the first firing of `agent.config.on('change')`, or
87
- // the `agent.on('started')` event. Which means that we can't finalize the
88
- // metrics client configuration until after the `started` event.
89
- agent.on('started', postReady)
90
- function postReady() {
91
- agent.removeListener('started', postReady)
92
-
93
- reader.collect().then(({ resourceMetrics: collectedMetrics }) => {
94
- proxyExporter.exporter = new OTLPMetricExporter({
95
- url: `https://${config.host}:${config.port}/v1/metrics`,
96
- headers: {
97
- 'api-key': config.license_key
98
- },
99
- temporalityPreference: AggregationTemporality.DELTA
100
- })
101
-
102
- const resource = resourceFromAttributes({ 'entity.guid': config.entity_guid })
103
- // Assigning the resource after having received the `entity.guid` from
104
- // the server is a key detail of this implementation. Unfortunately,
105
- // we don't have real public access to the object that retains the
106
- // resource reference. If upstream ever hides this from us, we'll be
107
- // in a bit of a bind.
108
- provider._sharedState.resource = resource
109
-
110
- // Attempt to ship any metrics recorded prior to the `started` event.
111
- collectedMetrics.resource = resource
112
- proxyExporter.exporter.export(collectedMetrics, () => {})
113
-
114
- agent.emit('otelMetricsBootstrapped')
115
- })
116
- }
117
- }
@@ -1,22 +0,0 @@
1
- /*
2
- * Copyright 2024 New Relic Corporation. All rights reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- 'use strict'
7
-
8
- const createConsumerSegment = require('./consumer')
9
- const createDbSegment = require('./database')
10
- const createHttpExternalSegment = require('./http-external')
11
- const createProducerSegment = require('./producer')
12
- const createServerSegment = require('./server')
13
- const createInternalSegment = require('./internal')
14
-
15
- module.exports = {
16
- createConsumerSegment,
17
- createDbSegment,
18
- createHttpExternalSegment,
19
- createInternalSegment,
20
- createProducerSegment,
21
- createServerSegment
22
- }