dd-trace 4.1.1 → 4.2.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.
@@ -5,6 +5,8 @@ require,@datadog/native-iast-rewriter,Apache license 2.0,Copyright 2018 Datadog
5
5
  require,@datadog/native-iast-taint-tracking,Apache license 2.0,Copyright 2018 Datadog Inc.
6
6
  require,@datadog/pprof,Apache license 2.0,Copyright 2019 Google Inc.
7
7
  require,@datadog/sketches-js,Apache license 2.0,Copyright 2020 Datadog Inc.
8
+ require,@opentelemetry/api,Apache license 2.0,Copyright OpenTelemetry Authors
9
+ require,@opentelemetry/core,Apache license 2.0,Copyright OpenTelemetry Authors
8
10
  require,crypto-randomuuid,MIT,Copyright 2021 Node.js Foundation and contributors
9
11
  require,diagnostics_channel,MIT,Copyright 2021 Simon D.
10
12
  require,ignore,MIT,Copyright 2013 Kael Zhang and contributors
package/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { ClientRequest, IncomingMessage, OutgoingMessage, ServerResponse } from
2
2
  import { LookupFunction } from 'net';
3
3
  import * as opentracing from "opentracing";
4
4
  import { SpanOptions } from "opentracing/lib/tracer";
5
+ import * as otel from "@opentelemetry/api";
5
6
 
6
7
  export { SpanOptions };
7
8
 
@@ -118,6 +119,8 @@ export declare interface Tracer extends opentracing.Tracer {
118
119
  setUser (user: User): Tracer;
119
120
 
120
121
  appsec: Appsec;
122
+
123
+ TracerProvider: opentelemetry.TracerProvider;
121
124
  }
122
125
 
