@dash0/sdk-web 0.13.5 → 0.14.1
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/README.md +21 -0
- package/dist/dash0.iife.js +1 -1
- package/dist/dash0.iife.js.map +1 -1
- package/dist/dash0.js +1 -1
- package/dist/dash0.js.map +1 -1
- package/dist/dash0.umd.cjs +1 -1
- package/dist/dash0.umd.cjs.map +1 -1
- package/dist/modules/api/init.js +19 -4
- package/dist/modules/api/init_test.js +86 -0
- package/dist/modules/attributes/url.js +22 -12
- package/dist/modules/attributes/url_test.js +326 -0
- package/dist/modules/types/options.js +1 -0
- package/dist/modules/utils/fn.js +3 -0
- package/dist/modules/vars.js +2 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/api/init.d.ts +1 -30
- package/dist/types/api/init_test.d.ts +1 -0
- package/dist/types/attributes/url.d.ts +10 -0
- package/dist/types/attributes/url_test.d.ts +1 -0
- package/dist/types/entrypoint/npm-package.d.ts +5 -1
- package/dist/types/types/options.d.ts +35 -0
- package/dist/types/utils/fn.d.ts +1 -0
- package/dist/types/vars.d.ts +10 -0
- package/package.json +1 -1
- package/src/api/init.ts +25 -52
- package/src/api/init_test.ts +108 -0
- package/src/attributes/url.ts +34 -12
- package/src/attributes/url_test.ts +417 -0
- package/src/entrypoint/npm-package.ts +8 -1
- package/src/types/options.ts +56 -0
- package/src/utils/fn.ts +4 -0
- package/src/vars.ts +13 -0
package/src/api/init.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { vars } from "../vars";
|
|
2
2
|
import {
|
|
3
3
|
DEPLOYMENT_ENVIRONMENT_NAME,
|
|
4
4
|
DEPLOYMENT_ID,
|
|
@@ -23,57 +23,12 @@ import {
|
|
|
23
23
|
import { trackSessions } from "./session";
|
|
24
24
|
import { startWebVitalsInstrumentation } from "../instrumentations/web-vitals";
|
|
25
25
|
import { startErrorInstrumentation } from "../instrumentations/errors";
|
|
26
|
-
import { addAttribute
|
|
26
|
+
import { addAttribute } from "../utils/otel";
|
|
27
27
|
import { instrumentFetch } from "../instrumentations/http/fetch";
|
|
28
28
|
import { startNavigationInstrumentation } from "../instrumentations/navigation";
|
|
29
29
|
import { merge } from "ts-deepmerge";
|
|
30
30
|
import { initializeTabId } from "../utils/tab-id";
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
export type InitOptions = {
|
|
34
|
-
serviceName: string;
|
|
35
|
-
serviceVersion?: string;
|
|
36
|
-
environment?: string;
|
|
37
|
-
deploymentName?: string;
|
|
38
|
-
deploymentId?: string;
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Additional attributes to include with transmitted signals
|
|
42
|
-
*/
|
|
43
|
-
additionalSignalAttributes?: Record<string, AttributeValueType | AnyValue>;
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* OTLP endpoints to which the generated telemetry should be sent to.
|
|
47
|
-
*/
|
|
48
|
-
endpoint: Endpoint | Endpoint[];
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* The session inactivity timeout. Session inactivity is the maximum
|
|
52
|
-
* allowed time to pass between two page loads before the session is considered
|
|
53
|
-
* to be expired. Also think of cache time-to-idle configuration options.
|
|
54
|
-
*/
|
|
55
|
-
sessionInactivityTimeoutMillis?: number;
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* The default session termination timeout. Session termination is the maximum
|
|
59
|
-
* allowed time to pass since session start before the session is considered
|
|
60
|
-
* to be expired. Also think of cache time-to-live configuration options.
|
|
61
|
-
*/
|
|
62
|
-
sessionTerminationTimeoutMillis?: number;
|
|
63
|
-
} & Partial<
|
|
64
|
-
Pick<
|
|
65
|
-
Vars,
|
|
66
|
-
| "ignoreUrls"
|
|
67
|
-
| "ignoreErrorMessages"
|
|
68
|
-
| "wrapEventHandlers"
|
|
69
|
-
| "wrapTimers"
|
|
70
|
-
| "propagateTraceHeadersCorsURLs"
|
|
71
|
-
| "maxWaitForResourceTimingsMillis"
|
|
72
|
-
| "maxToleranceForResourceTimingsMillis"
|
|
73
|
-
| "headersToCapture"
|
|
74
|
-
| "pageViewInstrumentation"
|
|
75
|
-
>
|
|
76
|
-
>;
|
|
31
|
+
import { InitOptions, InstrumentationName } from "../types/options";
|
|
77
32
|
|
|
78
33
|
let hasBeenInitialised: boolean = false;
|
|
79
34
|
|
|
@@ -112,6 +67,7 @@ export function init(opts: InitOptions) {
|
|
|
112
67
|
"maxWaitForResourceTimingsMillis",
|
|
113
68
|
"maxToleranceForResourceTimingsMillis",
|
|
114
69
|
"headersToCapture",
|
|
70
|
+
"urlAttributeScrubber",
|
|
115
71
|
"pageViewInstrumentation",
|
|
116
72
|
])
|
|
117
73
|
)
|
|
@@ -121,10 +77,19 @@ export function init(opts: InitOptions) {
|
|
|
121
77
|
initializeSignalAttributes(opts);
|
|
122
78
|
initializeTabId();
|
|
123
79
|
trackSessions(opts.sessionInactivityTimeoutMillis, opts.sessionTerminationTimeoutMillis);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
80
|
+
|
|
81
|
+
if (isInstrumentationEnabled("@dash0/navigation", opts)) {
|
|
82
|
+
startNavigationInstrumentation();
|
|
83
|
+
}
|
|
84
|
+
if (isInstrumentationEnabled("@dash0/web-vitals", opts)) {
|
|
85
|
+
startWebVitalsInstrumentation();
|
|
86
|
+
}
|
|
87
|
+
if (isInstrumentationEnabled("@dash0/error", opts)) {
|
|
88
|
+
startErrorInstrumentation();
|
|
89
|
+
}
|
|
90
|
+
if (isInstrumentationEnabled("@dash0/fetch", opts)) {
|
|
91
|
+
instrumentFetch();
|
|
92
|
+
}
|
|
128
93
|
|
|
129
94
|
hasBeenInitialised = true;
|
|
130
95
|
}
|
|
@@ -219,3 +184,11 @@ function detectDeploymentId(opts: InitOptions): string | undefined {
|
|
|
219
184
|
return undefined;
|
|
220
185
|
}
|
|
221
186
|
}
|
|
187
|
+
|
|
188
|
+
function isInstrumentationEnabled(name: InstrumentationName, opts: InitOptions): boolean {
|
|
189
|
+
const instrumentations = opts.enabledInstrumentations;
|
|
190
|
+
|
|
191
|
+
if (!instrumentations) return true;
|
|
192
|
+
|
|
193
|
+
return instrumentations.includes(name);
|
|
194
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { InitOptions, InstrumentationName } from "../types/options";
|
|
3
|
+
|
|
4
|
+
// Mock all the instrumentation modules
|
|
5
|
+
vi.mock("../instrumentations/web-vitals", () => ({
|
|
6
|
+
startWebVitalsInstrumentation: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
vi.mock("../instrumentations/errors", () => ({
|
|
10
|
+
startErrorInstrumentation: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock("../instrumentations/http/fetch", () => ({
|
|
14
|
+
instrumentFetch: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock("../instrumentations/navigation", () => ({
|
|
18
|
+
startNavigationInstrumentation: vi.fn(),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import { startWebVitalsInstrumentation } from "../instrumentations/web-vitals";
|
|
22
|
+
import { startErrorInstrumentation } from "../instrumentations/errors";
|
|
23
|
+
import { instrumentFetch } from "../instrumentations/http/fetch";
|
|
24
|
+
import { startNavigationInstrumentation } from "../instrumentations/navigation";
|
|
25
|
+
|
|
26
|
+
describe("init", () => {
|
|
27
|
+
const baseOptions: InitOptions = {
|
|
28
|
+
serviceName: "test-service",
|
|
29
|
+
endpoint: { url: "https://test-endpoint.com", authToken: "invalid" },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
vi.clearAllMocks();
|
|
34
|
+
// Reset the hasBeenInitialised flag by re-importing the module
|
|
35
|
+
vi.resetModules();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
vi.clearAllMocks();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("instrumentation enablement", () => {
|
|
43
|
+
it("should enable all instrumentations when enabledInstrumentations is undefined", async () => {
|
|
44
|
+
const { init } = await import("./init");
|
|
45
|
+
|
|
46
|
+
init({
|
|
47
|
+
...baseOptions,
|
|
48
|
+
enabledInstrumentations: undefined,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(startNavigationInstrumentation).toHaveBeenCalled();
|
|
52
|
+
expect(startWebVitalsInstrumentation).toHaveBeenCalled();
|
|
53
|
+
expect(startErrorInstrumentation).toHaveBeenCalled();
|
|
54
|
+
expect(instrumentFetch).toHaveBeenCalled();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const instrumentations: InstrumentationName[] = [
|
|
58
|
+
"@dash0/navigation",
|
|
59
|
+
"@dash0/web-vitals",
|
|
60
|
+
"@dash0/error",
|
|
61
|
+
"@dash0/fetch",
|
|
62
|
+
];
|
|
63
|
+
const instrumentationMocks = {
|
|
64
|
+
"@dash0/navigation": startNavigationInstrumentation,
|
|
65
|
+
"@dash0/web-vitals": startWebVitalsInstrumentation,
|
|
66
|
+
"@dash0/error": startErrorInstrumentation,
|
|
67
|
+
"@dash0/fetch": instrumentFetch,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
instrumentations.forEach((instrumentation) => {
|
|
71
|
+
it(`should enable ${instrumentation} instrumentation when present in enabledInstrumentations array`, async () => {
|
|
72
|
+
const { init } = await import("./init");
|
|
73
|
+
|
|
74
|
+
init({
|
|
75
|
+
...baseOptions,
|
|
76
|
+
enabledInstrumentations: [instrumentation],
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(instrumentationMocks[instrumentation]).toHaveBeenCalled();
|
|
80
|
+
|
|
81
|
+
// Check that other instrumentations are not called
|
|
82
|
+
Object.entries(instrumentationMocks).forEach(([name, mock]) => {
|
|
83
|
+
if (name !== instrumentation) {
|
|
84
|
+
expect(mock).not.toHaveBeenCalled();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it(`should not enable ${instrumentation} instrumentation when not present in enabledInstrumentations array`, async () => {
|
|
90
|
+
const { init } = await import("./init");
|
|
91
|
+
|
|
92
|
+
const otherInstrumentations = instrumentations.filter((i) => i !== instrumentation);
|
|
93
|
+
|
|
94
|
+
init({
|
|
95
|
+
...baseOptions,
|
|
96
|
+
enabledInstrumentations: otherInstrumentations,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(instrumentationMocks[instrumentation]).not.toHaveBeenCalled();
|
|
100
|
+
|
|
101
|
+
// Check that other instrumentations are called
|
|
102
|
+
otherInstrumentations.forEach((name) => {
|
|
103
|
+
expect(instrumentationMocks[name]).toHaveBeenCalled();
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
});
|
package/src/attributes/url.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
import { addAttribute, AttrPrefix, withPrefix } from "../utils/otel";
|
|
2
2
|
import { URL_DOMAIN, URL_FRAGMENT, URL_FULL, URL_PATH, URL_QUERY, URL_SCHEME } from "../semantic-conventions";
|
|
3
|
-
import { parseUrl } from "../utils";
|
|
3
|
+
import { identity, parseUrl } from "../utils";
|
|
4
4
|
import { KeyValue } from "../types/otlp";
|
|
5
|
+
import { vars } from "../vars";
|
|
6
|
+
|
|
7
|
+
export type UrlAttributeRecord = {
|
|
8
|
+
[URL_FULL]: string;
|
|
9
|
+
[URL_PATH]?: string | undefined;
|
|
10
|
+
[URL_DOMAIN]?: string | undefined;
|
|
11
|
+
[URL_SCHEME]?: string | undefined;
|
|
12
|
+
[URL_FRAGMENT]?: string | undefined;
|
|
13
|
+
[URL_QUERY]?: string | undefined;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type UrlAttributeScrubber = (attr: UrlAttributeRecord) => UrlAttributeRecord;
|
|
5
17
|
|
|
6
18
|
export function addUrlAttributes(attributes: KeyValue[], url: string | URL, prefix?: AttrPrefix) {
|
|
7
19
|
const applyPrefix = withPrefix(prefix);
|
|
@@ -11,17 +23,27 @@ export function addUrlAttributes(attributes: KeyValue[], url: string | URL, pref
|
|
|
11
23
|
if (parsed.username) parsed.username = "REDACTED";
|
|
12
24
|
if (parsed.password) parsed.password = "REDACTED";
|
|
13
25
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
const attrs = vars.urlAttributeScrubber({
|
|
27
|
+
[URL_FULL]: parsed.href,
|
|
28
|
+
[URL_PATH]: parsed.pathname,
|
|
29
|
+
[URL_DOMAIN]: parsed.hostname,
|
|
30
|
+
[URL_SCHEME]: parsed.protocol.replace(":", ""),
|
|
31
|
+
[URL_FRAGMENT]: parsed.hash ? parsed.hash.replace("#", "") : undefined,
|
|
32
|
+
[URL_QUERY]: parsed.search ? parsed.search.replace("?", "") : undefined,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
Object.entries(attrs).forEach(([key, value]) => {
|
|
36
|
+
if (value !== undefined) {
|
|
37
|
+
addAttribute(attributes, applyPrefix(key), value);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
24
40
|
} catch (_e) {
|
|
25
|
-
|
|
41
|
+
// This is fallback handling in case the url failed to parse or the user defined attribute scrubber behaved unexpectedly
|
|
42
|
+
// If the user did not attempt scrubbing we'll supply the full url for debugging purposes, otherwise we drop all url
|
|
43
|
+
// attributes
|
|
44
|
+
if (vars.urlAttributeScrubber === identity) {
|
|
45
|
+
// `identity` is the default assignment for this option
|
|
46
|
+
addAttribute(attributes, applyPrefix(URL_FULL), String(url));
|
|
47
|
+
}
|
|
26
48
|
}
|
|
27
49
|
}
|