@dash0/sdk-web 0.13.4 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +21 -0
  2. package/dist/dash0.iife.js +1 -1
  3. package/dist/dash0.iife.js.map +1 -1
  4. package/dist/dash0.js +1 -1
  5. package/dist/dash0.js.map +1 -1
  6. package/dist/dash0.umd.cjs +1 -1
  7. package/dist/dash0.umd.cjs.map +1 -1
  8. package/dist/modules/api/init.js +19 -4
  9. package/dist/modules/api/init_test.js +86 -0
  10. package/dist/modules/attributes/url.js +22 -12
  11. package/dist/modules/attributes/url_test.js +326 -0
  12. package/dist/modules/types/errors.js +1 -0
  13. package/dist/modules/types/globals.js +1 -0
  14. package/dist/modules/types/options.js +1 -0
  15. package/dist/modules/types/otlp.js +1 -0
  16. package/dist/modules/utils/fn.js +3 -0
  17. package/dist/modules/vars.js +2 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/dist/types/api/attributes.d.ts +1 -1
  20. package/dist/types/api/events.d.ts +1 -1
  21. package/dist/types/api/init.d.ts +1 -30
  22. package/dist/types/api/init_test.d.ts +1 -0
  23. package/dist/types/api/report-error.d.ts +1 -1
  24. package/dist/types/attributes/common.d.ts +1 -1
  25. package/dist/types/attributes/url.d.ts +11 -1
  26. package/dist/types/attributes/url_test.d.ts +1 -0
  27. package/dist/types/entrypoint/npm-package.d.ts +1 -1
  28. package/dist/types/instrumentations/errors/unhandled-error.d.ts +1 -1
  29. package/dist/types/transport/index.d.ts +1 -1
  30. package/dist/types/types/errors.d.ts +8 -0
  31. package/dist/types/types/globals.d.ts +12 -0
  32. package/dist/types/types/options.d.ts +35 -0
  33. package/dist/types/types/otlp.d.ts +104 -0
  34. package/dist/types/utils/fn.d.ts +1 -0
  35. package/dist/types/utils/otel/attributes.d.ts +1 -1
  36. package/dist/types/utils/otel/error.d.ts +1 -1
  37. package/dist/types/utils/otel/span.d.ts +1 -1
  38. package/dist/types/vars.d.ts +11 -1
  39. package/package.json +1 -1
  40. package/src/api/attributes.ts +1 -1
  41. package/src/api/events.ts +1 -1
  42. package/src/api/init.ts +25 -52
  43. package/src/api/init_test.ts +108 -0
  44. package/src/api/report-error.ts +1 -1
  45. package/src/attributes/common.ts +1 -1
  46. package/src/attributes/common_test.ts +1 -1
  47. package/src/attributes/url.ts +35 -13
  48. package/src/attributes/url_test.ts +417 -0
  49. package/src/entrypoint/npm-package.ts +2 -1
  50. package/src/instrumentations/errors/unhandled-error.ts +2 -2
  51. package/src/instrumentations/navigation/event.ts +1 -1
  52. package/src/instrumentations/navigation/page-load.ts +1 -1
  53. package/src/instrumentations/web-vitals.ts +1 -1
  54. package/src/transport/index.ts +1 -1
  55. package/src/types/errors.ts +9 -0
  56. package/src/types/globals.ts +19 -0
  57. package/src/types/options.ts +56 -0
  58. package/src/types/otlp.ts +121 -0
  59. package/src/utils/fn.ts +4 -0
  60. package/src/utils/otel/attributes.ts +1 -1
  61. package/src/utils/otel/attributes_test.ts +1 -1
  62. package/src/utils/otel/error.ts +1 -1
  63. package/src/utils/otel/span.ts +1 -1
  64. package/src/vars.ts +14 -3