123
126
  export declare interface TraceOptions extends Analyzable {
@@ -1579,6 +1582,277 @@ declare namespace plugins {
1579
1582
  interface winston extends Integration {}
1580
1583
  }
1581
1584
 
1585
+ export namespace opentelemetry {
1586
+ /**
1587
+ * A registry for creating named {@link Tracer}s.
1588
+ */
1589
+ export interface TracerProvider extends otel.TracerProvider {
1590
+ /**
1591
+ * Construct a new TracerProvider to register with @opentelemetry/api
1592
+ *
1593
+ * @returns TracerProvider A TracerProvider instance
1594
+ */
1595
+ new(): TracerProvider;
1596
+
1597
+ /**
1598
+ * Returns a Tracer, creating one if one with the given name and version is
1599
+ * not already created.
1600
+ *
1601
+ * This function may return different Tracer types (e.g.
1602
+ * {@link NoopTracerProvider} vs. a functional tracer).
1603
+ *
1604
+ * @param name The name of the tracer or instrumentation library.
1605
+ * @param version The version of the tracer or instrumentation library.
1606
+ * @param options The options of the tracer or instrumentation library.
1607
+ * @returns Tracer A Tracer with the given name and version
1608
+ */
1609
+ getTracer(name: string, version?: string): Tracer;
1610
+
1611
+ /**
1612
+ * Register this tracer provider with @opentelemetry/api
1613
+ */
1614
+ register(): void;
1615
+ }
1616
+
1617
+ /**
1618
+ * Tracer provides an interface for creating {@link Span}s.
1619
+ */
1620
+ export interface Tracer extends otel.Tracer {
1621
+ /**
1622
+ * Starts a new {@link Span}. Start the span without setting it on context.
1623
+ *
1624
+ * This method do NOT modify the current Context.
1625
+ *
1626
+ * @param name The name of the span
1627
+ * @param [options] SpanOptions used for span creation
1628
+ * @param [context] Context to use to extract parent
1629
+ * @returns Span The newly created span
1630
+ * @example
1631
+ * const span = tracer.startSpan('op');
1632
+ * span.setAttribute('key', 'value');
1633
+ * span.end();
1634
+ */
1635
+ startSpan(name: string, options?: SpanOptions, context?: Context): Span;
1636
+
1637
+ /**
1638
+ * Starts a new {@link Span} and calls the given function passing it the
1639
+ * created span as first argument.
1640
+ * Additionally the new span gets set in context and this context is activated
1641
+ * for the duration of the function call.
1642
+ *
1643
+ * @param name The name of the span
1644
+ * @param [options] SpanOptions used for span creation
1645
+ * @param [context] Context to use to extract parent
1646
+ * @param fn function called in the context of the span and receives the newly created span as an argument
1647
+ * @returns return value of fn
1648
+ * @example
1649
+ * const something = tracer.startActiveSpan('op', span => {
1650
+ * try {
1651
+ * do some work
1652
+ * span.setStatus({code: SpanStatusCode.OK});
1653
+ * return something;
1654
+ * } catch (err) {
1655
+ * span.setStatus({
1656
+ * code: SpanStatusCode.ERROR,
1657
+ * message: err.message,
1658
+ * });
1659
+ * throw err;
1660
+ * } finally {
1661
+ * span.end();
1662
+ * }
1663
+ * });
1664
+ *
1665
+ * @example
1666
+ * const span = tracer.startActiveSpan('op', span => {
1667
+ * try {
1668
+ * do some work
1669
+ * return span;
1670
+ * } catch (err) {
1671
+ * span.setStatus({
1672
+ * code: SpanStatusCode.ERROR,
1673
+ * message: err.message,
1674
+ * });
1675
+ * throw err;
1676
+ * }
1677
+ * });
1678
+ * do some more work
1679
+ * span.end();
1680
+ */
1681
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, fn: F): ReturnType<F>;
1682
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, options: SpanOptions, fn: F): ReturnType<F>;
1683
+ startActiveSpan<F extends (span: Span) => unknown>(name: string, options: SpanOptions, context: otel.Context, fn: F): ReturnType<F>;
1684
+ }
1685
+
1686
+ /**
1687
+ * An interface that represents a span. A span represents a single operation
1688
+ * within a trace. Examples of span might include remote procedure calls or a
1689
+ * in-process function calls to sub-components. A Trace has a single, top-level
1690
+ * "root" Span that in turn may have zero or more child Spans, which in turn
1691
+ * may have children.
1692
+ *
1693
+ * Spans are created by the {@link Tracer.startSpan} method.
1694
+ */
1695
+ export interface Span extends otel.Span {
1696
+ /**
1697
+ * Returns the {@link SpanContext} object associated with this Span.
1698
+ *
1699
+ * Get an immutable, serializable identifier for this span that can be used
1700
+ * to create new child spans. Returned SpanContext is usable even after the
1701
+ * span ends.
1702
+ *
1703
+ * @returns the SpanContext object associated with this Span.
1704
+ */
1705
+ spanContext(): SpanContext;
1706
+
1707
+ /**
1708
+ * Sets an attribute to the span.
1709
+ *
1710
+ * Sets a single Attribute with the key and value passed as arguments.
1711
+ *
1712
+ * @param key the key for this attribute.
1713
+ * @param value the value for this attribute. Setting a value null or
1714
+ * undefined is invalid and will result in undefined behavior.
1715
+ */
1716
+ setAttribute(key: string, value: SpanAttributeValue): this;
1717
+
1718
+ /**
1719
+ * Sets attributes to the span.
1720
+ *
1721
+ * @param attributes the attributes that will be added.
1722
+ * null or undefined attribute values
1723
+ * are invalid and will result in undefined behavior.
1724
+ */
1725
+ setAttributes(attributes: SpanAttributes): this;
1726
+
1727
+ /**
1728
+ * Adds an event to the Span.
1729
+ *
1730
+ * @param name the name of the event.
1731
+ * @param [attributesOrStartTime] the attributes that will be added; these are
1732
+ * associated with this event. Can be also a start time
1733
+ * if type is {@type TimeInput} and 3rd param is undefined
1734
+ * @param [startTime] start time of the event.
1735
+ */
1736
+ addEvent(name: string, attributesOrStartTime?: SpanAttributes | TimeInput, startTime?: TimeInput): this;
1737
+
1738
+ /**
1739
+ * Sets a status to the span. If used, this will override the default Span
1740
+ * status. Default is {@link SpanStatusCode.UNSET}. SetStatus overrides the value
1741
+ * of previous calls to SetStatus on the Span.
1742
+ *
1743
+ * @param status the SpanStatus to set.
1744
+ */
1745
+ setStatus(status: SpanStatus): this;
1746
+
1747
+ /**
1748
+ * Updates the Span name.
1749
+ *
1750
+ * This will override the name provided via {@link Tracer.startSpan}.
1751
+ *
1752
+ * Upon this update, any sampling behavior based on Span name will depend on
1753
+ * the implementation.
1754
+ *
1755
+ * @param name the Span name.
1756
+ */
1757
+ updateName(name: string): this;
1758
+
1759
+ /**
1760
+ * Marks the end of Span execution.
1761
+ *
1762
+ * Call to End of a Span MUST not have any effects on child spans. Those may
1763
+ * still be running and can be ended later.
1764
+ *
1765
+ * Do not return `this`. The Span generally should not be used after it
1766
+ * is ended so chaining is not desired in this context.
1767
+ *
1768
+ * @param [endTime] the time to set as Span's end time. If not provided,
1769
+ * use the current time as the span's end time.
1770
+ */
1771
+ end(endTime?: TimeInput): void;
1772
+
1773
+ /**
1774
+ * Returns the flag whether this span will be recorded.
1775
+ *
1776
+ * @returns true if this Span is active and recording information like events
1777
+ * with the `AddEvent` operation and attributes using `setAttributes`.
1778
+ */
1779
+ isRecording(): boolean;
1780
+
1781
+ /**
1782
+ * Sets exception as a span event
1783
+ * @param exception the exception the only accepted values are string or Error
1784
+ * @param [time] the time to set as Span's event time. If not provided,
1785
+ * use the current time.
1786
+ */
1787
+ recordException(exception: Exception, time?: TimeInput): void;
1788
+ }
1789
+
1790
+ /**
1791
+ * A SpanContext represents the portion of a {@link Span} which must be
1792
+ * serialized and propagated along side of a {@link Baggage}.
1793
+ */
1794
+ export interface SpanContext extends otel.SpanContext {
1795
+ /**
1796
+ * The ID of the trace that this span belongs to. It is worldwide unique
1797
+ * with practically sufficient probability by being made as 16 randomly
1798
+ * generated bytes, encoded as a 32 lowercase hex characters corresponding to
1799
+ * 128 bits.
1800
+ */
1801
+ traceId: string;
1802
+
1803
+ /**
1804
+ * The ID of the Span. It is globally unique with practically sufficient
1805
+ * probability by being made as 8 randomly generated bytes, encoded as a 16
1806
+ * lowercase hex characters corresponding to 64 bits.
1807
+ */
1808
+ spanId: string;
1809
+
1810
+ /**
1811
+ * Only true if the SpanContext was propagated from a remote parent.
1812
+ */
1813
+ isRemote?: boolean;
1814
+
1815
+ /**
1816
+ * Trace flags to propagate.
1817
+ *
1818
+ * It is represented as 1 byte (bitmap). Bit to represent whether trace is
1819
+ * sampled or not. When set, the least significant bit documents that the
1820
+ * caller may have recorded trace data. A caller who does not record trace
1821
+ * data out-of-band leaves this flag unset.
1822
+ *
1823
+ * see {@link TraceFlags} for valid flag values.
1824
+ */
1825
+ traceFlags: number;
1826
+
1827
+ /**
1828
+ * Tracing-system-specific info to propagate.
1829
+ *
1830
+ * The tracestate field value is a `list` as defined below. The `list` is a
1831
+ * series of `list-members` separated by commas `,`, and a list-member is a
1832
+ * key/value pair separated by an equals sign `=`. Spaces and horizontal tabs
1833
+ * surrounding `list-members` are ignored. There can be a maximum of 32
1834
+ * `list-members` in a `list`.
1835
+ * More Info: https://www.w3.org/TR/trace-context/#tracestate-field
1836
+ *
1837
+ * Examples:
1838
+ * Single tracing system (generic format):
1839
+ * tracestate: rojo=00f067aa0ba902b7
1840
+ * Multiple tracing systems (with different formatting):
1841
+ * tracestate: rojo=00f067aa0ba902b7,congo=t61rcWkgMzE
1842
+ */
1843
+ traceState?: TraceState;
1844
+ }
1845
+
1846
+ export type Context = otel.Context;
1847
+ export type Exception = otel.Exception;
1848
+ export type SpanAttributes = otel.SpanAttributes;
1849
+ export type SpanAttributeValue = otel.SpanAttributeValue;
1850
+ export type SpanOptions = otel.SpanOptions;
1851
+ export type SpanStatus = otel.SpanStatus;
1852
+ export type TimeInput = otel.TimeInput;
1853
+ export type TraceState = otel.TraceState;
1854
+ }
1855
+
1582
1856
  /**
1583
1857
  * Singleton returned by the module. It has to be initialized before it will
1584
1858
  * start tracing. If not initialized, or initialized and disabled, it will use
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dd-trace",
3
- "version": "4.1.1",
3
+ "version": "4.2.0",
4
4
  "description": "Datadog APM tracing client for JavaScript",
5
5
  "main": "index.js",
6
6
  "typings": "index.d.ts",
@@ -19,7 +19,7 @@
19
19
  "test:appsec:ci": "nyc --no-clean --include \"packages/dd-trace/src/appsec/**/*.js\" --exclude \"packages/dd-trace/test/appsec/**/*.plugin.spec.js\" -- npm run test:appsec",
