dd-trace 4.20.0 → 4.22.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 (76) hide show
  1. package/LICENSE-3rdparty.csv +1 -0
  2. package/index.d.ts +29 -0
  3. package/package.json +8 -7
  4. package/packages/datadog-instrumentations/src/aerospike.js +47 -0
  5. package/packages/datadog-instrumentations/src/apollo-server-core.js +41 -0
  6. package/packages/datadog-instrumentations/src/apollo-server.js +83 -0
  7. package/packages/datadog-instrumentations/src/graphql.js +18 -4
  8. package/packages/datadog-instrumentations/src/helpers/hooks.js +3 -0
  9. package/packages/datadog-instrumentations/src/http/client.js +10 -0
  10. package/packages/datadog-instrumentations/src/jest.js +11 -5
  11. package/packages/datadog-instrumentations/src/kafkajs.js +27 -0
  12. package/packages/datadog-instrumentations/src/next.js +18 -6
  13. package/packages/datadog-instrumentations/src/restify.js +1 -1
  14. package/packages/datadog-instrumentations/src/rhea.js +15 -9
  15. package/packages/datadog-plugin-aerospike/src/index.js +113 -0
  16. package/packages/datadog-plugin-graphql/src/resolve.js +26 -18
  17. package/packages/datadog-plugin-http/src/client.js +19 -2
  18. package/packages/datadog-plugin-kafkajs/src/consumer.js +51 -0
  19. package/packages/datadog-plugin-kafkajs/src/producer.js +55 -0
  20. package/packages/datadog-plugin-next/src/index.js +40 -14
  21. package/packages/dd-trace/src/appsec/activation.js +29 -0
  22. package/packages/dd-trace/src/appsec/addresses.js +3 -1
  23. package/packages/dd-trace/src/appsec/api_security_sampler.js +48 -0
  24. package/packages/dd-trace/src/appsec/blocked_templates.js +4 -1
  25. package/packages/dd-trace/src/appsec/blocking.js +95 -43
  26. package/packages/dd-trace/src/appsec/channels.js +4 -1
  27. package/packages/dd-trace/src/appsec/graphql.js +146 -0
  28. package/packages/dd-trace/src/appsec/iast/analyzers/analyzers.js +1 -0
  29. package/packages/dd-trace/src/appsec/iast/analyzers/header-injection-analyzer.js +105 -0
  30. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/constants.js +7 -0
  31. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +12 -19
  32. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/header-sensitive-analyzer.js +20 -0
  33. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/json-sensitive-analyzer.js +6 -10
  34. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/ldap-sensitive-analyzer.js +18 -25
  35. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +79 -85
  36. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +27 -36
  37. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-handler.js +14 -11
  38. package/packages/dd-trace/src/appsec/iast/vulnerabilities.js +1 -0
  39. package/packages/dd-trace/src/appsec/index.js +32 -31
  40. package/packages/dd-trace/src/appsec/recommended.json +1395 -2
  41. package/packages/dd-trace/src/appsec/remote_config/capabilities.js +2 -1
  42. package/packages/dd-trace/src/appsec/remote_config/index.js +36 -15
  43. package/packages/dd-trace/src/appsec/reporter.js +19 -0
  44. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  45. package/packages/dd-trace/src/appsec/waf/waf_context_wrapper.js +28 -13
  46. package/packages/dd-trace/src/appsec/waf/waf_manager.js +0 -1
  47. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +17 -1
  48. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +75 -56
  49. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-itr-configuration.js +15 -9
  50. package/packages/dd-trace/src/config.js +36 -2
  51. package/packages/dd-trace/src/datastreams/processor.js +107 -12
  52. package/packages/dd-trace/src/noop/proxy.js +4 -0
  53. package/packages/dd-trace/src/opentracing/span.js +2 -0
  54. package/packages/dd-trace/src/plugins/ci_plugin.js +2 -1
  55. package/packages/dd-trace/src/plugins/index.js +1 -0
  56. package/packages/dd-trace/src/plugins/util/git.js +2 -2
  57. package/packages/dd-trace/src/plugins/util/test.js +3 -2
  58. package/packages/dd-trace/src/plugins/util/user-provided-git.js +3 -2
  59. package/packages/dd-trace/src/profiler.js +5 -3
  60. package/packages/dd-trace/src/profiling/config.js +8 -0
  61. package/packages/dd-trace/src/profiling/profiler.js +17 -10
  62. package/packages/dd-trace/src/profiling/profilers/events.js +181 -83
  63. package/packages/dd-trace/src/profiling/profilers/shared.js +33 -3
  64. package/packages/dd-trace/src/profiling/profilers/space.js +2 -1
  65. package/packages/dd-trace/src/profiling/profilers/wall.js +17 -12
  66. package/packages/dd-trace/src/proxy.js +25 -1
  67. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +5 -0
  68. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +4 -0
  69. package/packages/dd-trace/src/spanleak.js +98 -0
  70. package/packages/dd-trace/src/startup-log.js +7 -1
  71. package/packages/dd-trace/src/telemetry/dependencies.js +55 -9
  72. package/packages/dd-trace/src/telemetry/index.js +135 -43
  73. package/packages/dd-trace/src/telemetry/logs/index.js +1 -1
  74. package/packages/dd-trace/src/telemetry/send-data.js +47 -5
  75. package/packages/dd-trace/src/tracer.js +4 -0
  76. package/scripts/install_plugin_modules.js +11 -3