@@ -0,0 +1,35 @@
1
+ import { AttributeValueType } from "../utils/otel";
2
+ import { AnyValue } from "./otlp";
3
+ import { Endpoint, Vars } from "../vars";
4
+ export type InstrumentationName = "@dash0/navigation" | "@dash0/web-vitals" | "@dash0/error" | "@dash0/fetch";
5
+ export type InitOptions = {
6
+ serviceName: string;
7
+ serviceVersion?: string;
8
+ environment?: string;
9
+ deploymentName?: string;
10
+ deploymentId?: string;
11
+ /**
12
+ * Additional attributes to include with transmitted signals
13
+ */
14
+ additionalSignalAttributes?: Record<string, AttributeValueType | AnyValue>;
15
+ /**
16
+ * OTLP endpoints to which the generated telemetry should be sent to.
17
+ */
18
+ endpoint: Endpoint | Endpoint[];
19
+ /**
20
+ * Which instrumentations to enable. Defaults to undefined, which means all instrumentations.
21
+ */
22
+ enabledInstrumentations?: InstrumentationName[];
23
+ /**
24
+ * The session inactivity timeout. Session inactivity is the maximum
25
+ * allowed time to pass between two page loads before the session is considered
26
+ * to be expired. Also think of cache time-to-idle configuration options.
27
+ */
28
+ sessionInactivityTimeoutMillis?: number;
29
+ /**
30
+ * The default session termination timeout. Session termination is the maximum
31
+ * allowed time to pass since session start before the session is considered
32
+ * to be expired. Also think of cache time-to-live configuration options.
33
+ */
34
+ sessionTerminationTimeoutMillis?: number;
35
+ } & Partial<Pick<Vars, "ignoreUrls" | "ignoreErrorMessages" | "wrapEventHandlers" | "wrapTimers" | "propagateTraceHeadersCorsURLs" | "maxWaitForResourceTimingsMillis" | "maxToleranceForResourceTimingsMillis" | "headersToCapture" | "urlAttributeScrubber" | "pageViewInstrumentation">>;
@@ -0,0 +1,104 @@
1
+ import { LOG_SEVERITY_NUMBER, LOG_SEVERITY_TEXT } from "../semantic-conventions";
2
+ export type AnyValue = {
3
+ ["stringValue"]?: string;
4
+ ["boolValue"]?: boolean;
5
+ /** Format: int64 */
6
+ ["intValue"]?: string;
7
+ /** Format: double */
8
+ ["doubleValue"]?: number;
9
+ ["arrayValue"]?: ArrayValue;
10
+ ["kvlistValue"]?: KeyValueList;
11
+ /** Format: byte */
12
+ ["bytesValue"]?: string;
13
+ };
14
+ export type ArrayValue = {
15
+ ["values"]: AnyValue[];
16
+ };
17
+ export type KeyValue = {
18
+ ["key"]: string;
19
+ ["value"]: AnyValue;
20
+ };
21
+ export type KeyValueList = {
22
+ ["values"]: KeyValue[];
23
+ };
24
+ export type Resource = {
25
+ ["attributes"]: KeyValue[];
26
+ };
27
+ export type InstrumentationScope = {
28
+ ["name"]?: string;
29
+ ["version"]?: string;
30
+ ["attributes"]: KeyValue[];
31
+ };
32
+ export type ExportLogsServiceRequest = {
33
+ resourceLogs: ResourceLogs[];
34
+ };
35
+ export type ResourceLogs = {
36
+ resource: Resource;
37
+ scopeLogs: ScopeLogs[];
38
+ };
39
+ export type ScopeLogs = {
40
+ scope?: InstrumentationScope;
41
+ logRecords: LogRecord[];
42
+ schemaUrl?: string;
43
+ };
44
+ export type LogRecord = {
45
+ /** Format: uint64 */
46
+ timeUnixNano: string;
47
+ severityNumber?: LOG_SEVERITY_NUMBER;
48
+ severityText?: LOG_SEVERITY_TEXT;
49
+ body?: AnyValue;
50
+ attributes: KeyValue[];
51
+ /** Format: byte */
52
+ traceId?: string;
53
+ /** Format: byte */
54
+ spanId?: string;
55
+ };
56
+ export type ExportTraceServiceRequest = {
57
+ resourceSpans: ResourceSpans[];
58
+ };
59
+ export type ResourceSpans = {
60
+ resource: Resource;
61
+ scopeSpans: ScopeSpans[];
62
+ };
63
+ export type ScopeSpans = {
64
+ scope?: InstrumentationScope;
65
+ spans: Span[];
66
+ };
67
+ export type SpanStatus = {
68
+ message?: string;
69
+ code: 0 | 1 | 2;
70
+ };
71
+ export type SpanEvent = {
72
+ timeUnixNano: string;
73
+ name: string;
74
+ attributes: KeyValue[];
75
+ };
76
+ export type SpanLink = {
77
+ /** Format: byte */
78
+ traceId: string;
79
+ /** Format: byte */
80
+ spanId: string;
81
+ traceState?: string;
82
+ attributes: KeyValue[];
83
+ droppedAttributesCount: number;
84
+ flags?: number;
85
+ };
86
+ export type Span = {
87
+ /** Format: byte */
88
+ traceId: string;
89
+ /** Format: byte */
90
+ spanId: string;
91
+ traceState?: string;
92
+ /** Format: byte */
93
+ parentSpanId?: string;
94
+ name: string;
95
+ kind: number;
96
+ startTimeUnixNano: string;
97
+ endTimeUnixNano: string;
98
+ attributes: KeyValue[];
99
+ events: SpanEvent[];
100
+ /** Format: int64 */
101
+ droppedEventsCount?: number;
102
+ links: SpanLink[];
103
+ status: SpanStatus;
104
+ };
@@ -1 +1,2 @@
1
1
  export declare function noop(): void;
