@rootcat/node 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/LICENSE +202 -0
  3. package/README.md +294 -2
  4. package/dist/attach.d.ts +1 -0
  5. package/dist/attach.js +24 -0
  6. package/dist/attach.js.map +1 -0
  7. package/dist/callsite.d.ts +109 -0
  8. package/dist/callsite.js +565 -0
  9. package/dist/callsite.js.map +1 -0
  10. package/dist/coexist.d.ts +183 -0
  11. package/dist/coexist.js +587 -0
  12. package/dist/coexist.js.map +1 -0
  13. package/dist/config.d.ts +75 -0
  14. package/dist/config.js +182 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/drizzle.d.ts +21 -0
  17. package/dist/drizzle.js +127 -0
  18. package/dist/drizzle.js.map +1 -0
  19. package/dist/esm.mjs +57 -0
  20. package/dist/esmhook.d.ts +52 -0
  21. package/dist/esmhook.js +158 -0
  22. package/dist/esmhook.js.map +1 -0
  23. package/dist/iitm-hook.mjs +83 -0
  24. package/dist/index.d.ts +12 -0
  25. package/dist/index.js +52 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/intake.d.ts +40 -0
  28. package/dist/intake.js +164 -0
  29. package/dist/intake.js.map +1 -0
  30. package/dist/keys.d.ts +20 -0
  31. package/dist/keys.js +33 -0
  32. package/dist/keys.js.map +1 -0
  33. package/dist/notices.d.ts +2 -0
  34. package/dist/notices.js +21 -0
  35. package/dist/notices.js.map +1 -0
  36. package/dist/otlp.d.ts +75 -0
  37. package/dist/otlp.js +449 -0
  38. package/dist/otlp.js.map +1 -0
  39. package/dist/pg.d.ts +16 -0
  40. package/dist/pg.js +170 -0
  41. package/dist/pg.js.map +1 -0
  42. package/dist/pgpool.d.ts +20 -0
  43. package/dist/pgpool.js +99 -0
  44. package/dist/pgpool.js.map +1 -0
  45. package/dist/postgresjs.d.ts +60 -0
  46. package/dist/postgresjs.js +400 -0
  47. package/dist/postgresjs.js.map +1 -0
  48. package/dist/privacy.d.ts +38 -0
  49. package/dist/privacy.js +161 -0
  50. package/dist/privacy.js.map +1 -0
  51. package/dist/processor.d.ts +197 -0
  52. package/dist/processor.js +494 -0
  53. package/dist/processor.js.map +1 -0
  54. package/dist/register.d.ts +1 -0
  55. package/dist/register.js +12 -0
  56. package/dist/register.js.map +1 -0
  57. package/dist/sampler.d.ts +30 -0
  58. package/dist/sampler.js +101 -0
  59. package/dist/sampler.js.map +1 -0
  60. package/dist/sdk.d.ts +129 -0
  61. package/dist/sdk.js +606 -0
  62. package/dist/sdk.js.map +1 -0
  63. package/package.json +72 -4