@@ -30,6 +30,7 @@ require,opentracing,MIT,Copyright 2016 Resonance Labs Inc
30
30
  require,path-to-regexp,MIT,Copyright 2014 Blake Embrey
31
31
  require,pprof-format,MIT,Copyright 2022 Stephen Belanger
32
32
  require,protobufjs,BSD-3-Clause,Copyright 2016 Daniel Wirtz
33
+ require,tlhunter-sorted-set,MIT,Copyright (c) 2023 Datadog Inc.
33
34
  require,retry,MIT,Copyright 2011 Tim Koschützki Felix Geisendörfer
34
35
  require,semver,ISC,Copyright Isaac Z. Schlueter and Contributors
35
36
  dev,@types/node,MIT,Copyright Authors
package/index.d.ts CHANGED
@@ -567,6 +567,11 @@ export declare interface TracerOptions {
567
567
  */
568
568
  blockedTemplateJson?: string,
569
569
 
570
+ /**
571
+ * Specifies a path to a custom blocking template json file for graphql requests
572
+ */
573
+ blockedTemplateGraphql?: string,
574
+
570
575
  /**
571
576
  * Controls the automated user event tracking configuration
572
577
  */
@@ -578,6 +583,22 @@ export declare interface TracerOptions {
578
583
  * @default 'safe'
579
584
  */
580
585
  mode?: 'safe' | 'extended' | 'disabled'
586
+ },
587
+
588
+ /**
589
+ * Configuration for Api Security sampling
590
+ */
591
+ apiSecurity?: {
592
+ /** Whether to enable Api Security.
593
+ * @default false
594
+ */
595
+ enabled?: boolean,
596
+
597
+ /** Controls the request sampling rate (between 0 and 1) in which Api Security is triggered.
598
+ * The value will be coerced back if it's outside of the 0-1 range.
599
+ * @default 0.1
600
+ */
601
+ requestSampling?: number
581
602
  }
582
603
  };
583
604
 
@@ -927,6 +948,14 @@ declare namespace plugins {
927
948
  * @default code => code < 500
928
949
  */
929
950
  validateStatus?: (code: number) => boolean;
951
+
952
+ /**
953
+ * Enable injection of tracing headers into requests signed with AWS IAM headers.
954
+ * Disable this if you get AWS signature errors (HTTP 403).
955
+ *
956
+ * @default false
957
+ */
958
+ enablePropagationWithAmazonHeaders?: boolean;
930
959
  }
931
960
 
932
961
  /** @hidden */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "4.20.0",