2
+ export declare function identity<T>(a: T): T;
@@ -1,4 +1,4 @@
1
- import { AnyValue, KeyValue } from "../../../types/otlp";
1
+ import { AnyValue, KeyValue } from "../../types/otlp";
2
2
  type PrimitiveAttributeValue = string | number | boolean;
3
3
  export type AttributeValueType = PrimitiveAttributeValue | Array<PrimitiveAttributeValue> | Record<string, PrimitiveAttributeValue>;
4
4
  export declare function toAnyValue(value: AttributeValueType | AnyValue): AnyValue;
@@ -1,5 +1,5 @@
1
1
  import { InProgressSpan } from "./span";
2
- import { SpanStatus } from "../../../types/otlp";
2
+ import { SpanStatus } from "../../types/otlp";
3
3
  interface ExceptionWithCode {
4
4
  code: string | number;
5
5
  name?: string;
@@ -1,4 +1,4 @@
1
- import { KeyValue, Span, SpanStatus } from "../../../types/otlp";
1
+ import { KeyValue, Span, SpanStatus } from "../../types/otlp";
2
2
  export type InProgressSpan = Omit<Span, "endTimeUnixNano">;
3
3
  export declare function startSpan(name: string): InProgressSpan;
4
4
  export declare function endSpan(span: InProgressSpan, status: SpanStatus | undefined, durationNano: number | undefined): Span;
@@ -1,5 +1,6 @@
1
- import { AnyValue, InstrumentationScope, KeyValue, Resource } from "../types/otlp";
2
1
  import { AttributeValueType } from "./utils/otel";
2
+ import { AnyValue, InstrumentationScope, KeyValue, Resource } from "./types/otlp";
3
+ import { UrlAttributeScrubber } from "./attributes";
3
4
  export type Endpoint = {
4
5
  /**
5
6
  * OTLP HTTP URL excluding the /v1/* prefix
@@ -118,6 +119,15 @@ export type Vars = {
118
119
  * These headers will be transferred as span attributes
119
120
  */
120
121
  headersToCapture: RegExp[];
122
+ /**
123
+ * Allows the application of a custom scrubbing function to url attributes before they are applied to signals.
124
+ * This is invoked for each url processed for inclusion in signal attributes. For example this applies both to `page.url.*`
125
+ * and `url.*` attribute namespaces.
126
+ * Sensitive parts of the url attributes should be replaced with `REDACTED`,
127
+ * avoid partially or fully dropping attributes to preserve telemetry quality.
128
+ * Note: basic auth credentials in urls are automatically redacted before this is invoked.
129
+ */
130
+ urlAttributeScrubber: UrlAttributeScrubber;
121
131
  pageViewInstrumentation: PageViewInstrumentationSettings;
122
132
  };
123
133
  export declare const vars: Vars;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dash0/sdk-web",
3
- "version": "0.13.4",
3
+ "version": "0.14.0",
4
4
  "description": "Dash0's Web SDK to collect telemetry from end-users' web browsers",
5
5
  "type": "module",
6
6
  "main": "dist/dash0.umd.cjs",
@@ -1,6 +1,6 @@
1
- import { AnyValue } from "../../types/otlp";
2
1
  import { addAttribute, AttributeValueType, removeAttribute } from "../utils/otel";
3
2
  import { vars } from "../vars";
3
+ import { AnyValue } from "../types/otlp";
4
4
 
5
5
  /**
6
6
  * Adds a signal attribute to be transmitted with every signal.
package/src/api/events.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { AnyValue, KeyValue } from "../../types/otlp";
2
1
  import { EVENT_NAME, EVENT_NAMES, WEB_EVENT_TITLE, LOG_SEVERITIES, LOG_SEVERITY_TEXT } from "../semantic-conventions";
3
2
  import { sendLog } from "../transport";
4
3
  import { addCommonAttributes } from "../attributes";
5
4
  import { addAttribute, AttributeValueType, toAnyValue } from "../utils/otel";
6
5
  import { nowNanos, TimeInput, toNanosTs, warn } from "../utils";
6
+ import { AnyValue, KeyValue } from "../types/otlp";
7
7
 
8
8
  type EventOptions = {
9
9
  /**
package/src/api/init.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Endpoint, Vars, vars } from "../vars";
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, AttributeValueType } from "../utils/otel";
26
+ import { addAttribute } from "../utils/otel";
27
27
  import { instrumentFetch } from "../instrumentations/http/fetch";
28
- import { AnyValue } from "../../types/otlp";
29
28
  import { startNavigationInstrumentation } from "../instrumentations/navigation";
30
29
  import { merge } from "ts-deepmerge";
31
30
  import { initializeTabId } from "../utils/tab-id";
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
- startNavigationInstrumentation();
125
- startWebVitalsInstrumentation();
126
- startErrorInstrumentation();
127
- instrumentFetch();
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
+ });
@@ -1,5 +1,5 @@
1
- import { ErrorLike, ReportErrorOpts } from "../../types/errors";
2
1
  import { reportError as reportErrorInternal } from "../instrumentations/errors/unhandled-error";
2
+ import { ReportErrorOpts, ErrorLike } from "../types/errors";
3
3
 
4
4
  export function reportError(error: string | ErrorLike, opts?: ReportErrorOpts) {
5
5
  reportErrorInternal(error, opts);
@@ -1,4 +1,3 @@
1
- import { KeyValue } from "../../types/otlp";
2
1
  import { generateUniqueId, nav, NO_VALUE_FALLBACK, WEB_EVENT_ID_BYTES, win } from "../utils";
3
2
  import {
4
3
  BROWSER_TAB_ID,
@@ -14,6 +13,7 @@ import { vars } from "../vars";
14
13
  import { addAttribute } from "../utils/otel";
15
14
  import { addUrlAttributes } from "./url";
16
15
  import { tabId } from "../utils/tab-id";
16
+ import { KeyValue } from "../types/otlp";
17
17
 
18
18
  type Options = {
19
19
  /**
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi, beforeEach } from "vitest";
2
2
  import { addCommonAttributes } from "./common";
3
- import { KeyValue } from "../../types/otlp";
4
3
  import * as utils from "../utils";
5
4
  import * as varsModule from "../vars";
6
5
  import * as urlModule from "./url";
7
6
  import * as sessionApi from "../api/session";
8
7
  import * as tabIdModule from "../utils/tab-id";
8
+ import { KeyValue } from "../types/otlp";
9
9
 
10
10
  describe("addCommonAttributes", () => {
11
11
  beforeEach(() => {
@@ -1,7 +1,19 @@
1
- import { KeyValue } from "../../types/otlp";
2
1
  import { addAttribute, AttrPrefix, withPrefix } from "../utils/otel";
3
2
  import { URL_DOMAIN, URL_FRAGMENT, URL_FULL, URL_PATH, URL_QUERY, URL_SCHEME } from "../semantic-conventions";
4
- import { parseUrl } from "../utils";
3
+ import { identity, parseUrl } from "../utils";
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
- addAttribute(attributes, applyPrefix(URL_FULL), parsed.href);
15
- addAttribute(attributes, applyPrefix(URL_PATH), parsed.pathname);
16
- addAttribute(attributes, applyPrefix(URL_DOMAIN), parsed.hostname);
17
- addAttribute(attributes, applyPrefix(URL_SCHEME), parsed.protocol.replace(":", ""));
18
- if (parsed.hash) {
19
- addAttribute(attributes, applyPrefix(URL_FRAGMENT), parsed.hash.replace("#", ""));
20
- }
21
- if (parsed.search) {
22
- addAttribute(attributes, applyPrefix(URL_QUERY), parsed.search.replace("?", ""));
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
- addAttribute(attributes, applyPrefix(URL_FULL), String(url));
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
  }