20
20
  "test:appsec:plugins": "mocha --colors --exit -r \"packages/dd-trace/test/setup/mocha.js\" \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\"",
21
21
  "test:appsec:plugins:ci": "yarn services && nyc --no-clean --include \"packages/dd-trace/test/appsec/**/*.@($(echo $PLUGINS)).plugin.spec.js\" -- npm run test:appsec:plugins",
22
- "test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,encode,exporters,opentracing,plugins,telemetry}/**/*.spec.js\"",
22
+ "test:trace:core": "tap packages/dd-trace/test/*.spec.js \"packages/dd-trace/test/{ci-visibility,encode,exporters,opentelemetry,opentracing,plugins,telemetry}/**/*.spec.js\"",
23
23
  "test:trace:core:ci": "npm run test:trace:core -- --coverage --nyc-arg=--include=\"packages/dd-trace/src/**/*.js\"",
24
24
  "test:instrumentations": "mocha --colors -r 'packages/dd-trace/test/setup/mocha.js' 'packages/datadog-instrumentations/test/**/*.spec.js'",
25
25
  "test:instrumentations:ci": "nyc --no-clean --include 'packages/datadog-instrumentations/src/**/*.js' -- npm run test:instrumentations",
@@ -72,6 +72,8 @@
72
72
  "@datadog/native-metrics": "^2.0.0",
73
73
  "@datadog/pprof": "^2.2.1",
74
74
  "@datadog/sketches-js": "^2.1.0",
75
+ "@opentelemetry/api": "^1.0.0",
76
+ "@opentelemetry/core": "<1.4.0",
75
77
  "crypto-randomuuid": "^1.0.0",
76
78
  "diagnostics_channel": "^1.1.0",
77
79
  "ignore": "^5.2.0",
@@ -133,6 +133,8 @@ module.exports = (on, config) => {
133
133
  'git.branch': branch
134
134
  } = testEnvironmentMetadata
135
135
 
136
+ const finishedTestsByFile = {}
137
+
136
138
  const testConfiguration = {
137
139
  repositoryUrl,
138
140
  sha,
@@ -158,6 +160,44 @@ module.exports = (on, config) => {
158
160
  let isCodeCoverageEnabled = false
159
161
  let testsToSkip = []
160
162
 
163
+ function getTestSpan (testName, testSuite) {
164
+ const testSuiteTags = {
165
+ [TEST_COMMAND]: command,
166
+ [TEST_COMMAND]: command,
167
+ [TEST_MODULE]: TEST_FRAMEWORK_NAME
168
+ }
169
+ if (testSuiteSpan) {
170
+ testSuiteTags[TEST_SUITE_ID] = testSuiteSpan.context().toSpanId()
171
+ }
172
+ if (testSessionSpan && testModuleSpan) {
173
+ testSuiteTags[TEST_SESSION_ID] = testSessionSpan.context().toTraceId()
174
+ testSuiteTags[TEST_MODULE_ID] = testModuleSpan.context().toSpanId()
175
+ }
176
+
177
+ const {
178
+ childOf,
179
+ resource,
180
+ ...testSpanMetadata
181
+ } = getTestSpanMetadata(tracer, testName, testSuite, config)
182
+
183
+ const codeOwners = getCodeOwnersForFilename(testSuite, codeOwnersEntries)
184
+
185
+ if (codeOwners) {
186
+ testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
187
+ }
188
+
189
+ return tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
190
+ childOf,
191
+ tags: {
192
+ [COMPONENT]: TEST_FRAMEWORK_NAME,
193
+ [ORIGIN_KEY]: CI_APP_ORIGIN,
194
+ ...testSpanMetadata,
195
+ ...testEnvironmentMetadata,
196
+ ...testSuiteTags
197
+ }
198
+ })
199
+ }
200
+
161
201
  on('before:run', (details) => {
162
202
  return getItrConfig(tracer, testConfiguration).then(({ err, itrConfig }) => {
163
203
  if (err) {
@@ -203,6 +243,58 @@ module.exports = (on, config) => {
203
243
  })
204
244
  })
205
245
  })