3
+ "version": "4.22.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -68,11 +68,11 @@
68
68
  "node": ">=16"
69
69
  },
70
70
  "dependencies": {
71
- "@datadog/native-appsec": "4.0.0",
72
- "@datadog/native-iast-rewriter": "2.2.1",
71
+ "@datadog/native-appsec": "6.0.0",
72
+ "@datadog/native-iast-rewriter": "2.2.2",
73
73
  "@datadog/native-iast-taint-tracking": "1.6.4",
74
74
  "@datadog/native-metrics": "^2.0.0",
75
- "@datadog/pprof": "4.0.1",
75
+ "@datadog/pprof": "4.1.0",
76
76
  "@datadog/sketches-js": "^2.1.0",
77
77
  "@opentelemetry/api": "^1.0.0",
78
78
  "@opentelemetry/core": "^1.14.0",
@@ -97,10 +97,11 @@
97
97
  "node-abort-controller": "^3.1.1",
98
98
  "opentracing": ">=0.12.1",
99
99
  "path-to-regexp": "^0.1.2",
100
- "pprof-format": "^2.0.7",
101
- "protobufjs": "^7.2.4",
100
+ "pprof-format": "^2.0.7",
101
+ "protobufjs": "^7.2.5",
102
102
  "retry": "^0.13.1",
103
- "semver": "^7.5.4"
103
+ "semver": "^7.5.4",
104
+ "tlhunter-sorted-set": "^0.1.0"
104
105
  },
