newrelic 12.6.1 → 12.8.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.
package/NEWS.md CHANGED
@@ -1,3 +1,61 @@
1
+ ### v12.8.0 (2024-11-19)
2
+
3
+ #### Features
4
+
5
+ * Added `labels.enabled` and `labels.exclude` to config defaults ([#2727](https://github.com/newrelic/node-newrelic/pull/2727)) ([36adb13](https://github.com/newrelic/node-newrelic/commit/36adb13caad41a5df5a5fa9345b9dcb9061db969))
6
+ * Added new configuration options to add custom tags (labels) to logs ([#2743](https://github.com/newrelic/node-newrelic/pull/2743)) ([d20c874](https://github.com/newrelic/node-newrelic/commit/d20c874638ebb19f8888003d4402bce17e56360f))
7
+ * With custom tags on logs, platform engineers can easily filter, search, and correlate log data for faster and more efficient troubleshooting, improved performance, and optimized resource utilization. To learn more about this feature see the [documentation](https://docs.newrelic.com/docs/logs/logs-context/custom-tags-agent-forwarder-logs/)
8
+ * Added support for Bedrock Claude 3.5 invoke model user request ([#2721](https://github.com/newrelic/node-newrelic/pull/2721)) ([a901a24](https://github.com/newrelic/node-newrelic/commit/a901a2499e73e0f1142054ead53e7b99df94b201))
9
+
10
+ * Moved `entity.guid`, `entity.name`, `entity.type`, and `hostname` to `common.attributes` on logs payload instead of in every log message ([#2736](https://github.com/newrelic/node-newrelic/pull/2736)) ([a7f14de](https://github.com/newrelic/node-newrelic/commit/a7f14def489000ab7492b01155069b1a084eeb36))
11
+
12
+ #### Bug fixes
13
+
14
+ * Corrected expected shape of user input in get Prompt() ([#2741](https://github.com/newrelic/node-newrelic/pull/2741)) ([a8eef8b](https://github.com/newrelic/node-newrelic/commit/a8eef8b0f80ec2b0db5834a722a69f58a32b5676))
15
+
16
+ #### Documentation
17
+
18
+ * Updated compatibility report ([#2755](https://github.com/newrelic/node-newrelic/pull/2755)) ([459a3f1](https://github.com/newrelic/node-newrelic/commit/459a3f19ee080a3a2e560636ce087f9e91bc529e))
19
+
20
+ #### Miscellaneous chores
21
+
22
+ * Fixed test reporter ([#2751](https://github.com/newrelic/node-newrelic/pull/2751)) ([5878ef9](https://github.com/newrelic/node-newrelic/commit/5878ef9844fd56038d08e3b2f8cd9e26ba8c4c92))
23
+ * Improved unit test speed ([#2728](https://github.com/newrelic/node-newrelic/pull/2728)) ([9640030](https://github.com/newrelic/node-newrelic/commit/96400304e451502ce2f09b569b46eaff271fd9ab))
24
+
25
+ #### Tests
26
+
27
+ * Fixed two assertions to work against older versions of `openai` ([#2726](https://github.com/newrelic/node-newrelic/pull/2726)) ([8996fc3](https://github.com/newrelic/node-newrelic/commit/8996fc3538735d7ad2d447bc7c4221b08c6c2be5))
28
+ * Migrated `ioredis`, `prisma` and `undici` versioned tests to `node:test` ([#2746](https://github.com/newrelic/node-newrelic/pull/2746)) ([449ae03](https://github.com/newrelic/node-newrelic/commit/449ae03883d2a5e92d11590372993eedc976ab99))
29
+ * Migrated `memcached`, `nestjs`, and `restify` versioned tests to `node:test` ([#2752](https://github.com/newrelic/node-newrelic/pull/2752)) ([653f614](https://github.com/newrelic/node-newrelic/commit/653f61410a0258683ac104cd8b8b2e7ae7c61043))
30
+ * Migrated `nextjs` versioned tests to `node:test` ([#2749](https://github.com/newrelic/node-newrelic/pull/2749)) ([d385af6](https://github.com/newrelic/node-newrelic/commit/d385af69c84a5425cee9ff19eaeadcba9e63a761))
31
+ * Migrated `redis` versioned tests to `node:test` ([#2725](https://github.com/newrelic/node-newrelic/pull/2725)) ([9752827](https://github.com/newrelic/node-newrelic/commit/97528270c70ecaac9161f4eb0cc78ffcc482be36))
32
+ * Updated `hapi` tests to `node:test` ([#2722](https://github.com/newrelic/node-newrelic/pull/2722)) ([636899b](https://github.com/newrelic/node-newrelic/commit/636899b487e196572a3bc0970e7338d0c9f113cf))
33
+ * Updated `kafkajs`, `langchain`, & `openai` tests to `node:test` ([#2723](https://github.com/newrelic/node-newrelic/pull/2723)) ([b6e4c5d](https://github.com/newrelic/node-newrelic/commit/b6e4c5d4c59032a615fea6d12623a881a6211917))
34
+ * Updated `koa` tests to `node:test` ([#2744](https://github.com/newrelic/node-newrelic/pull/2744)) ([86c22a4](https://github.com/newrelic/node-newrelic/commit/86c22a4813ec5a63ae8bd04f7d9d013979ac34ed))
35
+ * Updated `mongodb` tests to `node:test` ([#2738](https://github.com/newrelic/node-newrelic/pull/2738)) ([295e7e6](https://github.com/newrelic/node-newrelic/commit/295e7e6441b7220022ce4fcaaf33054945a678b0))
36
+ * Updated `pg` and `pg-esm` tests to `node:test` ([#2750](https://github.com/newrelic/node-newrelic/pull/2750)) ([40aea36](https://github.com/newrelic/node-newrelic/commit/40aea36320d15b201800431268be2c3d4c794a7b))
37
+
38
+ ### v12.7.0 (2024-11-11)
39
+
40
+ #### Features
41
+
42
+ * Added `cloud.resource_id` attribute to dynamo spans ([#2701](https://github.com/newrelic/node-newrelic/pull/2701)) ([904f41b](https://github.com/newrelic/node-newrelic/commit/904f41b26637394a24aa13f31ff94b100ae6d090))
43
+ * Enhance Proxy Request Handling to Display Actual External URLs ([#2698](https://github.com/newrelic/node-newrelic/pull/2698)) ([3ef7bbe](https://github.com/newrelic/node-newrelic/commit/3ef7bbe595860234c021a02235e6fd0615da5f69))
44
+ * Thanks for the contribution @mstarzec386
45
+
46
+ #### Documentation
47
+
48
+ * Updated compatibility report ([#2712](https://github.com/newrelic/node-newrelic/pull/2712)) ([82f0e98](https://github.com/newrelic/node-newrelic/commit/82f0e9806c88d14cba2e0cdf47593e036107bd7d)) ([#2699](https://github.com/newrelic/node-newrelic/pull/2699)) ([4432c42](https://github.com/newrelic/node-newrelic/commit/4432c4215d68cc79333ee3828f1ecd55476c63d8))
49
+
50
+ #### Miscellaneous chores
51
+
52
+ * Added a benchmark script for our sql parser ([#2708](https://github.com/newrelic/node-newrelic/pull/2708)) ([9b6de68](https://github.com/newrelic/node-newrelic/commit/9b6de6852747230b87a9873faffba6e5b39669f3))
53
+ * Updated express-esm, generic-pool, grpc, & grpc-esm tests to node:test ([#2702](https://github.com/newrelic/node-newrelic/pull/2702)) ([a229bbf](https://github.com/newrelic/node-newrelic/commit/a229bbf0dd92c43fb2da077d8dce831b84c0c972))
54
+
55
+ #### Tests
56
+
57
+ * Migrated `mysql` and `mysql2` versioned tests to `node:test` ([#2711](https://github.com/newrelic/node-newrelic/pull/2711)) ([fc767e0](https://github.com/newrelic/node-newrelic/commit/fc767e08d8b546d14c53c07bc2cfe65f3fb55368))
58
+
1
59
  ### v12.6.1 (2024-11-07)
2
60
 
3
61
  #### Features
package/lib/agent.js CHANGED
@@ -40,6 +40,7 @@ const {
40
40
  } = require('./util/attributes')
41
41
  const synthetics = require('./synthetics')
42
42
  const Harvester = require('./harvester')
43
+ const { createFeatureUsageMetrics } = require('./util/application-logging')
43
44
 
44
45
  // Map of valid states to whether or not data collection is valid
45
46
  const STATES = {
@@ -405,7 +406,7 @@ Agent.prototype.startStreaming = function startStreaming() {
405
406
  */
406
407
  Agent.prototype.onConnect = function onConnect(shouldImmediatelyHarvest, callback) {
407
408
  this.harvester.update(this.config)
408
- generateLoggingSupportMetrics(this)
409
+ createFeatureUsageMetrics(this)
409
410
 
410
411
  if (this.config.certificates && this.config.certificates.length > 0) {
411
412
  this.metrics.getOrCreateMetric(NAMES.FEATURES.CERTIFICATES).incrementCallCount()
@@ -821,6 +822,8 @@ Agent.prototype._listenForConfigChanges = function _listenForConfigChanges() {
821
822
  * This method returns an object with the following keys/data:
822
823
  * - `trace.id`: The current trace ID
823
824
  * - `span.id`: The current span ID
825
+ *
826
+ * If `excludeServiceLinks` is false it will also include the following:
824
827
  * - `entity.name`: The application name specified in the connect request as
825
828
  * app_name. If multiple application names are specified this will only be
826
829
  * the first name
@@ -830,17 +833,14 @@ Agent.prototype._listenForConfigChanges = function _listenForConfigChanges() {
830
833
  * utilization.full_hostname. If utilization.full_hostname is null or empty,
831
834
  * this will be the hostname specified in the connect request as host.
832
835
  *
836
+ * @param {boolean} excludeServiceLinks flag to indicate if you should include `entity.guid`, `entity.type`, `entity.name` and `hostname` from linking metadata
833
837
  * @returns {object} The LinkingMetadata object with the data above
834
838
  */
835
- Agent.prototype.getLinkingMetadata = function getLinkingMetadata() {
839
+ Agent.prototype.getLinkingMetadata = function getLinkingMetadata(excludeServiceLinks = false) {
836
840
  const segment = this.tracer.getSegment()
837
841
  const config = this.config
838
842
 
839
- const linkingMetadata = {
840
- 'entity.name': config.applications()[0],
841
- 'entity.type': 'SERVICE',
842
- 'hostname': config.getHostnameSafe()
843
- }
843
+ const linkingMetadata = {}
844
844
 
845
845
  if (config.distributed_tracing.enabled && segment) {
846
846
  linkingMetadata['trace.id'] = segment.transaction.traceId
@@ -852,10 +852,37 @@ Agent.prototype.getLinkingMetadata = function getLinkingMetadata() {
852
852
  logger.debug('getLinkingMetadata with no active transaction')
853
853
  }
854
854
 
855
+ if (!excludeServiceLinks) {
856
+ this.getServiceLinkingMetadata(linkingMetadata)
857
+ }
858
+
859
+ return linkingMetadata
860
+ }
861
+
862
+ /**
863
+ * This methods returns an object with the following keys/data:
864
+ * - `entity.name`: The application name specified in the connect request as
865
+ * app_name. If multiple application names are specified this will only be
866
+ * the first name
867
+ * - `entity.type`: The string "SERVICE"
868
+ * - `entity.guid`: The entity ID returned in the connect reply as entity_guid
869
+ * - `hostname`: The hostname as specified in the connect request as
870
+ * utilization.full_hostname. If utilization.full_hostname is null or empty,
871
+ * this will be the hostname specified in the connect request as host.
872
+ *
873
+ * @param {object} linkingMetadata object to add service linking keys
874
+ * @returns {object} with service linking metadata
875
+ */
876
+ Agent.prototype.getServiceLinkingMetadata = function getServiceLinkingMetadata(
877
+ linkingMetadata = {}
878
+ ) {
879
+ const config = this.config
855
880
  if (config.entity_guid) {
856
881
  linkingMetadata['entity.guid'] = config.entity_guid
857
882
  }
858
-
883
+ linkingMetadata['entity.name'] = config.applications()[0]
884
+ linkingMetadata['entity.type'] = 'SERVICE'
885
+ linkingMetadata.hostname = config.getHostnameSafe()
859
886
  return linkingMetadata
860
887
  }
861
888
 
@@ -927,23 +954,4 @@ function generateEventHarvestSupportMetrics(agent, harvestConfig) {
927
954
  }
928
955
  }
929
956
 
930
- /**
931
- * Increments the call counts of application logging supportability metrics
932
- * on every connect cycle
933
- *
934
- * @param {object} agent Instantiation of Node.js agent
935
- */
936
- function generateLoggingSupportMetrics(agent) {
937
- const loggingConfig = agent.config.application_logging
938
- const logNames = NAMES.LOGGING
939
-
940
- const configKeys = ['metrics', 'forwarding', 'local_decorating']
941
- configKeys.forEach((configValue) => {
942
- const configFlag =
943
- loggingConfig.enabled && loggingConfig[`${configValue}`].enabled ? 'enabled' : 'disabled'
944
- const metricName = logNames[`${configValue.toUpperCase()}`]
945
- agent.metrics.getOrCreateMetric(`${metricName}${configFlag}`).incrementCallCount()
946
- })
947
- }
948
-
949
957
  module.exports = Agent
@@ -6,6 +6,7 @@
6
6
  'use strict'
7
7
 
8
8
  const logger = require('../logger').child({ component: 'logs_aggregator' })
9
+ const { isLogLabelingEnabled } = require('../../lib/util/application-logging')
9
10
  const EventAggregator = require('./event-aggregator')
10
11
 
11
12
  const NAMES = require('../metrics/names')
@@ -56,7 +57,18 @@ class LogAggregator extends EventAggregator {
56
57
  return
57
58
  }
58
59
 
59
- return [{ logs: formattedLogs }]
60
+ let commonAttrs = this.agent.getServiceLinkingMetadata()
61
+
62
+ if (isLogLabelingEnabled(this.agent.config)) {
63
+ commonAttrs = { ...commonAttrs, ...this.agent.config.loggingLabels }
64
+ }
65
+
66
+ return [
67
+ {
68
+ common: { attributes: commonAttrs },
69
+ logs: formattedLogs
70
+ }
71
+ ]
60
72
  }
61
73
 
62
74
  add(logLine) {
@@ -8,7 +8,6 @@
8
8
  const fetchSystemInfo = require('../system-info')
9
9
  const defaultLogger = require('../logger').child({ component: 'facts' })
10
10
  const os = require('os')
11
- const parseLabels = require('../util/label-parser')
12
11
 
13
12
  module.exports = facts
14
13
 
@@ -49,7 +48,7 @@ async function facts(agent, callback, { logger = defaultLogger } = {}) {
49
48
  environment: environment,
50
49
  settings: agent.config.publicSettings(),
51
50
  high_security: agent.config.high_security,
52
- labels: parseLabels(agent.config.labels),
51
+ labels: agent.config.parsedLabels,
53
52
  metadata: Object.keys(process.env).reduce((obj, key) => {
54
53
  if (key.startsWith('NEW_RELIC_METADATA_')) {
55
54
  obj[key] = process.env[key]
@@ -1185,6 +1185,22 @@ defaultConfig.definition = () => ({
1185
1185
  max_samples_stored: {
1186
1186
  formatter: int,
1187
1187
  default: 10000
1188
+ },
1189
+ labels: {
1190
+ /**
1191
+ * If `true`, the agent attaches labels to log records.
1192
+ */
1193
+ enabled: {
1194
+ formatter: boolean,
1195
+ default: false
1196
+ },
1197
+ /**
1198
+ * A case-insensitive array containing the labels to exclude from log records.
1199
+ */
1200
+ exclude: {
1201
+ formatter: array,
1202
+ default: []
1203
+ }
1188
1204
  }
1189
1205
  },
1190
1206
  metrics: {
@@ -24,6 +24,7 @@ const harvestConfigValidator = require('./harvest-config-validator')
24
24
  const mergeServerConfig = new MergeServerConfig()
25
25
  const { boolean: isTruthular } = require('./formatters')
26
26
  const configDefinition = definition()
27
+ const parseLabels = require('../util/label-parser')
27
28
 
28
29
  /**
29
30
  * CONSTANTS -- we gotta lotta 'em
@@ -195,9 +196,38 @@ function Config(config) {
195
196
 
196
197
  // 9. Set instance attribute filter using updated context
197
198
  this.attributeFilter = new AttributeFilter(this)
199
+
200
+ // 10. Setup labels for both `collector/facts` and application logging
201
+ this.parsedLabels = parseLabels(this.labels, logger)
202
+ this.loggingLabels = this._setApplicationLoggingLabels()
198
203
  }
199
204
  util.inherits(Config, EventEmitter)
200
205
 
206
+ /**
207
+ * Compares the labels list to the application logging excluded label list and removes any labels that need to be excluded.
208
+ * Then prefixing each label with "tags."
209
+ *
210
+ * assigns labels to `config.loggingLabels`
211
+ */
212
+ Config.prototype._setApplicationLoggingLabels = function setApplicationLoggingLabels() {
213
+ if (this.application_logging.forwarding.labels.enabled === false) {
214
+ return
215
+ }
216
+
217
+ this.application_logging.forwarding.labels.exclude =
218
+ this.application_logging.forwarding.labels.exclude.map((k) => k.toLowerCase())
219
+
220
+ return this.parsedLabels.reduce((filteredLabels, label) => {
221
+ if (
222
+ !this.application_logging.forwarding.labels.exclude.includes(label.label_type.toLowerCase())
223
+ ) {
224
+ filteredLabels[`tags.${label.label_type}`] = label.label_value
225
+ }
226
+
227
+ return filteredLabels
228
+ }, {})
229
+ }
230
+
201
231
  /**
202
232
  * Because this module and logger depend on each other, the logger needs
203
233
  * a way to inject the actual logger instance once it's constructed.
@@ -72,21 +72,66 @@ function dynamoMiddleware(shim, config, next, context) {
72
72
  }
73
73
  }
74
74
 
75
- const dynamoMiddlewareConfig = {
76
- middleware: dynamoMiddleware,
77
- init(shim) {
78
- shim.setDatastore(shim.DYNAMODB)
79
- return true
80
- },
81
- type: InstrumentationDescriptor.TYPE_DATASTORE,
82
- config: {
83
- name: 'NewRelicDynamoMiddleware',
84
- step: 'initialize',
85
- priority: 'high',
86
- override: true
75
+ /**
76
+ * Wraps the deserialize middleware step to add the
77
+ * cloud.resource_id segment attributes for the AWS command
78
+ *
79
+ * @param {Shim} shim
80
+ * @param {Object} config AWS command configuration
81
+ * @param {function} next next function in middleware chain
82
+ * @returns {function}
83
+ */
84
+ function resourceIdMiddlerware(shim, config, next) {
85
+ return async function wrappedResourceIdMiddlerware(args) {
86
+ let region
87
+ try {
88
+ region = await config.region()
89
+ const segment = shim.getSegment()
90
+
91
+ const accountId = shim.agent.config.cloud.aws.account_id
92
+
93
+ if (accountId) {
94
+ const attributes = segment.getAttributes()
95
+ segment.addAttribute(
96
+ 'cloud.resource_id',
97
+ `arn:aws:dynamodb:${region}:${accountId}:table/${attributes.collection}`
98
+ )
99
+ }
100
+ } catch (err) {
101
+ shim.logger.debug(err, 'Failed to add AWS cloud resource id to segment')
102
+ }
103
+
104
+ return next(args)
87
105
  }
88
106
  }
89
107
 
108
+ const dynamoMiddlewareConfig = [
109
+ {
110
+ middleware: dynamoMiddleware,
111
+ init(shim) {
112
+ shim.setDatastore(shim.DYNAMODB)
113
+ return true
114
+ },
115
+ type: InstrumentationDescriptor.TYPE_DATASTORE,
116
+ config: {
117
+ name: 'NewRelicDynamoMiddleware',
118
+ step: 'initialize',
119
+ priority: 'high',
120
+ override: true
121
+ }
122
+ },
123
+ {
124
+ middleware: resourceIdMiddlerware,
125
+ type: InstrumentationDescriptor.TYPE_GENERIC,
126
+ config: {
127
+ name: 'NewRelicCloudResource',
128
+ step: 'deserialize',
129
+ priority: 'low',
130
+ override: true
131
+ }
132
+ }
133
+ ]
134
+
90
135
  module.exports = {
91
136
  dynamoMiddlewareConfig
92
137
  }
@@ -17,8 +17,8 @@ const middlewareByClient = {
17
17
  BedrockRuntime: [...middlewareConfig, bedrockMiddlewareConfig],
18
18
  SNS: [...middlewareConfig, snsMiddlewareConfig],
19
19
  SQS: [...middlewareConfig, sqsMiddlewareConfig],
20
- DynamoDB: [...middlewareConfig, dynamoMiddlewareConfig],
21
- DynamoDBDocument: [...middlewareConfig, dynamoMiddlewareConfig]
20
+ DynamoDB: [...middlewareConfig, ...dynamoMiddlewareConfig],
21
+ DynamoDBDocument: [...middlewareConfig, ...dynamoMiddlewareConfig]
22
22
  }
23
23
 
24
24
  module.exports = function instrumentSmithyClient(shim, smithyClientExport) {
@@ -15,6 +15,7 @@ const copy = require('../../util/copy')
15
15
  const symbols = require('../../symbols')
16
16
  const http = require('http')
17
17
  const synthetics = require('../../synthetics')
18
+ const { URL } = require('node:url')
18
19
 
19
20
  const NAMES = require('../../metrics/names')
20
21
  const DEFAULT_HOST = 'localhost'
@@ -93,6 +94,27 @@ function extractHostPort(opts) {
93
94
  return { host, hostname, port }
94
95
  }
95
96
 
97
+ /**
98
+ * Extracts the host, hostname, and port from HTTP request options when using a proxy
99
+ *
100
+ * @param {object} opts HTTP request options
101
+ * @returns {object|null} { host, hostname, port } if proxy request is detected, or null otherwise.
102
+ */
103
+ function extractHostPortViaProxy(opts) {
104
+ const pathname = opts.pathname || opts.path
105
+
106
+ if (pathname && (pathname.startsWith('https://') || pathname.startsWith('http://'))) {
107
+ const url = new URL(pathname)
108
+ return {
109
+ host: url.host,
110
+ hostname: url.hostname,
111
+ port: url.port || url.protocol === 'https:' ? '443' : '80'
112
+ }
113
+ }
114
+
115
+ return null
116
+ }
117
+
96
118
  /**
97
119
  * Instruments an outbound HTTP request.
98
120
  *
@@ -103,7 +125,9 @@ function extractHostPort(opts) {
103
125
  */
104
126
  module.exports = function instrumentOutbound(agent, opts, makeRequest) {
105
127
  opts = parseOpts(opts)
106
- const { host, hostname, port } = extractHostPort(opts)
128
+
129
+ const viaProxy = extractHostPortViaProxy(opts)
130
+ const { host, hostname, port } = viaProxy ? viaProxy : extractHostPort(opts)
107
131
 
108
132
  if (!hostname || port < 1) {
109
133
  logger.warn('Invalid host name (%s) or port (%s) for outbound request.', hostname, port)
@@ -127,7 +127,7 @@ function wrapResponse(shim, response) {
127
127
  if (!shim.isFunction(cb)) {
128
128
  ++cbIdx
129
129
  cb = function defaultRenderCB(err, str) {
130
- // https://github.com/expressjs/express/blob/4.x/lib/response.js#L961-L962
130
+ // https://github.com/expressjs/express/blob/4.x/lib/response.js#L961-L962
131
131
  if (err) {
132
132
  return res.req.next(err)
133
133
  }
@@ -35,7 +35,7 @@ class NrTransport extends TransportStream {
35
35
  * @param {Function} callback callback to invoke once we are done
36
36
  */
37
37
  log(logLine, callback) {
38
- const metadata = this.agent.getLinkingMetadata()
38
+ const metadata = this.agent.getLinkingMetadata(true)
39
39
  const formattedLine = reformatLogLine(logLine, metadata)
40
40
  this.agent.logs.add(formattedLine)
41
41
  callback()
@@ -45,7 +45,6 @@ module.exports = function instrument(shim, tools) {
45
45
  * @param {object} params.tools exported `pino/lib/tools`
46
46
  */
47
47
  function wrapAsJson({ shim, tools }) {
48
- const symbols = shim.require('./lib/symbols')
49
48
  const { agent } = shim
50
49
  const { config, metrics } = agent
51
50
 
@@ -88,12 +87,10 @@ function wrapAsJson({ shim, tools }) {
88
87
  const logLine = asJson.apply(this, args)
89
88
 
90
89
  if (isLogForwardingEnabled(config, agent)) {
91
- const chindings = this[symbols.chindingsSym]
92
90
  const formatLogLine = reformatLogLine({
93
91
  msg: useMergeObj === true ? args[0].msg : args[1],
94
92
  logLine,
95
93
  agent,
96
- chindings,
97
94
  level,
98
95
  logger: shim.logger
99
96
  })
@@ -114,22 +111,12 @@ function wrapAsJson({ shim, tools }) {
114
111
  * @param {object} params.logLine log line
115
112
  * @param {string} params.msg message of log line
116
113
  * @param {object} params.agent instance of agent
117
- * @param {string} params.chindings serialized string of all common log line data
118
114
  * @param {string} params.level log level
119
115
  * @param {object} params.logger instance of agent logger
120
116
  * @returns {function} wrapped log formatter function
121
117
  */
122
- function reformatLogLine({ logLine, msg, agent, chindings = '', level, logger }) {
123
- const metadata = agent.getLinkingMetadata()
124
-
125
- /**
126
- * pino adds this already for us at times
127
- * since asJson manually constructs the json string,
128
- * it will have hostname twice if we do not delete ours.
129
- */
130
- if (chindings.includes('hostname')) {
131
- delete metadata.hostname
132
- }
118
+ function reformatLogLine({ logLine, msg, agent, level, logger }) {
119
+ const metadata = agent.getLinkingMetadata(true)
133
120
 
134
121
  const agentMeta = Object.assign({}, { timestamp: Date.now() }, metadata)
135
122
  // eslint-disable-next-line eqeqeq
@@ -84,10 +84,19 @@ class BedrockCommand {
84
84
  ) {
85
85
  result = this.#body.prompt
86
86
  } else if (this.isClaude3() === true) {
87
- result = this.#body?.messages?.reduce((acc, curr) => {
88
- acc += curr?.content ?? ''
89
- return acc
90
- }, '')
87
+ const collected = []
88
+ for (const message of this.#body?.messages) {
89
+ if (message?.role === 'assistant') {
90
+ continue
91
+ }
92
+ if (typeof message?.content === 'string') {
93
+ collected.push(message?.content)
94
+ continue
95
+ }
96
+ const mappedMsgObj = message?.content.map((msgContent) => msgContent.text)
97
+ collected.push(mappedMsgObj)
98
+ }
99
+ result = collected.join(' ')
91
100
  }
92
101
  return result
93
102
  }
@@ -337,7 +337,8 @@ const LOGGING = {
337
337
  SENT: `${LOGGING_FORWARDING_PREFIX}/Sent`,
338
338
  FORWARDING: `${LOGGING_FORWARDING_PREFIX}/${NODEJS.PREFIX}`,
339
339
  METRICS: `${SUPPORTABILITY.LOGGING}/Metrics/${NODEJS.PREFIX}`,
340
- LOCAL_DECORATING: `${SUPPORTABILITY.LOGGING}/LocalDecorating/${NODEJS.PREFIX}`
340
+ LOCAL_DECORATING: `${SUPPORTABILITY.LOGGING}/LocalDecorating/${NODEJS.PREFIX}`,
341
+ LABELS: `${SUPPORTABILITY.LOGGING}/Labels/${NODEJS.PREFIX}`
341
342
  }
342
343
 
343
344
  const KAFKA = {
@@ -75,6 +75,16 @@ utils.isLogForwardingEnabled = function isLogForwardingEnabled(config, agent) {
75
75
  )
76
76
  }
77
77
 
78
+ /**
79
+ * Checks if application_logging.forwarding.labels is enabled
80
+ *
81
+ * @param {object} config agent config
82
+ * @returns {boolean} is labeling enabled
83
+ */
84
+ utils.isLogLabelingEnabled = function isLogLabelingEnabled(config) {
85
+ return !!config.application_logging.forwarding.labels.enabled
86
+ }
87
+
78
88
  /**
79
89
  * Increments both `Logging/lines` and `Logging/lines/<level>` call count
80
90
  *
@@ -108,3 +118,51 @@ function getLogLevel(level) {
108
118
  }
109
119
  return logLevel
110
120
  }
121
+
122
+ /**
123
+ * Increments the enabled/disabled metrics for the top line application logging features:
124
+ * 1. Supportability/Logging/Metrics/Nodejs/<enabled|disabled>
125
+ * 2. Supportability/Logging/Forwarding/Nodejs/<enabled|disabled>
126
+ * 3. Supportability/Logging/LocalDecorating/Nodejs/<enabled|disabled>
127
+ * 4. Supportability/Logging/Labels/Nodejs/<enabled|disabled>
128
+ *
129
+ * Run in `agent.onConnect`
130
+ *
131
+ * @param {Agent} agent instance
132
+ */
133
+ utils.createFeatureUsageMetrics = function createFeatureUsageMetrics(agent) {
134
+ const { config, metrics } = agent
135
+ const loggingConfig = config.application_logging.enabled
136
+ metrics
137
+ .getOrCreateMetric(
138
+ `${LOGGING.METRICS}${
139
+ loggingConfig && config.application_logging.metrics.enabled ? 'enabled' : 'disabled'
140
+ }`
141
+ )
142
+ .incrementCallCount()
143
+ metrics
144
+ .getOrCreateMetric(
145
+ `${LOGGING.FORWARDING}${
146
+ loggingConfig && config.application_logging.forwarding.enabled ? 'enabled' : 'disabled'
147
+ }`
148
+ )
149
+ .incrementCallCount()
150
+ metrics
151
+ .getOrCreateMetric(
152
+ `${LOGGING.LOCAL_DECORATING}${
153
+ loggingConfig && config.application_logging.local_decorating.enabled
154
+ ? 'enabled'
155
+ : 'disabled'
156
+ }`
157
+ )
158
+ .incrementCallCount()
159
+ metrics
160
+ .getOrCreateMetric(
161
+ `${LOGGING.LABELS}${
162
+ loggingConfig && config.application_logging.forwarding.labels.enabled
163
+ ? 'enabled'
164
+ : 'disabled'
165
+ }`
166
+ )
167
+ .incrementCallCount()
168
+ }
@@ -11,10 +11,11 @@ module.exports.fromMap = fromMap
11
11
 
12
12
  // this creates a copy of trim that can be used with map
13
13
  const trim = Function.prototype.call.bind(String.prototype.trim)
14
- const logger = require('../logger').child({ component: 'label-parser' })
15
14
  const stringify = require('json-stringify-safe')
16
15
 
17
- function parse(labels) {
16
+ // pass in parent logger to avoid circular deps
17
+ function parse(labels, parentLogger) {
18
+ const logger = parentLogger.child({ component: 'label-parser' })
18
19
  let results
19
20
 
20
21
  if (!labels) {
@@ -25,8 +26,8 @@ function parse(labels) {
25
26
  results = fromMap(labels)
26
27
  }
27
28
 
28
- results.warnings.forEach(function logWarnings(messaage) {
29
- logger.warn(messaage)
29
+ results.warnings.forEach(function logWarnings(message) {
30
+ logger.warn(message)
30
31
  })
31
32
 
32
33
  return results.labels
@@ -106,11 +107,11 @@ function fromMap(map) {
106
107
  try {
107
108
  warnings.unshift('Partially Invalid Label Setting: ' + stringify(map))
108
109
  } catch (err) {
109
- logger.debug(err, 'Failed to stringify labels')
110
+ warnings.unshift('Failed to stringify labels: ' + err.message)
110
111
  }
111
112
  }
112
113
 
113
- return { labels: labels, warnings: warnings }
114
+ return { labels, warnings }
114
115
  }
115
116
 
116
117
  function truncate(str, max) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newrelic",
3
- "version": "12.6.1",
3
+ "version": "12.8.0",
4
4
  "author": "New Relic Node.js agent team <nodejs@newrelic.com>",
5
5
  "license": "Apache-2.0",
6
6
  "contributors": [