newrelic 12.17.0 → 12.18.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 +53 -29
- package/THIRD_PARTY_NOTICES.md +7 -7
- package/lib/config/default.js +15 -1
- package/lib/grpc/connection.js +26 -6
- package/lib/instrumentation/@azure/functions.js +311 -0
- package/lib/instrumentations.js +1 -0
- package/lib/otel/rules.js +30 -7
- package/lib/spans/base-span-streamer.js +10 -9
- package/lib/spans/batch-span-streamer.js +28 -25
- package/lib/spans/create-span-event-aggregator.js +2 -1
- package/lib/spans/span-streamer.js +1 -1
- package/lib/util/logger.js +38 -6
- package/lib/utilization/aws-info.js +6 -3
- package/lib/utilization/azure-info.js +5 -2
- package/lib/utilization/azurefunction-info.js +3 -1
- package/lib/utilization/docker-info.js +12 -9
- package/lib/utilization/ecs-info.js +5 -4
- package/lib/utilization/gcp-info.js +5 -2
- package/lib/utilization/index.js +2 -1
- package/lib/utilization/kubernetes-info.js +4 -2
- package/lib/utilization/pcf-info.js +3 -2
- package/package.json +2 -2
package/NEWS.md
CHANGED
|
@@ -1,32 +1,56 @@
|
|
|
1
|
-
### v12.
|
|
2
|
-
|
|
3
|
-
#### Features
|
|
4
|
-
|
|
5
|
-
* Azure
|
|
6
|
-
* Support
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
### v12.18.0 (2025-04-14)
|
|
2
|
+
|
|
3
|
+
#### Features
|
|
4
|
+
|
|
5
|
+
* Support Azure functions background trigger types ([#3028](https://github.com/newrelic/node-newrelic/pull/3028)) ([3bf17b0](https://github.com/newrelic/node-newrelic/commit/3bf17b03abaa713c1f9ad544c54c24a6374359c2))
|
|
6
|
+
* Support Azure functions HTTP trigger type ([#3021](https://github.com/newrelic/node-newrelic/pull/3021)) ([90d894b](https://github.com/newrelic/node-newrelic/commit/90d894bf1eb4a48f4b7f314ae22eef2cc76c32b9))
|
|
7
|
+
|
|
8
|
+
#### Bug fixes
|
|
9
|
+
|
|
10
|
+
* Updated span streamer to properly retry failed batches and handle flushing batch queue every 5 seconds ([#3033](https://github.com/newrelic/node-newrelic/pull/3033)) ([7db0e7c](https://github.com/newrelic/node-newrelic/commit/7db0e7ca929d54858addd5255b46507f82333045))
|
|
11
|
+
|
|
12
|
+
#### Code refactoring
|
|
13
|
+
|
|
14
|
+
* Update otel bridge rules synthesizer to properly handle consumer kind rules ([#3003](https://github.com/newrelic/node-newrelic/pull/3003)) ([3295dd1](https://github.com/newrelic/node-newrelic/commit/3295dd12bbebb708f3eeca2ad32fbc157a892fec))
|
|
15
|
+
|
|
16
|
+
#### Documentation
|
|
17
|
+
|
|
18
|
+
* Updated compatibility report ([#3020](https://github.com/newrelic/node-newrelic/pull/3020)) ([ce32b67](https://github.com/newrelic/node-newrelic/commit/ce32b6754383b6b1fe95d1d37759810b4caaf34c))
|
|
19
|
+
|
|
20
|
+
#### Miscellaneous chores
|
|
21
|
+
|
|
22
|
+
* Fixed prisma CI issues ([#3031](https://github.com/newrelic/node-newrelic/pull/3031)) ([1349cae](https://github.com/newrelic/node-newrelic/commit/1349cae4d87941e4f55dffc2445c90e73d823bbc))
|
|
23
|
+
* Improved logging within utilization detection ([#3034](https://github.com/newrelic/node-newrelic/pull/3034)) ([b47bd46](https://github.com/newrelic/node-newrelic/commit/b47bd46ad144723ac62192eb9cc474ddd1fce98d))
|
|
24
|
+
|
|
25
|
+
### v12.17.0 (2025-04-02)
|
|
26
|
+
|
|
27
|
+
#### Features
|
|
28
|
+
|
|
29
|
+
* Azure Function utilization ([#3017](https://github.com/newrelic/node-newrelic/pull/3017)) ([80ab93e](https://github.com/newrelic/node-newrelic/commit/80ab93ee5599800a823e3828f9730d5421b460c2))
|
|
30
|
+
* Support honoring W3C `traceparent` sampled flag ([#3009](https://github.com/newrelic/node-newrelic/pull/3009)) ([d903413](https://github.com/newrelic/node-newrelic/commit/d903413a6216dae2cde3e6b6366c3a390d4ed4cd))
|
|
31
|
+
* By default the agent will not honor the `traceparent` sampled flag.
|
|
32
|
+
* To control how sampling works with `traceparent` set the following in config:
|
|
33
|
+
* `distributed_tracing.sampler.remote_parent_sampled`(when the traceparent sampled flag is `01`)
|
|
34
|
+
* `always_on`: the agent will sample spans
|
|
35
|
+
* `always_off`: the agent will not sample spans
|
|
36
|
+
* `default`: the agent will rely on existing priority sampling to make its decisions
|
|
37
|
+
* `distributed_tracing.sampler.remote_parent_not_sampled`(when the traceparent sampled flag is `00`)
|
|
38
|
+
* `always_on`: the agent will sample spans
|
|
39
|
+
* `always_off`: the agent will not sample spans
|
|
40
|
+
* `default`: the agent will rely on existing priority sampling to make its decisions
|
|
41
|
+
|
|
42
|
+
#### Code refactoring
|
|
43
|
+
|
|
44
|
+
* Updated otel bridge to centralize mapping rules for a given span attribute to accomondate semantic convention spec updates ([#3010](https://github.com/newrelic/node-newrelic/pull/3010)) ([c20c36b](https://github.com/newrelic/node-newrelic/commit/c20c36bbe0f63ab18fbbe85ad4bfaa3b80f1475d))
|
|
45
|
+
|
|
46
|
+
#### Documentation
|
|
47
|
+
|
|
48
|
+
* Updated compatibility report ([#3013](https://github.com/newrelic/node-newrelic/pull/3013)) ([8fab715](https://github.com/newrelic/node-newrelic/commit/8fab715581ccc5da7118884a0080557891329daa))
|
|
49
|
+
|
|
50
|
+
#### Tests
|
|
51
|
+
|
|
52
|
+
* Pinned `openai@4.90.0` to work with `@langchain/openai` ([#3019](https://github.com/newrelic/node-newrelic/pull/3019)) ([eaa3db0](https://github.com/newrelic/node-newrelic/commit/eaa3db0a556a19c44a05926dc339ea7bcbc9cebd))
|
|
53
|
+
|
|
30
54
|
### v12.16.1 (2025-03-24)
|
|
31
55
|
|
|
32
56
|
#### Features
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -91,7 +91,7 @@ code, the source code can be found at [https://github.com/newrelic/node-newrelic
|
|
|
91
91
|
|
|
92
92
|
### @grpc/grpc-js
|
|
93
93
|
|
|
94
|
-
This product includes source derived from [@grpc/grpc-js](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) ([v1.
|
|
94
|
+
This product includes source derived from [@grpc/grpc-js](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js) ([v1.13.2](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/tree/v1.13.2)), distributed under the [Apache-2.0 License](https://github.com/grpc/grpc-node/tree/master/packages/grpc-js/blob/v1.13.2/LICENSE):
|
|
95
95
|
|
|
96
96
|
```
|
|
97
97
|
Apache License
|
|
@@ -509,7 +509,7 @@ This product includes source derived from [@grpc/proto-loader](https://github.co
|
|
|
509
509
|
|
|
510
510
|
### @newrelic/security-agent
|
|
511
511
|
|
|
512
|
-
This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v2.
|
|
512
|
+
This product includes source derived from [@newrelic/security-agent](https://github.com/newrelic/csec-node-agent) ([v2.4.0](https://github.com/newrelic/csec-node-agent/tree/v2.4.0)), distributed under the [UNKNOWN License](https://github.com/newrelic/csec-node-agent/blob/v2.4.0/LICENSE):
|
|
513
513
|
|
|
514
514
|
```
|
|
515
515
|
## New Relic Software License v1.0
|
|
@@ -1271,7 +1271,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
1271
1271
|
|
|
1272
1272
|
### import-in-the-middle
|
|
1273
1273
|
|
|
1274
|
-
This product includes source derived from [import-in-the-middle](https://github.com/nodejs/import-in-the-middle) ([v1.13.
|
|
1274
|
+
This product includes source derived from [import-in-the-middle](https://github.com/nodejs/import-in-the-middle) ([v1.13.1](https://github.com/nodejs/import-in-the-middle/tree/v1.13.1)), distributed under the [Apache-2.0 License](https://github.com/nodejs/import-in-the-middle/blob/v1.13.1/LICENSE):
|
|
1275
1275
|
|
|
1276
1276
|
```
|
|
1277
1277
|
Apache License
|
|
@@ -1615,7 +1615,7 @@ IN THE SOFTWARE.
|
|
|
1615
1615
|
|
|
1616
1616
|
### require-in-the-middle
|
|
1617
1617
|
|
|
1618
|
-
This product includes source derived from [require-in-the-middle](https://github.com/elastic/require-in-the-middle) ([v7.
|
|
1618
|
+
This product includes source derived from [require-in-the-middle](https://github.com/elastic/require-in-the-middle) ([v7.5.0](https://github.com/elastic/require-in-the-middle/tree/v7.5.0)), distributed under the [MIT License](https://github.com/elastic/require-in-the-middle/blob/v7.5.0/LICENSE):
|
|
1619
1619
|
|
|
1620
1620
|
```
|
|
1621
1621
|
The MIT License (MIT)
|
|
@@ -1701,7 +1701,7 @@ SOFTWARE.
|
|
|
1701
1701
|
|
|
1702
1702
|
### @aws-sdk/client-s3
|
|
1703
1703
|
|
|
1704
|
-
This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.
|
|
1704
|
+
This product includes source derived from [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3) ([v3.726.1](https://github.com/aws/aws-sdk-js-v3/tree/v3.726.1)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.726.1/LICENSE):
|
|
1705
1705
|
|
|
1706
1706
|
```
|
|
1707
1707
|
Apache License
|
|
@@ -1910,7 +1910,7 @@ This product includes source derived from [@aws-sdk/client-s3](https://github.co
|
|
|
1910
1910
|
|
|
1911
1911
|
### @aws-sdk/s3-request-presigner
|
|
1912
1912
|
|
|
1913
|
-
This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.
|
|
1913
|
+
This product includes source derived from [@aws-sdk/s3-request-presigner](https://github.com/aws/aws-sdk-js-v3) ([v3.726.1](https://github.com/aws/aws-sdk-js-v3/tree/v3.726.1)), distributed under the [Apache-2.0 License](https://github.com/aws/aws-sdk-js-v3/blob/v3.726.1/LICENSE):
|
|
1914
1914
|
|
|
1915
1915
|
```
|
|
1916
1916
|
Apache License
|
|
@@ -3770,7 +3770,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
3770
3770
|
|
|
3771
3771
|
### eslint
|
|
3772
3772
|
|
|
3773
|
-
This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v9.
|
|
3773
|
+
This product includes source derived from [eslint](https://github.com/eslint/eslint) ([v9.18.0](https://github.com/eslint/eslint/tree/v9.18.0)), distributed under the [MIT License](https://github.com/eslint/eslint/blob/v9.18.0/LICENSE):
|
|
3774
3774
|
|
|
3775
3775
|
```
|
|
3776
3776
|
Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
|
package/lib/config/default.js
CHANGED
|
@@ -1197,7 +1197,14 @@ defaultConfig.definition = () => ({
|
|
|
1197
1197
|
port: {
|
|
1198
1198
|
formatter: int,
|
|
1199
1199
|
default: 443
|
|
1200
|
-
}
|
|
1200
|
+
},
|
|
1201
|
+
/**
|
|
1202
|
+
* For testing only. This allows the connection to an insecure gRPC server.
|
|
1203
|
+
*/
|
|
1204
|
+
insecure: {
|
|
1205
|
+
formatter: boolean,
|
|
1206
|
+
default: false
|
|
1207
|
+
},
|
|
1201
1208
|
},
|
|
1202
1209
|
span_events: {
|
|
1203
1210
|
/**
|
|
@@ -1206,6 +1213,13 @@ defaultConfig.definition = () => ({
|
|
|
1206
1213
|
queue_size: {
|
|
1207
1214
|
formatter: int,
|
|
1208
1215
|
default: 10000
|
|
1216
|
+
},
|
|
1217
|
+
/**
|
|
1218
|
+
* Size of batches to post to 8T server
|
|
1219
|
+
*/
|
|
1220
|
+
batch_size: {
|
|
1221
|
+
formatter: int,
|
|
1222
|
+
default: 750
|
|
1209
1223
|
}
|
|
1210
1224
|
},
|
|
1211
1225
|
batching: {
|
package/lib/grpc/connection.js
CHANGED
|
@@ -62,6 +62,7 @@ class GrpcConnection extends EventEmitter {
|
|
|
62
62
|
|
|
63
63
|
const traceObserverConfig = infiniteTracingConfig.trace_observer
|
|
64
64
|
this._endpoint = this.getTraceObserverEndpoint(traceObserverConfig)
|
|
65
|
+
this._insecureConnection = traceObserverConfig.insecure
|
|
65
66
|
|
|
66
67
|
this._client = null
|
|
67
68
|
this.stream = null
|
|
@@ -202,11 +203,13 @@ class GrpcConnection extends EventEmitter {
|
|
|
202
203
|
|
|
203
204
|
_disconnect() {
|
|
204
205
|
logger.trace('Disconnecting from gRPC endpoint.')
|
|
206
|
+
this._setState(connectionStates.disconnected)
|
|
205
207
|
|
|
206
208
|
if (this.stream) {
|
|
207
209
|
this.stream.removeAllListeners()
|
|
208
210
|
|
|
209
211
|
const oldStream = this.stream
|
|
212
|
+
|
|
210
213
|
this.stream.on('status', function endStreamStatusHandler(grpcStatus) {
|
|
211
214
|
logger.trace('End stream status received [%s]: %s', grpcStatus.code, grpcStatus.details)
|
|
212
215
|
|
|
@@ -228,8 +231,6 @@ class GrpcConnection extends EventEmitter {
|
|
|
228
231
|
|
|
229
232
|
this.stream = null
|
|
230
233
|
}
|
|
231
|
-
|
|
232
|
-
this._setState(connectionStates.disconnected)
|
|
233
234
|
}
|
|
234
235
|
|
|
235
236
|
/**
|
|
@@ -374,12 +375,10 @@ class GrpcConnection extends EventEmitter {
|
|
|
374
375
|
|
|
375
376
|
const traceApi = protoDescriptor.com.newrelic.trace.v1
|
|
376
377
|
|
|
377
|
-
const credentials = this._generateCredentials(grpc)
|
|
378
|
-
|
|
379
|
-
// If you want to use mock server use insecure creds
|
|
380
|
-
// const credentials = grpc.credentials.createInsecure()
|
|
378
|
+
const credentials = this._insecureConnection ? grpc.credentials.createInsecure() : this._generateCredentials(grpc)
|
|
381
379
|
|
|
382
380
|
const opts = {}
|
|
381
|
+
|
|
383
382
|
if (this._compression) {
|
|
384
383
|
// 2 = gzip compression
|
|
385
384
|
// see: https://github.com/grpc/grpc-node/blob/master/packages/grpc-js/src/compression-algorithms.ts#L21
|
|
@@ -393,6 +392,27 @@ class GrpcConnection extends EventEmitter {
|
|
|
393
392
|
.incrementCallCount()
|
|
394
393
|
}
|
|
395
394
|
|
|
395
|
+
// this defines retries of writes to stream if it fails with `UNAVAILABLE` or `INTERNAL`
|
|
396
|
+
const serviceConfig = {
|
|
397
|
+
methodConfig: [
|
|
398
|
+
{
|
|
399
|
+
name: [
|
|
400
|
+
{
|
|
401
|
+
service: 'com.newrelic.trace.v1.IngestService'
|
|
402
|
+
}
|
|
403
|
+
],
|
|
404
|
+
retryPolicy: {
|
|
405
|
+
maxAttempts: 10,
|
|
406
|
+
initialBackoff: '0.1s',
|
|
407
|
+
maxBackoff: '1s',
|
|
408
|
+
backoffMultiplier: 2,
|
|
409
|
+
retryableStatusCodes: ['UNAVAILABLE', 'INTERNAL']
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
]
|
|
413
|
+
}
|
|
414
|
+
opts['grpc.service_config'] = JSON.stringify(serviceConfig)
|
|
415
|
+
|
|
396
416
|
return new traceApi.IngestService(endpoint, credentials, opts)
|
|
397
417
|
}
|
|
398
418
|
}
|
|
@@ -0,0 +1,311 @@
|
|
|
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('../../logger').child({ component: 'azure-functions' })
|
|
9
|
+
const urltils = require('../../util/urltils')
|
|
10
|
+
const headerProcessing = require('../../header-processing')
|
|
11
|
+
const synthetics = require('../../synthetics')
|
|
12
|
+
|
|
13
|
+
const backgroundRecorder = require('../../metrics/recorders/other.js')
|
|
14
|
+
const recordWeb = require('../../metrics/recorders/http')
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
DESTINATIONS: DESTS,
|
|
18
|
+
TYPES
|
|
19
|
+
} = require('../../transaction/index.js')
|
|
20
|
+
|
|
21
|
+
const {
|
|
22
|
+
WEBSITE_OWNER_NAME,
|
|
23
|
+
WEBSITE_RESOURCE_GROUP,
|
|
24
|
+
WEBSITE_SITE_NAME
|
|
25
|
+
} = process.env
|
|
26
|
+
const SUBSCRIPTION_ID = WEBSITE_OWNER_NAME?.split('+').shift()
|
|
27
|
+
const RESOURCE_GROUP_NAME = WEBSITE_RESOURCE_GROUP ?? WEBSITE_OWNER_NAME?.split('+').pop().split('-Linux').shift()
|
|
28
|
+
const AZURE_FUNCTION_APP_NAME = WEBSITE_SITE_NAME
|
|
29
|
+
|
|
30
|
+
let coldStart = true
|
|
31
|
+
let _agent
|
|
32
|
+
let _logger
|
|
33
|
+
|
|
34
|
+
module.exports = function initialize(agent, azureFunctions, _moduleName, shim, { logger = defaultLogger } = {}) {
|
|
35
|
+
_agent = agent
|
|
36
|
+
_logger = logger
|
|
37
|
+
if (!SUBSCRIPTION_ID || !RESOURCE_GROUP_NAME || !AZURE_FUNCTION_APP_NAME) {
|
|
38
|
+
logger.warn(
|
|
39
|
+
{
|
|
40
|
+
data: {
|
|
41
|
+
expectedVars: ['WEBSITE_OWNER_NAME', 'WEBSITE_RESOURCE_GROUP', 'WEBSITE_SITE_NAME'],
|
|
42
|
+
found: { WEBSITE_OWNER_NAME, WEBSITE_RESOURCE_GROUP, WEBSITE_SITE_NAME }
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
'could not initialize azure functions instrumentation due to missing environment variables'
|
|
46
|
+
)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const httpMethods = ['http', 'get', 'put', 'post', 'patch', 'deleteRequest']
|
|
51
|
+
shim.wrap(azureFunctions.app, httpMethods, wrapAzureHttpMethods)
|
|
52
|
+
|
|
53
|
+
const backgroundMethods = [
|
|
54
|
+
'cosmosDB',
|
|
55
|
+
'eventGrid',
|
|
56
|
+
'eventHub',
|
|
57
|
+
'mySql',
|
|
58
|
+
'serviceBusQueue',
|
|
59
|
+
'serviceBusTopic',
|
|
60
|
+
'sql',
|
|
61
|
+
'storageBlob',
|
|
62
|
+
'storageQueue',
|
|
63
|
+
'timer',
|
|
64
|
+
'warmup',
|
|
65
|
+
'webPubSub'
|
|
66
|
+
]
|
|
67
|
+
shim.wrap(azureFunctions.app, backgroundMethods, wrapAzureBackgroundMethods)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function wrapAzureHttpMethods(shim, appMethod) {
|
|
71
|
+
return async function wrappedAzureHttpMethod(...args) {
|
|
72
|
+
// If the app doesn't need an options object, the user can pass the
|
|
73
|
+
// handler function as the second argument
|
|
74
|
+
// (e.g. `app.get('name', handler)`).
|
|
75
|
+
// See https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=javascript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#registering-a-function
|
|
76
|
+
let handler
|
|
77
|
+
if (typeof args[1] === 'function') {
|
|
78
|
+
handler = args[1]
|
|
79
|
+
args[1] = { handler }
|
|
80
|
+
} else {
|
|
81
|
+
handler = args[1].handler
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const tracer = shim.tracer
|
|
85
|
+
args[1].handler = tracer.transactionProxy(async function wrappedHandler(...args) {
|
|
86
|
+
const [request, context] = args
|
|
87
|
+
const ctx = tracer.getContext()
|
|
88
|
+
const tx = tracer.getTransaction()
|
|
89
|
+
|
|
90
|
+
// Set the transaction name according to our spec (category + function name).
|
|
91
|
+
tx.setPartialName(`AzureFunction/${context.functionName}`)
|
|
92
|
+
|
|
93
|
+
const url = new URL(request.url)
|
|
94
|
+
const segment = tracer.createSegment({
|
|
95
|
+
name: request.url,
|
|
96
|
+
recorder: recordWeb,
|
|
97
|
+
parent: ctx.segment,
|
|
98
|
+
transaction: tx
|
|
99
|
+
})
|
|
100
|
+
segment.start()
|
|
101
|
+
|
|
102
|
+
const transport = url.protocol === 'https:' ? 'HTTPS' : 'HTTP'
|
|
103
|
+
tx.type = TYPES.WEB
|
|
104
|
+
tx.baseSegment = segment
|
|
105
|
+
tx.parsedUrl = url
|
|
106
|
+
tx.url = urltils.obfuscatePath(_agent.config, url.pathname)
|
|
107
|
+
tx.verb = request.method
|
|
108
|
+
if (url.port === '') {
|
|
109
|
+
tx.port = transport === 'HTTPS' ? '443' : '80'
|
|
110
|
+
} else {
|
|
111
|
+
tx.port = url.port
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
tx.trace.attributes.addAttribute(
|
|
115
|
+
DESTS.TRANS_EVENT | DESTS.ERROR_EVENT,
|
|
116
|
+
'request.uri',
|
|
117
|
+
tx.url
|
|
118
|
+
)
|
|
119
|
+
segment.addSpanAttribute('request.uri', tx.url)
|
|
120
|
+
if (request.method != null) {
|
|
121
|
+
segment.addSpanAttribute('request.method', request.method)
|
|
122
|
+
}
|
|
123
|
+
addAttributes({ transaction: tx, functionContext: context })
|
|
124
|
+
|
|
125
|
+
const queueTimeStamp = headerProcessing.getQueueTime(_logger, request.headers)
|
|
126
|
+
if (queueTimeStamp) {
|
|
127
|
+
tx.queueTime = Date.now() - queueTimeStamp
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
synthetics.assignHeadersToTransaction(_agent.config, tx, request.headers)
|
|
131
|
+
if (_agent.config.distributed_tracing.enabled === true) {
|
|
132
|
+
tx.acceptDistributedTraceHeaders(transport, request.headers)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const newContext = ctx.enterSegment({ segment })
|
|
136
|
+
const boundHandler = tracer.bindFunction(handler, newContext)
|
|
137
|
+
|
|
138
|
+
const result = await boundHandler(...args)
|
|
139
|
+
// Responses should have a shape as described at:
|
|
140
|
+
// https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=javascript%2Cwindows%2Cazure-cli&pivots=nodejs-model-v4#http-response
|
|
141
|
+
if (result.status) {
|
|
142
|
+
tx.trace.attributes.addAttribute(
|
|
143
|
+
DESTS.TRANS_COMMON,
|
|
144
|
+
'http.statusCode',
|
|
145
|
+
result.status
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (coldStart === true) {
|
|
150
|
+
tx.trace.attributes.addAttribute(DESTS.TRANS_COMMON, 'faas.coldStart', true)
|
|
151
|
+
coldStart = false
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
tx.end()
|
|
155
|
+
return result
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
await appMethod(...args)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function wrapAzureBackgroundMethods(shim, appMethod) {
|
|
163
|
+
return async function wrappedAzureBackgroundMethod(...args) {
|
|
164
|
+
let handler
|
|
165
|
+
if (typeof args[1] === 'function') {
|
|
166
|
+
handler = args[1]
|
|
167
|
+
args[1] = { handler }
|
|
168
|
+
} else {
|
|
169
|
+
handler = args[1].handler
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const tracer = shim.tracer
|
|
173
|
+
args[1].handler = tracer.transactionProxy(async function wrappedHandler(...args) {
|
|
174
|
+
const [, context] = args
|
|
175
|
+
const ctx = tracer.getContext()
|
|
176
|
+
const tx = tracer.getTransaction()
|
|
177
|
+
|
|
178
|
+
tx.setPartialName(`AzureFunction/${context.functionName}`)
|
|
179
|
+
|
|
180
|
+
const segment = tracer.createSegment({
|
|
181
|
+
name: `${appMethod}-trigger`,
|
|
182
|
+
recorder: backgroundRecorder,
|
|
183
|
+
parent: ctx.segment,
|
|
184
|
+
transaction: tx
|
|
185
|
+
})
|
|
186
|
+
segment.start()
|
|
187
|
+
|
|
188
|
+
tx.type = TYPES.BG
|
|
189
|
+
tx.baseSegment = segment
|
|
190
|
+
addAttributes({ transaction: tx, functionContext: context })
|
|
191
|
+
|
|
192
|
+
const newContext = ctx.enterSegment({ segment })
|
|
193
|
+
const boundHandler = tracer.bindFunction(handler, newContext)
|
|
194
|
+
|
|
195
|
+
const result = await boundHandler(...args)
|
|
196
|
+
if (coldStart === true) {
|
|
197
|
+
tx.trace.attributes.addAttribute(DESTS.TRANS_COMMON, 'faas.coldStart', true)
|
|
198
|
+
coldStart = false
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
tx.end()
|
|
202
|
+
return result
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
await appMethod(...args)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Add required New Relic attributes to the transaction.
|
|
211
|
+
*
|
|
212
|
+
* @param {object} params Function parameters.
|
|
213
|
+
* @param {Transaction} params.transaction The transaction to update.
|
|
214
|
+
* @param {object} params.functionContext The function invocation context
|
|
215
|
+
* provided by the Azure functions runtime.
|
|
216
|
+
*/
|
|
217
|
+
function addAttributes({ transaction, functionContext }) {
|
|
218
|
+
transaction.trace.attributes.addAttribute(
|
|
219
|
+
DESTS.TRANS_COMMON,
|
|
220
|
+
'faas.invocation_id',
|
|
221
|
+
functionContext.invocationId ?? 'unknown'
|
|
222
|
+
)
|
|
223
|
+
transaction.trace.attributes.addAttribute(
|
|
224
|
+
DESTS.TRANS_COMMON,
|
|
225
|
+
'faas.name',
|
|
226
|
+
functionContext.functionName ?? 'unknown'
|
|
227
|
+
)
|
|
228
|
+
transaction.trace.attributes.addAttribute(
|
|
229
|
+
DESTS.TRANS_COMMON,
|
|
230
|
+
'faas.trigger',
|
|
231
|
+
mapTriggerType({ functionContext })
|
|
232
|
+
)
|
|
233
|
+
transaction.trace.attributes.addAttribute(
|
|
234
|
+
DESTS.TRANS_COMMON,
|
|
235
|
+
'cloud.resource_id',
|
|
236
|
+
buildCloudResourceId({ functionContext })
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Inspects the provided function invocation context and returns a recognized
|
|
242
|
+
* trigger type suitable for sending to the collector as `faas.trigger`.
|
|
243
|
+
*
|
|
244
|
+
* @param {object} params Function parameters
|
|
245
|
+
* @param {object} params.functionContext The function context as provided by
|
|
246
|
+
* the Azure functions runtime.
|
|
247
|
+
*
|
|
248
|
+
* @returns {string} A string appropriate for New Relic.
|
|
249
|
+
*/
|
|
250
|
+
function mapTriggerType({ functionContext }) {
|
|
251
|
+
const input = functionContext.options?.trigger?.type
|
|
252
|
+
|
|
253
|
+
// Input types are found at:
|
|
254
|
+
// https://github.com/Azure/azure-functions-nodejs-library/blob/138c021/src/trigger.ts
|
|
255
|
+
// https://learn.microsoft.com/en-us/azure/azure-functions/functions-triggers-bindings?tabs=isolated-process%2Cnode-v4%2Cpython-v2&pivots=programming-language-javascript#supported-bindings
|
|
256
|
+
switch (input) {
|
|
257
|
+
case 'httpTrigger': {
|
|
258
|
+
return 'http'
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
case 'timerTrigger': {
|
|
262
|
+
return 'timer'
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
case 'blobTrigger':
|
|
266
|
+
case 'cosmosDBTrigger':
|
|
267
|
+
case 'daprBindingTrigger':
|
|
268
|
+
case 'mysqlTrigger':
|
|
269
|
+
case 'queueTrigger':
|
|
270
|
+
case 'sqlTrigger': {
|
|
271
|
+
return 'datasource'
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
case 'daprTopicTrigger':
|
|
275
|
+
case 'eventGridTrigger':
|
|
276
|
+
case 'eventHubTrigger':
|
|
277
|
+
case 'kafkaTrigger':
|
|
278
|
+
case 'rabbitMQTrigger':
|
|
279
|
+
case 'redisListTrigger':
|
|
280
|
+
case 'redisPubSubTrigger':
|
|
281
|
+
case 'redisStreamTrigger':
|
|
282
|
+
case 'serviceBusTrigger':
|
|
283
|
+
case 'signalRTrigger':
|
|
284
|
+
case 'webPubSubTrigger': {
|
|
285
|
+
return 'pubsub'
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
default: {
|
|
289
|
+
return 'other'
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function buildCloudResourceId({ functionContext }) {
|
|
295
|
+
return [
|
|
296
|
+
'/subscriptions/',
|
|
297
|
+
SUBSCRIPTION_ID,
|
|
298
|
+
'/resourceGroups/',
|
|
299
|
+
RESOURCE_GROUP_NAME,
|
|
300
|
+
'/providers/Microsoft.Web/sites/',
|
|
301
|
+
AZURE_FUNCTION_APP_NAME,
|
|
302
|
+
'/functions/',
|
|
303
|
+
functionContext.functionName
|
|
304
|
+
].join('')
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports.internals = {
|
|
308
|
+
addAttributes,
|
|
309
|
+
mapTriggerType,
|
|
310
|
+
buildCloudResourceId
|
|
311
|
+
}
|
package/lib/instrumentations.js
CHANGED
|
@@ -10,6 +10,7 @@ const InstrumentationDescriptor = require('./instrumentation-descriptor')
|
|
|
10
10
|
// Return a new copy of this array every time we're called
|
|
11
11
|
module.exports = function instrumentations() {
|
|
12
12
|
return {
|
|
13
|
+
'@azure/functions': { type: InstrumentationDescriptor.TYPE_GENERIC },
|
|
13
14
|
'@elastic/elasticsearch': { type: InstrumentationDescriptor.TYPE_DATASTORE },
|
|
14
15
|
'@opensearch-project/opensearch': { type: InstrumentationDescriptor.TYPE_DATASTORE },
|
|
15
16
|
'@grpc/grpc-js': { module: './instrumentation/grpc-js' },
|
package/lib/otel/rules.js
CHANGED
|
@@ -46,6 +46,7 @@ class Rule {
|
|
|
46
46
|
static OTEL_SPAN_KIND_SERVER = 'server'
|
|
47
47
|
static OTEL_SPAN_KIND_CLIENT = 'client'
|
|
48
48
|
static OTEL_SPAN_KIND_PRODUCER = 'producer'
|
|
49
|
+
static OTEL_SPAN_KIND_CONSUMER = 'consumer'
|
|
49
50
|
static OTEL_SPAN_KIND_INTERNAL = 'internal'
|
|
50
51
|
|
|
51
52
|
#name
|
|
@@ -87,10 +88,6 @@ class Rule {
|
|
|
87
88
|
return this.#spanKinds.includes(Rule.OTEL_SPAN_KIND_CLIENT)
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
get isConsumer() {
|
|
91
|
-
return this.#spanKinds.includes('consumer')
|
|
92
|
-
}
|
|
93
|
-
|
|
94
91
|
get isInternalRule() {
|
|
95
92
|
return this.#spanKinds.includes('internal')
|
|
96
93
|
}
|
|
@@ -99,8 +96,12 @@ class Rule {
|
|
|
99
96
|
return this.#spanKinds.includes(Rule.OTEL_SPAN_KIND_PRODUCER)
|
|
100
97
|
}
|
|
101
98
|
|
|
99
|
+
get isConsumerRule() {
|
|
100
|
+
return this.#spanKinds.includes(Rule.OTEL_SPAN_KIND_CONSUMER)
|
|
101
|
+
}
|
|
102
|
+
|
|
102
103
|
get isServerRule() {
|
|
103
|
-
return this.#spanKinds.includes(Rule.OTEL_SPAN_KIND_SERVER)
|
|
104
|
+
return this.#spanKinds.includes(Rule.OTEL_SPAN_KIND_SERVER)
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
/**
|
|
@@ -129,7 +130,9 @@ class Rule {
|
|
|
129
130
|
|
|
130
131
|
class RulesEngine {
|
|
131
132
|
#serverRules = new Map()
|
|
133
|
+
#consumerRules = new Map()
|
|
132
134
|
#fallbackServerRules = new Map()
|
|
135
|
+
#fallbackConsumerRules = new Map()
|
|
133
136
|
#clientRules = new Map()
|
|
134
137
|
#fallbackClientRules = new Map()
|
|
135
138
|
#fallbackInternalRules = new Map()
|
|
@@ -142,6 +145,8 @@ class RulesEngine {
|
|
|
142
145
|
if (/fallback/i.test(rule.name) === true) {
|
|
143
146
|
if (rule.isServerRule === true) {
|
|
144
147
|
this.#fallbackServerRules.set(rule.name, rule)
|
|
148
|
+
} else if (rule.isConsumerRule === true) {
|
|
149
|
+
this.#fallbackConsumerRules.set(rule.name, rule)
|
|
145
150
|
} else if (rule.isClientRule === true) {
|
|
146
151
|
this.#fallbackClientRules.set(rule.name, rule)
|
|
147
152
|
} else if (rule.isProducerRule === true) {
|
|
@@ -154,6 +159,8 @@ class RulesEngine {
|
|
|
154
159
|
|
|
155
160
|
if (rule.isServerRule === true) {
|
|
156
161
|
this.#serverRules.set(rule.name, rule)
|
|
162
|
+
} else if (rule.isConsumerRule === true) {
|
|
163
|
+
this.#consumerRules.set(rule.name, rule)
|
|
157
164
|
} else if (rule.isClientRule === true) {
|
|
158
165
|
this.#clientRules.set(rule.name, rule)
|
|
159
166
|
}
|
|
@@ -174,8 +181,7 @@ class RulesEngine {
|
|
|
174
181
|
|
|
175
182
|
// eslint-disable-next-line sonarjs/no-labels, no-labels
|
|
176
183
|
detector: switch (otelSpan.kind) {
|
|
177
|
-
case SpanKind.SERVER:
|
|
178
|
-
case SpanKind.CONSUMER: {
|
|
184
|
+
case SpanKind.SERVER: {
|
|
179
185
|
for (const rule of this.#serverRules.values()) {
|
|
180
186
|
if (rule.matches(otelSpan) === true) {
|
|
181
187
|
result = rule
|
|
@@ -192,6 +198,23 @@ class RulesEngine {
|
|
|
192
198
|
break
|
|
193
199
|
}
|
|
194
200
|
|
|
201
|
+
case SpanKind.CONSUMER: {
|
|
202
|
+
for (const rule of this.#consumerRules.values()) {
|
|
203
|
+
if (rule.matches(otelSpan) === true) {
|
|
204
|
+
result = rule
|
|
205
|
+
// eslint-disable-next-line no-labels
|
|
206
|
+
break detector
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
for (const rule of this.#fallbackConsumerRules.values()) {
|
|
210
|
+
if (rule.matches(otelSpan) === true) {
|
|
211
|
+
result = rule
|
|
212
|
+
break
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
break
|
|
216
|
+
}
|
|
217
|
+
|
|
195
218
|
case SpanKind.CLIENT: {
|
|
196
219
|
for (const rule of this.#clientRules.values()) {
|
|
197
220
|
if (rule.matches(otelSpan) === true) {
|
|
@@ -25,14 +25,14 @@ class BaseSpanStreamer {
|
|
|
25
25
|
// 'connected' indicates a safely writeable stream.
|
|
26
26
|
// May still be mid-connect to gRPC server.
|
|
27
27
|
this.connection.on('connected', (stream) => {
|
|
28
|
-
logger.
|
|
28
|
+
logger.trace('Span streamer connected, flushing queue')
|
|
29
29
|
this.stream = stream
|
|
30
30
|
this._writable = true
|
|
31
31
|
this.sendQueue()
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
this.connection.on('disconnected', () => {
|
|
35
|
-
logger.
|
|
35
|
+
logger.trace('Span streamer disconnected')
|
|
36
36
|
this.stream = null
|
|
37
37
|
this._writable = false
|
|
38
38
|
})
|
|
@@ -52,9 +52,10 @@ class BaseSpanStreamer {
|
|
|
52
52
|
write(span) {
|
|
53
53
|
this._metrics.getOrCreateMetric(NAMES.SEEN).incrementCallCount()
|
|
54
54
|
|
|
55
|
-
// If not
|
|
55
|
+
// If not writable (because of backpressure) queue the span
|
|
56
56
|
if (!this._writable) {
|
|
57
57
|
if (this.spans.length < this.queue_size) {
|
|
58
|
+
logger.trace('stream not writable, add spans to queue')
|
|
58
59
|
this.addToQueue(span)
|
|
59
60
|
return
|
|
60
61
|
}
|
|
@@ -78,16 +79,17 @@ class BaseSpanStreamer {
|
|
|
78
79
|
* span, a drain event handler is setup to continue writing when possible.
|
|
79
80
|
*
|
|
80
81
|
* @param {*} data spans or span
|
|
81
|
-
* @param {number} [spanLen] number of spans sent in a stream(defaults to 1)
|
|
82
82
|
*/
|
|
83
|
-
send(data
|
|
83
|
+
send(data) {
|
|
84
|
+
const spans = data?.spans?.length || 1
|
|
85
|
+
this._writable = this.stream.write(data)
|
|
86
|
+
this._metrics.getOrCreateMetric(NAMES.SENT).incrementCallCount(spans)
|
|
87
|
+
|
|
84
88
|
// false indicates the stream has reached the highWaterMark
|
|
85
89
|
// and future writes should be avoided until drained. written items,
|
|
86
90
|
// including the one that returned false, will still be buffered.
|
|
87
|
-
this._writable = this.stream.write(data)
|
|
88
|
-
this._metrics.getOrCreateMetric(NAMES.SENT).incrementCallCount(spanLen)
|
|
89
|
-
|
|
90
91
|
if (!this._writable) {
|
|
92
|
+
logger.trace('Stream is not writable, adding a drain listener to flush queue when it is writable.')
|
|
91
93
|
const waitDrainStart = Date.now()
|
|
92
94
|
const onDrain = this.drain.bind(this, waitDrainStart)
|
|
93
95
|
this.stream.once('drain', onDrain)
|
|
@@ -112,7 +114,6 @@ class BaseSpanStreamer {
|
|
|
112
114
|
|
|
113
115
|
// Once the 'drain' event fires we can begin writing to the stream again
|
|
114
116
|
this._writable = true
|
|
115
|
-
|
|
116
117
|
this.sendQueue()
|
|
117
118
|
}
|
|
118
119
|
|
|
@@ -9,22 +9,11 @@ const logger = require('../logger').child({ component: 'batch-span-streamer' })
|
|
|
9
9
|
const BaseSpanStreamer = require('./base-span-streamer')
|
|
10
10
|
|
|
11
11
|
class BatchSpanStreamer extends BaseSpanStreamer {
|
|
12
|
-
constructor(licenseKey, connection, metrics, queueSize) {
|
|
12
|
+
constructor(licenseKey, connection, metrics, queueSize, batchSize) {
|
|
13
13
|
super(licenseKey, connection, metrics, queueSize)
|
|
14
14
|
this.sendTimer = null
|
|
15
|
-
this.batchSize =
|
|
16
|
-
this.
|
|
17
|
-
logger.debug('Setting up batch interval')
|
|
18
|
-
this.sendTimer = setInterval(this.sendQueue.bind(this), 5000)
|
|
19
|
-
this.sendTimer.unref()
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
this.connection.on('disconnected', () => {
|
|
23
|
-
logger.debug('Clearing batch interval')
|
|
24
|
-
if (this.sendTimer) {
|
|
25
|
-
clearInterval(this.sendTimer)
|
|
26
|
-
}
|
|
27
|
-
})
|
|
15
|
+
this.batchSize = batchSize
|
|
16
|
+
this.queueInterval = 5000
|
|
28
17
|
}
|
|
29
18
|
|
|
30
19
|
addToQueue(span) {
|
|
@@ -35,6 +24,11 @@ class BatchSpanStreamer extends BaseSpanStreamer {
|
|
|
35
24
|
* or drops it depending on stream/queue state
|
|
36
25
|
*/
|
|
37
26
|
write(span) {
|
|
27
|
+
const currTime = Date.now()
|
|
28
|
+
|
|
29
|
+
if (!this.time) {
|
|
30
|
+
this.time = currTime
|
|
31
|
+
}
|
|
38
32
|
super.write(span)
|
|
39
33
|
|
|
40
34
|
if (!this._writable) {
|
|
@@ -43,18 +37,29 @@ class BatchSpanStreamer extends BaseSpanStreamer {
|
|
|
43
37
|
|
|
44
38
|
this.addToQueue(span)
|
|
45
39
|
|
|
46
|
-
if (this.
|
|
47
|
-
|
|
40
|
+
if (this.batchReady(currTime)) {
|
|
41
|
+
this.sendQueue()
|
|
42
|
+
this.time = currTime
|
|
48
43
|
}
|
|
44
|
+
}
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
/**
|
|
47
|
+
* The Infinite Tracing trace observer will treat spans that are more than 10 seconds apart as not belonging to the same group.
|
|
48
|
+
* It is therefore recommended that the agent perform a RecordSpanBatch request when either a target maximum batch size is reached or 5 seconds have elapsed since the creation of a batch (giving precedence to whichever of these events takes place first).
|
|
49
|
+
* Otherwise, delayed recording of spans will lead to their being discarded.
|
|
50
|
+
*
|
|
51
|
+
* @param {number} currTime timestamp in milliseconds
|
|
52
|
+
* @returns {boolean} if a batch can we sent
|
|
53
|
+
*/
|
|
54
|
+
batchReady(currTime) {
|
|
55
|
+
return this.spans.length >= this.batchSize || currTime - this.time >= this.queueInterval
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
/**
|
|
54
|
-
* Chunks the span queue into n per batch
|
|
59
|
+
* Chunks the span queue into n per batch.
|
|
55
60
|
* The avg span is generally 1kb, picking an option slightly under to avoid
|
|
56
61
|
* being over 1MB uncompressed limit being imposed on the gRPC server.
|
|
57
|
-
* Since the processing happens
|
|
62
|
+
* Since the processing happens async it'll be very hard to split further
|
|
58
63
|
* if a span batch is too big. We are being conservative here and other
|
|
59
64
|
* language agents transmit even smaller batches(100 per batch).
|
|
60
65
|
*/
|
|
@@ -64,15 +69,13 @@ class BatchSpanStreamer extends BaseSpanStreamer {
|
|
|
64
69
|
return
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
|
|
68
|
-
const spans = this.spans.
|
|
72
|
+
while (this.spans.length > 0 && this._writable) {
|
|
73
|
+
const spans = this.spans.splice(0, this.batchSize)
|
|
69
74
|
logger.trace('Sending spans from queue: %s', spans.length)
|
|
70
|
-
this.send({ spans }
|
|
75
|
+
this.send({ spans })
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
logger.trace('Finished sending spans from queue.')
|
|
78
|
+
logger.trace('Finished sending spans from queue. Items left in queue: %s', this.spans.length)
|
|
76
79
|
}
|
|
77
80
|
}
|
|
78
81
|
|
|
@@ -54,7 +54,8 @@ function createStreamingAggregator(config, agent) {
|
|
|
54
54
|
config.license_key,
|
|
55
55
|
connection,
|
|
56
56
|
metrics,
|
|
57
|
-
config.infinite_tracing.span_events.queue_size
|
|
57
|
+
config.infinite_tracing.span_events.queue_size,
|
|
58
|
+
config.infinite_tracing.span_events.batch_size
|
|
58
59
|
)
|
|
59
60
|
metrics.getOrCreateMetric(`${NAMES.BATCHING}/enabled`).incrementCallCount()
|
|
60
61
|
} else {
|
|
@@ -44,7 +44,7 @@ class SpanStreamer extends BaseSpanStreamer {
|
|
|
44
44
|
|
|
45
45
|
logger.trace('Sending spans from queue: %s.', this.spans.length)
|
|
46
46
|
|
|
47
|
-
// Continue sending the spans that were in the queue. _writable is checked
|
|
47
|
+
// Continue sending the spans that were in the queue. this._writable is checked
|
|
48
48
|
// so that if a send fails while clearing the queue, this drain handler can
|
|
49
49
|
// finish, and the drain handler setup on the failed send will then attempt
|
|
50
50
|
// to clear the queue
|
package/lib/util/logger.js
CHANGED
|
@@ -10,6 +10,33 @@ const util = require('util')
|
|
|
10
10
|
const Readable = require('readable-stream')
|
|
11
11
|
const os = require('os')
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* A logging utility with methods `info`, `warn`, `error`, `fatal`, `debug`,
|
|
15
|
+
* and `trace`. This logger is very similar to Pino.
|
|
16
|
+
*
|
|
17
|
+
* Each method supports the following invocation signatures:
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // Logs a single string.
|
|
21
|
+
* logger.info('hello world')
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Logs an interpolated string.
|
|
25
|
+
* logger.info('hello %s', 'world')
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* // Logs with data properties inlined:
|
|
29
|
+
* logger.info({a: 1, b: 2}, 'logged data')
|
|
30
|
+
* // e.g. `{ a: 1, b: 2, msg: 'logged data', level: 30 }`
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Recommended for keeping data usable:
|
|
34
|
+
* logger.info({ data: { the: 'data', that: 'is important' }}, 'some message')
|
|
35
|
+
* // ^ only the `data` key will be inlined at the top level. The sub-properties
|
|
36
|
+
* // will still be nested under the data key.
|
|
37
|
+
*
|
|
38
|
+
* @type {Object}
|
|
39
|
+
*/
|
|
13
40
|
module.exports = Logger
|
|
14
41
|
|
|
15
42
|
const LEVELS = {
|
|
@@ -294,12 +321,17 @@ function getPropertiesToLog(extra) {
|
|
|
294
321
|
const obj = Object.assign(Object.create(null), extra)
|
|
295
322
|
// Error properties (message, stack) are not enumerable, so getting them directly
|
|
296
323
|
if (extra instanceof Error) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
}
|
|
324
|
+
decorateFromErrorInstance(obj, extra)
|
|
325
|
+
} else if (extra?.error != null) {
|
|
326
|
+
obj.error = {}
|
|
327
|
+
decorateFromErrorInstance(obj.error, extra.error)
|
|
303
328
|
}
|
|
304
329
|
return obj
|
|
305
330
|
}
|
|
331
|
+
|
|
332
|
+
function decorateFromErrorInstance(toDecorate, errorInstance) {
|
|
333
|
+
const names = Object.getOwnPropertyNames(errorInstance)
|
|
334
|
+
for (const name of names) {
|
|
335
|
+
toDecorate[name] = errorInstance[name]
|
|
336
|
+
}
|
|
337
|
+
}
|
|
@@ -19,10 +19,12 @@ module.exports.clearCache = function clearAWSCache() {
|
|
|
19
19
|
|
|
20
20
|
function fetchAWSInfo(agent, callback) {
|
|
21
21
|
if (!agent.config.utilization || !agent.config.utilization.detect_aws) {
|
|
22
|
+
logger.trace({ utilization: 'aws' }, 'Skipping AWS due to being disabled via config.')
|
|
22
23
|
return setImmediate(callback, null)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
if (results) {
|
|
27
|
+
logger.trace({ utilization: 'aws' }, 'Returning previously found results.')
|
|
26
28
|
return setImmediate(callback, null, results)
|
|
27
29
|
}
|
|
28
30
|
|
|
@@ -35,7 +37,7 @@ function fetchAWSInfo(agent, callback) {
|
|
|
35
37
|
}
|
|
36
38
|
common.request(authTokenOpts, agent, function getAuthToken(err, authToken) {
|
|
37
39
|
if (err) {
|
|
38
|
-
logger.debug('Failed to get AWS auth token.')
|
|
40
|
+
logger.debug({ utilization: 'aws' }, 'Failed to get AWS auth token.')
|
|
39
41
|
return callback(err)
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -49,19 +51,20 @@ function fetchAWSInfo(agent, callback) {
|
|
|
49
51
|
|
|
50
52
|
common.request(metadataOpts, agent, function getMetadata(metaErr, data) {
|
|
51
53
|
if (metaErr) {
|
|
54
|
+
logger.trace({ utilization: 'aws' }, 'Returning due to error from metadata service.')
|
|
52
55
|
return callback(metaErr)
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
try {
|
|
56
59
|
data = JSON.parse(data)
|
|
57
60
|
} catch (e) {
|
|
58
|
-
logger.debug(e, 'Failed to parse AWS metadata.')
|
|
61
|
+
logger.debug({ utilization: 'aws', error: e }, 'Failed to parse AWS metadata.')
|
|
59
62
|
data = null
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
results = common.getKeys(data, ['availabilityZone', 'instanceId', 'instanceType'])
|
|
63
66
|
if (results == null) {
|
|
64
|
-
logger.debug('AWS metadata was invalid.')
|
|
67
|
+
logger.debug({ utilization: 'aws' }, 'AWS metadata was invalid.')
|
|
65
68
|
agent.metrics.getOrCreateMetric(NAMES.UTILIZATION.AWS_ERROR).incrementCallCount()
|
|
66
69
|
}
|
|
67
70
|
callback(null, results)
|
|
@@ -17,10 +17,12 @@ module.exports.clearCache = function clearAzureCache() {
|
|
|
17
17
|
|
|
18
18
|
function fetchAzureInfo(agent, callback) {
|
|
19
19
|
if (!agent.config.utilization || !agent.config.utilization.detect_azure) {
|
|
20
|
+
logger.trace({ utilization: 'azure' }, 'Skipping Azure due to being disabled via config.')
|
|
20
21
|
return setImmediate(callback, null, null)
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
if (results) {
|
|
25
|
+
logger.trace({ utilization: 'azure' }, 'Returning previously found results.')
|
|
24
26
|
return setImmediate(callback, null, results)
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -37,6 +39,7 @@ function fetchAzureInfo(agent, callback) {
|
|
|
37
39
|
agent,
|
|
38
40
|
function getMetadata(err, data) {
|
|
39
41
|
if (err) {
|
|
42
|
+
logger.trace({ utilization: 'azure', error: err }, 'Failed to query metadata endpoint.')
|
|
40
43
|
return callback(err)
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -44,14 +47,14 @@ function fetchAzureInfo(agent, callback) {
|
|
|
44
47
|
try {
|
|
45
48
|
data = JSON.parse(data)
|
|
46
49
|
} catch (e) {
|
|
47
|
-
logger.debug(e, 'Failed to parse Azure metadata.')
|
|
50
|
+
logger.debug({ utilization: 'azure', error: e }, 'Failed to parse Azure metadata.')
|
|
48
51
|
data = null
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
// Get out just the keys we care about.
|
|
52
55
|
results = common.getKeys(data, ['location', 'name', 'vmId', 'vmSize'])
|
|
53
56
|
if (results == null) {
|
|
54
|
-
logger.debug('Azure metadata was invalid.')
|
|
57
|
+
logger.debug({ utilization: 'azure' }, 'Azure metadata was invalid.')
|
|
55
58
|
agent.metrics.getOrCreateMetric(NAMES.UTILIZATION.AZURE_ERROR).incrementCallCount()
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -16,17 +16,19 @@ module.exports.clearCache = function clearAzureFunctionCache() {
|
|
|
16
16
|
|
|
17
17
|
function fetchAzureFunctionInfo(agent, callback) {
|
|
18
18
|
if (!agent.config.utilization || !agent.config.utilization.detect_azurefunction) {
|
|
19
|
+
logger.trace({ utilization: 'azurefunction' }, 'Skipping Azure function due to being disabled via config.')
|
|
19
20
|
return setImmediate(callback, null, null)
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
if (results) {
|
|
24
|
+
logger.trace({ utilization: 'azurefunction' }, 'Returning previously found results.')
|
|
23
25
|
return setImmediate(callback, null, results)
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// Detect if Azure Function.
|
|
27
29
|
const { REGION_NAME, WEBSITE_OWNER_NAME, WEBSITE_SITE_NAME, WEBSITE_RESOURCE_GROUP } = process.env
|
|
28
30
|
if (!REGION_NAME || !WEBSITE_OWNER_NAME || !WEBSITE_SITE_NAME) {
|
|
29
|
-
logger.debug('Azure Function metadata was invalid.')
|
|
31
|
+
logger.debug({ utilization: 'azurefunction' }, 'Azure Function metadata was invalid.')
|
|
30
32
|
agent.metrics.getOrCreateMetric(NAMES.UTILIZATION.AZURE_ERROR).incrementCallCount()
|
|
31
33
|
return setImmediate(callback, null, null)
|
|
32
34
|
}
|
|
@@ -28,7 +28,7 @@ function clearDockerVendorCache() {
|
|
|
28
28
|
|
|
29
29
|
function getBootId(agent, callback, logger = log) {
|
|
30
30
|
if (!/linux/i.test(os.platform())) {
|
|
31
|
-
logger.debug('Platform is not a flavor of linux, omitting boot info')
|
|
31
|
+
logger.debug({ utilization: 'docker' }, 'Platform is not a flavor of linux, omitting boot info')
|
|
32
32
|
return setImmediate(callback, null, null)
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -40,7 +40,7 @@ function getBootId(agent, callback, logger = log) {
|
|
|
40
40
|
})
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
logger.debug('Container boot id is not available in cgroups info')
|
|
43
|
+
logger.debug({ utilization: 'docker' }, 'Container boot id is not available in cgroups info')
|
|
44
44
|
callback(null, null)
|
|
45
45
|
})
|
|
46
46
|
}
|
|
@@ -98,6 +98,7 @@ function readProcBootId({ data, agent, callback }) {
|
|
|
98
98
|
*/
|
|
99
99
|
function fetchDockerVendorInfo(agent, callback, logger = log) {
|
|
100
100
|
if (!agent.config.utilization || !agent.config.utilization.detect_docker) {
|
|
101
|
+
logger.trace({ utilization: 'docker' }, 'Skipping Docker due to being disabled via config.')
|
|
101
102
|
return callback(null, null)
|
|
102
103
|
}
|
|
103
104
|
|
|
@@ -106,7 +107,7 @@ function fetchDockerVendorInfo(agent, callback, logger = log) {
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
if (!os.platform().match(/linux/i)) {
|
|
109
|
-
logger.debug('Platform is not a flavor of linux, omitting docker info')
|
|
110
|
+
logger.debug({ utilization: 'docker' }, 'Platform is not a flavor of linux, omitting docker info')
|
|
110
111
|
return callback(null, null)
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -114,6 +115,7 @@ function fetchDockerVendorInfo(agent, callback, logger = log) {
|
|
|
114
115
|
common.readProc(CGROUPS_V2_PATH, function getV2CGroup(_, data) {
|
|
115
116
|
if (data === null) {
|
|
116
117
|
logger.debug(
|
|
118
|
+
{ utilization: 'docker' },
|
|
117
119
|
`${CGROUPS_V2_PATH} not found, trying to parse container id from ${CGROUPS_V1_PATH}`
|
|
118
120
|
)
|
|
119
121
|
findCGroupsV1(callback, logger)
|
|
@@ -126,13 +128,14 @@ function fetchDockerVendorInfo(agent, callback, logger = log) {
|
|
|
126
128
|
if (v2Data !== null) {
|
|
127
129
|
// We found a valid Docker identifier in the v2 file, so we are going
|
|
128
130
|
// to prioritize it.
|
|
131
|
+
logger.debug({ utilization: 'docker', v2Data }, 'Found identifier in cgroups v2 file.')
|
|
129
132
|
return callback(null, v2Data)
|
|
130
133
|
}
|
|
131
134
|
|
|
132
135
|
// For some reason, we have a /proc/self/mountinfo but it does not have
|
|
133
136
|
// any Docker information in it (that we have detected). So we will
|
|
134
137
|
// fall back to trying the cgroups v1 file.
|
|
135
|
-
logger.debug('Attempting to fall back to cgroups v1 parsing.')
|
|
138
|
+
logger.debug({ utilization: 'docker' }, 'Attempting to fall back to cgroups v1 parsing.')
|
|
136
139
|
findCGroupsV1(callback, logger)
|
|
137
140
|
},
|
|
138
141
|
logger
|
|
@@ -152,10 +155,10 @@ function parseCGroupsV2(data, callback, logger = log) {
|
|
|
152
155
|
const containerLine = /\/docker\/containers\/([0-9a-f]{64})\//
|
|
153
156
|
const line = containerLine.exec(data)
|
|
154
157
|
if (line) {
|
|
155
|
-
logger.debug(`Found docker id from cgroups v2: ${line[1]}`)
|
|
158
|
+
logger.debug({ utilization: 'docker' }, `Found docker id from cgroups v2: ${line[1]}`)
|
|
156
159
|
callback(null, { id: line[1] })
|
|
157
160
|
} else {
|
|
158
|
-
logger.debug(`Found ${CGROUPS_V2_PATH} but failed to parse Docker container id.`)
|
|
161
|
+
logger.debug({ utilization: 'docker' }, `Found ${CGROUPS_V2_PATH} but failed to parse Docker container id.`)
|
|
159
162
|
callback(null, null)
|
|
160
163
|
}
|
|
161
164
|
}
|
|
@@ -170,7 +173,7 @@ function parseCGroupsV2(data, callback, logger = log) {
|
|
|
170
173
|
function findCGroupsV1(callback, logger = log) {
|
|
171
174
|
common.readProc(CGROUPS_V1_PATH, function getCGroup(_, data) {
|
|
172
175
|
if (!data) {
|
|
173
|
-
logger.debug(`${CGROUPS_V1_PATH} not found, exiting parsing containerId.`)
|
|
176
|
+
logger.debug({ utilization: 'docker' }, `${CGROUPS_V1_PATH} not found, exiting parsing containerId.`)
|
|
174
177
|
return callback(null)
|
|
175
178
|
}
|
|
176
179
|
|
|
@@ -187,10 +190,10 @@ function findCGroupsV1(callback, logger = log) {
|
|
|
187
190
|
|
|
188
191
|
if (id) {
|
|
189
192
|
vendorInfo = { id }
|
|
190
|
-
logger.debug(`Found docker id from cgroups v1: ${id}`)
|
|
193
|
+
logger.debug({ utilization: 'docker' }, `Found docker id from cgroups v1: ${id}`)
|
|
191
194
|
callback(null, vendorInfo)
|
|
192
195
|
} else {
|
|
193
|
-
logger.debug('No matching cpu group found.')
|
|
196
|
+
logger.debug({ utilization: 'docker' }, 'No matching cpu group found.')
|
|
194
197
|
callback(null, null)
|
|
195
198
|
}
|
|
196
199
|
})
|
|
@@ -21,11 +21,12 @@ module.exports = function fetchEcsInfo(
|
|
|
21
21
|
// Per spec, we do not have a `detect_ecs` key. Since ECS is a service of AWS,
|
|
22
22
|
// we rely on the `detect_aws` setting.
|
|
23
23
|
if (!agent.config.utilization || !agent.config.utilization.detect_aws) {
|
|
24
|
+
logger.trace({ utilization: 'ecs' }, 'Skipping ECS due to being disabled via config.')
|
|
24
25
|
return setImmediate(callback, null)
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
if (hasAwsContainerApi() === false) {
|
|
28
|
-
logger.debug('ECS API not available, omitting ECS container id info')
|
|
29
|
+
logger.debug({ utilization: 'ecs' }, 'ECS API not available, omitting ECS container id info')
|
|
29
30
|
recordIdError(agent)
|
|
30
31
|
return callback(null, null)
|
|
31
32
|
}
|
|
@@ -70,13 +71,13 @@ function _getEcsContainerId({ agent, callback, logger, recordIdError }) {
|
|
|
70
71
|
const json = body.toString('utf8')
|
|
71
72
|
const data = JSON.parse(json)
|
|
72
73
|
if (data.DockerId == null) {
|
|
73
|
-
logger.debug('Failed to find DockerId in response, omitting boot info')
|
|
74
|
+
logger.debug({ utilization: 'ecs' }, 'Failed to find DockerId in response, omitting boot info')
|
|
74
75
|
recordIdError(agent)
|
|
75
76
|
return callback(null, null)
|
|
76
77
|
}
|
|
77
78
|
callback(null, data.DockerId)
|
|
78
79
|
} catch (error) {
|
|
79
|
-
logger.debug('Failed to process ECS API response, omitting boot info: ' + error.message)
|
|
80
|
+
logger.debug({ utilization: 'ecs' }, 'Failed to process ECS API response, omitting boot info: ' + error.message)
|
|
80
81
|
recordIdError(agent)
|
|
81
82
|
callback(null, null)
|
|
82
83
|
}
|
|
@@ -84,7 +85,7 @@ function _getEcsContainerId({ agent, callback, logger, recordIdError }) {
|
|
|
84
85
|
})
|
|
85
86
|
|
|
86
87
|
req.on('error', () => {
|
|
87
|
-
logger.debug('Failed to query ECS endpoint, omitting boot info')
|
|
88
|
+
logger.debug({ utilization: 'ecs' }, 'Failed to query ECS endpoint, omitting boot info')
|
|
88
89
|
recordIdError(agent)
|
|
89
90
|
callback(null, null)
|
|
90
91
|
})
|
|
@@ -18,10 +18,12 @@ module.exports.clearCache = function clearGCPCache() {
|
|
|
18
18
|
|
|
19
19
|
function fetchGCPInfo(agent, callback) {
|
|
20
20
|
if (!agent.config.utilization || !agent.config.utilization.detect_gcp) {
|
|
21
|
+
logger.trace({ utilization: 'gcp' }, 'Skipping GCP due to being disabled via config.')
|
|
21
22
|
return setImmediate(callback, null)
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
if (resultDict) {
|
|
26
|
+
logger.trace({ utilization: 'gcp' }, 'Returning previously found results.')
|
|
25
27
|
return setImmediate(callback, null, resultDict)
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -36,6 +38,7 @@ function fetchGCPInfo(agent, callback) {
|
|
|
36
38
|
agent,
|
|
37
39
|
function getMetadata(err, data) {
|
|
38
40
|
if (err) {
|
|
41
|
+
logger.trace({ utilization: 'gcp', error: err }, 'Failed to communicate with metadata endpoint.')
|
|
39
42
|
return callback(err)
|
|
40
43
|
}
|
|
41
44
|
try {
|
|
@@ -44,13 +47,13 @@ function fetchGCPInfo(agent, callback) {
|
|
|
44
47
|
data.id = data.id.toString()
|
|
45
48
|
}
|
|
46
49
|
} catch (e) {
|
|
47
|
-
logger.debug(e, 'Failed to parse GCP metadata.')
|
|
50
|
+
logger.debug({ utilization: 'gcp', error: e }, 'Failed to parse GCP metadata.')
|
|
48
51
|
data = null
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
const results = common.getKeys(data, ['id', 'machineType', 'name', 'zone'])
|
|
52
55
|
if (results == null) {
|
|
53
|
-
logger.debug('GCP metadata was invalid.')
|
|
56
|
+
logger.debug({ utilization: 'gcp' }, 'GCP metadata was invalid.')
|
|
54
57
|
agent.metrics.getOrCreateMetric(NAMES.UTILIZATION.GCP_ERROR).incrementCallCount()
|
|
55
58
|
} else {
|
|
56
59
|
// normalize
|
package/lib/utilization/index.js
CHANGED
|
@@ -24,8 +24,9 @@ function getVendors(agent, callback) {
|
|
|
24
24
|
let done = 0
|
|
25
25
|
let vendors = null
|
|
26
26
|
VENDOR_NAMES.forEach(function getVendorInfo(vendor) {
|
|
27
|
+
logger.trace({ utilization: vendor }, 'Detecting utilization info for vendor %s', vendor)
|
|
27
28
|
VENDOR_METHODS[vendor](agent, function getInfo(_, result) {
|
|
28
|
-
logger.trace('Vendor %s finished.', vendor)
|
|
29
|
+
logger.trace({ utilization: vendor, data: { result } }, 'Vendor %s finished.', vendor)
|
|
29
30
|
if (result) {
|
|
30
31
|
vendors = vendors || Object.create(null)
|
|
31
32
|
vendors[vendor] = result
|
|
@@ -9,21 +9,23 @@ const logger = require('../logger').child({ component: 'kubernetes-info' })
|
|
|
9
9
|
let info = null
|
|
10
10
|
|
|
11
11
|
module.exports = getKubernetesInfo
|
|
12
|
-
module.exports.clearCache = function
|
|
12
|
+
module.exports.clearCache = function clearKubernetesCache() {
|
|
13
13
|
info = null
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function getKubernetesInfo(agent, callback) {
|
|
17
17
|
if (!agent.config.utilization || !agent.config.utilization.detect_kubernetes) {
|
|
18
|
+
logger.trace({ utilization: 'kubernetes' }, 'Skipping Kubernetes due to being disabled via config.')
|
|
18
19
|
return setImmediate(callback, null, null)
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
if (info) {
|
|
23
|
+
logger.trace({ utilization: 'kubernetes' }, 'Returning previously found results.')
|
|
22
24
|
return setImmediate(callback, null, info)
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
if (!process.env.KUBERNETES_SERVICE_HOST) {
|
|
26
|
-
logger.debug('No Kubernetes service host found.')
|
|
28
|
+
logger.debug({ utilization: 'kubernetes' }, 'No Kubernetes service host found.')
|
|
27
29
|
return setImmediate(callback, null, null)
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -13,6 +13,7 @@ module.exports = fetchPCFInfo
|
|
|
13
13
|
|
|
14
14
|
function fetchPCFInfo(agent, callback) {
|
|
15
15
|
if (!agent.config.utilization || !agent.config.utilization.detect_pcf) {
|
|
16
|
+
logger.trace({ utilization: 'pcf' }, 'Skipping PCF due to being disabled via config.')
|
|
16
17
|
return setImmediate(callback, null, null)
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -28,11 +29,11 @@ function fetchPCFInfo(agent, callback) {
|
|
|
28
29
|
const key = keys[i]
|
|
29
30
|
const value = process.env[key]
|
|
30
31
|
if (value == null) {
|
|
31
|
-
logger.trace('Could not find environment value for %s', key)
|
|
32
|
+
logger.trace({ utilization: 'pcf' }, 'Could not find environment value for %s', key)
|
|
32
33
|
return setImmediate(callback, null, null)
|
|
33
34
|
}
|
|
34
35
|
if (!common.checkValueString(value)) {
|
|
35
|
-
logger.trace('Invalid environment value for %s: %j', key, value)
|
|
36
|
+
logger.trace({ utilization: 'pcf' }, 'Invalid environment value for %s: %j', key, value)
|
|
36
37
|
agent.metrics.getOrCreateMetric(NAMES.UTILIZATION.PCF_ERROR).incrementCallCount()
|
|
37
38
|
return setImmediate(callback, null, null)
|
|
38
39
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newrelic",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.18.0",
|
|
4
4
|
"author": "New Relic Node.js agent team <nodejs@newrelic.com>",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"contributors": [
|
|
@@ -200,7 +200,7 @@
|
|
|
200
200
|
"#test/assert": "./test/lib/custom-assertions/index.js"
|
|
201
201
|
},
|
|
202
202
|
"dependencies": {
|
|
203
|
-
"@grpc/grpc-js": "^1.
|
|
203
|
+
"@grpc/grpc-js": "^1.13.2",
|
|
204
204
|
"@grpc/proto-loader": "^0.7.5",
|
|
205
205
|
"@newrelic/security-agent": "^2.3.0",
|
|
206
206
|
"@opentelemetry/api": "^1.9.0",
|