@@ -0,0 +1,197 @@
1
+ import { type Attributes, type Context, type Span as ApiSpan, type SpanStatus } from '@opentelemetry/api';
2
+ import type { Resource } from '@opentelemetry/resources';
3
+ import type { ReadableSpan, Span, SpanProcessor } from '@opentelemetry/sdk-trace-base';
4
+ import { type CallSiteLocation } from './callsite.js';
5
+ import { type StatementMode } from './privacy.js';
6
+ export { callSiteContextKey } from './keys.js';
7
+ /**
8
+ * CallSiteSampling is the gate every capture goes through: the
9
+ * processor's own and the entry wrappers' (pg, pg pool, Drizzle,
10
+ * postgres.js), so a capture is never paid for a span that will not be
11
+ * exported or that the one-in-N setting skips.
12
+ */
13
+ export declare class CallSiteSampling {
14
+ /** sample is the one-in-N setting; the benchmark changes it between blocks. */
15
+ sample: number;
16
+ /** ratio is the trace sampling share; the benchmark changes it between blocks. */
17
+ ratio: number;
18
+ private counter;
19
+ /** sample is ROOTCAT_CALLSITE_SAMPLE; ratio is ROOTCAT_SAMPLE. */
20
+ constructor(sample: number, ratio?: number);
21
+ /** take counts one occasion and reports whether it captures. */
22
+ take(): boolean;
23
+ /** on reports whether any capture happens at all. */
24
+ get on(): boolean;
25
+ /**
26
+ * carry is what an entry wrapper calls before the operation it wraps:
27
+ * it returns the context to run the operation in, or undefined to run
28
+ * it as it is. Under an unrecorded span nothing happens. Outside any
29
+ * span with a ratio below 1, the trace decision is made here (the
30
+ * sampler follows it), and an unsampled operation captures nothing. A
31
+ * site already carried by an outer layer of the same operation stands,
32
+ * unless override (a capture that costs nothing, such as postgres.js's
33
+ * own origin, and names the operation more exactly). Otherwise, one
34
+ * occasion in N, capture() runs and its site is carried.
35
+ */
36
+ carry(capture: () => CallSiteLocation | undefined, override?: boolean): Context | undefined;
37
+ }
38
+ /**
39
+ * recorded reports whether a span in this process is part of a recorded
40
+ * trace: it is recording (beside an errors-only Sentry the package
41
+ * records spans without the sampled flag, see RecordOnlySampler), or it
42
+ * carries the flag (a span context an application wrapped itself, to
43
+ * continue a trace from a queued job).
44
+ */
45
+ export declare function recorded(span: ApiSpan): boolean;
46
+ /**
47
+ * droppedMark is the trace-state key on the spans the package itself
48
+ * decided not to record beside Sentry (see RecordOnlySampler), so they
49
+ * can be told from another SDK's unrecorded spans.
50
+ */
51
+ export declare const droppedMark = "rootcat";
52
+ /**
53
+ * parentPolicy says how an unrecorded parent from another SDK counts.
54
+ * Beside an errors-only Sentry every span on Sentry's provider is
55
+ * unrecorded (Next.js starts its own through it), and the package's spans
56
+ * below one must not be lost: such a parent is passed through, as if the
57
+ * span had none, and the trace ratio decides on its trace id. Set once,
58
+ * when the package starts beside Sentry.
59
+ */
60
+ export declare const parentPolicy: {
61
+ transparentForeign: boolean;
62
+ };
63
+ /**
64
+ * localParent is the span in ctx when it was started in this process
65
+ * (not a parent from an incoming header), and counts as one (see
66
+ * parentPolicy).
67
+ */
68
+ export declare function localParent(ctx: Context): ApiSpan | undefined;
69
+ /**
70
+ * unsampled reports whether a span started under ctx would not be
71
+ * recorded (an unrecorded parent, or a root an entry wrapper decided
72
+ * against): the drivers then run untouched, without even a
73
+ * non-recording span or a context of their own.
74
+ */
75
+ export declare function unsampled(ctx: Context): boolean;
76
+ /** runCarried runs fn under the context carry returned, if any. */
77
+ export declare function runCarried<T>(ctx: Context | undefined, fn: () => T): T;
78
+ /** CallSiteProcessorOptions configures the processor. */
79
+ export interface CallSiteProcessorOptions {
80
+ /** sampling gates the captures; a number is the one-in-N setting. */
81
+ sampling: CallSiteSampling | number;
82
+ /** depth bounds the frames walked. */
83
+ depth: number;
84
+ /** statement is passed to the privacy rules. */
85
+ statement: StatementMode;
86
+ /** isApp decides which files are application code (tests override). */
87
+ isApp?: (file: string) => boolean;
88
+ /**
89
+ * above is the function a capture from the processor starts below (the
90
+ * SDK tracer's startSpan), so the walk skips the SDK's own frames.
91
+ */
92
+ above?: Function;
93
+ /**
94
+ * readOnly leaves the spans as they are: the processor sits in another
95
+ * SDK's provider, which sends those spans on. The call site and the
96
+ * privacy rules then apply only to the copy the package exports
97
+ * (exportView).
98
+ */
99
+ readOnly?: boolean;
100
+ /**
101
+ * keepHostKeys leaves another SDK's keys (sentry.*) on the live span
102
+ * at its end: beside Sentry, Sentry reads an ended span still active in
103
+ * a request's context (a call made after the response), and its source
104
+ * must stay what UrlSource pinned. The exported copy drops them (see
105
+ * FlaggedExport).
106
+ */
107
+ keepHostKeys?: boolean;
108
+ }
109
+ /** Attribute keys written. */
110
+ export declare const codeFilepath = "code.filepath";
111
+ export declare const codeLineno = "code.lineno";
112
+ export declare const codeFunction = "code.function";
113
+ /**
114
+ * eligible reports whether a span is a client operation worth a call
115
+ * site: client and producer spans, anything carrying db.system, and the
116
+ * operation spans of the ORMs that start their work in application code.
117
+ * A processor runs for recorded spans only, so every span here exports.
118
+ */
119
+ export declare function eligible(span: ReadableSpan): boolean;
120
+ /**
121
+ * clientOperationByOp reports whether a Sentry span is a client
122
+ * operation by its op, the one attribute Sentry sets when it starts a
123
+ * span (its postgres.js span gets db.system.name only after the start).
124
+ */
125
+ export declare function clientOperationByOp(span: ReadableSpan): boolean;
126
+ /** CallSiteProcessor implements SpanProcessor. */
127
+ export declare class CallSiteProcessor implements SpanProcessor {
128
+ readonly sampling: CallSiteSampling;
129
+ private readonly depth;
130
+ private readonly statement;
131
+ private readonly isApp;
132
+ /** above is where a processor capture starts (the SDK tracer startSpan); set once the provider exists. */
133
+ above: Function | undefined;
134
+ readonly readOnly: boolean;
135
+ private readonly keepHostKeys;
136
+ private readonly sites;
137
+ /** captured counts spans given a call site (tests and the benchmark). */
138
+ captured: number;
139
+ constructor(options: CallSiteProcessorOptions);
140
+ /**
141
+ * assign gives a started span a call site found after its start (a
142
+ * driver's own origin, read once another SDK's span exists); it
143
+ * replaces any the processor found at the start.
144
+ */
145
+ assign(span: object, site: CallSiteLocation): void;
146
+ /** siteOf is the call site the processor holds for a span. */
147
+ siteOf(span: object): CallSiteLocation | undefined;
148
+ onStart(span: Span, parentContext: Context): void;
149
+ /**
150
+ * locate takes a site already carried in the context or by the parent
151
+ * span (an entry wrapper paid for it, under the same gate), else, one
152
+ * occasion in N, captures the synchronous stack.
153
+ */
154
+ private locate;
155
+ onEnd(span: ReadableSpan): void;
156
+ /**
157
+ * exportView is the span as the package exports it from a read-only
158
+ * processor: a copy of its attributes, events and status with the
159
+ * privacy rules and the call site applied, under the package's
160
+ * resource. The span itself is not touched.
161
+ */
162
+ exportView(span: ReadableSpan, resource?: Resource): ReadableSpan;
163
+ /** statementName is a statement-shaped span name under the statement rule. */
164
+ private statementName;
165
+ shutdown(): Promise<void>;
166
+ forceFlush(): Promise<void>;
167
+ }
168
+ /**
169
+ * routeFromHost drops the keys never exported and turns what a Sentry
170
+ * framework instrumentation wrote on the active span into the package's own
171
+ * vocabulary. @sentry/hono renames the server span to "GET /users/:id"
172
+ * once the route is known (sentry.source "route"): the template becomes
173
+ * http.route.
174
+ */
175
+ export declare function routeFromHost(name: string, attributes: Record<string, unknown>): unknown;
176
+ /**
177
+ * exportedName is the name a span is exported under. Another SDK's HTTP
178
+ * span is named by the method and the path or URL it served or called
179
+ * (Sentry's: "GET /reset/3f9c...", "GET https://api.example.com/u/42"):
180
+ * the target is masked as an attribute URL is, unless the name is a
181
+ * route template a framework set (sentry.source "route").
182
+ */
183
+ export declare function exportedName(name: string, source: unknown): string;
184
+ /** Overrides is what a view replaces; the rest reads through to the span. */
185
+ interface Overrides {
186
+ name?: string;
187
+ attributes?: Attributes;
188
+ events?: ReadableSpan['events'];
189
+ status?: SpanStatus;
190
+ resource?: Resource;
191
+ }
192
+ /**
193
+ * view is a ReadableSpan with the span's fields except the given ones,
194
+ * and always flagged sampled: the package exports only spans it chose to
195
+ * record, whatever flag they carry inside the process.
196
+ */
197
+ export declare function view(span: ReadableSpan, overrides?: Overrides): ReadableSpan;
@@ -0,0 +1,494 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CallSiteProcessor = exports.codeFunction = exports.codeLineno = exports.codeFilepath = exports.parentPolicy = exports.droppedMark = exports.CallSiteSampling = exports.callSiteContextKey = void 0;
4
+ exports.recorded = recorded;
5
+ exports.localParent = localParent;
6
+ exports.unsampled = unsampled;
7
+ exports.runCarried = runCarried;
8
+ exports.eligible = eligible;
9
+ exports.clientOperationByOp = clientOperationByOp;
10
+ exports.routeFromHost = routeFromHost;
11
+ exports.exportedName = exportedName;
12
+ exports.view = view;
13
+ // CallSiteProcessor: a span processor that, when a client span starts,
14
+ // records the application frame that caused it as code.filepath,
15
+ // code.lineno and code.function, and applies the privacy rules to the
16
+ // span's attributes at start and at end.
17
+ //
18
+ // Where the frame comes from, in order: the synchronous stack at span
19
+ // start (the usual case: the driver was called from application code);
20
+ // else a call site an entry wrapper stored in the active context (a pg
21
+ // pool hands queries to a client on nextTick, where the stack is the
22
+ // pool's); else the parent span's call site (an ORM's operation span
23
+ // started in application code, its driver span on a later tick).
24
+ //
25
+ // In another SDK's provider (Sentry's, with Sentry tracing on) the
26
+ // processor is read-only: it finds the call site the same way but writes
27
+ // nothing on the span, which that SDK sends on; the package exports a
28
+ // copy with the site and the privacy rules applied (exportView).
29
+ const api_1 = require("@opentelemetry/api");
30
+ const callsite_js_1 = require("./callsite.js");
31
+ const keys_js_1 = require("./keys.js");
32
+ const privacy_js_1 = require("./privacy.js");
33
+ var keys_js_2 = require("./keys.js");
34
+ Object.defineProperty(exports, "callSiteContextKey", { enumerable: true, get: function () { return keys_js_2.callSiteContextKey; } });
35
+ /**
36
+ * CallSiteSampling is the gate every capture goes through: the
37
+ * processor's own and the entry wrappers' (pg, pg pool, Drizzle,
38
+ * postgres.js), so a capture is never paid for a span that will not be
39
+ * exported or that the one-in-N setting skips.
40
+ */
41
+ class CallSiteSampling {
42
+ /** sample is the one-in-N setting; the benchmark changes it between blocks. */
43
+ sample;
44
+ /** ratio is the trace sampling share; the benchmark changes it between blocks. */
45
+ ratio;
46
+ counter = 0;
47
+ /** sample is ROOTCAT_CALLSITE_SAMPLE; ratio is ROOTCAT_SAMPLE. */
48
+ constructor(sample, ratio = 1) {
49
+ this.sample = Math.max(0, Math.floor(sample));
50
+ this.ratio = Math.min(1, Math.max(0, ratio));
51
+ }
52
+ /** take counts one occasion and reports whether it captures. */
53
+ take() {
54
+ if (this.sample === 0) {
55
+ return false;
56
+ }
57
+ this.counter++;
58
+ return this.counter % this.sample === 0;
59
+ }
60
+ /** on reports whether any capture happens at all. */
61
+ get on() {
62
+ return this.sample > 0;
63
+ }
64
+ /**
65
+ * carry is what an entry wrapper calls before the operation it wraps:
66
+ * it returns the context to run the operation in, or undefined to run
67
+ * it as it is. Under an unrecorded span nothing happens. Outside any
68
+ * span with a ratio below 1, the trace decision is made here (the
69
+ * sampler follows it), and an unsampled operation captures nothing. A
70
+ * site already carried by an outer layer of the same operation stands,
71
+ * unless override (a capture that costs nothing, such as postgres.js's
72
+ * own origin, and names the operation more exactly). Otherwise, one
73
+ * occasion in N, capture() runs and its site is carried.
74
+ */
75
+ carry(capture, override = false) {
76
+ const active = api_1.context.active();
77
+ const local = localParent(active);
78
+ let ctx = active;
79
+ // A remote parent (an incoming traceparent) decides nothing: see the sampler.
80
+ if (local) {
81
+ if (!recorded(local)) {
82
+ return undefined;
83
+ }
84
+ }
85
+ else if (this.ratio < 1 && active.getValue(keys_js_1.rootDecisionKey) === undefined) {
86
+ const decided = Math.random() < this.ratio;
87
+ ctx = ctx.setValue(keys_js_1.rootDecisionKey, decided);
88
+ if (!decided) {
89
+ return ctx;
90
+ }
91
+ }
92
+ else if (active.getValue(keys_js_1.rootDecisionKey) === false) {
93
+ return undefined;
94
+ }
95
+ const existing = active.getValue(keys_js_1.callSiteContextKey) !== undefined;
96
+ if ((existing && !override) || (!existing && !this.take())) {
97
+ return ctx === active ? undefined : ctx;
98
+ }
99
+ const site = capture();
100
+ if (site) {
101
+ ctx = ctx.setValue(keys_js_1.callSiteContextKey, site);
102
+ }
103
+ return ctx === active ? undefined : ctx;
104
+ }
105
+ }
106
+ exports.CallSiteSampling = CallSiteSampling;
107
+ /**
108
+ * recorded reports whether a span in this process is part of a recorded
109
+ * trace: it is recording (beside an errors-only Sentry the package
110
+ * records spans without the sampled flag, see RecordOnlySampler), or it
111
+ * carries the flag (a span context an application wrapped itself, to
112
+ * continue a trace from a queued job).
113
+ */
114
+ function recorded(span) {
115
+ return span.isRecording() || (span.spanContext().traceFlags & api_1.TraceFlags.SAMPLED) !== 0;
116
+ }
117
+ /**
118
+ * droppedMark is the trace-state key on the spans the package itself
119
+ * decided not to record beside Sentry (see RecordOnlySampler), so they
120
+ * can be told from another SDK's unrecorded spans.
121
+ */
122
+ exports.droppedMark = 'rootcat';
123
+ /**
124
+ * parentPolicy says how an unrecorded parent from another SDK counts.
125
+ * Beside an errors-only Sentry every span on Sentry's provider is
126
+ * unrecorded (Next.js starts its own through it), and the package's spans
127
+ * below one must not be lost: such a parent is passed through, as if the
128
+ * span had none, and the trace ratio decides on its trace id. Set once,
129
+ * when the package starts beside Sentry.
130
+ */
131
+ exports.parentPolicy = { transparentForeign: false };
132
+ /**
133
+ * localParent is the span in ctx when it was started in this process
134
+ * (not a parent from an incoming header), and counts as one (see
135
+ * parentPolicy).
136
+ */
137
+ function localParent(ctx) {
138
+ const span = api_1.trace.getSpan(ctx);
139
+ if (!span) {
140
+ return undefined;
141
+ }
142
+ const spanContext = span.spanContext();
143
+ if (!api_1.trace.isSpanContextValid(spanContext) || spanContext.isRemote) {
144
+ return undefined;
145
+ }
146
+ if (exports.parentPolicy.transparentForeign && !recorded(span) && spanContext.traceState?.get(exports.droppedMark) === undefined) {
147
+ return undefined;
148
+ }
149
+ return span;
150
+ }
151
+ /**
152
+ * unsampled reports whether a span started under ctx would not be
153
+ * recorded (an unrecorded parent, or a root an entry wrapper decided
154
+ * against): the drivers then run untouched, without even a
155
+ * non-recording span or a context of their own.
156
+ */
157
+ function unsampled(ctx) {
158
+ const local = localParent(ctx);
159
+ if (local) {
160
+ return !recorded(local);
161
+ }
162
+ return ctx.getValue(keys_js_1.rootDecisionKey) === false;
163
+ }
164
+ /** runCarried runs fn under the context carry returned, if any. */
165
+ function runCarried(ctx, fn) {
166
+ return ctx ? api_1.context.with(ctx, fn) : fn();
167
+ }
168
+ /** Attribute keys written. */
169
+ exports.codeFilepath = 'code.filepath';
170
+ exports.codeLineno = 'code.lineno';
171
+ exports.codeFunction = 'code.function';
172
+ /**
173
+ * eligible reports whether a span is a client operation worth a call
174
+ * site: client and producer spans, anything carrying db.system, and the
175
+ * operation spans of the ORMs that start their work in application code.
176
+ * A processor runs for recorded spans only, so every span here exports.
177
+ */
178
+ function eligible(span) {
179
+ if (span.kind === api_1.SpanKind.CLIENT || span.kind === api_1.SpanKind.PRODUCER) {
180
+ return true;
181
+ }
182
+ if (isDatabase(span.attributes)) {
183
+ return true;
184
+ }
185
+ return span.name.startsWith('drizzle.') || span.name.startsWith('prisma:client:');
186
+ }
187
+ /**
188
+ * clientOperationByOp reports whether a Sentry span is a client
189
+ * operation by its op, the one attribute Sentry sets when it starts a
190
+ * span (its postgres.js span gets db.system.name only after the start).
191
+ */
192
+ function clientOperationByOp(span) {
193
+ const op = span.attributes['sentry.op'];
194
+ return typeof op === 'string' && /^(db|cache|http\.client|queue\.publish)\b/.test(op);
195
+ }
196
+ /** CallSiteProcessor implements SpanProcessor. */
197
+ class CallSiteProcessor {
198
+ sampling;
199
+ depth;
200
+ statement;
201
+ isApp;
202
+ /** above is where a processor capture starts (the SDK tracer startSpan); set once the provider exists. */
203
+ above;
204
+ readOnly;
205
+ keepHostKeys;
206
+ sites = new WeakMap();
207
+ /** captured counts spans given a call site (tests and the benchmark). */
208
+ captured = 0;
209
+ constructor(options) {
210
+ this.sampling = typeof options.sampling === 'number' ? new CallSiteSampling(options.sampling) : options.sampling;
211
+ this.depth = Math.max(1, Math.floor(options.depth));
212
+ this.statement = options.statement;
213
+ this.isApp = options.isApp ?? callsite_js_1.isAppFile;
214
+ this.above = options.above;
215
+ this.readOnly = options.readOnly === true;
216
+ this.keepHostKeys = options.keepHostKeys === true;
217
+ }
218
+ /**
219
+ * assign gives a started span a call site found after its start (a
220
+ * driver's own origin, read once another SDK's span exists); it
221
+ * replaces any the processor found at the start.
222
+ */
223
+ assign(span, site) {
224
+ if (!this.sites.has(span)) {
225
+ this.captured++;
226
+ }
227
+ this.sites.set(span, site);
228
+ if (!this.readOnly) {
229
+ stamp(span, site);
230
+ }
231
+ }
232
+ /** siteOf is the call site the processor holds for a span. */
233
+ siteOf(span) {
234
+ return this.sites.get(span);
235
+ }
236
+ onStart(span, parentContext) {
237
+ try {
238
+ if (!this.readOnly) {
239
+ const changes = (0, privacy_js_1.scrubbed)(span.attributes, this.statement);
240
+ if (changes) {
241
+ span.setAttributes(changes);
242
+ }
243
+ }
244
+ if (!this.sampling.on || !(eligible(span) || (this.readOnly && clientOperationByOp(span)))) {
245
+ return;
246
+ }
247
+ const site = this.locate(parentContext);
248
+ if (!site) {
249
+ return;
250
+ }
251
+ this.sites.set(span, site);
252
+ this.captured++;
253
+ if (!this.readOnly) {
254
+ stamp(span, site);
255
+ }
256
+ }
257
+ catch {
258
+ // A call site is an extra; a failure to find one never reaches the app.
259
+ }
260
+ }
261
+ /**
262
+ * locate takes a site already carried in the context or by the parent
263
+ * span (an entry wrapper paid for it, under the same gate), else, one
264
+ * occasion in N, captures the synchronous stack.
265
+ */
266
+ locate(parentContext) {
267
+ const carried = parentContext.getValue(keys_js_1.callSiteContextKey);
268
+ if (isLocation(carried)) {
269
+ return carried;
270
+ }
271
+ const parent = api_1.trace.getSpan(parentContext);
272
+ const inherited = parent ? this.sites.get(parent) : undefined;
273
+ if (inherited) {
274
+ return inherited;
275
+ }
276
+ if (!this.sampling.take()) {
277
+ return undefined;
278
+ }
279
+ return (0, callsite_js_1.captureCallSite)(this.depth, this.isApp, this.above);
280
+ }
281
+ onEnd(span) {
282
+ if (this.readOnly) {
283
+ return; // exportView makes the copy the package sends
284
+ }
285
+ try {
286
+ // The span is ended; the exporter reads these same objects, so the
287
+ // rules hold for attributes and events set after the start as well.
288
+ const attributes = span.attributes;
289
+ const changes = (0, privacy_js_1.scrubbed)(span.attributes, this.statement);
290
+ if (changes) {
291
+ for (const [key, value] of Object.entries(changes)) {
292
+ attributes[key] = value;
293
+ }
294
+ }
295
+ if (!this.keepHostKeys) {
296
+ routeFromHost(span.name, attributes);
297
+ }
298
+ const exceptionMessage = scrubEvents(span.events);
299
+ // A database span's status message is the server's text, which can
300
+ // quote the input that failed; the instrumentation's own exception
301
+ // message (the pg one says only the class and SQLSTATE) stands in.
302
+ if (span.status.code === api_1.SpanStatusCode.ERROR && isDatabase(span.attributes) && span.status.message) {
303
+ span.status.message = exceptionMessage ?? 'database error';
304
+ }
305
+ }
306
+ catch {
307
+ // never reaches the app
308
+ }
309
+ }
310
+ /**
311
+ * exportView is the span as the package exports it from a read-only
312
+ * processor: a copy of its attributes, events and status with the
313
+ * privacy rules and the call site applied, under the package's
314
+ * resource. The span itself is not touched.
315
+ */
316
+ exportView(span, resource) {
317
+ const attributes = { ...span.attributes };
318
+ const changes = (0, privacy_js_1.scrubbed)(attributes, this.statement);
319
+ if (changes) {
320
+ Object.assign(attributes, changes);
321
+ }
322
+ const source = routeFromHost(span.name, attributes);
323
+ const site = this.sites.get(span);
324
+ if (site) {
325
+ attributes[exports.codeFilepath] = (0, callsite_js_1.displayPath)(site.file);
326
+ attributes[exports.codeLineno] = site.line;
327
+ if (site.function !== '') {
328
+ attributes[exports.codeFunction] = site.function;
329
+ }
330
+ }
331
+ const events = span.events.map((event) => ({ ...event, attributes: event.attributes ? { ...event.attributes } : undefined }));
332
+ const exceptionMessage = scrubEvents(events);
333
+ let status = span.status;
334
+ if (status.code === api_1.SpanStatusCode.ERROR && isDatabase(attributes) && status.message) {
335
+ status = { code: status.code, message: exceptionMessage ?? 'database error' };
336
+ }
337
+ // A database span another SDK named after its statement (Sentry's
338
+ // postgres.js span, whose own sanitizer misses $$ and E'' literals)
339
+ // is named as the statement attribute is kept.
340
+ const name = isDatabase(attributes) ? this.statementName(span.name) : exportedName(span.name, source);
341
+ return view(span, { name, attributes: attributes, events, status, ...(resource ? { resource } : {}) });
342
+ }
343
+ /** statementName is a statement-shaped span name under the statement rule. */
344
+ statementName(name) {
345
+ const bounded = name.length > privacy_js_1.maxStatementLength ? name.slice(0, privacy_js_1.maxStatementLength) : name;
346
+ return this.statement === 'off' ? (0, privacy_js_1.statementKeyword)(bounded) : (0, privacy_js_1.maskLiterals)(bounded);
347
+ }
348
+ shutdown() {
349
+ return Promise.resolve();
350
+ }
351
+ forceFlush() {
352
+ return Promise.resolve();
353
+ }
354
+ }
355
+ exports.CallSiteProcessor = CallSiteProcessor;
356
+ /** stamp writes a call site on a span. */
357
+ function stamp(span, site) {
358
+ span.setAttribute(exports.codeFilepath, (0, callsite_js_1.displayPath)(site.file));
359
+ span.setAttribute(exports.codeLineno, site.line);
360
+ if (site.function !== '') {
361
+ span.setAttribute(exports.codeFunction, site.function);
362
+ }
363
+ }
364
+ /**
365
+ * scrubEvents applies the stack rule to recorded exceptions (in place)
366
+ * and returns the last exception message. A recorded exception's stack
367
+ * opens with its message line, which a database server may fill with the
368
+ * input that failed; the frames alone say where it happened.
369
+ */
370
+ function scrubEvents(events) {
371
+ let exceptionMessage;
372
+ for (const event of events) {
373
+ const attributes = event.attributes;
374
+ const stack = attributes?.['exception.stacktrace'];
375
+ if (typeof stack === 'string') {
376
+ attributes['exception.stacktrace'] = (0, privacy_js_1.framesOnly)(stack);
377
+ }
378
+ if (event.name === 'exception' && typeof attributes?.['exception.message'] === 'string') {
379
+ exceptionMessage = attributes['exception.message'];
380
+ }
381
+ }
382
+ return exceptionMessage;
383
+ }
384
+ /**
385
+ * dropped reports the keys never exported: Sentry's bookkeeping
386
+ * (sentry.*), and request and response headers, which Sentry's http
387
+ * instrumentation records on its spans (cookies and authorization travel
388
+ * there) and the package's own never does.
389
+ */
390
+ function dropped(key) {
391
+ const first = key.charCodeAt(0);
392
+ if (first === 115 /* s */) {
393
+ return key.startsWith('sentry.');
394
+ }
395
+ if (first === 99 /* c */) {
396
+ return key === 'cache.key'; // Sentry's cache spans: the keys
397
+ }
398
+ if (first === 114 /* r */) {
399
+ return key === 'rootcat.source'; // a source kept aside (see UrlSource)
400
+ }
401
+ return first === 104 /* h */ && (key.startsWith('http.request.header.') || key.startsWith('http.response.header.'));
402
+ }
403
+ /**
404
+ * routeFromHost drops the keys never exported and turns what a Sentry
405
+ * framework instrumentation wrote on the active span into the package's own
406
+ * vocabulary. @sentry/hono renames the server span to "GET /users/:id"
407
+ * once the route is known (sentry.source "route"): the template becomes
408
+ * http.route.
409
+ */
410
+ function routeFromHost(name, attributes) {
411
+ let source;
412
+ let any = false;
413
+ for (const key in attributes) {
414
+ if (dropped(key)) {
415
+ any = true;
416
+ if (key === 'sentry.source' && source === undefined) {
417
+ source = attributes[key];
418
+ }
419
+ if (key === 'rootcat.source') {
420
+ source = attributes[key]; // what another SDK set, kept aside
421
+ }
422
+ }
423
+ }
424
+ if (!any) {
425
+ return undefined;
426
+ }
427
+ for (const key of Object.keys(attributes)) {
428
+ if (dropped(key)) {
429
+ delete attributes[key];
430
+ }
431
+ }
432
+ if (source === 'route' && attributes['http.route'] === undefined) {
433
+ const space = name.indexOf(' ');
434
+ if (space > 0 && name.charCodeAt(space + 1) === 47 /* / */) {
435
+ attributes['http.route'] = name.slice(space + 1, space + 1 + 256);
436
+ }
437
+ }
438
+ return source;
439
+ }
440
+ /** methodAndTarget matches a span name of an HTTP method and a path or URL. */
441
+ const methodAndTarget = /^(GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS|CONNECT|TRACE) (\S+)$/;
442
+ /**
443
+ * exportedName is the name a span is exported under. Another SDK's HTTP
444
+ * span is named by the method and the path or URL it served or called
445
+ * (Sentry's: "GET /reset/3f9c...", "GET https://api.example.com/u/42"):
446
+ * the target is masked as an attribute URL is, unless the name is a
447
+ * route template a framework set (sentry.source "route").
448
+ */
449
+ function exportedName(name, source) {
450
+ if (source === 'route') {
451
+ return name;
452
+ }
453
+ const match = methodAndTarget.exec(name);
454
+ return match ? `${match[1]} ${(0, privacy_js_1.maskPath)(match[2])}` : name;
455
+ }
456
+ /**
457
+ * view is a ReadableSpan with the span's fields except the given ones,
458
+ * and always flagged sampled: the package exports only spans it chose to
459
+ * record, whatever flag they carry inside the process.
460
+ */
461
+ function view(span, overrides = {}) {
462
+ const spanContext = span.spanContext();
463
+ const flagged = (spanContext.traceFlags & api_1.TraceFlags.SAMPLED) !== 0 ? spanContext : { ...spanContext, traceFlags: spanContext.traceFlags | api_1.TraceFlags.SAMPLED };
464
+ return {
465
+ name: overrides.name ?? span.name,
466
+ kind: span.kind,
467
+ spanContext: () => flagged,
468
+ parentSpanContext: span.parentSpanContext,
469
+ startTime: span.startTime,
470
+ endTime: span.endTime,
471
+ status: overrides.status ?? span.status,
472
+ attributes: overrides.attributes ?? span.attributes,
473
+ links: span.links,
474
+ events: overrides.events ?? span.events,
475
+ duration: span.duration,
476
+ ended: span.ended,
477
+ resource: overrides.resource ?? span.resource,
478
+ instrumentationScope: span.instrumentationScope,
479
+ droppedAttributesCount: span.droppedAttributesCount,
480
+ droppedEventsCount: span.droppedEventsCount,
481
+ droppedLinksCount: span.droppedLinksCount,
482
+ };
483
+ }
484
+ function isDatabase(attributes) {
485
+ return attributes['db.system'] !== undefined || attributes['db.system.name'] !== undefined;
486
+ }
487
+ function isLocation(value) {
488
+ if (!value || typeof value !== 'object') {
489
+ return false;
490
+ }
491
+ const record = value;
492
+ return typeof record.file === 'string' && typeof record.line === 'number';
493
+ }
494
+ //# sourceMappingURL=processor.js.map