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.
- package/LICENSE-3rdparty.csv +2 -0
- package/index.d.ts +274 -0
- package/package.json +4 -2
- package/packages/datadog-plugin-cypress/src/plugin.js +109 -47
- package/packages/datadog-plugin-cypress/src/support.js +3 -2
- package/packages/dd-trace/src/appsec/iast/analyzers/weak-hash-analyzer.js +24 -0
- package/packages/dd-trace/src/opentelemetry/context_manager.js +74 -0
- package/packages/dd-trace/src/opentelemetry/sampler.js +18 -0
- package/packages/dd-trace/src/opentelemetry/span.js +151 -0
- package/packages/dd-trace/src/opentelemetry/span_context.js +44 -0
- package/packages/dd-trace/src/opentelemetry/span_processor.js +50 -0
- package/packages/dd-trace/src/opentelemetry/tracer.js +124 -0
- package/packages/dd-trace/src/opentelemetry/tracer_provider.js +72 -0
- package/packages/dd-trace/src/opentracing/span.js +14 -4
- package/packages/dd-trace/src/proxy.js +4 -0
package/LICENSE-3rdparty.csv
CHANGED
|
@@ -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.
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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 (
|
|
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
|
-
|
|
171
|
+
startTime = dateNow()
|
|
164
172
|
}
|
|
165
173
|
} else {
|
|
166
174
|
const spanId = id()
|
|
167
|
-
|
|
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
|
}
|