105
106
  "devDependencies": {
106
107
  "@types/node": ">=16",
@@ -0,0 +1,47 @@
1
+ 'use strict'
2
+
3
+ const {
4
+ addHook
5
+ } = require('./helpers/instrument')
6
+ const shimmer = require('../../datadog-shimmer')
7
+
8
+ const tracingChannel = require('dc-polyfill').tracingChannel
9
+ const ch = tracingChannel('apm:aerospike:command')
10
+
11
+ function wrapCreateCommand (createCommand) {
12
+ if (typeof createCommand !== 'function') return createCommand
13
+
14
+ return function commandWithTrace () {
15
+ const CommandClass = createCommand.apply(this, arguments)
16
+
17
+ if (!CommandClass) return CommandClass
18
+
19
+ shimmer.wrap(CommandClass.prototype, 'process', wrapProcess)
20
+
21
+ return CommandClass
22
+ }
23
+ }
24
+
25
+ function wrapProcess (process) {
26
+ return function (...args) {
27
+ const cb = args[0]
28
+ if (typeof cb !== 'function') return process.apply(this, args)
29
+
30
+ const ctx = {
31
+ commandName: this.constructor.name,
32
+ commandArgs: this.args,
33
+ clientConfig: this.client.config
34
+ }
35
+
36
+ return ch.traceCallback(process, -1, ctx, this, ...args)
37
+ }
38
+ }
39
+
40
+ addHook({
41
+ name: 'aerospike',
42
+ file: 'lib/commands/command.js',
43
+ versions: ['^3.16.2', '4', '5']
44
+ },
45
+ commandFactory => {
46
+ return shimmer.wrap(commandFactory, wrapCreateCommand(commandFactory))
47
+ })
@@ -0,0 +1,41 @@
1
+ 'use strict'
2
+
3
+ const { AbortController } = require('node-abort-controller')
4
+ const { addHook } = require('./helpers/instrument')
5
+ const shimmer = require('../../datadog-shimmer')
6
+ const dc = require('dc-polyfill')
7
+
8
+ const requestChannel = dc.tracingChannel('datadog:apollo-server-core:request')
9
+
10
+ addHook({ name: 'apollo-server-core', file: 'dist/runHttpQuery.js', versions: ['>3.0.0'] }, runHttpQueryModule => {
11
+ const HttpQueryError = runHttpQueryModule.HttpQueryError
12
+
13
+ shimmer.wrap(runHttpQueryModule, 'runHttpQuery', function wrapRunHttpQuery (originalRunHttpQuery) {
14
+ return async function runHttpQuery () {
15
+ if (!requestChannel.start.hasSubscribers) {
16
+ return originalRunHttpQuery.apply(this, arguments)
17
+ }
18
+
19
+ const abortController = new AbortController()
20
+ const abortData = {}
21
+
22
+ const runHttpQueryResult = requestChannel.tracePromise(
23
+ originalRunHttpQuery,
24
+ { abortController, abortData },
25
+ this,
26
+ ...arguments)
27
+
28
+ const abortPromise = new Promise((resolve, reject) => {
29
+ abortController.signal.addEventListener('abort', (event) => {
30
+ // runHttpQuery callbacks are writing the response on resolve/reject.
31
+ // We should return blocking data in the apollo-server-core HttpQueryError object
32
+ reject(new HttpQueryError(abortData.statusCode, abortData.message, true, abortData.headers))
33
+ }, { once: true })
34
+ })
35
+
36
+ return Promise.race([runHttpQueryResult, abortPromise])
37
+ }
38
+ })
39
+
40
+ return runHttpQueryModule
41
+ })
@@ -0,0 +1,83 @@
1
+ 'use strict'
2
+
3
+ const { AbortController } = require('node-abort-controller')
4
+ const dc = require('dc-polyfill')
5
+
6
+ const { addHook } = require('./helpers/instrument')
7
+ const shimmer = require('../../datadog-shimmer')
8
+
9
+ const graphqlMiddlewareChannel = dc.tracingChannel('datadog:apollo:middleware')
10
+
11
+ const requestChannel = dc.tracingChannel('datadog:apollo:request')
12
+
13
+ let HeaderMap
14
+
15
+ function wrapExecuteHTTPGraphQLRequest (originalExecuteHTTPGraphQLRequest) {
16
+ return async function executeHTTPGraphQLRequest () {
17
+ if (!HeaderMap || !requestChannel.start.hasSubscribers) {
18
+ return originalExecuteHTTPGraphQLRequest.apply(this, arguments)
19
+ }
20
+
21
+ const abortController = new AbortController()
22
+ const abortData = {}
23
+
24
+ const graphqlResponseData = requestChannel.tracePromise(
25
+ originalExecuteHTTPGraphQLRequest,
26
+ { abortController, abortData },
27
+ this,
28
+ ...arguments)
29
+
30
+ const abortPromise = new Promise((resolve, reject) => {
31
+ abortController.signal.addEventListener('abort', (event) => {
32
+ // This method is expected to return response data
33
+ // with headers, status and body
34
+ const headers = new HeaderMap()
35
+ Object.keys(abortData.headers).forEach(key => {
36
+ headers.set(key, abortData.headers[key])
37
+ })
38
+
39
+ resolve({
40
+ headers: headers,
41
+ status: abortData.statusCode,
42
+ body: {
43
+ kind: 'complete',
44
+ string: abortData.message
45
+ }
46
+ })
47
+ }, { once: true })
48
+ })
49
+
50
+ return Promise.race([abortPromise, graphqlResponseData])
51
+ }
52
+ }
53
+
54
+ function apolloExpress4Hook (express4) {
55
+ shimmer.wrap(express4, 'expressMiddleware', function wrapExpressMiddleware (originalExpressMiddleware) {
56
+ return function expressMiddleware (server, options) {
57
+ const originalMiddleware = originalExpressMiddleware.apply(this, arguments)
58
+
59
+ return shimmer.wrap(originalMiddleware, function (req, res, next) {
60
+ if (!graphqlMiddlewareChannel.start.hasSubscribers) {
61
+ return originalMiddleware.apply(this, arguments)
62
+ }
63
+
64
+ return graphqlMiddlewareChannel.traceSync(originalMiddleware, { req }, this, ...arguments)
65
+ })
66
+ }
67
+ })
68
+ return express4
69
+ }
70
+
71
+ function apolloHeaderMapHook (headerMap) {
72
+ HeaderMap = headerMap.HeaderMap
73
+ return headerMap
74
+ }
75
+
76
+ function apolloServerHook (apolloServer) {
77
+ shimmer.wrap(apolloServer.ApolloServer.prototype, 'executeHTTPGraphQLRequest', wrapExecuteHTTPGraphQLRequest)
78
+ return apolloServer
79
+ }
80
+
81
+ addHook({ name: '@apollo/server', file: 'dist/cjs/ApolloServer.js', versions: ['>=4.0.0'] }, apolloServerHook)
82
+ addHook({ name: '@apollo/server', file: 'dist/cjs/express4/index.js', versions: ['>=4.0.0'] }, apolloExpress4Hook)
83
+ addHook({ name: '@apollo/server', file: 'dist/cjs/utils/HeaderMap.js', versions: ['>=4.0.0'] }, apolloHeaderMapHook)
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { AbortController } = require('node-abort-controller')
4
+
3
5
  const {
4
6
  addHook,
5
7
  channel,
@@ -37,6 +39,13 @@ const validateStartCh = channel('apm:graphql:validate:start')
37
39
  const validateFinishCh = channel('apm:graphql:validate:finish')
38
40
  const validateErrorCh = channel('apm:graphql:validate:error')
39
41
 
42
+ class AbortError extends Error {
43
+ constructor (message) {
44
+ super(message)
45
+ this.name = 'AbortError'
46
+ }
47
+ }
48
+
40
49
  function getOperation (document, operationName) {
41
50
  if (!document || !Array.isArray(document.definitions)) {
42
51
  return
@@ -175,11 +184,11 @@ function wrapExecute (execute) {
175
184
  docSource: documentSources.get(document)
176
185
  })
177
186
 
178
- const context = { source, asyncResource, fields: {} }
187
+ const context = { source, asyncResource, fields: {}, abortController: new AbortController() }
179
188
 
180
189
  contexts.set(contextValue, context)
181
190
 
182
- return callInAsyncScope(exe, asyncResource, this, arguments, (err, res) => {
191
+ return callInAsyncScope(exe, asyncResource, this, arguments, context.abortController, (err, res) => {
183
192
  if (finishResolveCh.hasSubscribers) finishResolvers(context)
184
193
 
185
194
  const error = err || (res && res.errors && res.errors[0])
@@ -207,7 +216,7 @@ function wrapResolve (resolve) {
207
216
 
208
217
  const field = assertField(context, info, args)
209
218
 
210
- return callInAsyncScope(resolve, field.asyncResource, this, arguments, (err) => {
219
+ return callInAsyncScope(resolve, field.asyncResource, this, arguments, context.abortController, (err) => {
211
220
  updateFieldCh.publish({ field, info, err })
212
221
  })
213
222
  }
@@ -217,10 +226,15 @@ function wrapResolve (resolve) {
217
226
  return resolveAsync
218
227
  }
219
228
 
220
- function callInAsyncScope (fn, aR, thisArg, args, cb) {
229
+ function callInAsyncScope (fn, aR, thisArg, args, abortController, cb) {
221
230
  cb = cb || (() => {})
222
231
 
223
232
  return aR.runInAsyncScope(() => {
233
+ if (abortController?.signal.aborted) {
234
+ cb(null, null)
235
+ throw new AbortError('Aborted')
236
+ }
237
+
224
238
  try {
225
239
  const result = fn.apply(thisArg, args)
226
240
  if (result && typeof result.then === 'function') {
@@ -1,6 +1,8 @@
1
1
  'use strict'
2
2
 
3
3
  module.exports = {
4
+ '@apollo/server': () => require('../apollo-server'),
5
+ 'apollo-server-core': () => require('../apollo-server-core'),
4
6
  '@aws-sdk/smithy-client': () => require('../aws-sdk'),
5
7
  '@cucumber/cucumber': () => require('../cucumber'),
6
8
  '@playwright/test': () => require('../playwright'),
@@ -20,6 +22,7 @@ module.exports = {
20
22
  '@opentelemetry/sdk-trace-node': () => require('../otel-sdk-trace'),
21
23
  '@redis/client': () => require('../redis'),
22
24
  '@smithy/smithy-client': () => require('../aws-sdk'),
25
+ 'aerospike': () => require('../aerospike'),
23
26
  'amqp10': () => require('../amqp10'),
24
27
  'amqplib': () => require('../amqplib'),
25
28
  'aws-sdk': () => require('../aws-sdk'),
@@ -58,6 +58,7 @@ function patch (http, methodName) {
58
58
  }
59
59
 
60
60
  const options = args.options
61
+
61
62
  const finish = () => {
62
63
  if (!finished) {
63
64
  finished = true
@@ -68,9 +69,17 @@ function patch (http, methodName) {
68
69
  try {
69
70
  const req = request.call(this, options, callback)
70
71
  const emit = req.emit
72
+ const setTimeout = req.setTimeout
71
73
 
72
74
  ctx.req = req
73
75
 
76
+ // tracked to accurately discern custom request socket timeout
77
+ let customRequestTimeout = false
78
+ req.setTimeout = function () {
79
+ customRequestTimeout = true
80
+ return setTimeout.apply(this, arguments)
81
+ }
82
+
74
83
  req.emit = function (eventName, arg) {
75
84
  switch (eventName) {
76
85
  case 'response': {
@@ -88,6 +97,7 @@ function patch (http, methodName) {
88
97
  case 'error':
89
98
  case 'timeout':
90
99
  ctx.error = arg
100
+ ctx.customRequestTimeout = customRequestTimeout
91
101
  errorChannel.publish(ctx)
92
102
  case 'abort': // deprecated and replaced by `close` in node 17
93
103
  case 'close':
@@ -44,6 +44,7 @@ const itrSkippedSuitesCh = channel('ci:jest:itr:skipped-suites')
44
44
  let skippableSuites = []
45
45
  let isCodeCoverageEnabled = false
46
46
  let isSuitesSkippingEnabled = false
47
+ let isUserCodeCoverageEnabled = false
47
48
  let isSuitesSkipped = false
48
49
  let numSkippedSuites = 0
49
50
  let hasUnskippableSuites = false
@@ -289,11 +290,14 @@ function cliWrapper (cli, jestVersion) {
289
290
  } = result
290
291
 
291
292
  let testCodeCoverageLinesTotal
292
- try {
293
- const { pct, total } = coverageMap.getCoverageSummary().lines
294
- testCodeCoverageLinesTotal = total !== 0 ? pct : 0
295
- } catch (e) {
296
- // ignore errors
293
+
294
+ if (isUserCodeCoverageEnabled) {
295
+ try {
296
+ const { pct, total } = coverageMap.getCoverageSummary().lines
297
+ testCodeCoverageLinesTotal = total !== 0 ? pct : 0
298
+ } catch (e) {
299
+ // ignore errors
300
+ }
297
301
  }
298
302
  let status, error
299
303
 
@@ -436,6 +440,8 @@ function configureTestEnvironment (readConfigsResult) {
436
440
  config.testEnvironmentOptions._ddTestCodeCoverageEnabled = isCodeCoverageEnabled
437
441
  })
438
442
 
443
+ isUserCodeCoverageEnabled = !!readConfigsResult.globalConfig.collectCoverage
444
+
439
445
  if (isCodeCoverageEnabled) {
440
446
  const globalConfig = {
441
447
  ...readConfigsResult.globalConfig,
@@ -8,13 +8,31 @@ const {
8
8
  const shimmer = require('../../datadog-shimmer')
9
9
 
10
10
  const producerStartCh = channel('apm:kafkajs:produce:start')
11
+ const producerCommitCh = channel('apm:kafkajs:produce:commit')
11
12
  const producerFinishCh = channel('apm:kafkajs:produce:finish')
12
13
  const producerErrorCh = channel('apm:kafkajs:produce:error')
13
14
 
14
15
  const consumerStartCh = channel('apm:kafkajs:consume:start')
16
+ const consumerCommitCh = channel('apm:kafkajs:consume:commit')
15
17
  const consumerFinishCh = channel('apm:kafkajs:consume:finish')
16
18
  const consumerErrorCh = channel('apm:kafkajs:consume:error')
17
19
 
20
+ function commitsFromEvent (event) {
21
+ const { payload: { groupId, topics } } = event
22
+ const commitList = []
23
+ for (const { topic, partitions } of topics) {
24
+ for (const { partition, offset } of partitions) {
25
+ commitList.push({
26
+ groupId,
27
+ partition,
28
+ offset,
29
+ topic
30
+ })
31
+ }
32
+ }
33
+ consumerCommitCh.publish(commitList)
34
+ }
35
+
18
36
  addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKafka) => {
19
37
  class Kafka extends BaseKafka {
20
38
  constructor (options) {
@@ -58,6 +76,12 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
58
76
  })
59
77
  )
60
78
 
79
+ result.then(res => {
80
+ if (producerCommitCh.hasSubscribers) {
81
+ producerCommitCh.publish(res)
82
+ }
83
+ })
84
+
61
85
  return result
62
86
  } catch (e) {
63
87
  producerErrorCh.publish(e)
@@ -75,6 +99,9 @@ addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKaf
75
99
  }
76
100
 
77
101
  const consumer = createConsumer.apply(this, arguments)
102
+
103
+ consumer.on(consumer.events.COMMIT_OFFSETS, commitsFromEvent)
104
+
78
105
  const run = consumer.run
79
106
 
80
107
  const groupId = arguments[0].groupId
@@ -65,7 +65,7 @@ function wrapRenderToHTML (renderToHTML) {
65
65
 
66
66
  function wrapRenderErrorToHTML (renderErrorToHTML) {
67
67
  return function (err, req, res, pathname, query) {
68
- return instrument(req, res, () => renderErrorToHTML.apply(this, arguments))
68
+ return instrument(req, res, err, () => renderErrorToHTML.apply(this, arguments))
69
69
  }
70
70
  }
71
71
 
@@ -76,8 +76,8 @@ function wrapRenderToResponse (renderToResponse) {
76
76
  }
77
77
 
78
78
  function wrapRenderErrorToResponse (renderErrorToResponse) {
79
- return function (ctx) {
80
- return instrument(ctx.req, ctx.res, () => renderErrorToResponse.apply(this, arguments))
79
+ return function (ctx, err) {
80
+ return instrument(ctx.req, ctx.res, err, () => renderErrorToResponse.apply(this, arguments))
81
81
  }
82
82
  }
83
83
 
@@ -111,13 +111,23 @@ function getPageFromPath (page, dynamicRoutes = []) {
111
111
  return getPagePath(page)
112
112
  }
113
113
 
114
- function instrument (req, res, handler) {
114
+ function instrument (req, res, error, handler) {
115
+ if (typeof error === 'function') {
116
+ handler = error
117
+ error = null
118
+ }
119
+
115
120
  req = req.originalRequest || req
116
121
  res = res.originalResponse || res
117
122
 
118
123
  // TODO support middleware properly in the future?
119
124
  const isMiddleware = req.headers[MIDDLEWARE_HEADER]
120
- if (isMiddleware || requests.has(req)) return handler()
125
+ if (isMiddleware || requests.has(req)) {
126
+ if (error) {
127
+ errorChannel.publish({ error })
128
+ }
129
+ return handler()
130
+ }
121
131
 
122
132
  requests.add(req)
123
133
 
@@ -144,7 +154,9 @@ function instrument (req, res, handler) {
144
154
  function wrapServeStatic (serveStatic) {
145
155
  return function (req, res, path) {
146
156
  return instrument(req, res, () => {
147
- if (pageLoadChannel.hasSubscribers && path) pageLoadChannel.publish({ page: path })
157
+ if (pageLoadChannel.hasSubscribers && path) {
158
+ pageLoadChannel.publish({ page: path, isStatic: true })
159
+ }
148
160
 
149
161
  return serveStatic.apply(this, arguments)
150
162
  })
@@ -55,7 +55,7 @@ function wrapFn (fn) {
55
55
  return result.then(function () {
56
56
  nextChannel.publish({ req })
57
57
  finishChannel.publish({ req })
58
- return arguments
58
+ return arguments[0]
59
59
  }).catch(function (error) {
60
60
  errorChannel.publish({ req, error })
61
61
  nextChannel.publish({ req })
@@ -22,7 +22,7 @@ const dispatchReceiveCh = channel('apm:rhea:receive:dispatch')
22
22
  const errorReceiveCh = channel('apm:rhea:receive:error')
23
23
  const finishReceiveCh = channel('apm:rhea:receive:finish')
24
24
 
25
- const contexts = new WeakMap()
25
+ const contexts = new WeakMap() // key: delivery Fn, val: context
26
26
 
27
27
  addHook({ name: 'rhea', versions: ['>=1'] }, rhea => {
28
28
  shimmer.wrap(rhea.message, 'encode', encode => function (msg) {
@@ -52,7 +52,8 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/link.js' }, obj => {
52
52
  startSendCh.publish({ targetAddress, host, port, msg })
53
53
  const delivery = send.apply(this, arguments)
54
54
  const context = {
55
- asyncResource
55
+ asyncResource,
56
+ connection: this.connection
56
57
  }
57
58
  contexts.set(delivery, context)
58
59
 
@@ -80,7 +81,8 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/link.js' }, obj => {
80
81
 
81
82
  if (msgObj.delivery) {
82
83
  const context = {
83
- asyncResource
84
+ asyncResource,
85
+ connection: this.connection
84
86
  }
85
87
  contexts.set(msgObj.delivery, context)
86
88
  msgObj.delivery.update = wrapDeliveryUpdate(msgObj.delivery, msgObj.delivery.update)
@@ -114,7 +116,7 @@ addHook({ name: 'rhea', versions: ['>=1'], file: 'lib/connection.js' }, Connecti
114
116
 
115
117
  asyncResource.runInAsyncScope(() => {
116
118
  errorReceiveCh.publish(error)
117
- beforeFinish(delivery, null)
119
+ exports.beforeFinish(delivery, null)
118
120
  finishReceiveCh.publish()
119
121
  })
120
122
  })
@@ -187,7 +189,7 @@ function patchCircularBuffer (proto, Session) {
187
189
  const state = remoteState && remoteState.constructor
188
190
  ? entry.remote_state.constructor.composite_type : undefined
189
191
  asyncResource.runInAsyncScope(() => {
190
- beforeFinish(entry, state)
192
+ exports.beforeFinish(entry, state)
191
193
  finishSendCh.publish()
192
194
  })
193
195
  }
@@ -217,13 +219,13 @@ function addToInFlightDeliveries (connection, delivery) {
217
219
  }
218
220
 
219
221
  function beforeFinish (delivery, state) {
220
- const obj = contexts.get(delivery)
221
- if (obj) {
222
+ const context = contexts.get(delivery)
223
+ if (context) {
222
224
  if (state) {
223
225
  dispatchReceiveCh.publish({ state })
224
226
  }
225
- if (obj.connection && obj.connection[inFlightDeliveries]) {
226
- obj.connection[inFlightDeliveries].delete(delivery)
227
+ if (context.connection && context.connection[inFlightDeliveries]) {
228
+ context.connection[inFlightDeliveries].delete(delivery)
227
229
  }
228
230
  }
229
231
  }
@@ -238,3 +240,7 @@ function getStateFromData (stateData) {
238
240
  }
239
241
  }
240
242
  }
243
+
244
+ module.exports.inFlightDeliveries = inFlightDeliveries
245
+ module.exports.beforeFinish = beforeFinish
246
+ module.exports.contexts = contexts