@genn-inc/cluebase-frontend-sdk 0.0.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 +222 -0
- package/dist/_generated/correlation-attribute-keys.d.ts +2 -0
- package/dist/_generated/correlation-attribute-keys.js +33 -0
- package/dist/_generated/identity-pii-policy.d.ts +38 -0
- package/dist/_generated/identity-pii-policy.js +103 -0
- package/dist/_generated/ingest-privacy-tables.d.ts +120 -0
- package/dist/_generated/ingest-privacy-tables.js +890 -0
- package/dist/_generated/otel-resource-keys.d.ts +21 -0
- package/dist/_generated/otel-resource-keys.js +72 -0
- package/dist/_generated/otel-span-conversion.d.ts +48 -0
- package/dist/_generated/otel-span-conversion.js +69 -0
- package/dist/_generated/parameter-snapshot-leaf.d.ts +11 -0
- package/dist/_generated/parameter-snapshot-leaf.js +37 -0
- package/dist/_generated/sdk-runtime-policy.d.ts +7 -0
- package/dist/_generated/sdk-runtime-policy.js +14 -0
- package/dist/_generated/usage-governor-tables.d.ts +15 -0
- package/dist/_generated/usage-governor-tables.js +44 -0
- package/dist/context/browser-storage.d.ts +15 -0
- package/dist/context/browser-storage.js +38 -0
- package/dist/context/environment.d.ts +37 -0
- package/dist/context/environment.js +229 -0
- package/dist/context/identity.d.ts +16 -0
- package/dist/context/identity.js +139 -0
- package/dist/context/organization.d.ts +16 -0
- package/dist/context/organization.js +60 -0
- package/dist/context/session.d.ts +28 -0
- package/dist/context/session.js +130 -0
- package/dist/context/tab.d.ts +22 -0
- package/dist/context/tab.js +117 -0
- package/dist/context/trace.d.ts +76 -0
- package/dist/context/trace.js +163 -0
- package/dist/exporter/batch.d.ts +94 -0
- package/dist/exporter/batch.js +485 -0
- package/dist/exporter/browser-outbox-records.d.ts +159 -0
- package/dist/exporter/browser-outbox-records.js +341 -0
- package/dist/exporter/browser-outbox.d.ts +112 -0
- package/dist/exporter/browser-outbox.js +876 -0
- package/dist/exporter/client.d.ts +92 -0
- package/dist/exporter/client.js +541 -0
- package/dist/exporter/delivery-response-contract.d.ts +103 -0
- package/dist/exporter/delivery-response-contract.js +147 -0
- package/dist/exporter/delivery-response.d.ts +37 -0
- package/dist/exporter/delivery-response.js +151 -0
- package/dist/exporter/diagnostics.d.ts +42 -0
- package/dist/exporter/diagnostics.js +148 -0
- package/dist/exporter/event-id.d.ts +10 -0
- package/dist/exporter/event-id.js +75 -0
- package/dist/exporter/frontend-resource.d.ts +20 -0
- package/dist/exporter/frontend-resource.js +52 -0
- package/dist/exporter/local-minimization.d.ts +29 -0
- package/dist/exporter/local-minimization.js +87 -0
- package/dist/exporter/observation-source-event-exporter.d.ts +69 -0
- package/dist/exporter/observation-source-event-exporter.js +154 -0
- package/dist/exporter/parameter-snapshot.d.ts +14 -0
- package/dist/exporter/parameter-snapshot.js +215 -0
- package/dist/exporter/path-template.d.ts +1 -0
- package/dist/exporter/path-template.js +67 -0
- package/dist/exporter/raw-event-to-source-signal.d.ts +12 -0
- package/dist/exporter/raw-event-to-source-signal.js +297 -0
- package/dist/exporter/retry.d.ts +8 -0
- package/dist/exporter/retry.js +8 -0
- package/dist/exporter/sanitize.d.ts +30 -0
- package/dist/exporter/sanitize.js +177 -0
- package/dist/exporter/schema-evidence.d.ts +20 -0
- package/dist/exporter/schema-evidence.js +232 -0
- package/dist/exporter/source-event.d.ts +99 -0
- package/dist/exporter/source-event.js +13 -0
- package/dist/exporter/span-to-source-signal.d.ts +8 -0
- package/dist/exporter/span-to-source-signal.js +174 -0
- package/dist/exporter/text-fingerprint.d.ts +9 -0
- package/dist/exporter/text-fingerprint.js +16 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +158 -0
- package/dist/instrumentation/action.d.ts +2 -0
- package/dist/instrumentation/action.js +171 -0
- package/dist/instrumentation/cluebase-authored-attributes.d.ts +4 -0
- package/dist/instrumentation/cluebase-authored-attributes.js +32 -0
- package/dist/instrumentation/common.d.ts +73 -0
- package/dist/instrumentation/common.js +90 -0
- package/dist/instrumentation/dom.d.ts +63 -0
- package/dist/instrumentation/dom.js +640 -0
- package/dist/instrumentation/drag.d.ts +2 -0
- package/dist/instrumentation/drag.js +82 -0
- package/dist/instrumentation/error.d.ts +2 -0
- package/dist/instrumentation/error.js +318 -0
- package/dist/instrumentation/form.d.ts +2 -0
- package/dist/instrumentation/form.js +132 -0
- package/dist/instrumentation/input.d.ts +2 -0
- package/dist/instrumentation/input.js +180 -0
- package/dist/instrumentation/location.d.ts +11 -0
- package/dist/instrumentation/location.js +69 -0
- package/dist/instrumentation/naming-evidence.d.ts +20 -0
- package/dist/instrumentation/naming-evidence.js +11 -0
- package/dist/instrumentation/navigation.d.ts +2 -0
- package/dist/instrumentation/navigation.js +144 -0
- package/dist/instrumentation/network-request.d.ts +103 -0
- package/dist/instrumentation/network-request.js +285 -0
- package/dist/instrumentation/network-span-attributes.d.ts +25 -0
- package/dist/instrumentation/network-span-attributes.js +153 -0
- package/dist/instrumentation/network.d.ts +16 -0
- package/dist/instrumentation/network.js +502 -0
- package/dist/instrumentation/page.d.ts +2 -0
- package/dist/instrumentation/page.js +56 -0
- package/dist/instrumentation/performance.d.ts +2 -0
- package/dist/instrumentation/performance.js +365 -0
- package/dist/integrations/next-router.d.ts +16 -0
- package/dist/integrations/next-router.js +18 -0
- package/dist/integrations/react-router.d.ts +7 -0
- package/dist/integrations/react-router.js +61 -0
- package/dist/internal/internal-log.d.ts +23 -0
- package/dist/internal/internal-log.js +47 -0
- package/dist/internal/metrics.d.ts +10 -0
- package/dist/internal/metrics.js +50 -0
- package/dist/provider/_version.d.ts +1 -0
- package/dist/provider/_version.js +3 -0
- package/dist/provider/config.d.ts +12 -0
- package/dist/provider/config.js +177 -0
- package/dist/provider/constants.d.ts +62 -0
- package/dist/provider/constants.js +118 -0
- package/dist/provider/contracts.d.ts +51 -0
- package/dist/provider/contracts.js +46 -0
- package/dist/provider/privacy-policy-store.d.ts +20 -0
- package/dist/provider/privacy-policy-store.js +56 -0
- package/dist/provider/sampling.d.ts +2 -0
- package/dist/provider/sampling.js +17 -0
- package/dist/provider/sdk-event-pipeline.d.ts +60 -0
- package/dist/provider/sdk-event-pipeline.js +478 -0
- package/dist/provider/sdk-input-normalization.d.ts +7 -0
- package/dist/provider/sdk-input-normalization.js +113 -0
- package/dist/provider/sdk-otel-pipeline.d.ts +14 -0
- package/dist/provider/sdk-otel-pipeline.js +148 -0
- package/dist/provider/sdk-runtime-surfaces.d.ts +49 -0
- package/dist/provider/sdk-runtime-surfaces.js +216 -0
- package/dist/provider/sdk.d.ts +2 -0
- package/dist/provider/sdk.js +614 -0
- package/dist/provider/types.d.ts +323 -0
- package/dist/provider/types.js +1 -0
- package/dist/provider/usage-governor.d.ts +7 -0
- package/dist/provider/usage-governor.js +116 -0
- package/package.json +59 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const randomBytes = (length) => {
|
|
2
|
+
if (typeof globalThis.crypto === "undefined" ||
|
|
3
|
+
typeof globalThis.crypto.getRandomValues !== "function") {
|
|
4
|
+
throw new Error("Cluebase SDK requires crypto.getRandomValues for ID generation");
|
|
5
|
+
}
|
|
6
|
+
const bytes = new Uint8Array(length);
|
|
7
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
8
|
+
return bytes;
|
|
9
|
+
};
|
|
10
|
+
const toHex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
11
|
+
let generatorId = null;
|
|
12
|
+
let monotonicCounter = 0;
|
|
13
|
+
let lastMonotonicTime = 0;
|
|
14
|
+
const getGeneratorId = () => {
|
|
15
|
+
generatorId ?? (generatorId = toHex(randomBytes(16)));
|
|
16
|
+
return generatorId;
|
|
17
|
+
};
|
|
18
|
+
const nextMonotonicParts = () => {
|
|
19
|
+
const performanceNow = typeof globalThis.performance !== "undefined" &&
|
|
20
|
+
typeof globalThis.performance.now === "function"
|
|
21
|
+
? Math.floor(globalThis.performance.now() * 1000)
|
|
22
|
+
: Date.now();
|
|
23
|
+
const monotonicTime = performanceNow > lastMonotonicTime
|
|
24
|
+
? performanceNow
|
|
25
|
+
: lastMonotonicTime + 1;
|
|
26
|
+
lastMonotonicTime = monotonicTime;
|
|
27
|
+
monotonicCounter = (monotonicCounter + 1) % Number.MAX_SAFE_INTEGER;
|
|
28
|
+
return { monotonicTime, counter: monotonicCounter };
|
|
29
|
+
};
|
|
30
|
+
export const generateMonotonicId = (prefix) => {
|
|
31
|
+
const { monotonicTime, counter } = nextMonotonicParts();
|
|
32
|
+
const generator = getGeneratorId();
|
|
33
|
+
const id = [
|
|
34
|
+
generator,
|
|
35
|
+
monotonicTime.toString(36),
|
|
36
|
+
counter.toString(36),
|
|
37
|
+
toHex(randomBytes(8)),
|
|
38
|
+
].join("_");
|
|
39
|
+
return prefix ? `${prefix}_${id}` : id;
|
|
40
|
+
};
|
|
41
|
+
export const generateTraceId = () => {
|
|
42
|
+
const { monotonicTime, counter } = nextMonotonicParts();
|
|
43
|
+
const generator = getGeneratorId();
|
|
44
|
+
return [
|
|
45
|
+
generator.slice(0, 12),
|
|
46
|
+
monotonicTime.toString(16).padStart(12, "0").slice(-12),
|
|
47
|
+
counter.toString(16).padStart(4, "0").slice(-4),
|
|
48
|
+
toHex(randomBytes(2)),
|
|
49
|
+
].join("");
|
|
50
|
+
};
|
|
51
|
+
export const generateEventId = () => {
|
|
52
|
+
return generateMonotonicId();
|
|
53
|
+
};
|
|
54
|
+
export const generateAnonymousId = () => {
|
|
55
|
+
return generateMonotonicId("anon");
|
|
56
|
+
};
|
|
57
|
+
export const generateSessionId = () => {
|
|
58
|
+
return generateMonotonicId("sess");
|
|
59
|
+
};
|
|
60
|
+
export const generateTabId = () => {
|
|
61
|
+
return generateMonotonicId("tab");
|
|
62
|
+
};
|
|
63
|
+
export const generateRequestId = () => {
|
|
64
|
+
return generateMonotonicId("req");
|
|
65
|
+
};
|
|
66
|
+
export const generateRequestSpanId = () => {
|
|
67
|
+
const { counter } = nextMonotonicParts();
|
|
68
|
+
return `${counter.toString(16).padStart(4, "0").slice(-4)}${toHex(randomBytes(6))}`;
|
|
69
|
+
};
|
|
70
|
+
export const generateInteractionId = () => {
|
|
71
|
+
return generateMonotonicId("int");
|
|
72
|
+
};
|
|
73
|
+
export const generateViewId = () => {
|
|
74
|
+
return generateMonotonicId("view");
|
|
75
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ResolvedCluebaseConfig } from "../provider/types.js";
|
|
2
|
+
export type FrontendResourceAttributes = Readonly<Record<string, string>>;
|
|
3
|
+
export type FrontendResourceInput = FrontendResourceAttributes | {
|
|
4
|
+
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
type ResourceRecord = {
|
|
7
|
+
[key: string]: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Build the only Resource attributes emitted by the browser SDK's own OTel
|
|
11
|
+
* provider. Values are intentionally strings so the same snapshot can be
|
|
12
|
+
* used to construct an OTel Resource and to enrich raw source signals.
|
|
13
|
+
*/
|
|
14
|
+
export declare const createFrontendResourceAttributes: (resolved: ResolvedCluebaseConfig) => FrontendResourceAttributes;
|
|
15
|
+
/**
|
|
16
|
+
* Copy only approved frontend Resource attributes into the serialized source
|
|
17
|
+
* signal. The returned object is a detached, bounded JSON-safe snapshot.
|
|
18
|
+
*/
|
|
19
|
+
export declare const toFrontendSourceResource: (resource: FrontendResourceInput | undefined, deniedKeys?: readonly string[]) => ResourceRecord;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isDeniedKey } from "./sanitize.js";
|
|
2
|
+
import { SDK_VERSION } from "../provider/constants.js";
|
|
3
|
+
import { CLUEBASE_OTEL_SDK_SOURCE_REQUIRED_RESOURCE_KEYS, CLUEBASE_OTEL_SDK_SOURCE_RESOURCE_KEYS, } from "../_generated/otel-resource-keys.js";
|
|
4
|
+
const FRONTEND_RESOURCE_KEYS = new Set(CLUEBASE_OTEL_SDK_SOURCE_RESOURCE_KEYS);
|
|
5
|
+
const FRONTEND_REQUIRED_RESOURCE_KEYS = new Set(CLUEBASE_OTEL_SDK_SOURCE_REQUIRED_RESOURCE_KEYS);
|
|
6
|
+
const MAX_RESOURCE_VALUE_BYTES = 512;
|
|
7
|
+
/**
|
|
8
|
+
* Build the only Resource attributes emitted by the browser SDK's own OTel
|
|
9
|
+
* provider. Values are intentionally strings so the same snapshot can be
|
|
10
|
+
* used to construct an OTel Resource and to enrich raw source signals.
|
|
11
|
+
*/
|
|
12
|
+
export const createFrontendResourceAttributes = (resolved) => ({
|
|
13
|
+
"service.name": resolved.producerId,
|
|
14
|
+
"service.version": resolved.release.frontendRelease ?? "unknown",
|
|
15
|
+
"cluebase.project_key": resolved.projectKey,
|
|
16
|
+
"cluebase.producer_id": resolved.producerId,
|
|
17
|
+
"cluebase.service_type": "frontend",
|
|
18
|
+
"cluebase.sdk.type": "browser",
|
|
19
|
+
"cluebase.sdk.version": SDK_VERSION,
|
|
20
|
+
"cluebase.sdk.collection_mode": resolved.collection?.mode ?? "standard",
|
|
21
|
+
"cluebase.runtime_language": "browser",
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Copy only approved frontend Resource attributes into the serialized source
|
|
25
|
+
* signal. The returned object is a detached, bounded JSON-safe snapshot.
|
|
26
|
+
*/
|
|
27
|
+
export const toFrontendSourceResource = (resource, deniedKeys = []) => {
|
|
28
|
+
const input = resource && "attributes" in resource ? resource.attributes : resource;
|
|
29
|
+
const output = {};
|
|
30
|
+
let entries;
|
|
31
|
+
try {
|
|
32
|
+
entries = Object.entries(input ?? {});
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return output;
|
|
36
|
+
}
|
|
37
|
+
for (const [key, value] of entries) {
|
|
38
|
+
if (!FRONTEND_RESOURCE_KEYS.has(key) ||
|
|
39
|
+
(isDeniedKey(key, deniedKeys) && !FRONTEND_REQUIRED_RESOURCE_KEYS.has(key))) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (key === "cluebase.service_type" && value !== "frontend") {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === "string" &&
|
|
46
|
+
value.trim().length > 0 &&
|
|
47
|
+
new TextEncoder().encode(value).byteLength <= MAX_RESOURCE_VALUE_BYTES) {
|
|
48
|
+
output[key] = value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return output;
|
|
52
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
type LocalValueType = "string" | "number" | "boolean" | "null" | "object" | "array";
|
|
2
|
+
export type LocalTextDescriptor = {
|
|
3
|
+
present: true;
|
|
4
|
+
length: number;
|
|
5
|
+
};
|
|
6
|
+
export type LocalValueDescriptor = {
|
|
7
|
+
present: true;
|
|
8
|
+
valueType: LocalValueType;
|
|
9
|
+
length?: number;
|
|
10
|
+
itemCount?: number;
|
|
11
|
+
keyCount?: number;
|
|
12
|
+
fileCount?: number;
|
|
13
|
+
};
|
|
14
|
+
export type LocalStructuredSummary = LocalValueDescriptor | {
|
|
15
|
+
present: true;
|
|
16
|
+
valueType: "object";
|
|
17
|
+
keyCount: number;
|
|
18
|
+
fields: Record<string, LocalStructuredSummary | null>;
|
|
19
|
+
} | {
|
|
20
|
+
present: true;
|
|
21
|
+
valueType: "array";
|
|
22
|
+
itemCount: number;
|
|
23
|
+
items: Array<LocalStructuredSummary | null>;
|
|
24
|
+
};
|
|
25
|
+
export declare const buildLocalTextDescriptor: (value: string | null | undefined) => LocalTextDescriptor | null;
|
|
26
|
+
export declare const buildLocalValueDescriptor: (value: unknown) => LocalValueDescriptor | null;
|
|
27
|
+
export declare const buildLocalStructuredSummary: (value: unknown, depth?: number) => LocalStructuredSummary | null;
|
|
28
|
+
export declare const buildLocalQuerySummary: (query: Record<string, string | string[]>) => Record<string, LocalStructuredSummary | null>;
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const MAX_SUMMARY_DEPTH = 4;
|
|
2
|
+
const MAX_SUMMARY_KEYS = 32;
|
|
3
|
+
const MAX_SUMMARY_ITEMS = 12;
|
|
4
|
+
const isReadableObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
export const buildLocalTextDescriptor = (value) => {
|
|
6
|
+
if (value == null) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
const trimmed = value.trim();
|
|
10
|
+
if (!trimmed) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
present: true,
|
|
15
|
+
length: trimmed.length,
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export const buildLocalValueDescriptor = (value) => {
|
|
19
|
+
if (value == null) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === "string") {
|
|
23
|
+
const trimmed = value.trim();
|
|
24
|
+
return trimmed
|
|
25
|
+
? { present: true, valueType: "string", length: trimmed.length }
|
|
26
|
+
: null;
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === "number") {
|
|
29
|
+
return { present: true, valueType: "number" };
|
|
30
|
+
}
|
|
31
|
+
if (typeof value === "boolean") {
|
|
32
|
+
return { present: true, valueType: "boolean" };
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
return {
|
|
36
|
+
present: true,
|
|
37
|
+
valueType: "array",
|
|
38
|
+
itemCount: value.length,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (isReadableObject(value)) {
|
|
42
|
+
const fileCount = typeof value.fileCount === "number" && Number.isFinite(value.fileCount)
|
|
43
|
+
? Math.max(0, Math.floor(value.fileCount))
|
|
44
|
+
: undefined;
|
|
45
|
+
return {
|
|
46
|
+
present: true,
|
|
47
|
+
valueType: "object",
|
|
48
|
+
keyCount: Object.keys(value).length,
|
|
49
|
+
...(typeof fileCount === "number" ? { fileCount } : {}),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
};
|
|
54
|
+
export const buildLocalStructuredSummary = (value, depth = 0) => {
|
|
55
|
+
if (depth >= MAX_SUMMARY_DEPTH) {
|
|
56
|
+
return buildLocalValueDescriptor(value);
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
return {
|
|
60
|
+
present: true,
|
|
61
|
+
valueType: "array",
|
|
62
|
+
itemCount: value.length,
|
|
63
|
+
items: value
|
|
64
|
+
.slice(0, MAX_SUMMARY_ITEMS)
|
|
65
|
+
.map((entry) => buildLocalStructuredSummary(entry, depth + 1)),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (isReadableObject(value)) {
|
|
69
|
+
const limitedEntries = Object.entries(value).slice(0, MAX_SUMMARY_KEYS);
|
|
70
|
+
return {
|
|
71
|
+
present: true,
|
|
72
|
+
valueType: "object",
|
|
73
|
+
keyCount: Object.keys(value).length,
|
|
74
|
+
fields: Object.fromEntries(limitedEntries.map(([key, child]) => [
|
|
75
|
+
key,
|
|
76
|
+
buildLocalStructuredSummary(child, depth + 1),
|
|
77
|
+
])),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return buildLocalValueDescriptor(value);
|
|
81
|
+
};
|
|
82
|
+
export const buildLocalQuerySummary = (query) => {
|
|
83
|
+
return Object.fromEntries(Object.entries(query).map(([key, value]) => [
|
|
84
|
+
key,
|
|
85
|
+
buildLocalStructuredSummary(value),
|
|
86
|
+
]));
|
|
87
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-SDK observation source event exporter.
|
|
3
|
+
*
|
|
4
|
+
* Production OTel {@link SpanExporter} for the Frontend SDK. Forwards each
|
|
5
|
+
* {@link ReadableSpan} as a source-signal envelope and routes it onto the
|
|
6
|
+
* SDK's existing transport buffer. Final classification and canonical shaping
|
|
7
|
+
* happen in the Cluebase backend ingestion core.
|
|
8
|
+
*
|
|
9
|
+
* The exporter is registered onto the global `WebTracerProvider` in
|
|
10
|
+
* `provider/sdk.ts:cluebase.init`. `enqueue(event)` accepts source-signal
|
|
11
|
+
* envelopes from capture modules.
|
|
12
|
+
*/
|
|
13
|
+
import { type ExportResult, ExportResultCode } from "@opentelemetry/core";
|
|
14
|
+
import type { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
15
|
+
import type { CluebaseContext } from "../provider/types.js";
|
|
16
|
+
import type { CollectionPermit } from "./browser-outbox.js";
|
|
17
|
+
import type { BrowserSourceEvent, BrowserSourceSignalEvent } from "./source-event.js";
|
|
18
|
+
export declare const EXPORT_RESULT_SUCCESS: ExportResultCode;
|
|
19
|
+
export declare const EXPORT_RESULT_FAILED: ExportResultCode;
|
|
20
|
+
export interface ObservationSourceEventExporterOptions {
|
|
21
|
+
enqueueImpl: (event: BrowserSourceEvent, permit?: CollectionPermit) => void;
|
|
22
|
+
getCollectionPermit?: (span: ReadableSpan | null) => CollectionPermit | undefined;
|
|
23
|
+
flushImpl: () => Promise<void>;
|
|
24
|
+
shutdownImpl?: () => Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Returns the active {@link CluebaseContext} for the current SDK instance.
|
|
27
|
+
* Called once per `export()` invocation so browser-environment fields
|
|
28
|
+
* (device, browser, locale, etc.) reflect the SDK state at export time.
|
|
29
|
+
*/
|
|
30
|
+
getContext: () => CluebaseContext;
|
|
31
|
+
deniedKeys?: readonly string[];
|
|
32
|
+
/**
|
|
33
|
+
* URL prefixes that should NOT produce source-signal events.
|
|
34
|
+
* Used to skip spans that the Cluebase ingest BatchTransport itself
|
|
35
|
+
* generates when the host installs ``@opentelemetry/instrumentation-fetch``
|
|
36
|
+
* (otherwise Cluebase's own POSTs would loop back through the exporter as
|
|
37
|
+
* fetch spans, generating duplicate ingest events ad-infinitum).
|
|
38
|
+
*/
|
|
39
|
+
ignoreUrlPrefixes?: readonly string[];
|
|
40
|
+
}
|
|
41
|
+
export declare class ObservationSourceEventExporter implements SpanExporter {
|
|
42
|
+
private closed;
|
|
43
|
+
private readonly enqueueImpl;
|
|
44
|
+
private readonly getCollectionPermit;
|
|
45
|
+
private readonly flushImpl;
|
|
46
|
+
private readonly shutdownImpl?;
|
|
47
|
+
private readonly getContext;
|
|
48
|
+
private readonly deniedKeys;
|
|
49
|
+
private readonly ignoreUrlPrefixes;
|
|
50
|
+
private exportedSpanCount;
|
|
51
|
+
private droppedSpanCount;
|
|
52
|
+
private skippedLoopSpanCount;
|
|
53
|
+
constructor(options: ObservationSourceEventExporterOptions);
|
|
54
|
+
private ignoreSpanReason;
|
|
55
|
+
/**
|
|
56
|
+
* Enqueue a source-signal event emitted by capture modules through
|
|
57
|
+
* `context.onEvent`.
|
|
58
|
+
*/
|
|
59
|
+
enqueue(event: BrowserSourceSignalEvent): void;
|
|
60
|
+
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void;
|
|
61
|
+
forceFlush(): Promise<void>;
|
|
62
|
+
shutdown(): Promise<void>;
|
|
63
|
+
internalMetrics(): {
|
|
64
|
+
exported_span_count: number;
|
|
65
|
+
dropped_span_count: number;
|
|
66
|
+
skipped_loop_span_count: number;
|
|
67
|
+
closed: boolean;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-SDK observation source event exporter.
|
|
3
|
+
*
|
|
4
|
+
* Production OTel {@link SpanExporter} for the Frontend SDK. Forwards each
|
|
5
|
+
* {@link ReadableSpan} as a source-signal envelope and routes it onto the
|
|
6
|
+
* SDK's existing transport buffer. Final classification and canonical shaping
|
|
7
|
+
* happen in the Cluebase backend ingestion core.
|
|
8
|
+
*
|
|
9
|
+
* The exporter is registered onto the global `WebTracerProvider` in
|
|
10
|
+
* `provider/sdk.ts:cluebase.init`. `enqueue(event)` accepts source-signal
|
|
11
|
+
* envelopes from capture modules.
|
|
12
|
+
*/
|
|
13
|
+
import { ExportResultCode } from "@opentelemetry/core";
|
|
14
|
+
import { logSwallowed } from "../internal/internal-log.js";
|
|
15
|
+
import { spanToSourceSignalEvent } from "./span-to-source-signal.js";
|
|
16
|
+
export const EXPORT_RESULT_SUCCESS = ExportResultCode.SUCCESS;
|
|
17
|
+
export const EXPORT_RESULT_FAILED = ExportResultCode.FAILED;
|
|
18
|
+
export class ObservationSourceEventExporter {
|
|
19
|
+
constructor(options) {
|
|
20
|
+
this.closed = false;
|
|
21
|
+
this.exportedSpanCount = 0;
|
|
22
|
+
this.droppedSpanCount = 0;
|
|
23
|
+
this.skippedLoopSpanCount = 0;
|
|
24
|
+
this.enqueueImpl = options.enqueueImpl;
|
|
25
|
+
this.getCollectionPermit = options.getCollectionPermit ?? null;
|
|
26
|
+
this.flushImpl = options.flushImpl;
|
|
27
|
+
this.shutdownImpl = options.shutdownImpl;
|
|
28
|
+
this.getContext = options.getContext;
|
|
29
|
+
this.deniedKeys = options.deniedKeys ?? [];
|
|
30
|
+
this.ignoreUrlPrefixes = (options.ignoreUrlPrefixes ?? []).filter((p) => typeof p === "string" && p.length > 0);
|
|
31
|
+
}
|
|
32
|
+
ignoreSpanReason(span) {
|
|
33
|
+
const spanFields = span;
|
|
34
|
+
const attrs = spanFields.attributes ?? {};
|
|
35
|
+
if (attrs["cluebase.capture.skip"] === true)
|
|
36
|
+
return "loop";
|
|
37
|
+
const httpUrl = attrs["http.url"];
|
|
38
|
+
const fullUrl = attrs["url.full"];
|
|
39
|
+
const url = typeof httpUrl === "string"
|
|
40
|
+
? httpUrl
|
|
41
|
+
: typeof fullUrl === "string"
|
|
42
|
+
? fullUrl
|
|
43
|
+
: undefined;
|
|
44
|
+
if (url &&
|
|
45
|
+
this.ignoreUrlPrefixes.some((prefix) => url.startsWith(prefix))) {
|
|
46
|
+
return "loop";
|
|
47
|
+
}
|
|
48
|
+
if (!Object.keys(attrs).some((key) => key.startsWith("cluebase."))) {
|
|
49
|
+
return "unmarked";
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Enqueue a source-signal event emitted by capture modules through
|
|
55
|
+
* `context.onEvent`.
|
|
56
|
+
*/
|
|
57
|
+
enqueue(event) {
|
|
58
|
+
if (this.closed)
|
|
59
|
+
return;
|
|
60
|
+
const permit = this.getCollectionPermit?.(null);
|
|
61
|
+
if (this.getCollectionPermit && permit === undefined) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.enqueueImpl(event, permit);
|
|
65
|
+
}
|
|
66
|
+
export(spans, resultCallback) {
|
|
67
|
+
if (this.closed) {
|
|
68
|
+
resultCallback({
|
|
69
|
+
code: ExportResultCode.FAILED,
|
|
70
|
+
error: new Error("exporter_closed"),
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const context = this.getContext();
|
|
76
|
+
for (const span of spans) {
|
|
77
|
+
const ignoreReason = this.ignoreSpanReason(span);
|
|
78
|
+
if (ignoreReason) {
|
|
79
|
+
if (ignoreReason === "loop") {
|
|
80
|
+
this.skippedLoopSpanCount += 1;
|
|
81
|
+
}
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const permit = this.getCollectionPermit?.(span);
|
|
86
|
+
if (this.getCollectionPermit && permit === undefined) {
|
|
87
|
+
this.droppedSpanCount += 1;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const sourceSignal = spanToSourceSignalEvent(span, {
|
|
91
|
+
context,
|
|
92
|
+
deniedKeys: this.deniedKeys,
|
|
93
|
+
});
|
|
94
|
+
this.enqueueImpl(sourceSignal, permit);
|
|
95
|
+
this.exportedSpanCount += 1;
|
|
96
|
+
}
|
|
97
|
+
catch (cause) {
|
|
98
|
+
this.droppedSpanCount += 1;
|
|
99
|
+
logSwallowed("spanToSourceSignalEvent", cause, {
|
|
100
|
+
spanName: span.name,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
void this.flushImpl()
|
|
105
|
+
.then(() => resultCallback({ code: ExportResultCode.SUCCESS }))
|
|
106
|
+
.catch((cause) => resultCallback({
|
|
107
|
+
code: ExportResultCode.FAILED,
|
|
108
|
+
error: cause instanceof Error ? cause : new Error(String(cause)),
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
catch (cause) {
|
|
112
|
+
resultCallback({
|
|
113
|
+
code: ExportResultCode.FAILED,
|
|
114
|
+
error: cause instanceof Error ? cause : new Error(String(cause)),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async forceFlush() {
|
|
119
|
+
if (this.closed)
|
|
120
|
+
return;
|
|
121
|
+
try {
|
|
122
|
+
await this.flushImpl();
|
|
123
|
+
}
|
|
124
|
+
catch (cause) {
|
|
125
|
+
// forceFlush is best-effort; swallow to keep the OTel contract.
|
|
126
|
+
logSwallowed("forceFlush", cause);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async shutdown() {
|
|
130
|
+
if (this.closed)
|
|
131
|
+
return;
|
|
132
|
+
this.closed = true;
|
|
133
|
+
try {
|
|
134
|
+
if (this.shutdownImpl) {
|
|
135
|
+
await this.shutdownImpl();
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
await this.flushImpl();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (cause) {
|
|
142
|
+
// shutdown is non-raising per the OTel contract.
|
|
143
|
+
logSwallowed("shutdown", cause);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
internalMetrics() {
|
|
147
|
+
return {
|
|
148
|
+
exported_span_count: this.exportedSpanCount,
|
|
149
|
+
dropped_span_count: this.droppedSpanCount,
|
|
150
|
+
skipped_loop_span_count: this.skippedLoopSpanCount,
|
|
151
|
+
closed: this.closed,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ParameterSnapshotValue } from "../provider/types.js";
|
|
2
|
+
export type ParameterSnapshotBundle = {
|
|
3
|
+
snapshot: ParameterSnapshotValue;
|
|
4
|
+
allowedValueKeys: string[];
|
|
5
|
+
};
|
|
6
|
+
type ParameterSnapshotOptions = {
|
|
7
|
+
allowedPaths: readonly string[];
|
|
8
|
+
deniedKeys: readonly string[];
|
|
9
|
+
pathPrefix?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const buildUnavailableParameterSnapshot: () => ParameterSnapshotValue;
|
|
12
|
+
export declare const truncateParameterSnapshotValue: (value: ParameterSnapshotValue) => ParameterSnapshotValue;
|
|
13
|
+
export declare const buildParameterSnapshotBundle: (value: unknown, options: ParameterSnapshotOptions) => ParameterSnapshotBundle;
|
|
14
|
+
export {};
|