@kubit-ai/sentry 0.1.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 +17 -0
- package/README.md +132 -0
- package/dist/browser/kubit-sentry.global.js +505 -0
- package/dist/browser.d.ts +60 -0
- package/dist/browser.js +121 -0
- package/dist/browser.js.map +1 -0
- package/dist/config.d.ts +41 -0
- package/dist/config.js +34 -0
- package/dist/config.js.map +1 -0
- package/dist/exporter.d.ts +35 -0
- package/dist/exporter.js +94 -0
- package/dist/exporter.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/integration.d.ts +41 -0
- package/dist/integration.js +97 -0
- package/dist/integration.js.map +1 -0
- package/dist/sentryToOtel.d.ts +23 -0
- package/dist/sentryToOtel.js +316 -0
- package/dist/sentryToOtel.js.map +1 -0
- package/dist/session.d.ts +52 -0
- package/dist/session.js +125 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +67 -0
- package/dist/types.js +13 -0
- package/dist/types.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser entry for `@kubit-ai/sentry`.
|
|
3
|
+
*
|
|
4
|
+
* Bundles `@sentry/browser` together with the Kubit tee into a single IIFE so a
|
|
5
|
+
* site with NO bundler can wire "Sentry + Kubit behavior analytics" from one
|
|
6
|
+
* `<script>` tag (see `scripts/build-browser.mjs` → `dist/browser/`):
|
|
7
|
+
*
|
|
8
|
+
* <script src="kubit-sentry.global.js"></script>
|
|
9
|
+
* <script>
|
|
10
|
+
* KubitSentry.init({
|
|
11
|
+
* dsn: "https://...@sentry.io/...", // your Sentry project
|
|
12
|
+
* apiKey: "rg.v1...", // Kubit ingestion key
|
|
13
|
+
* endpoint: "https://otel.kubit.ai/v1/traces",
|
|
14
|
+
* serviceName: "my-app",
|
|
15
|
+
* environment: "production",
|
|
16
|
+
* });
|
|
17
|
+
* KubitSentry.trackEvent("Product Added", { productId: 1, price: 49.99 });
|
|
18
|
+
* </script>
|
|
19
|
+
*
|
|
20
|
+
* `init` runs `Sentry.init` with browser tracing (pageload / navigation /
|
|
21
|
+
* `http.client` spans — the "page view" and "api call" families) plus the
|
|
22
|
+
* `kubitSentryIntegration` that tees every event Sentry sends to the Kubit
|
|
23
|
+
* collector. `trackEvent` emits a named, zero-duration root transaction (the
|
|
24
|
+
* "clicked / action" family) whose name flows straight through to Kubit.
|
|
25
|
+
*
|
|
26
|
+
* This entry is additive: the CommonJS `index.ts` (bundler-based consumers) is
|
|
27
|
+
* untouched. Only `<script>`-tag consumers use this file.
|
|
28
|
+
*/
|
|
29
|
+
import * as Sentry from "@sentry/browser";
|
|
30
|
+
import type { KubitSentryOptions } from "./config";
|
|
31
|
+
/** Attribute values accepted on a behavior event. */
|
|
32
|
+
type EventAttributeValue = string | number | boolean;
|
|
33
|
+
export interface KubitInitOptions extends KubitSentryOptions {
|
|
34
|
+
/** Sentry DSN for your error/transaction project. Omit to skip Sentry capture. */
|
|
35
|
+
dsn?: string;
|
|
36
|
+
/** `deployment.environment` (e.g. "staging", "production"). */
|
|
37
|
+
environment?: string;
|
|
38
|
+
/** Release identifier — also used as `service.version` when set. */
|
|
39
|
+
release?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Fraction of transactions sampled. Behavior analytics wants full capture, so
|
|
42
|
+
* this defaults to 1.0 — lower it only if Sentry is also your perf tool.
|
|
43
|
+
*/
|
|
44
|
+
tracesSampleRate?: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Initialize Sentry browser + the Kubit tee in one call. Safe to call once per
|
|
48
|
+
* page load. Returns the Sentry namespace for advanced use (manual captures).
|
|
49
|
+
*/
|
|
50
|
+
export declare const init: (options?: KubitInitOptions) => typeof Sentry;
|
|
51
|
+
/**
|
|
52
|
+
* Emit a named behavior event. Creates a zero-duration root transaction
|
|
53
|
+
* (`forceTransaction`) so the Kubit tee exports it as its own OTLP span named
|
|
54
|
+
* exactly `name`, carrying `attributes` as span attributes. No-op (warns) when
|
|
55
|
+
* called before `init()`.
|
|
56
|
+
*/
|
|
57
|
+
export declare const trackEvent: (name: string, attributes?: Record<string, EventAttributeValue>) => void;
|
|
58
|
+
export { Sentry };
|
|
59
|
+
export { kubitSentryIntegration } from "./integration";
|
|
60
|
+
export { createRollingSession, type RollingSessionOptions, type SessionIdProvider, type SessionStorageLike, } from "./session";
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Browser entry for `@kubit-ai/sentry`.
|
|
4
|
+
*
|
|
5
|
+
* Bundles `@sentry/browser` together with the Kubit tee into a single IIFE so a
|
|
6
|
+
* site with NO bundler can wire "Sentry + Kubit behavior analytics" from one
|
|
7
|
+
* `<script>` tag (see `scripts/build-browser.mjs` → `dist/browser/`):
|
|
8
|
+
*
|
|
9
|
+
* <script src="kubit-sentry.global.js"></script>
|
|
10
|
+
* <script>
|
|
11
|
+
* KubitSentry.init({
|
|
12
|
+
* dsn: "https://...@sentry.io/...", // your Sentry project
|
|
13
|
+
* apiKey: "rg.v1...", // Kubit ingestion key
|
|
14
|
+
* endpoint: "https://otel.kubit.ai/v1/traces",
|
|
15
|
+
* serviceName: "my-app",
|
|
16
|
+
* environment: "production",
|
|
17
|
+
* });
|
|
18
|
+
* KubitSentry.trackEvent("Product Added", { productId: 1, price: 49.99 });
|
|
19
|
+
* </script>
|
|
20
|
+
*
|
|
21
|
+
* `init` runs `Sentry.init` with browser tracing (pageload / navigation /
|
|
22
|
+
* `http.client` spans — the "page view" and "api call" families) plus the
|
|
23
|
+
* `kubitSentryIntegration` that tees every event Sentry sends to the Kubit
|
|
24
|
+
* collector. `trackEvent` emits a named, zero-duration root transaction (the
|
|
25
|
+
* "clicked / action" family) whose name flows straight through to Kubit.
|
|
26
|
+
*
|
|
27
|
+
* This entry is additive: the CommonJS `index.ts` (bundler-based consumers) is
|
|
28
|
+
* untouched. Only `<script>`-tag consumers use this file.
|
|
29
|
+
*/
|
|
30
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
31
|
+
if (k2 === undefined) k2 = k;
|
|
32
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
33
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
34
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
35
|
+
}
|
|
36
|
+
Object.defineProperty(o, k2, desc);
|
|
37
|
+
}) : (function(o, m, k, k2) {
|
|
38
|
+
if (k2 === undefined) k2 = k;
|
|
39
|
+
o[k2] = m[k];
|
|
40
|
+
}));
|
|
41
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
42
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
43
|
+
}) : function(o, v) {
|
|
44
|
+
o["default"] = v;
|
|
45
|
+
});
|
|
46
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
47
|
+
var ownKeys = function(o) {
|
|
48
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
49
|
+
var ar = [];
|
|
50
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
51
|
+
return ar;
|
|
52
|
+
};
|
|
53
|
+
return ownKeys(o);
|
|
54
|
+
};
|
|
55
|
+
return function (mod) {
|
|
56
|
+
if (mod && mod.__esModule) return mod;
|
|
57
|
+
var result = {};
|
|
58
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
59
|
+
__setModuleDefault(result, mod);
|
|
60
|
+
return result;
|
|
61
|
+
};
|
|
62
|
+
})();
|
|
63
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
64
|
+
exports.createRollingSession = exports.kubitSentryIntegration = exports.Sentry = exports.trackEvent = exports.init = void 0;
|
|
65
|
+
const Sentry = __importStar(require("@sentry/browser"));
|
|
66
|
+
exports.Sentry = Sentry;
|
|
67
|
+
const integration_1 = require("./integration");
|
|
68
|
+
const session_1 = require("./session");
|
|
69
|
+
let initialized = false;
|
|
70
|
+
/**
|
|
71
|
+
* Initialize Sentry browser + the Kubit tee in one call. Safe to call once per
|
|
72
|
+
* page load. Returns the Sentry namespace for advanced use (manual captures).
|
|
73
|
+
*/
|
|
74
|
+
const init = (options = {}) => {
|
|
75
|
+
const { dsn, environment, release, tracesSampleRate = 1.0, apiKey, endpoint, serviceName, serviceVersion, debug, sessionId, } = options;
|
|
76
|
+
Sentry.init({
|
|
77
|
+
dsn,
|
|
78
|
+
environment,
|
|
79
|
+
release,
|
|
80
|
+
tracesSampleRate,
|
|
81
|
+
integrations: [
|
|
82
|
+
Sentry.browserTracingIntegration(),
|
|
83
|
+
(0, integration_1.kubitSentryIntegration)({
|
|
84
|
+
apiKey,
|
|
85
|
+
endpoint,
|
|
86
|
+
serviceName,
|
|
87
|
+
serviceVersion: serviceVersion ?? release,
|
|
88
|
+
debug,
|
|
89
|
+
// Default to a 30-min rolling session so every browser consumer gets a
|
|
90
|
+
// `session.id` for free. Pass your own `sessionId` (string or function,
|
|
91
|
+
// e.g. createRollingSession({ initialId: ... })) to override.
|
|
92
|
+
sessionId: sessionId ?? (0, session_1.createRollingSession)(),
|
|
93
|
+
}),
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
initialized = true;
|
|
97
|
+
return Sentry;
|
|
98
|
+
};
|
|
99
|
+
exports.init = init;
|
|
100
|
+
/**
|
|
101
|
+
* Emit a named behavior event. Creates a zero-duration root transaction
|
|
102
|
+
* (`forceTransaction`) so the Kubit tee exports it as its own OTLP span named
|
|
103
|
+
* exactly `name`, carrying `attributes` as span attributes. No-op (warns) when
|
|
104
|
+
* called before `init()`.
|
|
105
|
+
*/
|
|
106
|
+
const trackEvent = (name, attributes = {}) => {
|
|
107
|
+
if (!initialized) {
|
|
108
|
+
console.warn("[kubit-sentry] trackEvent called before init()");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
Sentry.startSpan({ name, op: "ui.action", forceTransaction: true, attributes }, () => {
|
|
112
|
+
// Zero-duration: the span starts and ends immediately, producing a
|
|
113
|
+
// standalone transaction event the tee converts to one OTLP span.
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
exports.trackEvent = trackEvent;
|
|
117
|
+
var integration_2 = require("./integration");
|
|
118
|
+
Object.defineProperty(exports, "kubitSentryIntegration", { enumerable: true, get: function () { return integration_2.kubitSentryIntegration; } });
|
|
119
|
+
var session_2 = require("./session");
|
|
120
|
+
Object.defineProperty(exports, "createRollingSession", { enumerable: true, get: function () { return session_2.createRollingSession; } });
|
|
121
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEH,wDAA0C;AAyFjC,wBAAM;AAxFf,+CAAuD;AACvD,uCAAiD;AAoBjD,IAAI,WAAW,GAAG,KAAK,CAAC;AAExB;;;GAGG;AACI,MAAM,IAAI,GAAG,CAAC,UAA4B,EAAE,EAAiB,EAAE;IACpE,MAAM,EACJ,GAAG,EACH,WAAW,EACX,OAAO,EACP,gBAAgB,GAAG,GAAG,EACtB,MAAM,EACN,QAAQ,EACR,WAAW,EACX,cAAc,EACd,KAAK,EACL,SAAS,GACV,GAAG,OAAO,CAAC;IAEZ,MAAM,CAAC,IAAI,CAAC;QACV,GAAG;QACH,WAAW;QACX,OAAO;QACP,gBAAgB;QAChB,YAAY,EAAE;YACZ,MAAM,CAAC,yBAAyB,EAAE;YAClC,IAAA,oCAAsB,EAAC;gBACrB,MAAM;gBACN,QAAQ;gBACR,WAAW;gBACX,cAAc,EAAE,cAAc,IAAI,OAAO;gBACzC,KAAK;gBACL,uEAAuE;gBACvE,wEAAwE;gBACxE,8DAA8D;gBAC9D,SAAS,EAAE,SAAS,IAAI,IAAA,8BAAoB,GAAE;aAC/C,CAAC;SACH;KACF,CAAC,CAAC;IACH,WAAW,GAAG,IAAI,CAAC;IACnB,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AApCW,QAAA,IAAI,QAoCf;AAEF;;;;;GAKG;AACI,MAAM,UAAU,GAAG,CACxB,IAAY,EACZ,aAAkD,EAAE,EAC9C,EAAE;IACR,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,CAAC,IAAI,CAAC,gDAAgD,CAAC,CAAC;QAC/D,OAAO;IACT,CAAC;IACD,MAAM,CAAC,SAAS,CACd,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,gBAAgB,EAAE,IAAI,EAAE,UAAU,EAAE,EAC7D,GAAG,EAAE;QACH,mEAAmE;QACnE,kEAAkE;IACpE,CAAC,CACF,CAAC;AACJ,CAAC,CAAC;AAfW,QAAA,UAAU,cAerB;AAGF,6CAAuD;AAA9C,qHAAA,sBAAsB,OAAA;AAC/B,qCAKmB;AAJjB,+GAAA,oBAAoB,OAAA"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration resolution for the Kubit Sentry SDK.
|
|
3
|
+
*
|
|
4
|
+
* Precedence: explicit options > environment variables > built-in default.
|
|
5
|
+
* Env reads are guarded so the SDK also works in browser bundles where
|
|
6
|
+
* `process` is undefined (bundlers typically inline `process.env.FOO` at
|
|
7
|
+
* build time, but a runtime guard avoids a ReferenceError when they don't).
|
|
8
|
+
*/
|
|
9
|
+
/** Prod ingestion endpoint — same OTLP/HTTP entrypoint the LLM SDK uses. */
|
|
10
|
+
export declare const DEFAULT_ENDPOINT = "https://otel.kubit.ai/v1/traces";
|
|
11
|
+
/** User-facing options accepted by the integration / hook factories. */
|
|
12
|
+
export interface KubitSentryOptions {
|
|
13
|
+
/** Workspace ingestion key (`rg.v1.*`). Falls back to `KUBIT_OTEL_API_KEY`. */
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
/** OTLP/HTTP traces endpoint. Falls back to `KUBIT_OTEL_ENDPOINT`. */
|
|
16
|
+
endpoint?: string;
|
|
17
|
+
/** `service.name` resource attribute. Falls back to `KUBIT_SERVICE_NAME`. */
|
|
18
|
+
serviceName?: string;
|
|
19
|
+
/** `service.version` resource attribute. Falls back to the event `release`. */
|
|
20
|
+
serviceVersion?: string;
|
|
21
|
+
/** When true, failed exports are logged via `console.warn`. */
|
|
22
|
+
debug?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Session id source. A string, or a function called per teed event to read
|
|
25
|
+
* the current id (e.g. `createRollingSession(...)`). When set, every exported
|
|
26
|
+
* span carries a `session.id` attribute. The browser `init()` defaults this to
|
|
27
|
+
* a 30-min rolling session; elsewhere, omit it for no session id.
|
|
28
|
+
*/
|
|
29
|
+
sessionId?: string | (() => string | undefined);
|
|
30
|
+
}
|
|
31
|
+
/** Fully-resolved config consumed by the transform + exporter. */
|
|
32
|
+
export interface KubitSentryConfig {
|
|
33
|
+
apiKey: string;
|
|
34
|
+
endpoint: string;
|
|
35
|
+
serviceName?: string;
|
|
36
|
+
serviceVersion?: string;
|
|
37
|
+
debug: boolean;
|
|
38
|
+
/** Resolved session-id reader; absent when no session id was configured. */
|
|
39
|
+
getSessionId?: () => string | undefined;
|
|
40
|
+
}
|
|
41
|
+
export declare const resolveConfig: (options?: KubitSentryOptions) => KubitSentryConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration resolution for the Kubit Sentry SDK.
|
|
4
|
+
*
|
|
5
|
+
* Precedence: explicit options > environment variables > built-in default.
|
|
6
|
+
* Env reads are guarded so the SDK also works in browser bundles where
|
|
7
|
+
* `process` is undefined (bundlers typically inline `process.env.FOO` at
|
|
8
|
+
* build time, but a runtime guard avoids a ReferenceError when they don't).
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.resolveConfig = exports.DEFAULT_ENDPOINT = void 0;
|
|
12
|
+
/** Prod ingestion endpoint — same OTLP/HTTP entrypoint the LLM SDK uses. */
|
|
13
|
+
exports.DEFAULT_ENDPOINT = "https://otel.kubit.ai/v1/traces";
|
|
14
|
+
const readEnv = (key) => typeof process !== "undefined" && process.env ? process.env[key] : undefined;
|
|
15
|
+
/** Normalize the `sessionId` option (string | function | undefined) to a reader. */
|
|
16
|
+
const resolveSessionProvider = (sessionId) => {
|
|
17
|
+
if (sessionId === undefined) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
if (typeof sessionId === "function") {
|
|
21
|
+
return sessionId;
|
|
22
|
+
}
|
|
23
|
+
return () => sessionId;
|
|
24
|
+
};
|
|
25
|
+
const resolveConfig = (options = {}) => ({
|
|
26
|
+
apiKey: options.apiKey ?? readEnv("KUBIT_OTEL_API_KEY") ?? "",
|
|
27
|
+
endpoint: options.endpoint ?? readEnv("KUBIT_OTEL_ENDPOINT") ?? exports.DEFAULT_ENDPOINT,
|
|
28
|
+
serviceName: options.serviceName ?? readEnv("KUBIT_SERVICE_NAME"),
|
|
29
|
+
serviceVersion: options.serviceVersion,
|
|
30
|
+
debug: options.debug ?? false,
|
|
31
|
+
getSessionId: resolveSessionProvider(options.sessionId),
|
|
32
|
+
});
|
|
33
|
+
exports.resolveConfig = resolveConfig;
|
|
34
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAEH,4EAA4E;AAC/D,QAAA,gBAAgB,GAAG,iCAAiC,CAAC;AAkClE,MAAM,OAAO,GAAG,CAAC,GAAW,EAAsB,EAAE,CAClD,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAE/E,oFAAoF;AACpF,MAAM,sBAAsB,GAAG,CAC7B,SAA0C,EACF,EAAE;IAC1C,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,SAAS,KAAK,UAAU,EAAE,CAAC;QACpC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;AACzB,CAAC,CAAC;AAEK,MAAM,aAAa,GAAG,CAC3B,UAA8B,EAAE,EACb,EAAE,CAAC,CAAC;IACvB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,oBAAoB,CAAC,IAAI,EAAE;IAC7D,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,qBAAqB,CAAC,IAAI,wBAAgB;IAChF,WAAW,EAAE,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,oBAAoB,CAAC;IACjE,cAAc,EAAE,OAAO,CAAC,cAAc;IACtC,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,KAAK;IAC7B,YAAY,EAAE,sBAAsB,CAAC,OAAO,CAAC,SAAS,CAAC;CACxD,CAAC,CAAC;AATU,QAAA,aAAa,iBASvB"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-rolled OTLP/HTTP+JSON exporter targeting the Kubit collector
|
|
3
|
+
* (`POST {endpoint}`, `x-api-key` header).
|
|
4
|
+
*
|
|
5
|
+
* Collector contract (OTLP/HTTP):
|
|
6
|
+
* POST {endpoint} — e.g. https://otel.kubit.ai/v1/traces
|
|
7
|
+
* Headers:
|
|
8
|
+
* x-api-key: {key} (required, missing -> 401)
|
|
9
|
+
* Content-Type: application/json
|
|
10
|
+
* Body: standard OTLP `ExportTraceServiceRequest` JSON
|
|
11
|
+
*
|
|
12
|
+
* Response:
|
|
13
|
+
* 200 + body { partial_success?: { rejected_spans, error_message } }
|
|
14
|
+
* 401 -> bad / missing key
|
|
15
|
+
* 415 -> wrong Content-Type
|
|
16
|
+
* 400 -> malformed body
|
|
17
|
+
* 413 -> body too large
|
|
18
|
+
* 503 -> transient (queue full) — has Retry-After
|
|
19
|
+
*/
|
|
20
|
+
import type { KubitSentryConfig } from "./config";
|
|
21
|
+
import type { OtlpExportRequest } from "./types";
|
|
22
|
+
export interface PostResult {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
/** HTTP status, or 0 on network failure. */
|
|
25
|
+
status: number;
|
|
26
|
+
/** Human-readable explanation, populated on failure or partial success. */
|
|
27
|
+
message?: string;
|
|
28
|
+
/** Number of spans rejected by the collector (200 with partial_success). */
|
|
29
|
+
rejectedSpans?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Fire-and-forget OTLP POST. Resolves with the result; never throws — callers
|
|
33
|
+
* (Sentry `afterSendEvent` / `beforeSend`) cannot tolerate exceptions.
|
|
34
|
+
*/
|
|
35
|
+
export declare const postOtlp: (payload: OtlpExportRequest, config: Pick<KubitSentryConfig, "endpoint" | "apiKey">) => Promise<PostResult>;
|
package/dist/exporter.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Hand-rolled OTLP/HTTP+JSON exporter targeting the Kubit collector
|
|
4
|
+
* (`POST {endpoint}`, `x-api-key` header).
|
|
5
|
+
*
|
|
6
|
+
* Collector contract (OTLP/HTTP):
|
|
7
|
+
* POST {endpoint} — e.g. https://otel.kubit.ai/v1/traces
|
|
8
|
+
* Headers:
|
|
9
|
+
* x-api-key: {key} (required, missing -> 401)
|
|
10
|
+
* Content-Type: application/json
|
|
11
|
+
* Body: standard OTLP `ExportTraceServiceRequest` JSON
|
|
12
|
+
*
|
|
13
|
+
* Response:
|
|
14
|
+
* 200 + body { partial_success?: { rejected_spans, error_message } }
|
|
15
|
+
* 401 -> bad / missing key
|
|
16
|
+
* 415 -> wrong Content-Type
|
|
17
|
+
* 400 -> malformed body
|
|
18
|
+
* 413 -> body too large
|
|
19
|
+
* 503 -> transient (queue full) — has Retry-After
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.postOtlp = void 0;
|
|
23
|
+
const FETCH_TIMEOUT_MS = 10_000;
|
|
24
|
+
/**
|
|
25
|
+
* Browsers cap the aggregate in-flight `keepalive` body at ~64 KB and reject
|
|
26
|
+
* larger requests outright, so keepalive is only set for small payloads (and
|
|
27
|
+
* only in browsers — Node fetch implementations may not support the flag).
|
|
28
|
+
*/
|
|
29
|
+
const KEEPALIVE_MAX_BODY_BYTES = 60_000;
|
|
30
|
+
/**
|
|
31
|
+
* Fire-and-forget OTLP POST. Resolves with the result; never throws — callers
|
|
32
|
+
* (Sentry `afterSendEvent` / `beforeSend`) cannot tolerate exceptions.
|
|
33
|
+
*/
|
|
34
|
+
const postOtlp = async (payload, config) => {
|
|
35
|
+
let response;
|
|
36
|
+
try {
|
|
37
|
+
const body = JSON.stringify(payload);
|
|
38
|
+
const init = {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "application/json",
|
|
42
|
+
"x-api-key": config.apiKey,
|
|
43
|
+
},
|
|
44
|
+
body,
|
|
45
|
+
};
|
|
46
|
+
if (typeof AbortSignal !== "undefined" &&
|
|
47
|
+
typeof AbortSignal.timeout === "function") {
|
|
48
|
+
init.signal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
|
|
49
|
+
}
|
|
50
|
+
if (typeof document !== "undefined" && body.length < KEEPALIVE_MAX_BODY_BYTES) {
|
|
51
|
+
// Lets events fired near page unload survive navigation.
|
|
52
|
+
init.keepalive = true;
|
|
53
|
+
}
|
|
54
|
+
response = await fetch(config.endpoint, init);
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
const message = e instanceof Error ? e.message : "unknown network error";
|
|
58
|
+
return { ok: false, status: 0, message };
|
|
59
|
+
}
|
|
60
|
+
if (response.ok) {
|
|
61
|
+
let body = null;
|
|
62
|
+
try {
|
|
63
|
+
body = (await response.json());
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// empty body or non-JSON — treat as full success
|
|
67
|
+
return { ok: true, status: response.status };
|
|
68
|
+
}
|
|
69
|
+
const rejectedRaw = body?.partial_success?.rejected_spans;
|
|
70
|
+
const rejected = typeof rejectedRaw === "string" ? Number(rejectedRaw) : rejectedRaw;
|
|
71
|
+
if (typeof rejected === "number" && rejected > 0) {
|
|
72
|
+
return {
|
|
73
|
+
ok: true,
|
|
74
|
+
status: response.status,
|
|
75
|
+
rejectedSpans: rejected,
|
|
76
|
+
message: body?.partial_success?.error_message,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
return { ok: true, status: response.status };
|
|
80
|
+
}
|
|
81
|
+
let message = `HTTP ${response.status}`;
|
|
82
|
+
try {
|
|
83
|
+
const text = await response.text();
|
|
84
|
+
if (text.length > 0) {
|
|
85
|
+
message = text.length > 200 ? `${text.slice(0, 200)}…` : text;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// ignore — fall back to status-only message
|
|
90
|
+
}
|
|
91
|
+
return { ok: false, status: response.status, message };
|
|
92
|
+
};
|
|
93
|
+
exports.postOtlp = postOtlp;
|
|
94
|
+
//# sourceMappingURL=exporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exporter.js","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @kubit-ai/sentry — tee Sentry events (errors + transactions) to Kubit as
|
|
3
|
+
* OTLP for behavior analytics.
|
|
4
|
+
*
|
|
5
|
+
* Usage (idiomatic):
|
|
6
|
+
* import * as Sentry from "@sentry/react";
|
|
7
|
+
* import { kubitSentryIntegration } from "@kubit-ai/sentry";
|
|
8
|
+
*
|
|
9
|
+
* Sentry.init({
|
|
10
|
+
* dsn: "https://...@sentry.io/...",
|
|
11
|
+
* integrations: [
|
|
12
|
+
* kubitSentryIntegration({ apiKey: "rg.v1...", serviceName: "my-app" }),
|
|
13
|
+
* ],
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* `apiKey` is the workspace ingestion key you mint in the Kubit app; pass it as
|
|
17
|
+
* an option (required in the browser) or set `KUBIT_OTEL_API_KEY` in Node. The
|
|
18
|
+
* endpoint defaults to `https://otel.kubit.ai/v1/traces` (override via `endpoint`
|
|
19
|
+
* or `KUBIT_OTEL_ENDPOINT` only for a non-prod Kubit env).
|
|
20
|
+
*
|
|
21
|
+
* This SDK does NOT replace Sentry — it runs alongside it and ships a copy of
|
|
22
|
+
* each event to Kubit so the data can be modeled and displayed in the Kubit
|
|
23
|
+
* product.
|
|
24
|
+
*/
|
|
25
|
+
export { kubitSentryIntegration } from "./integration";
|
|
26
|
+
export { resolveConfig, DEFAULT_ENDPOINT, type KubitSentryOptions, type KubitSentryConfig, } from "./config";
|
|
27
|
+
export { createRollingSession, type RollingSessionOptions, type SessionIdProvider, type SessionStorageLike, } from "./session";
|
|
28
|
+
export { sentryEventToOtlp, sentryTransactionToOtlp, sentryErrorToOtlp, } from "./sentryToOtel";
|
|
29
|
+
export { postOtlp, type PostResult } from "./exporter";
|
|
30
|
+
export type { OtlpExportRequest, OtlpSpan, OtlpSpanEvent, OtlpKeyValue, OtlpAnyValue, } from "./types";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @kubit-ai/sentry — tee Sentry events (errors + transactions) to Kubit as
|
|
4
|
+
* OTLP for behavior analytics.
|
|
5
|
+
*
|
|
6
|
+
* Usage (idiomatic):
|
|
7
|
+
* import * as Sentry from "@sentry/react";
|
|
8
|
+
* import { kubitSentryIntegration } from "@kubit-ai/sentry";
|
|
9
|
+
*
|
|
10
|
+
* Sentry.init({
|
|
11
|
+
* dsn: "https://...@sentry.io/...",
|
|
12
|
+
* integrations: [
|
|
13
|
+
* kubitSentryIntegration({ apiKey: "rg.v1...", serviceName: "my-app" }),
|
|
14
|
+
* ],
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* `apiKey` is the workspace ingestion key you mint in the Kubit app; pass it as
|
|
18
|
+
* an option (required in the browser) or set `KUBIT_OTEL_API_KEY` in Node. The
|
|
19
|
+
* endpoint defaults to `https://otel.kubit.ai/v1/traces` (override via `endpoint`
|
|
20
|
+
* or `KUBIT_OTEL_ENDPOINT` only for a non-prod Kubit env).
|
|
21
|
+
*
|
|
22
|
+
* This SDK does NOT replace Sentry — it runs alongside it and ships a copy of
|
|
23
|
+
* each event to Kubit so the data can be modeled and displayed in the Kubit
|
|
24
|
+
* product.
|
|
25
|
+
*/
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.postOtlp = exports.sentryErrorToOtlp = exports.sentryTransactionToOtlp = exports.sentryEventToOtlp = exports.createRollingSession = exports.DEFAULT_ENDPOINT = exports.resolveConfig = exports.kubitSentryIntegration = void 0;
|
|
28
|
+
var integration_1 = require("./integration");
|
|
29
|
+
Object.defineProperty(exports, "kubitSentryIntegration", { enumerable: true, get: function () { return integration_1.kubitSentryIntegration; } });
|
|
30
|
+
var config_1 = require("./config");
|
|
31
|
+
Object.defineProperty(exports, "resolveConfig", { enumerable: true, get: function () { return config_1.resolveConfig; } });
|
|
32
|
+
Object.defineProperty(exports, "DEFAULT_ENDPOINT", { enumerable: true, get: function () { return config_1.DEFAULT_ENDPOINT; } });
|
|
33
|
+
var session_1 = require("./session");
|
|
34
|
+
Object.defineProperty(exports, "createRollingSession", { enumerable: true, get: function () { return session_1.createRollingSession; } });
|
|
35
|
+
var sentryToOtel_1 = require("./sentryToOtel");
|
|
36
|
+
Object.defineProperty(exports, "sentryEventToOtlp", { enumerable: true, get: function () { return sentryToOtel_1.sentryEventToOtlp; } });
|
|
37
|
+
Object.defineProperty(exports, "sentryTransactionToOtlp", { enumerable: true, get: function () { return sentryToOtel_1.sentryTransactionToOtlp; } });
|
|
38
|
+
Object.defineProperty(exports, "sentryErrorToOtlp", { enumerable: true, get: function () { return sentryToOtel_1.sentryErrorToOtlp; } });
|
|
39
|
+
var exporter_1 = require("./exporter");
|
|
40
|
+
Object.defineProperty(exports, "postOtlp", { enumerable: true, get: function () { return exporter_1.postOtlp; } });
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;;;AAEH,6CAAuD;AAA9C,qHAAA,sBAAsB,OAAA;AAC/B,mCAKkB;AAJhB,uGAAA,aAAa,OAAA;AACb,0GAAA,gBAAgB,OAAA;AAIlB,qCAKmB;AAJjB,+GAAA,oBAAoB,OAAA;AAKtB,+CAIwB;AAHtB,iHAAA,iBAAiB,OAAA;AACjB,uHAAA,uBAAuB,OAAA;AACvB,iHAAA,iBAAiB,OAAA;AAEnB,uCAAuD;AAA9C,oGAAA,QAAQ,OAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public wiring for teeing Sentry events into Kubit.
|
|
3
|
+
*
|
|
4
|
+
* `kubitSentryIntegration(options)` — the idiomatic Sentry v8+ functional
|
|
5
|
+
* integration. Add it to `Sentry.init({ integrations: [...] })`. It registers
|
|
6
|
+
* on the client's single `afterSendEvent` hook, which covers BOTH error and
|
|
7
|
+
* transaction events, so Kubit receives a copy of exactly what Sentry sends —
|
|
8
|
+
* after sampling, event processors, and the app's own `beforeSend` /
|
|
9
|
+
* `beforeSendTransaction` scrubbing. Events dropped before sending never reach
|
|
10
|
+
* Kubit. The integration is non-destructive: it never drops or mutates the
|
|
11
|
+
* event Sentry sends.
|
|
12
|
+
*
|
|
13
|
+
* `teeEvent` does the work: translate -> fire-and-forget OTLP POST. Export
|
|
14
|
+
* errors are swallowed (optionally logged when `debug: true`) so a failing
|
|
15
|
+
* Kubit endpoint can never break the host app's Sentry pipeline.
|
|
16
|
+
*/
|
|
17
|
+
import type { Integration } from "@sentry/core";
|
|
18
|
+
import { type KubitSentryOptions } from "./config";
|
|
19
|
+
/**
|
|
20
|
+
* Sentry functional integration. Usage:
|
|
21
|
+
*
|
|
22
|
+
* Sentry.init({
|
|
23
|
+
* dsn: "...",
|
|
24
|
+
* environment: "production",
|
|
25
|
+
* integrations: [
|
|
26
|
+
* kubitSentryIntegration({ apiKey: "rg.v1...", serviceName: "my-app" }),
|
|
27
|
+
* ],
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* `apiKey` is the Kubit workspace ingestion key (required; pass it as an option
|
|
31
|
+
* — mandatory in the browser — or set `KUBIT_OTEL_API_KEY` in Node). `endpoint`
|
|
32
|
+
* defaults to the prod collector (override via `endpoint` / `KUBIT_OTEL_ENDPOINT`
|
|
33
|
+
* only for a non-prod Kubit env). `environment` is a Sentry.init field, not a
|
|
34
|
+
* Kubit option — the tee copies the event's environment to
|
|
35
|
+
* `deployment.environment`.
|
|
36
|
+
*
|
|
37
|
+
* Tees on `afterSendEvent` (not `processEvent`) so the copy sent to Kubit
|
|
38
|
+
* reflects sampling, event processors, and the app's `beforeSend` scrubbing —
|
|
39
|
+
* events Sentry drops are never exported.
|
|
40
|
+
*/
|
|
41
|
+
export declare const kubitSentryIntegration: (options?: KubitSentryOptions) => Integration;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Public wiring for teeing Sentry events into Kubit.
|
|
4
|
+
*
|
|
5
|
+
* `kubitSentryIntegration(options)` — the idiomatic Sentry v8+ functional
|
|
6
|
+
* integration. Add it to `Sentry.init({ integrations: [...] })`. It registers
|
|
7
|
+
* on the client's single `afterSendEvent` hook, which covers BOTH error and
|
|
8
|
+
* transaction events, so Kubit receives a copy of exactly what Sentry sends —
|
|
9
|
+
* after sampling, event processors, and the app's own `beforeSend` /
|
|
10
|
+
* `beforeSendTransaction` scrubbing. Events dropped before sending never reach
|
|
11
|
+
* Kubit. The integration is non-destructive: it never drops or mutates the
|
|
12
|
+
* event Sentry sends.
|
|
13
|
+
*
|
|
14
|
+
* `teeEvent` does the work: translate -> fire-and-forget OTLP POST. Export
|
|
15
|
+
* errors are swallowed (optionally logged when `debug: true`) so a failing
|
|
16
|
+
* Kubit endpoint can never break the host app's Sentry pipeline.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.kubitSentryIntegration = void 0;
|
|
20
|
+
const config_1 = require("./config");
|
|
21
|
+
const exporter_1 = require("./exporter");
|
|
22
|
+
const sentryToOtel_1 = require("./sentryToOtel");
|
|
23
|
+
const INTEGRATION_NAME = "KubitSentry";
|
|
24
|
+
const teeEvent = (event, config) => {
|
|
25
|
+
if (!config.apiKey) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Only tee error and transaction events; ignore replay/profile/feedback/etc.
|
|
29
|
+
if (event.type !== undefined && event.type !== "transaction") {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Resolved here, at the impure boundary, so the transform stays pure: a
|
|
33
|
+
// rolling session reads the clock / storage on each tee.
|
|
34
|
+
const sessionId = config.getSessionId?.();
|
|
35
|
+
let payload;
|
|
36
|
+
try {
|
|
37
|
+
payload = (0, sentryToOtel_1.sentryEventToOtlp)(event, config, sessionId);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if ((payload.resourceSpans[0]?.scopeSpans[0]?.spans.length ?? 0) === 0) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
void (0, exporter_1.postOtlp)(payload, config)
|
|
46
|
+
.then((result) => {
|
|
47
|
+
if (!config.debug) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const spans = payload.resourceSpans[0]?.scopeSpans[0]?.spans ?? [];
|
|
51
|
+
const names = spans.map((s) => s.name);
|
|
52
|
+
if (result.ok) {
|
|
53
|
+
console.info(`[kubit-sentry] sent ${spans.length} span(s) → ${config.endpoint} [${result.status}]`, names);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.warn(`[kubit-sentry] export failed: ${result.status} ${result.message ?? ""}`, names);
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
.catch(() => {
|
|
60
|
+
// postOtlp never throws; defend against it anyway.
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Sentry functional integration. Usage:
|
|
65
|
+
*
|
|
66
|
+
* Sentry.init({
|
|
67
|
+
* dsn: "...",
|
|
68
|
+
* environment: "production",
|
|
69
|
+
* integrations: [
|
|
70
|
+
* kubitSentryIntegration({ apiKey: "rg.v1...", serviceName: "my-app" }),
|
|
71
|
+
* ],
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* `apiKey` is the Kubit workspace ingestion key (required; pass it as an option
|
|
75
|
+
* — mandatory in the browser — or set `KUBIT_OTEL_API_KEY` in Node). `endpoint`
|
|
76
|
+
* defaults to the prod collector (override via `endpoint` / `KUBIT_OTEL_ENDPOINT`
|
|
77
|
+
* only for a non-prod Kubit env). `environment` is a Sentry.init field, not a
|
|
78
|
+
* Kubit option — the tee copies the event's environment to
|
|
79
|
+
* `deployment.environment`.
|
|
80
|
+
*
|
|
81
|
+
* Tees on `afterSendEvent` (not `processEvent`) so the copy sent to Kubit
|
|
82
|
+
* reflects sampling, event processors, and the app's `beforeSend` scrubbing —
|
|
83
|
+
* events Sentry drops are never exported.
|
|
84
|
+
*/
|
|
85
|
+
const kubitSentryIntegration = (options = {}) => {
|
|
86
|
+
const config = (0, config_1.resolveConfig)(options);
|
|
87
|
+
return {
|
|
88
|
+
name: INTEGRATION_NAME,
|
|
89
|
+
setup(client) {
|
|
90
|
+
client.on("afterSendEvent", (event) => {
|
|
91
|
+
teeEvent(event, config);
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
exports.kubitSentryIntegration = kubitSentryIntegration;
|
|
97
|
+
//# sourceMappingURL=integration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.js","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAGH,qCAIkB;AAClB,yCAAsC;AACtC,iDAAmD;AAEnD,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAEvC,MAAM,QAAQ,GAAG,CAAC,KAAY,EAAE,MAAyB,EAAQ,EAAE;IACjE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO;IACT,CAAC;IACD,6EAA6E;IAC7E,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;QAC7D,OAAO;IACT,CAAC;IAED,wEAAwE;IACxE,yDAAyD;IACzD,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,EAAE,EAAE,CAAC;IAE1C,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,IAAA,gCAAiB,EAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QACvE,OAAO;IACT,CAAC;IAED,KAAK,IAAA,mBAAQ,EAAC,OAAO,EAAE,MAAM,CAAC;SAC3B,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;QACf,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;QACnE,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACd,OAAO,CAAC,IAAI,CACV,uBAAuB,KAAK,CAAC,MAAM,cAAc,MAAM,CAAC,QAAQ,KAAK,MAAM,CAAC,MAAM,GAAG,EACrF,KAAK,CACN,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CACV,iCAAiC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,EAAE,EACxE,KAAK,CACN,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;SACD,KAAK,CAAC,GAAG,EAAE;QACV,mDAAmD;IACrD,CAAC,CAAC,CAAC;AACP,CAAC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACI,MAAM,sBAAsB,GAAG,CACpC,UAA8B,EAAE,EACnB,EAAE;IACf,MAAM,MAAM,GAAG,IAAA,sBAAa,EAAC,OAAO,CAAC,CAAC;IACtC,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,KAAK,CAAC,MAAM;YACV,MAAM,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,EAAE;gBACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;YAC1B,CAAC,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAZW,QAAA,sBAAsB,0BAYjC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentry `Event` -> OTLP/HTTP+JSON (`ExportTraceServiceRequest`).
|
|
3
|
+
*
|
|
4
|
+
* Two entry points behind one dispatcher (`sentryEventToOtlp`):
|
|
5
|
+
* - transactions (`event.type === 'transaction'`) -> spans (root from
|
|
6
|
+
* `contexts.trace`, one child per `event.spans[]`).
|
|
7
|
+
* - errors (everything else) -> a single span carrying an `exception` span
|
|
8
|
+
* event per `exception.values[]`, with `status.code = ERROR`.
|
|
9
|
+
*
|
|
10
|
+
* Pure functions — no Sentry SDK calls, no I/O, no randomness. Error events
|
|
11
|
+
* without a trace context are anchored on the Sentry `event_id` (a 32-hex
|
|
12
|
+
* string, valid as an OTLP trace id) so the transform stays deterministic.
|
|
13
|
+
*/
|
|
14
|
+
import type { Event, TransactionEvent } from "@sentry/core";
|
|
15
|
+
import type { OtlpExportRequest } from "./types";
|
|
16
|
+
import type { KubitSentryConfig } from "./config";
|
|
17
|
+
export declare const sentryTransactionToOtlp: (event: TransactionEvent, config: KubitSentryConfig, sessionId?: string) => OtlpExportRequest;
|
|
18
|
+
export declare const sentryErrorToOtlp: (event: Event, config: KubitSentryConfig, sessionId?: string) => OtlpExportRequest;
|
|
19
|
+
/**
|
|
20
|
+
* Dispatcher: route a Sentry event to the right transform by `event.type`.
|
|
21
|
+
* Transactions carry `type: 'transaction'`; error events leave it undefined.
|
|
22
|
+
*/
|
|
23
|
+
export declare const sentryEventToOtlp: (event: Event, config: KubitSentryConfig, sessionId?: string) => OtlpExportRequest;
|