246
+ on('after:spec', (spec, { tests, stats }) => {
247
+ const cypressTests = tests || []
248
+ const finishedTests = finishedTestsByFile[spec.relative] || []
249
+
250
+ // Get tests that didn't go through `dd:afterEach` and haven't been skipped by ITR
251
+ // and create a skipped test span for each of them
252
+ cypressTests.filter(({ title }) => {
253
+ const cypressTestName = title.join(' ')
254
+ const isSkippedByItr = testsToSkip.find(test =>
255
+ cypressTestName === test.name && spec.relative === test.suite
256
+ )
257
+ const isTestFinished = finishedTests.find(({ testName }) => cypressTestName === testName)
258
+
259
+ return !isSkippedByItr && !isTestFinished
260
+ }).forEach(({ title }) => {
261
+ const skippedTestSpan = getTestSpan(title.join(' '), spec.relative)
262
+ skippedTestSpan.setTag(TEST_STATUS, 'skip')
263
+ skippedTestSpan.finish()
264
+ })
265
+
266
+ // Make sure that reported test statuses are the same as Cypress reports.
267
+ // This is not always the case, such as when an `after` hook fails:
268
+ // Cypress will report the last run test as failed, but we don't know that yet at `dd:afterEach`
269
+ let latestError
270
+ finishedTests.forEach((finishedTest) => {
271
+ const cypressTest = cypressTests.find(test => test.title.join(' ') === finishedTest.testName)
272
+ if (!cypressTest) {
273
+ return
274
+ }
275
+ if (cypressTest.displayError) {
276
+ latestError = new Error(cypressTest.displayError)
277
+ }
278
+ const cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state]
279
+ // update test status
280
+ if (cypressTestStatus !== finishedTest.testStatus) {
281
+ finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
282
+ finishedTest.testSpan.setTag('error', latestError)
283
+ }
284
+ finishedTest.testSpan.finish(finishedTest.finishTime)
285
+ })
286
+
287
+ if (testSuiteSpan) {
288
+ const status = getSuiteStatus(stats)
289
+ testSuiteSpan.setTag(TEST_STATUS, status)
290
+
291
+ if (latestError) {
292
+ testSuiteSpan.setTag('error', latestError)
293
+ }
294
+ testSuiteSpan.finish()
295
+ testSuiteSpan = null
296
+ }
297
+ })
206
298
 
207
299
  on('after:run', (suiteStats) => {
208
300
  if (testSessionSpan && testModuleSpan) {
@@ -254,15 +346,6 @@ module.exports = (on, config) => {
254
346
  })
255
347
  return null
256
348
  },
257
- 'dd:testSuiteFinish': (stats) => {
258
- if (testSuiteSpan) {
259
- const status = getSuiteStatus(stats)
260
- testSuiteSpan.setTag(TEST_STATUS, status)
261
- testSuiteSpan.finish()
262
- testSuiteSpan = null
263
- }
264
- return null
265
- },
266
349
  'dd:beforeEach': (test) => {
267
350
  const { testName, testSuite } = test
268
351
  // skip test
@@ -272,47 +355,14 @@ module.exports = (on, config) => {
272
355
  return { shouldSkip: true }
273
356
  }
274
357
 
275
- const testSuiteTags = {
276
- [TEST_COMMAND]: command,
277
- [TEST_COMMAND]: command,
278
- [TEST_MODULE]: TEST_FRAMEWORK_NAME
279
- }
280
- if (testSuiteSpan) {
281
- testSuiteTags[TEST_SUITE_ID] = testSuiteSpan.context().toSpanId()
282
- }
283
- if (testSessionSpan && testModuleSpan) {
284
- testSuiteTags[TEST_SESSION_ID] = testSessionSpan.context().toTraceId()
285
- testSuiteTags[TEST_MODULE_ID] = testModuleSpan.context().toSpanId()
286
- }
287
-
288
- const {
289
- childOf,
290
- resource,
291
- ...testSpanMetadata
292
- } = getTestSpanMetadata(tracer, testName, testSuite, config)
293
-
294
- const codeOwners = getCodeOwnersForFilename(testSuite, codeOwnersEntries)
295
-
296
- if (codeOwners) {
297
- testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
298
- }
299
-
300
358
  if (!activeSpan) {
301
- activeSpan = tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
302
- childOf,
303
- tags: {
304
- [COMPONENT]: TEST_FRAMEWORK_NAME,
305
- [ORIGIN_KEY]: CI_APP_ORIGIN,
306
- ...testSpanMetadata,
307
- ...testEnvironmentMetadata,
308
- ...testSuiteTags
309
- }
310
- })
359
+ activeSpan = getTestSpan(testName, testSuite)
311
360
  }
361
+
312
362
  return activeSpan ? { traceId: activeSpan.context().toTraceId() } : {}
313
363
  },
314
364
  'dd:afterEach': ({ test, coverage }) => {
315
- const { state, error, isRUMActive, testSourceLine } = test
365
+ const { state, error, isRUMActive, testSourceLine, testSuite, testName } = test
316
366
  if (activeSpan) {
317
367
  if (coverage && tracer._tracer._exporter.exportCoverage && isCodeCoverageEnabled) {
318
368
  const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
@@ -326,8 +376,9 @@ module.exports = (on, config) => {
326
376
  }
327
377
  tracer._tracer._exporter.exportCoverage(formattedCoverage)
328
378
  }
379
+ const testStatus = CYPRESS_STATUS_TO_TEST_STATUS[state]
380
+ activeSpan.setTag(TEST_STATUS, testStatus)
329
381
 
330
- activeSpan.setTag(TEST_STATUS, CYPRESS_STATUS_TO_TEST_STATUS[state])
331
382
  if (error) {
332
383
  activeSpan.setTag('error', error)
333
384
  }
@@ -337,7 +388,18 @@ module.exports = (on, config) => {
337
388
  if (testSourceLine) {
338
389
  activeSpan.setTag(TEST_SOURCE_START, testSourceLine)
339
390
  }
340
- activeSpan.finish()
391
+ const finishedTest = {
392
+ testName,
393
+ testStatus,
394
+ finishTime: activeSpan._getTime(), // we store the finish time here
395
+ testSpan: activeSpan
396
+ }
397
+ if (finishedTestsByFile[testSuite]) {
398
+ finishedTestsByFile[testSuite].push(finishedTest)
399
+ } else {
400
+ finishedTestsByFile[testSuite] = [finishedTest]
401
+ }
402
+ // test spans are finished at after:spec
341
403
  }
342
404
  activeSpan = null
343
405
  return null
@@ -16,9 +16,10 @@ before(() => {
16
16
  })
17
17
 
18
18
  after(() => {
19
- cy.task('dd:testSuiteFinish', Cypress.mocha.getRunner().stats)
20
19
  cy.window().then(win => {
21
- win.dispatchEvent(new Event('beforeunload'))
20
+ if (win.DD_RUM) {
21
+ win.dispatchEvent(new Event('beforeunload'))
22
+ }
22
23
  })
23
24
  })
24
25
 
@@ -1,4 +1,7 @@
1
1
  'use strict'
2
+
3
+ const path = require('path')
4
+
2
5
  const Analyzer = require('./vulnerability-analyzer')
3
6
  const { WEAK_HASH } = require('../vulnerabilities')
4
7
 
@@ -8,6 +11,17 @@ const INSECURE_HASH_ALGORITHMS = new Set([
8
11
  'RSA-SHA1', 'RSA-SHA1-2', 'sha1', 'md5-sha1', 'sha1WithRSAEncryption', 'ssl3-sha1'
9
12
  ].map(algorithm => algorithm.toLowerCase()))
10
13
 
14
+ const EXCLUDED_LOCATIONS = [
15
+ path.join('node_modules', 'etag', 'index.js'),
16
+ path.join('node_modules', 'redlock', 'dist', 'cjs'),
17
+ path.join('node_modules', 'ws', 'lib', 'websocket-server.js'),
18
+ path.join('node_modules', 'mysql2', 'lib', 'auth_41.js'),
19
+ path.join('node_modules', '@mikro-orm', 'core', 'utils', 'Utils.js')
20
+ ]
21
+
22
+ const EXCLUDED_PATHS_FROM_STACK = [
23
+ path.join('node_modules', 'object-hash', path.sep)
24
+ ]
11
25
  class WeakHashAnalyzer extends Analyzer {
12
26
  constructor () {
13
27
  super(WEAK_HASH)
@@ -20,6 +34,16 @@ class WeakHashAnalyzer extends Analyzer {
20
34
  }
21
35
  return false
22
36
  }
37
+
38
+ _isExcluded (location) {
39
+ return EXCLUDED_LOCATIONS.some(excludedLocation => {
40
+ return location.path.includes(excludedLocation)
41
+ })
42
+ }
43
+
44
+ _getExcludedPaths () {
45
+ return EXCLUDED_PATHS_FROM_STACK
46
+ }
23
47
  }
24
48
 
25
49
  module.exports = new WeakHashAnalyzer()
@@ -0,0 +1,74 @@
1
+ 'use strict'
2
+
3
+ const { AsyncLocalStorage } = require('async_hooks')
4
+ const { trace, ROOT_CONTEXT } = require('@opentelemetry/api')
5
+
6
+ const SpanContext = require('./span_context')
7
+ const tracer = require('../../')
8
+
9
+ // Horrible hack to acquire the otherwise inaccessible SPAN_KEY so we can redirect it...
10
+ // This is used for getting the current span context in OpenTelemetry, but the SPAN_KEY value is
11
+ // not exposed as it's meant to be read-only from outside the module. We want to hijack this logic
12
+ // so we can instead get the span context from the datadog context manager instead.
13
+ let SPAN_KEY
14
+ trace.getSpan({
15
+ getValue (key) {
16
+ SPAN_KEY = key
17
+ }
18
+ })
19
+
20
+ // Whenever a value is acquired from the context map we should mostly delegate to the real getter,
21
+ // but when accessing the current span we should hijack that access to instead provide a fake span
22
+ // which we can use to get an OTel span context wrapping the datadog active scope span context.
23
+ function wrappedGetValue (target) {
24
+ return (key) => {
25
+ if (key === SPAN_KEY) {
26
+ return {
27
+ spanContext () {
28
+ const activeSpan = tracer.scope().active()
29
+ const context = activeSpan && activeSpan.context()
30
+ return new SpanContext(context)
31
+ }
32
+ }
33
+ }
34
+ return target.getValue(key)
35
+ }
36
+ }
37
+
38
+ class ContextManager {
39
+ constructor () {
40
+ this._store = new AsyncLocalStorage()
41
+ }
42
+
43
+ active () {
44
+ const active = this._store.getStore() || ROOT_CONTEXT
45
+
46
+ return new Proxy(active, {
47
+ get (target, key) {
48
+ return key === 'getValue' ? wrappedGetValue(target) : target[key]
49
+ }
50
+ })
51
+ }
52
+
53
+ with (context, fn, thisArg, ...args) {
54
+ const span = trace.getSpan(context)
55
+ const ddScope = tracer.scope()
56
+ return ddScope.activate(span._ddSpan, () => {
57
+ const cb = thisArg == null ? fn : fn.bind(thisArg)
58
+ return this._store.run(context, cb, ...args)
59
+ })
60
+ }
61
+
62
+ bind (context, target) {
63
+ const self = this
64
+ return function (...args) {
65
+ return self.with(context, target, this, args)
66
+ }
67
+ }
68
+
69
+ // Not part of the spec but the Node.js API expects these
70
+ enable () {}
71
+ disable () {}
72
+ }
73
+
74
+ module.exports = ContextManager
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ // This isn't used yet. We currently delegate to dd-trace core for sampling decisions.
4
+ // Leaving here for future use.
5
+ class Sampler {
6
+ shouldSample (context, traceId, spanName, spanKind, attributes, links) {
7
+ // 0 = no, 1 = record, 2 = record and sample
8
+ // TODO: Make this actually do sampling...
9
+ return { decision: 2 }
10
+ }
11
+
12
+ /** Returns the sampler name or short description with the configuration. */
13
+ toString () {
14
+ return 'DatadogSampler'
15
+ }
16
+ }
17
+
18
+ module.exports = Sampler
@@ -0,0 +1,151 @@
1
+ 'use strict'
2
+
3
+ const api = require('@opentelemetry/api')
4
+
5
+ const { performance } = require('perf_hooks')
6
+ const { timeOrigin } = performance
7
+
8
+ const { timeInputToHrTime } = require('@opentelemetry/core')
9
+
10
+ const tracer = require('../../')
11
+ const DatadogSpan = require('../opentracing/span')
12
+ const { ERROR_MESSAGE, ERROR_TYPE, ERROR_STACK } = require('../constants')
13
+
14
+ const SpanContext = require('./span_context')
15
+
16
+ // The one built into OTel rounds so we lose sub-millisecond precision.
17
+ function hrTimeToMilliseconds (time) {
18
+ return time[0] * 1e3 + time[1] / 1e6
19
+ }
20
+
21
+ class Span {
22
+ constructor (
23
+ parentTracer,
24
+ context,
25
+ spanName,
26
+ spanContext,
27
+ kind,
28
+ links = [],
29
+ timeInput
30
+ ) {
31
+ const { _tracer } = tracer
32
+
33
+ const hrStartTime = timeInputToHrTime(timeInput || (performance.now() + timeOrigin))
34
+ const startTime = hrTimeToMilliseconds(hrStartTime)
35
+
36
+ this._ddSpan = new DatadogSpan(_tracer, _tracer._processor, _tracer._prioritySampler, {
37
+ operationName: spanName,
38
+ context: spanContext._ddContext,
39
+ startTime,
40
+ hostname: _tracer._hostname,
41
+ tags: {
42
+ 'service.name': _tracer._service
43
+ }
44
+ }, _tracer._debug)
45
+
46
+ this._parentTracer = parentTracer
47
+ this._context = context
48
+
49
+ this._hasStatus = false
50
+
51
+ // NOTE: Need to grab the value before setting it on the span because the
52
+ // math for computing opentracing timestamps is apparently lossy...
53
+ this.startTime = hrStartTime
54
+ this.kind = kind
55
+ this.links = links
56
+ this._spanProcessor.onStart(this, context)
57
+ }
58
+
59
+ get parentSpanId () {
60
+ const { _parentId } = this._ddSpan.context()
61
+ return _parentId && _parentId.toString(16)
62
+ }
63
+
64
+ // Expected by OTel
65
+ get resource () {
66
+ return this._parentTracer.resource
67
+ }
68
+ get instrumentationLibrary () {
69
+ return this._parentTracer.instrumentationLibrary
70
+ }
71
+ get _spanProcessor () {
72
+ return this._parentTracer.getActiveSpanProcessor()
73
+ }
74
+
75
+ get name () {
76
+ return this._ddSpan.context()._name
77
+ }
78
+
79
+ spanContext () {
80
+ return new SpanContext(this._ddSpan.context())
81
+ }
82
+
83
+ setAttribute (key, value) {
84
+ this._ddSpan.setTag(key, value)
85
+ return this
86
+ }
87
+
88
+ setAttributes (attributes) {
89
+ this._ddSpan.addTags(attributes)
90
+ return this
91
+ }
92
+
93
+ addEvent (name, attributesOrStartTime, startTime) {
94
+ api.diag.warn('Events not supported')
95
+ return this
96
+ }
97
+
98
+ setStatus ({ code, message }) {
99
+ if (!this.ended && !this._hasStatus && code) {
100
+ this._hasStatus = true
101
+ if (code === 2) {
102
+ this._ddSpan.addTags({
103
+ [ERROR_MESSAGE]: message
104
+ })
105
+ }
106
+ }
107
+ return this
108
+ }
109
+
110
+ updateName (name) {
111
+ if (!this.ended) {
112
+ this._ddSpan.setOperationName(name)
113
+ }
114
+ return this
115
+ }
116
+
117
+ end (timeInput) {
118
+ if (this.ended) {
119
+ api.diag.error('You can only call end() on a span once.')
120
+ return
121
+ }
122
+
123
+ const hrEndTime = timeInputToHrTime(timeInput || (performance.now() + timeOrigin))
124
+ const endTime = hrTimeToMilliseconds(hrEndTime)
125
+
126
+ this._ddSpan.finish(endTime)
127
+ this._spanProcessor.onEnd(this)
128
+ }
129
+
130
+ isRecording () {
131
+ return this.ended === false
132
+ }
133
+
134
+ recordException (exception) {
135
+ this._ddSpan.addTags({
136
+ [ERROR_TYPE]: exception.name,
137
+ [ERROR_MESSAGE]: exception.message,
138
+ [ERROR_STACK]: exception.stack
139
+ })
140
+ }
141
+
142
+ get duration () {
143
+ return this._ddSpan._duration
144
+ }
145
+
146
+ get ended () {
147
+ return typeof this.duration !== 'undefined'
148
+ }
149
+ }
150
+
151
+ module.exports = Span
@@ -0,0 +1,44 @@
1
+ 'use strict'
2
+
3
+ const api = require('@opentelemetry/api')
4
+ const { AUTO_KEEP } = require('../../../../ext/priority')
5
+ const DatadogSpanContext = require('../opentracing/span_context')
6
+ const id = require('../id')
7
+
8
+ function newContext () {
9
+ const spanId = id()
10
+ return new DatadogSpanContext({
11
+ traceId: spanId,
12
+ spanId
13
+ })
14
+ }
15
+
16
+ class SpanContext {
17
+ constructor (context) {
18
+ if (!(context instanceof DatadogSpanContext)) {
19
+ context = context
20
+ ? new DatadogSpanContext(context)
21
+ : newContext()
22
+ }
23
+ this._ddContext = context
24
+ }
25
+
26
+ get traceId () {
27
+ return this._ddContext._traceId.toString(16)
28
+ }
29
+
30
+ get spanId () {
31
+ return this._ddContext._spanId.toString(16)
32
+ }
33
+
34
+ get traceFlags () {
35
+ return this._ddContext._sampling.priority >= AUTO_KEEP ? 1 : 0
36
+ }
37
+
38
+ get traceState () {
39
+ const ts = this._ddContext._tracestate
40
+ return api.createTraceState(ts ? ts.toString() : '')
41
+ }
42
+ }
43
+
44
+ module.exports = SpanContext
@@ -0,0 +1,50 @@
1
+ 'use strict'
2
+
3
+ class NoopSpanProcessor {
4
+ forceFlush () {
5
+ return Promise.resolve()
6
+ }
7
+
8
+ onStart (span, context) { }
9
+ onEnd (span) { }
10
+
11
+ shutdown () {
12
+ return Promise.resolve()
13
+ }
14
+ }
15
+
16
+ class MultiSpanProcessor extends NoopSpanProcessor {
17
+ constructor (spanProcessors) {
18
+ super()
19
+ this._processors = spanProcessors
20
+ }
21
+
22
+ forceFlush () {
23
+ return Promise.all(
24
+ this._processors.map(p => p.forceFlush())
25
+ )
26
+ }
27
+
28
+ onStart (span, context) {
29
+ for (const processor of this._processors) {
30
+ processor.onStart(span, context)
31
+ }
32
+ }
33
+
34
+ onEnd (span) {
35
+ for (const processor of this._processors) {
36
+ processor.onEnd(span)
37
+ }
38
+ }
39
+
40
+ shutdown () {
41
+ return Promise.all(
42
+ this._processors.map(p => p.shutdown())
43
+ )
44
+ }
45
+ }
46
+
47
+ module.exports = {
48
+ MultiSpanProcessor,
49
+ NoopSpanProcessor
50
+ }
@@ -0,0 +1,124 @@
1
+ 'use strict'
2
+
3
+ const api = require('@opentelemetry/api')
4
+ const { sanitizeAttributes } = require('@opentelemetry/core')
5
+
6
+ const Sampler = require('./sampler')
7
+ const Span = require('./span')
8
+ const id = require('../id')
9
+ const SpanContext = require('./span_context')
10
+
11
+ class Tracer {
12
+ constructor (library, config, tracerProvider) {
13
+ this._sampler = new Sampler()
14
+ this._config = config
15
+ this._tracerProvider = tracerProvider
16
+ // Is there a reason this is public?
17
+ this.instrumentationLibrary = library
18
+ }
19
+
20
+ get resource () {
21
+ return this._tracerProvider.resource
22
+ }
23
+
24
+ startSpan (name, options = {}, context = api.context.active()) {
25
+ // remove span from context in case a root span is requested via options
26
+ if (options.root) {
27
+ context = api.trace.deleteSpan(context)
28
+ }
29
+ const parentSpan = api.trace.getSpan(context)
30
+ const parentSpanContext = parentSpan && parentSpan.spanContext()
31
+
32
+ let spanContext
33
+ // TODO: Need a way to get 128-bit trace IDs for the validity check API to work...
34
+ // if (parent && api.trace.isSpanContextValid(parent)) {
35
+ if (parentSpanContext && parentSpanContext.traceId) {
36
+ const parent = parentSpanContext._ddContext
37
+ spanContext = new SpanContext({
38
+ traceId: parent._traceId,
39
+ spanId: id(),
40
+ parentId: parent._spanId,
41
+ sampling: parent._sampling,
42
+ baggageItems: Object.assign({}, parent._baggageItems),
43
+ trace: parent._trace,
44
+ tracestate: parent._tracestate
45
+ })
46
+ } else {
47
+ spanContext = new SpanContext()
48
+ }
49
+
50
+ const spanKind = options.kind || api.SpanKind.INTERNAL
51
+ const links = (options.links || []).map(link => {
52
+ return {
53
+ context: link.context,
54
+ attributes: sanitizeAttributes(link.attributes)
55
+ }
56
+ })
57
+ const attributes = sanitizeAttributes(options.attributes)
58
+
59
+ // TODO: sampling API is not yet supported
60
+ // // make sampling decision
61
+ // const samplingResult = this._sampler.shouldSample(
62
+ // context,
63
+ // spanContext.traceId,
64
+ // name,
65
+ // spanKind,
66
+ // attributes,
67
+ // links
68
+ // )
69
+
70
+ // // Should use new span context
71
+ // spanContext._ddContext._sampling.priority =
72
+ // samplingResult.decision === api.SamplingDecision.RECORD_AND_SAMPLED
73
+ // ? AUTO_KEEP
74
+ // : AUTO_REJECT
75
+
76
+ // if (samplingResult.decision === api.SamplingDecision.NOT_RECORD) {
77
+ // api.diag.debug('Recording is off, propagating context in a non-recording span')
78
+ // return api.trace.wrapSpanContext(spanContext)
79
+ // }
80
+
81
+ const span = new Span(
82
+ this,
83
+ context,
84
+ name,
85
+ spanContext,
86
+ spanKind,
87
+ links,
88
+ options.startTime
89
+ )
90
+ // Set initial span attributes. The attributes object may have been mutated
91
+ // by the sampler, so we sanitize the merged attributes before setting them.
92
+ const initAttributes = sanitizeAttributes(
93
+ // Object.assign(attributes, samplingResult.attributes)
94
+ attributes
95
+ )
96
+ span.setAttributes(initAttributes)
97
+ return span
98
+ }
99
+
100
+ startActiveSpan (name, options, context, fn) {
101
+ if (arguments.length === 2) {
102
+ fn = options
103
+ context = undefined
104
+ options = undefined
105
+ } else if (arguments.length === 3) {
106
+ fn = context
107
+ context = undefined
108
+ } else if (arguments.length !== 4) {
109
+ return
110
+ }
111
+
112
+ const parentContext = context || api.context.active()
113
+ const span = this.startSpan(name, options, parentContext)
114
+ const contextWithSpanSet = api.trace.setSpan(parentContext, span)
115
+
116
+ return api.context.with(contextWithSpanSet, fn, undefined, span)
117
+ }
118
+
119
+ getActiveSpanProcessor () {
120
+ return this._tracerProvider.getActiveSpanProcessor()
121
+ }
122
+ }
123
+
124
+ module.exports = Tracer
@@ -0,0 +1,72 @@
1
+ 'use strict'
2
+
3
+ const { trace, context } = require('@opentelemetry/api')
4
+
5
+ const tracer = require('../../')
6
+
7
+ const ContextManager = require('./context_manager')
8
+ const { MultiSpanProcessor, NoopSpanProcessor } = require('./span_processor')
9
+ const Tracer = require('./tracer')
10
+
11
+ class TracerProvider {
12
+ constructor (config = {}) {
13
+ this.config = config
14
+ this.resource = config.resource
15
+
16
+ this._processors = []
17
+ this._tracers = new Map()
18
+ this._activeProcessor = new NoopSpanProcessor()
19
+ this._contextManager = new ContextManager()
20
+ }
21
+
22
+ getTracer (name = 'opentelemetry', version = '0.0.0', options) {
23
+ const key = `${name}@${version}`
24
+ if (!this._tracers.has(key)) {
25
+ this._tracers.set(key, new Tracer(
26
+ { ...options, name, version },
27
+ this.config,
28
+ this
29
+ ))
30
+ }
31
+ return this._tracers.get(key)
32
+ }
33
+
34
+ addSpanProcessor (spanProcessor) {
35
+ if (!this._processors.length) {
36
+ this._activeProcessor.shutdown()
37
+ }
38
+ this._processors.push(spanProcessor)
39
+ this._activeProcessor = new MultiSpanProcessor(
40
+ this._processors
41
+ )
42
+ }
43
+
44
+ getActiveSpanProcessor () {
45
+ return this._activeProcessor
46
+ }
47
+
48
+ // Not actually required by the SDK spec, but the official Node.js SDK does
49
+ // this and the docs reflect that so we should do this too for familiarity.
50
+ register (config = {}) {
51
+ context.setGlobalContextManager(this._contextManager)
52
+ if (!trace.setGlobalTracerProvider(this)) {
53
+ trace.getTracerProvider().setDelegate(this)
54
+ }
55
+ }
56
+
57
+ forceFlush () {
58
+ const exporter = tracer._tracer._exporter
59
+ if (!exporter) {
60
+ return Promise.reject(new Error('Not started'))
61
+ }
62
+
63
+ exporter._writer.flush()
64
+ return this._activeProcessor.forceFlush()
65
+ }
66
+
67
+ shutdown () {
68
+ return this._activeProcessor.shutdown()
69
+ }
70
+ }
71
+
72
+ module.exports = TracerProvider
@@ -44,6 +44,8 @@ class DatadogSpan {
44
44
  this._spanContext._tags = tags
45
45
  this._spanContext._hostname = hostname
46
46
 
47
+ this._spanContext._trace.started.push(this)
48
+
47
49
  this._startTime = fields.startTime || this._getTime()
48
50
 
49
51
  if (DD_TRACE_EXPERIMENTAL_SPAN_COUNTS && finishedRegistry) {
@@ -147,8 +149,14 @@ class DatadogSpan {
147
149
 
148
150
  _createContext (parent, fields) {
149
151
  let spanContext
152
+ let startTime
150
153
 
151
- if (parent) {
154
+ if (fields.context) {
155
+ spanContext = fields.context
156
+ if (!spanContext._trace.startTime) {
157
+ startTime = dateNow()
158
+ }
159
+ } else if (parent) {
152
160
  spanContext = new SpanContext({
153
161
  traceId: parent._traceId,
154
162
  spanId: id(),
@@ -160,11 +168,11 @@ class DatadogSpan {
160
168
  })
161
169
 
162
170
  if (!spanContext._trace.startTime) {
163
- spanContext._trace.startTime = dateNow()
171
+ startTime = dateNow()
164
172
  }
165
173
  } else {
166
174
  const spanId = id()
167
- const startTime = dateNow()
175
+ startTime = dateNow()
168
176
  spanContext = new SpanContext({
169
177
  traceId: spanId,
170
178
  spanId
@@ -178,8 +186,10 @@ class DatadogSpan {
178
186
  }
179
187
  }
180
188
 
181
- spanContext._trace.started.push(this)
182
189
  spanContext._trace.ticks = spanContext._trace.ticks || now()
190
+ if (startTime) {
191
+ spanContext._trace.startTime = startTime
192
+ }
183
193
 
184
194
  return spanContext
185
195
  }
@@ -76,6 +76,10 @@ class Tracer extends NoopProxy {
76
76
  this._pluginManager.configurePlugin(...arguments)
77
77
  return this
78
78
  }
79
+
80
+ get TracerProvider () {
81
+ return require('./opentelemetry/tracer_provider')
82
+ }
79
83
  }
80
84
 
81
85
  module.exports = Tracer