@0xchain/telemetry 1.1.0-beta.18 → 1.1.0-beta.19

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/dist/index.js CHANGED
@@ -1,401 +1,609 @@
1
- import { NodeSDK as U } from "@opentelemetry/sdk-node";
2
- import { OTLPTraceExporter as G } from "@opentelemetry/exporter-trace-otlp-http";
3
- import { resourceFromAttributes as q } from "@opentelemetry/resources";
4
- import { ATTR_SERVICE_VERSION as B, ATTR_SERVICE_NAME as k } from "@opentelemetry/semantic-conventions";
5
- import { SamplingDecision as l, TraceIdRatioBasedSampler as M, AlwaysOnSampler as z, BatchSpanProcessor as $ } from "@opentelemetry/sdk-trace-base";
6
- import { trace as j, TraceFlags as O, propagation as D, context as H, SpanStatusCode as Q } from "@opentelemetry/api";
7
- import { readFile as X } from "node:fs/promises";
8
- import { HttpInstrumentation as W } from "@opentelemetry/instrumentation-http";
9
- import { UndiciInstrumentation as J } from "@opentelemetry/instrumentation-undici";
10
- import { c as K, m as Z, a as tt, d as et } from "./ignore-CMi2r3b2.js";
11
- const rt = "deployment.environment.name", T = (t, e) => {
12
- if (!t) return;
13
- const r = t[e.toLowerCase()];
14
- if (Array.isArray(r)) {
15
- const n = r[0];
16
- return typeof n == "string" ? n : void 0;
17
- }
18
- return typeof r == "string" ? r : void 0;
19
- }, R = (t) => {
20
- if (!t) return "";
1
+ import { NodeSDK } from "@opentelemetry/sdk-node";
2
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
3
+ import { resourceFromAttributes } from "@opentelemetry/resources";
4
+ import { ATTR_SERVICE_VERSION, ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
5
+ import { ATTR_DEPLOYMENT_ENVIRONMENT_NAME } from "@opentelemetry/semantic-conventions/incubating";
6
+ import { SamplingDecision, TraceIdRatioBasedSampler, AlwaysOnSampler, BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
7
+ import { trace, TraceFlags, propagation, context, SpanStatusCode } from "@opentelemetry/api";
8
+ import { readFile } from "node:fs/promises";
9
+ import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
10
+ import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
11
+ import { c as createIgnoreMatcher, m as matchesPathPattern, a as createSpanNameMatcher, d as defaultIgnoreConfig } from "./ignore-BvHZTNsF.js";
12
+ const getHeaderValue = (headers, name) => {
13
+ if (!headers) return void 0;
14
+ const value = headers[name.toLowerCase()];
15
+ if (Array.isArray(value)) {
16
+ const first = value[0];
17
+ return typeof first === "string" ? first : void 0;
18
+ }
19
+ return typeof value === "string" ? value : void 0;
20
+ };
21
+ const getPathname = (url) => {
22
+ if (!url) return "";
21
23
  try {
22
- return t.startsWith("http://") || t.startsWith("https://") ? new URL(t).pathname : new URL(t, "http://localhost").pathname;
24
+ if (url.startsWith("http://") || url.startsWith("https://")) {
25
+ return new URL(url).pathname;
26
+ }
27
+ return new URL(url, "http://localhost").pathname;
23
28
  } catch {
24
- return t.split("?")[0] || t;
29
+ return url.split("?")[0] || url;
25
30
  }
26
- }, A = /* @__PURE__ */ new WeakMap();
27
- function nt(t) {
28
- const e = K(t.ignore), r = (t.inspectionHeader ?? "x-inspection").toLowerCase(), n = (t.userTypeHeader ?? "x-telemetry-user-type").toLowerCase(), s = (t.requestStartHeader ?? "x-telemetry-start").toLowerCase();
31
+ };
32
+ const spanStartTime = /* @__PURE__ */ new WeakMap();
33
+ function getInstrumentations(config) {
34
+ const shouldIgnore = createIgnoreMatcher(config.ignore);
35
+ const inspectionHeader = (config.inspectionHeader ?? "x-inspection").toLowerCase();
36
+ const userTypeHeader = (config.userTypeHeader ?? "x-telemetry-user-type").toLowerCase();
37
+ const requestStartHeader = (config.requestStartHeader ?? "x-telemetry-start").toLowerCase();
29
38
  return [
30
- new W({
31
- startIncomingSpanHook: (o) => {
32
- const i = "headers" in o ? o.headers : void 0, a = T(i, r), c = T(i, n), m = T(i, s), p = "method" in o ? o.method : void 0, E = R("url" in o ? o.url : void 0), d = Number(m || Date.now()), u = {
33
- "telemetry.request.start_time_ms": Number.isFinite(d) ? d : Date.now()
39
+ new HttpInstrumentation({
40
+ startIncomingSpanHook: (request) => {
41
+ const headers = "headers" in request ? request.headers : void 0;
42
+ const inspectionValue = getHeaderValue(headers, inspectionHeader);
43
+ const userTypeValue = getHeaderValue(headers, userTypeHeader);
44
+ const startTimeValue = getHeaderValue(headers, requestStartHeader);
45
+ const method = "method" in request ? request.method : void 0;
46
+ const pathname = getPathname("url" in request ? request.url : void 0);
47
+ const startTime = Number(startTimeValue || Date.now());
48
+ const attributes = {
49
+ "telemetry.request.start_time_ms": Number.isFinite(startTime) ? startTime : Date.now()
34
50
  };
35
- return E && (u["url.path"] = E), p && (u["http.request.method"] = p), a && (u["telemetry.request.inspection"] = !0, u["http.request.header.x-inspection"] = a), c && (u["enduser.type"] = c, u["telemetry.request.user_type"] = c), u;
51
+ if (pathname) {
52
+ attributes["url.path"] = pathname;
53
+ }
54
+ if (method) {
55
+ attributes["http.request.method"] = method;
56
+ }
57
+ if (inspectionValue) {
58
+ attributes["telemetry.request.inspection"] = true;
59
+ attributes["http.request.header.x-inspection"] = inspectionValue;
60
+ }
61
+ if (userTypeValue) {
62
+ attributes["enduser.type"] = userTypeValue;
63
+ attributes["telemetry.request.user_type"] = userTypeValue;
64
+ }
65
+ return attributes;
36
66
  },
37
- requestHook: (o, i) => {
38
- const a = "headers" in i ? i.headers : void 0, c = T(a, s), m = Number(c || Date.now());
39
- A.set(o, Number.isFinite(m) ? m : Date.now());
40
- const p = "method" in i && i.method || "GET", E = R("url" in i ? i.url : void 0);
41
- E && o.updateName(`${p} ${E}`);
67
+ requestHook: (span, request) => {
68
+ const headers = "headers" in request ? request.headers : void 0;
69
+ const startTimeValue = getHeaderValue(headers, requestStartHeader);
70
+ const startTime = Number(startTimeValue || Date.now());
71
+ spanStartTime.set(span, Number.isFinite(startTime) ? startTime : Date.now());
72
+ const method = "method" in request ? request.method || "GET" : "GET";
73
+ const pathname = getPathname("url" in request ? request.url : void 0);
74
+ if (pathname) {
75
+ span.updateName(`${method} ${pathname}`);
76
+ }
42
77
  },
43
- responseHook: (o, i) => {
44
- const a = "statusCode" in i ? i.statusCode : void 0;
45
- typeof a == "number" && (o.setAttribute("http.response.status_code", a), o.setAttribute("telemetry.response.status_code", a), a >= 500 && o.setAttribute("telemetry.request.error", !0));
46
- const c = A.get(o);
47
- typeof c == "number" && o.setAttribute("telemetry.request.duration_ms", Date.now() - c);
78
+ responseHook: (span, response) => {
79
+ const statusCode = "statusCode" in response ? response.statusCode : void 0;
80
+ if (typeof statusCode === "number") {
81
+ span.setAttribute("http.response.status_code", statusCode);
82
+ span.setAttribute("telemetry.response.status_code", statusCode);
83
+ if (statusCode >= 500) {
84
+ span.setAttribute("telemetry.request.error", true);
85
+ }
86
+ }
87
+ const startTime = spanStartTime.get(span);
88
+ if (typeof startTime === "number") {
89
+ span.setAttribute("telemetry.request.duration_ms", Date.now() - startTime);
90
+ }
48
91
  },
49
- ignoreIncomingRequestHook: (o) => {
50
- const i = "url" in o ? o.url : void 0, a = R(i), c = "method" in o ? o.method : void 0;
51
- return e(a, c);
92
+ ignoreIncomingRequestHook: (request) => {
93
+ const url = "url" in request ? request.url : void 0;
94
+ const pathname = getPathname(url);
95
+ const method = "method" in request ? request.method : void 0;
96
+ return shouldIgnore(pathname, method);
52
97
  },
53
- ignoreOutgoingRequestHook: (o) => {
54
- const i = typeof o == "string" ? o : o.path || "";
55
- return e(R(i));
98
+ ignoreOutgoingRequestHook: (request) => {
99
+ const url = typeof request === "string" ? request : request.path || "";
100
+ return shouldIgnore(getPathname(url));
56
101
  }
57
102
  }),
58
- new J()
103
+ new UndiciInstrumentation()
59
104
  ];
60
105
  }
61
- const y = (t) => {
62
- if (typeof t == "string") return t;
63
- if (Array.isArray(t)) {
64
- const e = t[0];
65
- return typeof e == "string" ? e : void 0;
66
- }
67
- }, b = (t) => {
68
- if (typeof t == "number") return t;
69
- if (typeof t == "string") {
70
- const e = Number(t);
71
- return Number.isFinite(e) ? e : void 0;
72
- }
73
- if (Array.isArray(t)) {
74
- const e = t[0];
75
- return b(e);
76
- }
77
- }, f = (t, e) => {
78
- for (const r of e)
79
- if (r in t)
80
- return t[r];
81
- }, ot = (t) => {
82
- const e = f(t, ["url.path", "http.target", "http.route"]), r = f(t, ["http.request.method", "http.method"]), n = f(t, ["enduser.type", "telemetry.request.user_type"]), s = f(t, [
106
+ const toStringValue = (value) => {
107
+ if (typeof value === "string") return value;
108
+ if (Array.isArray(value)) {
109
+ const first = value[0];
110
+ return typeof first === "string" ? first : void 0;
111
+ }
112
+ return void 0;
113
+ };
114
+ const toNumberValue = (value) => {
115
+ if (typeof value === "number") return value;
116
+ if (typeof value === "string") {
117
+ const parsed = Number(value);
118
+ return Number.isFinite(parsed) ? parsed : void 0;
119
+ }
120
+ if (Array.isArray(value)) {
121
+ const first = value[0];
122
+ return toNumberValue(first);
123
+ }
124
+ return void 0;
125
+ };
126
+ const pickAttribute = (attributes, keys) => {
127
+ for (const key of keys) {
128
+ if (key in attributes) {
129
+ return attributes[key];
130
+ }
131
+ }
132
+ return void 0;
133
+ };
134
+ const buildSamplingContextFromAttributes = (attributes) => {
135
+ const pathValue = pickAttribute(attributes, ["url.path", "http.target", "http.route"]);
136
+ const methodValue = pickAttribute(attributes, ["http.request.method", "http.method"]);
137
+ const userTypeValue = pickAttribute(attributes, ["enduser.type", "telemetry.request.user_type"]);
138
+ const statusValue = pickAttribute(attributes, [
83
139
  "http.response.status_code",
84
140
  "http.status_code",
85
141
  "telemetry.response.status_code"
86
- ]), o = f(t, [
142
+ ]);
143
+ const inspectionValue = pickAttribute(attributes, [
87
144
  "telemetry.request.inspection",
88
145
  "http.request.header.x-inspection",
89
146
  "x-inspection"
90
- ]), i = o === !0 || o === "true" || o === "1" || o === 1;
147
+ ]);
148
+ const inspection = inspectionValue === true || inspectionValue === "true" || inspectionValue === "1" || inspectionValue === 1;
91
149
  return {
92
- path: y(e),
93
- method: y(r),
94
- userType: y(n),
95
- statusCode: b(s),
96
- inspection: i
150
+ path: toStringValue(pathValue),
151
+ method: toStringValue(methodValue),
152
+ userType: toStringValue(userTypeValue),
153
+ statusCode: toNumberValue(statusValue),
154
+ inspection
97
155
  };
98
- }, w = (t, e) => !t || !(e != null && e.length) ? !1 : e.some((r) => r.toLowerCase() === t.toLowerCase()), st = (t, e) => !t || !(e != null && e.length) ? !1 : e.some((r) => Z(r, t)), it = (t, e) => {
99
- const r = t.when;
100
- if (!r) return !0;
101
- if (r.inspection !== void 0 && r.inspection !== e.inspection || r.path && !st(e.path, r.path) || r.method && !w(e.method, r.method) || r.userType && !w(e.userType, r.userType))
102
- return !1;
103
- if (r.statusCode && typeof e.statusCode == "number") {
104
- if (!r.statusCode.includes(e.statusCode))
105
- return !1;
106
- } else if (r.statusCode && r.statusCode.length)
107
- return !1;
108
- return !(typeof r.minStatusCode == "number" && (typeof e.statusCode != "number" || e.statusCode < r.minStatusCode) || typeof r.maxStatusCode == "number" && (typeof e.statusCode != "number" || e.statusCode > r.maxStatusCode));
109
- }, N = (t) => Math.max(0, Math.min(1, t)), at = (t, e, r) => {
110
- if (!(t != null && t.length)) return N(r);
111
- for (const n of t)
112
- if (it(n, e))
113
- return N(n.ratio);
114
- return N(r);
115
- }, I = /* @__PURE__ */ new Map(), ct = (t) => {
116
- if (t <= 0)
117
- return new M(0);
118
- if (t >= 1)
119
- return new z();
120
- const e = Number(t.toFixed(6)), r = I.get(e);
121
- if (r) return r;
122
- const n = new M(e);
123
- return I.set(e, n), n;
124
- }, P = (t, e) => (t !== e.ignore && (e.ignore = t, e.matcher = tt(t)), e.matcher), ut = (t) => {
125
- const e = {};
156
+ };
157
+ const matchStringList = (value, list) => {
158
+ if (!value || !list?.length) return false;
159
+ return list.some((item) => item.toLowerCase() === value.toLowerCase());
160
+ };
161
+ const matchPathList = (path, list) => {
162
+ if (!path || !list?.length) return false;
163
+ return list.some((pattern) => matchesPathPattern(pattern, path));
164
+ };
165
+ const matchSamplingRule = (rule, context2) => {
166
+ const condition = rule.when;
167
+ if (!condition) return true;
168
+ if (condition.inspection !== void 0 && condition.inspection !== context2.inspection) {
169
+ return false;
170
+ }
171
+ if (condition.path && !matchPathList(context2.path, condition.path)) {
172
+ return false;
173
+ }
174
+ if (condition.method && !matchStringList(context2.method, condition.method)) {
175
+ return false;
176
+ }
177
+ if (condition.userType && !matchStringList(context2.userType, condition.userType)) {
178
+ return false;
179
+ }
180
+ if (condition.statusCode && typeof context2.statusCode === "number") {
181
+ if (!condition.statusCode.includes(context2.statusCode)) {
182
+ return false;
183
+ }
184
+ } else if (condition.statusCode && condition.statusCode.length) {
185
+ return false;
186
+ }
187
+ if (typeof condition.minStatusCode === "number") {
188
+ if (typeof context2.statusCode !== "number" || context2.statusCode < condition.minStatusCode) {
189
+ return false;
190
+ }
191
+ }
192
+ if (typeof condition.maxStatusCode === "number") {
193
+ if (typeof context2.statusCode !== "number" || context2.statusCode > condition.maxStatusCode) {
194
+ return false;
195
+ }
196
+ }
197
+ return true;
198
+ };
199
+ const clampRatio = (ratio) => Math.max(0, Math.min(1, ratio));
200
+ const resolveSamplingRatio = (rules, context2, defaultRatio) => {
201
+ if (!rules?.length) return clampRatio(defaultRatio);
202
+ for (const rule of rules) {
203
+ if (matchSamplingRule(rule, context2)) {
204
+ return clampRatio(rule.ratio);
205
+ }
206
+ }
207
+ return clampRatio(defaultRatio);
208
+ };
209
+ const samplerCache = /* @__PURE__ */ new Map();
210
+ const getSamplerForRatio = (ratio) => {
211
+ if (ratio <= 0) {
212
+ return new TraceIdRatioBasedSampler(0);
213
+ }
214
+ if (ratio >= 1) {
215
+ return new AlwaysOnSampler();
216
+ }
217
+ const normalized = Number(ratio.toFixed(6));
218
+ const cached = samplerCache.get(normalized);
219
+ if (cached) return cached;
220
+ const sampler = new TraceIdRatioBasedSampler(normalized);
221
+ samplerCache.set(normalized, sampler);
222
+ return sampler;
223
+ };
224
+ const getSpanNameMatcher = (config, cache) => {
225
+ if (config !== cache.ignore) {
226
+ cache.ignore = config;
227
+ cache.matcher = createSpanNameMatcher(config);
228
+ }
229
+ return cache.matcher;
230
+ };
231
+ const createRuleBasedSampler = (runtime2) => {
232
+ const matcherCache = {};
126
233
  return {
127
- shouldSample(r, n, s, o, i, a) {
128
- const c = j.getSpanContext(r);
129
- if (c) {
130
- if (c.isRemote) {
131
- const u = t.getConfig(), S = P(u.ignore, e);
132
- if (S != null && S(s))
133
- return { decision: l.NOT_RECORD };
234
+ shouldSample(context2, traceId, spanName, spanKind, attributes, links) {
235
+ const parentContext = trace.getSpanContext(context2);
236
+ if (parentContext) {
237
+ if (parentContext.isRemote) {
238
+ const config2 = runtime2.getConfig();
239
+ const matcher2 = getSpanNameMatcher(config2.ignore, matcherCache);
240
+ if (matcher2?.(spanName)) {
241
+ return { decision: SamplingDecision.NOT_RECORD };
242
+ }
134
243
  }
135
- return (c.traceFlags & O.SAMPLED) === O.SAMPLED ? { decision: l.RECORD_AND_SAMPLED } : { decision: l.NOT_RECORD };
244
+ if ((parentContext.traceFlags & TraceFlags.SAMPLED) === TraceFlags.SAMPLED) {
245
+ return { decision: SamplingDecision.RECORD_AND_SAMPLED };
246
+ }
247
+ return { decision: SamplingDecision.NOT_RECORD };
248
+ }
249
+ const config = runtime2.getConfig();
250
+ const matcher = getSpanNameMatcher(config.ignore, matcherCache);
251
+ if (matcher?.(spanName)) {
252
+ return { decision: SamplingDecision.NOT_RECORD };
136
253
  }
137
- const m = t.getConfig(), p = P(m.ignore, e);
138
- if (p != null && p(s))
139
- return { decision: l.NOT_RECORD };
140
- const E = ot(i);
141
- if (E.inspection)
142
- return { decision: l.RECORD_AND_SAMPLED };
143
- const d = at(
144
- m.samplingRules,
145
- E,
146
- m.defaultSamplingRatio ?? 0.01
254
+ const samplingContext = buildSamplingContextFromAttributes(attributes);
255
+ if (samplingContext.inspection) {
256
+ return { decision: SamplingDecision.RECORD_AND_SAMPLED };
257
+ }
258
+ const ratio = resolveSamplingRatio(
259
+ config.samplingRules,
260
+ samplingContext,
261
+ config.defaultSamplingRatio ?? 0.01
147
262
  );
148
- return ct(d).shouldSample(r, n, s, o, i, a);
263
+ return getSamplerForRatio(ratio).shouldSample(context2, traceId, spanName, spanKind, attributes, links);
149
264
  },
150
265
  toString: () => "RuleBasedSampler"
151
266
  };
152
- }, mt = (t) => t;
153
- let _, h;
154
- const g = (t) => Array.from(new Set(t)), C = (t) => (t == null ? void 0 : t.split(",").map((e) => e.trim()).filter(Boolean)) ?? [], pt = (t) => {
155
- if (!t) return;
156
- const e = {};
157
- for (const r of t.split(",")) {
158
- const [n, ...s] = r.split("="), o = n == null ? void 0 : n.trim();
159
- o && (e[o] = s.join("=").trim());
160
- }
161
- return e;
162
- }, L = (t) => {
163
- if (t)
164
- try {
165
- return JSON.parse(t);
166
- } catch {
167
- return;
168
- }
169
- }, Et = async (t) => {
170
- if (t)
171
- try {
172
- const e = await X(t, "utf8");
173
- return L(e);
174
- } catch {
175
- return;
176
- }
177
- }, v = (t, e) => {
178
- const r = g([...(t == null ? void 0 : t.paths) ?? [], ...(e == null ? void 0 : e.paths) ?? []]), n = g([
179
- ...(t == null ? void 0 : t.extensions) ?? [],
180
- ...(e == null ? void 0 : e.extensions) ?? []
181
- ]), s = g([...(t == null ? void 0 : t.methods) ?? [], ...(e == null ? void 0 : e.methods) ?? []]), o = g([...(t == null ? void 0 : t.spanNames) ?? [], ...(e == null ? void 0 : e.spanNames) ?? []]);
267
+ };
268
+ const createFilteringTraceExporter = (exporter) => exporter;
269
+ let sdk;
270
+ let runtime;
271
+ const uniqueList = (items) => Array.from(new Set(items));
272
+ const parseList = (value) => value?.split(",").map((item) => item.trim()).filter(Boolean) ?? [];
273
+ const parseHeaders = (value) => {
274
+ if (!value) return void 0;
275
+ const headers = {};
276
+ for (const pair of value.split(",")) {
277
+ const [key, ...rest] = pair.split("=");
278
+ const normalizedKey = key?.trim();
279
+ if (!normalizedKey) continue;
280
+ headers[normalizedKey] = rest.join("=").trim();
281
+ }
282
+ return headers;
283
+ };
284
+ const parseJson = (value) => {
285
+ if (!value) return void 0;
286
+ try {
287
+ return JSON.parse(value);
288
+ } catch {
289
+ return void 0;
290
+ }
291
+ };
292
+ const readConfigFile = async (path) => {
293
+ if (!path) return void 0;
294
+ try {
295
+ const raw = await readFile(path, "utf8");
296
+ return parseJson(raw);
297
+ } catch {
298
+ return void 0;
299
+ }
300
+ };
301
+ const mergeIgnoreConfig = (base, overrides) => {
302
+ const mergedPaths = uniqueList([...base?.paths ?? [], ...overrides?.paths ?? []]);
303
+ const mergedExtensions = uniqueList([
304
+ ...base?.extensions ?? [],
305
+ ...overrides?.extensions ?? []
306
+ ]);
307
+ const mergedMethods = uniqueList([...base?.methods ?? [], ...overrides?.methods ?? []]);
308
+ const mergedSpanNames = uniqueList([...base?.spanNames ?? [], ...overrides?.spanNames ?? []]);
182
309
  return {
183
- paths: r.length ? r : void 0,
184
- extensions: n.length ? n : void 0,
185
- methods: s.length ? s : void 0,
186
- spanNames: o.length ? o : void 0
310
+ paths: mergedPaths.length ? mergedPaths : void 0,
311
+ extensions: mergedExtensions.length ? mergedExtensions : void 0,
312
+ methods: mergedMethods.length ? mergedMethods : void 0,
313
+ spanNames: mergedSpanNames.length ? mergedSpanNames : void 0
187
314
  };
188
- }, ht = (t) => {
189
- if (t != null && t.length)
190
- return t.map((e) => ({
191
- ...e,
192
- ratio: Number.isFinite(e.ratio) ? Math.max(0, Math.min(1, e.ratio)) : 0
193
- })).filter((e) => e.ratio >= 0);
194
- }, lt = async (t = process.env) => {
195
- const e = await Et(t.TELEMETRY_CONFIG_FILE), r = C(t.TELEMETRY_IGNORE_PATHS), n = C(t.TELEMETRY_IGNORE_EXTENSIONS), s = C(t.TELEMETRY_IGNORE_METHODS), o = L(t.TELEMETRY_SAMPLING_RULES), i = typeof (e == null ? void 0 : e.samplingRules) == "string" ? L(e.samplingRules) : e == null ? void 0 : e.samplingRules, a = {
196
- serviceName: t.OTEL_SERVICE_NAME || t.TELEMETRY_SERVICE_NAME,
197
- serviceVersion: t.OTEL_SERVICE_VERSION || t.TELEMETRY_SERVICE_VERSION,
198
- environment: t.OTEL_RESOURCE_ATTRIBUTES || t.TELEMETRY_ENVIRONMENT || (t.VERCEL ? t.VERCEL_ENV : void 0),
199
- defaultSamplingRatio: t.TELEMETRY_SAMPLING_RATIO ? Number(t.TELEMETRY_SAMPLING_RATIO) : void 0,
200
- url: t.OTEL_EXPORTER_OTLP_ENDPOINT || t.TELEMETRY_OTLP_ENDPOINT,
201
- headers: pt(t.OTEL_EXPORTER_OTLP_HEADERS || t.TELEMETRY_OTLP_HEADERS),
202
- inspectionHeader: t.TELEMETRY_INSPECTION_HEADER,
203
- userTypeHeader: t.TELEMETRY_USER_TYPE_HEADER,
204
- requestStartHeader: t.TELEMETRY_REQUEST_START_HEADER,
205
- samplingRules: o || i,
206
- ignore: r.length || n.length || s.length ? {
207
- paths: r.length ? r : void 0,
208
- extensions: n.length ? n : void 0,
209
- methods: s.length ? s : void 0
315
+ };
316
+ const normalizeSamplingRules = (rules) => {
317
+ if (!rules?.length) return void 0;
318
+ return rules.map((rule) => ({
319
+ ...rule,
320
+ ratio: Number.isFinite(rule.ratio) ? Math.max(0, Math.min(1, rule.ratio)) : 0
321
+ })).filter((rule) => rule.ratio >= 0);
322
+ };
323
+ const loadTelemetryConfigFromEnv = async (env = process.env) => {
324
+ const fileConfig = await readConfigFile(env.TELEMETRY_CONFIG_FILE);
325
+ const ignorePaths = parseList(env.TELEMETRY_IGNORE_PATHS);
326
+ const ignoreExtensions = parseList(env.TELEMETRY_IGNORE_EXTENSIONS);
327
+ const ignoreMethods = parseList(env.TELEMETRY_IGNORE_METHODS);
328
+ const samplingRules = parseJson(env.TELEMETRY_SAMPLING_RULES);
329
+ const fileSamplingRules = typeof fileConfig?.samplingRules === "string" ? parseJson(fileConfig.samplingRules) : fileConfig?.samplingRules;
330
+ const envConfig = {
331
+ serviceName: env.OTEL_SERVICE_NAME || env.TELEMETRY_SERVICE_NAME,
332
+ serviceVersion: env.OTEL_SERVICE_VERSION || env.TELEMETRY_SERVICE_VERSION,
333
+ environment: env.OTEL_RESOURCE_ATTRIBUTES || env.TELEMETRY_ENVIRONMENT || (env.VERCEL ? env.VERCEL_ENV : void 0),
334
+ defaultSamplingRatio: env.TELEMETRY_SAMPLING_RATIO ? Number(env.TELEMETRY_SAMPLING_RATIO) : void 0,
335
+ url: env.OTEL_EXPORTER_OTLP_ENDPOINT || env.TELEMETRY_OTLP_ENDPOINT,
336
+ headers: parseHeaders(env.OTEL_EXPORTER_OTLP_HEADERS || env.TELEMETRY_OTLP_HEADERS),
337
+ inspectionHeader: env.TELEMETRY_INSPECTION_HEADER,
338
+ userTypeHeader: env.TELEMETRY_USER_TYPE_HEADER,
339
+ requestStartHeader: env.TELEMETRY_REQUEST_START_HEADER,
340
+ samplingRules: samplingRules || fileSamplingRules,
341
+ ignore: ignorePaths.length || ignoreExtensions.length || ignoreMethods.length ? {
342
+ paths: ignorePaths.length ? ignorePaths : void 0,
343
+ extensions: ignoreExtensions.length ? ignoreExtensions : void 0,
344
+ methods: ignoreMethods.length ? ignoreMethods : void 0
210
345
  } : void 0
211
346
  };
212
- return e && typeof e == "object" ? { ...e, ...a } : a;
213
- }, ft = (t) => {
214
- if (!t) return process.env.NODE_ENV || "development";
215
- if (t.includes("deployment.environment")) {
216
- const r = t.split(",").find((s) => s.includes("deployment.environment"));
217
- if (!r) return t;
218
- const [, n] = r.split("=");
219
- return (n == null ? void 0 : n.trim()) || t;
220
- }
221
- return t;
222
- }, dt = (t, e) => {
223
- var n, s;
224
- const r = [
225
- { name: "inspection", ratio: 1, when: { inspection: !0 } },
347
+ if (fileConfig && typeof fileConfig === "object") {
348
+ return { ...fileConfig, ...envConfig };
349
+ }
350
+ return envConfig;
351
+ };
352
+ const resolveEnvironmentName = (environment) => {
353
+ if (!environment) return process.env.NODE_ENV || "development";
354
+ if (environment.includes("deployment.environment")) {
355
+ const entries = environment.split(",");
356
+ const match = entries.find((entry) => entry.includes("deployment.environment"));
357
+ if (!match) return environment;
358
+ const [, value] = match.split("=");
359
+ return value?.trim() || environment;
360
+ }
361
+ return environment;
362
+ };
363
+ const buildLayeredSamplingRules = (layered, defaultRatio) => {
364
+ const rules = [
365
+ { name: "inspection", ratio: 1, when: { inspection: true } },
226
366
  { name: "error", ratio: 1, when: { minStatusCode: 500 } }
227
367
  ];
228
- if ((n = t == null ? void 0 : t.criticalPaths) != null && n.length && r.push({
229
- name: "critical-path",
230
- ratio: 1,
231
- when: { path: t.criticalPaths }
232
- }), (s = t == null ? void 0 : t.criticalUserTypes) != null && s.length && r.push({
233
- name: "critical-user",
234
- ratio: 1,
235
- when: { userType: t.criticalUserTypes }
236
- }), t != null && t.pathSampling)
237
- for (const [o, i] of Object.entries(t.pathSampling))
238
- r.push({
239
- name: `path:${o}`,
240
- ratio: i,
241
- when: { path: [o] }
368
+ if (layered?.criticalPaths?.length) {
369
+ rules.push({
370
+ name: "critical-path",
371
+ ratio: 1,
372
+ when: { path: layered.criticalPaths }
373
+ });
374
+ }
375
+ if (layered?.criticalUserTypes?.length) {
376
+ rules.push({
377
+ name: "critical-user",
378
+ ratio: 1,
379
+ when: { userType: layered.criticalUserTypes }
380
+ });
381
+ }
382
+ if (layered?.pathSampling) {
383
+ for (const [pattern, ratio] of Object.entries(layered.pathSampling)) {
384
+ rules.push({
385
+ name: `path:${pattern}`,
386
+ ratio,
387
+ when: { path: [pattern] }
242
388
  });
243
- if (t != null && t.userTypeSampling)
244
- for (const [o, i] of Object.entries(t.userTypeSampling))
245
- r.push({
246
- name: `user:${o}`,
247
- ratio: i,
248
- when: { userType: [o] }
389
+ }
390
+ }
391
+ if (layered?.userTypeSampling) {
392
+ for (const [userType, ratio] of Object.entries(layered.userTypeSampling)) {
393
+ rules.push({
394
+ name: `user:${userType}`,
395
+ ratio,
396
+ when: { userType: [userType] }
249
397
  });
250
- return r.push({ name: "default", ratio: e }), r;
251
- }, F = (t) => {
252
- const e = ft(t.environment), r = v(et, t.ignore), n = typeof t.defaultSamplingRatio == "number" ? Math.max(0, Math.min(1, t.defaultSamplingRatio)) : 0.01, s = ht(t.samplingRules) ?? dt(t.layeredSampling, n);
398
+ }
399
+ }
400
+ rules.push({ name: "default", ratio: defaultRatio });
401
+ return rules;
402
+ };
403
+ const normalizeResolvedConfig = (merged) => {
404
+ const environment = resolveEnvironmentName(merged.environment);
405
+ const ignore = mergeIgnoreConfig(defaultIgnoreConfig, merged.ignore);
406
+ const defaultSamplingRatio = typeof merged.defaultSamplingRatio === "number" ? Math.max(0, Math.min(1, merged.defaultSamplingRatio)) : 0.01;
407
+ const samplingRules = normalizeSamplingRules(merged.samplingRules) ?? buildLayeredSamplingRules(merged.layeredSampling, defaultSamplingRatio);
253
408
  return {
254
- ...t,
255
- serviceName: t.serviceName || "unknown-service",
256
- serviceVersion: t.serviceVersion,
257
- environment: e,
258
- defaultSamplingRatio: n,
259
- ignore: r,
260
- samplingRules: s,
261
- inspectionHeader: t.inspectionHeader ?? "x-inspection",
262
- userTypeHeader: t.userTypeHeader ?? "x-telemetry-user-type",
263
- requestStartHeader: t.requestStartHeader ?? "x-telemetry-start",
264
- scheduledDelayMillis: t.scheduledDelayMillis ?? 1e3,
265
- maxQueueSize: t.maxQueueSize ?? 2048,
266
- maxExportBatchSize: t.maxExportBatchSize ?? 512,
267
- exporterTimeoutMillis: t.exporterTimeoutMillis ?? 3e4
409
+ ...merged,
410
+ serviceName: merged.serviceName || "unknown-service",
411
+ serviceVersion: merged.serviceVersion,
412
+ environment,
413
+ defaultSamplingRatio,
414
+ ignore,
415
+ samplingRules,
416
+ inspectionHeader: merged.inspectionHeader ?? "x-inspection",
417
+ userTypeHeader: merged.userTypeHeader ?? "x-telemetry-user-type",
418
+ requestStartHeader: merged.requestStartHeader ?? "x-telemetry-start",
419
+ scheduledDelayMillis: merged.scheduledDelayMillis ?? 1e3,
420
+ maxQueueSize: merged.maxQueueSize ?? 2048,
421
+ maxExportBatchSize: merged.maxExportBatchSize ?? 512,
422
+ exporterTimeoutMillis: merged.exporterTimeoutMillis ?? 3e4
268
423
  };
269
- }, Y = async (t = {}, e = process.env) => {
270
- const r = await lt(e);
271
- return F({ ...r, ...t });
272
- }, Tt = async (t) => {
273
- let e = await Y(t);
424
+ };
425
+ const resolveTelemetryConfig = async (options = {}, env = process.env) => {
426
+ const envConfig = await loadTelemetryConfigFromEnv(env);
427
+ return normalizeResolvedConfig({ ...envConfig, ...options });
428
+ };
429
+ const createTelemetryRuntime = async (initialConfig) => {
430
+ let current = await resolveTelemetryConfig(initialConfig);
274
431
  return {
275
- getConfig: () => e,
276
- updateConfig: (r) => {
277
- e = F({
278
- ...e,
279
- ...r,
280
- ignore: v(e.ignore, r.ignore)
432
+ getConfig: () => current,
433
+ updateConfig: (partial) => {
434
+ current = normalizeResolvedConfig({
435
+ ...current,
436
+ ...partial,
437
+ ignore: mergeIgnoreConfig(current.ignore, partial.ignore)
281
438
  });
282
439
  }
283
440
  };
284
- }, Rt = (t) => t[0] * 1e3 + t[1] / 1e6, gt = (t, e) => {
285
- for (const r of e) {
286
- const n = t.attributes[r];
287
- if (typeof n == "number") return n;
288
- }
289
- }, _t = (t) => {
290
- const e = t.events.find((n) => n.name === "exception");
291
- if (!(e != null && e.attributes)) return;
292
- const r = e.attributes["exception.message"];
293
- return typeof r == "string" ? r : void 0;
294
- }, V = /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$/, x = (t) => {
295
- for (const r of ["url.path", "http.target", "http.route", "next.route"]) {
296
- const n = t[r];
297
- if (n && typeof n == "string") return n;
298
- }
299
- const e = t["http.url"] ?? t["url.full"];
300
- if (e && typeof e == "string")
441
+ };
442
+ const hrTimeToMilliseconds = (time) => time[0] * 1e3 + time[1] / 1e6;
443
+ const getSpanAttributeNumber = (span, keys) => {
444
+ for (const key of keys) {
445
+ const value = span.attributes[key];
446
+ if (typeof value === "number") return value;
447
+ }
448
+ return void 0;
449
+ };
450
+ const extractExceptionMessage = (span) => {
451
+ const exceptionEvent = span.events.find((event) => event.name === "exception");
452
+ if (!exceptionEvent?.attributes) return void 0;
453
+ const message = exceptionEvent.attributes["exception.message"];
454
+ return typeof message === "string" ? message : void 0;
455
+ };
456
+ const BARE_HTTP_METHOD = /^(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)$/;
457
+ const extractPathFromSpanAttributes = (attrs) => {
458
+ for (const key of ["url.path", "http.target", "http.route", "next.route"]) {
459
+ const val = attrs[key];
460
+ if (val && typeof val === "string") return val;
461
+ }
462
+ const url = attrs["http.url"] ?? attrs["url.full"];
463
+ if (url && typeof url === "string") {
301
464
  try {
302
- return new URL(e).pathname;
465
+ return new URL(url).pathname;
303
466
  } catch {
304
- return e.split("?")[0];
467
+ return url.split("?")[0];
305
468
  }
306
- }, St = () => ({
307
- onStart: (t) => {
308
- const e = t, r = e.name;
309
- if (!r || !V.test(r)) return;
310
- const n = x(e.attributes);
311
- n && t.updateName(`${r} ${n}`);
312
- },
313
- onEnd: (t) => {
314
- const e = t.name;
315
- if (!e || !V.test(e)) return;
316
- const r = x(t.attributes);
317
- if (!r) return;
318
- const n = t.attributes["http.request.method"] ?? t.attributes["http.method"], s = typeof n == "string" ? n : e;
319
- t.name = `${s} ${r}`;
320
- },
321
- shutdown: () => Promise.resolve(),
322
- forceFlush: () => Promise.resolve()
323
- }), yt = () => ({
324
- onStart: () => {
325
- },
326
- onEnd: (t) => {
327
- const e = Rt(t.duration);
328
- t.attributes["telemetry.request.duration_ms"] = Math.round(e), typeof t.attributes["telemetry.request.start_time_ms"] != "number" && (t.attributes["telemetry.request.start_time_ms"] = Date.now() - Math.round(e));
329
- const r = gt(t, [
330
- "http.response.status_code",
331
- "http.status_code",
332
- "telemetry.response.status_code"
333
- ]);
334
- if (typeof r == "number" && (t.attributes["telemetry.response.status_code"] = r), t.status.code === Q.ERROR || typeof r == "number" && r >= 500) {
335
- t.attributes["telemetry.request.error"] = !0;
336
- const s = t.status.message || _t(t);
337
- s && (t.attributes["telemetry.request.error_message"] = s);
338
- }
339
- },
340
- shutdown: () => Promise.resolve(),
341
- forceFlush: () => Promise.resolve()
342
- }), xt = async (t) => {
343
- const e = await Y(t);
344
- h = await Tt(e);
345
- const r = process.env, n = {
346
- [k]: e.serviceName,
347
- [B]: e.serviceVersion || "1.0.0",
348
- [rt]: e.environment || r.VERCEL_ENV || r.NODE_ENV || "development"
469
+ }
470
+ return void 0;
471
+ };
472
+ const createSpanNamingProcessor = () => {
473
+ return {
474
+ onStart: (span) => {
475
+ const sdkSpan = span;
476
+ const name = sdkSpan.name;
477
+ if (!name || !BARE_HTTP_METHOD.test(name)) return;
478
+ const path = extractPathFromSpanAttributes(sdkSpan.attributes);
479
+ if (path) {
480
+ span.updateName(`${name} ${path}`);
481
+ }
482
+ },
483
+ onEnd: (span) => {
484
+ const name = span.name;
485
+ if (!name || !BARE_HTTP_METHOD.test(name)) return;
486
+ const path = extractPathFromSpanAttributes(span.attributes);
487
+ if (!path) return;
488
+ const method = span.attributes["http.request.method"] ?? span.attributes["http.method"];
489
+ const resolvedMethod = typeof method === "string" ? method : name;
490
+ span.name = `${resolvedMethod} ${path}`;
491
+ },
492
+ shutdown: () => Promise.resolve(),
493
+ forceFlush: () => Promise.resolve()
349
494
  };
350
- r.VERCEL && (r.VERCEL_REGION && (n["vercel.region"] = r.VERCEL_REGION), r.NEXT_RUNTIME && (n["vercel.runtime"] = r.NEXT_RUNTIME), r.VERCEL_GIT_COMMIT_SHA && (n["vercel.sha"] = r.VERCEL_GIT_COMMIT_SHA), r.VERCEL_URL && (n["vercel.host"] = r.VERCEL_URL), r.VERCEL_BRANCH_URL && (n["vercel.branch_host"] = r.VERCEL_BRANCH_URL), r.VERCEL_DEPLOYMENT_ID && (n["vercel.deployment_id"] = r.VERCEL_DEPLOYMENT_ID));
351
- const s = q(n), o = new G(e), i = mt(o), a = [
352
- St(),
353
- yt(),
354
- new $(i, {
355
- scheduledDelayMillis: e.scheduledDelayMillis,
356
- maxQueueSize: e.maxQueueSize,
357
- maxExportBatchSize: e.maxExportBatchSize,
358
- exportTimeoutMillis: e.exporterTimeoutMillis
495
+ };
496
+ const createRequestSpanProcessor = () => {
497
+ return {
498
+ onStart: () => void 0,
499
+ onEnd: (span) => {
500
+ const durationMs = hrTimeToMilliseconds(span.duration);
501
+ span.attributes["telemetry.request.duration_ms"] = Math.round(durationMs);
502
+ if (typeof span.attributes["telemetry.request.start_time_ms"] !== "number") {
503
+ span.attributes["telemetry.request.start_time_ms"] = Date.now() - Math.round(durationMs);
504
+ }
505
+ const statusCode = getSpanAttributeNumber(span, [
506
+ "http.response.status_code",
507
+ "http.status_code",
508
+ "telemetry.response.status_code"
509
+ ]);
510
+ if (typeof statusCode === "number") {
511
+ span.attributes["telemetry.response.status_code"] = statusCode;
512
+ }
513
+ const isError = span.status.code === SpanStatusCode.ERROR || typeof statusCode === "number" && statusCode >= 500;
514
+ if (isError) {
515
+ span.attributes["telemetry.request.error"] = true;
516
+ const message = span.status.message || extractExceptionMessage(span);
517
+ if (message) {
518
+ span.attributes["telemetry.request.error_message"] = message;
519
+ }
520
+ }
521
+ },
522
+ shutdown: () => Promise.resolve(),
523
+ forceFlush: () => Promise.resolve()
524
+ };
525
+ };
526
+ const startTelemetry = async (options) => {
527
+ const config = await resolveTelemetryConfig(options);
528
+ runtime = await createTelemetryRuntime(config);
529
+ const env = process.env;
530
+ const resourceAttrs = {
531
+ [ATTR_SERVICE_NAME]: config.serviceName,
532
+ [ATTR_SERVICE_VERSION]: config.serviceVersion || "1.0.0",
533
+ [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: config.environment || env.VERCEL_ENV || env.NODE_ENV || "development"
534
+ };
535
+ if (env.VERCEL) {
536
+ if (env.VERCEL_REGION) resourceAttrs["vercel.region"] = env.VERCEL_REGION;
537
+ if (env.NEXT_RUNTIME) resourceAttrs["vercel.runtime"] = env.NEXT_RUNTIME;
538
+ if (env.VERCEL_GIT_COMMIT_SHA) resourceAttrs["vercel.sha"] = env.VERCEL_GIT_COMMIT_SHA;
539
+ if (env.VERCEL_URL) resourceAttrs["vercel.host"] = env.VERCEL_URL;
540
+ if (env.VERCEL_BRANCH_URL) resourceAttrs["vercel.branch_host"] = env.VERCEL_BRANCH_URL;
541
+ if (env.VERCEL_DEPLOYMENT_ID) resourceAttrs["vercel.deployment_id"] = env.VERCEL_DEPLOYMENT_ID;
542
+ }
543
+ const resource = resourceFromAttributes(resourceAttrs);
544
+ const traceExporter = new OTLPTraceExporter(config);
545
+ const filteringExporter = createFilteringTraceExporter(traceExporter);
546
+ const spanProcessors = [
547
+ createSpanNamingProcessor(),
548
+ createRequestSpanProcessor(),
549
+ new BatchSpanProcessor(filteringExporter, {
550
+ scheduledDelayMillis: config.scheduledDelayMillis,
551
+ maxQueueSize: config.maxQueueSize,
552
+ maxExportBatchSize: config.maxExportBatchSize,
553
+ exportTimeoutMillis: config.exporterTimeoutMillis
359
554
  })
360
555
  ];
361
- _ = new U({
362
- serviceName: e.serviceName,
363
- resource: s,
364
- spanProcessors: a,
365
- sampler: ut(h),
366
- instrumentations: nt(e)
556
+ sdk = new NodeSDK({
557
+ serviceName: config.serviceName,
558
+ resource,
559
+ spanProcessors,
560
+ sampler: createRuleBasedSampler(runtime),
561
+ instrumentations: getInstrumentations(config)
367
562
  });
368
563
  try {
369
- await _.start();
370
- } catch (c) {
371
- console.warn("Telemetry start failed:", c);
372
- }
373
- return h;
374
- }, Dt = (t) => {
375
- h == null || h.updateConfig(t);
376
- }, Ht = (t) => (D.inject(H.active(), t), t), bt = (t = fetch) => async (e, r) => {
377
- const n = (r == null ? void 0 : r.headers) ?? (e instanceof Request ? e.headers : void 0), s = new Headers(n);
378
- return D.inject(H.active(), s, {
379
- set: (o, i, a) => {
380
- o.set(i, a);
381
- }
382
- }), t(e, { ...r, headers: s });
383
- }, vt = async () => {
384
- _ && await _.shutdown();
564
+ await sdk.start();
565
+ } catch (error) {
566
+ console.warn("Telemetry start failed:", error);
567
+ }
568
+ return runtime;
569
+ };
570
+ const updateTelemetryConfig = (partial) => {
571
+ runtime?.updateConfig(partial);
572
+ };
573
+ const injectTraceHeaders = (headers) => {
574
+ propagation.inject(context.active(), headers);
575
+ return headers;
576
+ };
577
+ const createPropagatingFetch = (fetchImpl = fetch) => {
578
+ return async (input, init) => {
579
+ const baseHeaders = init?.headers ?? (input instanceof Request ? input.headers : void 0);
580
+ const headers = new Headers(baseHeaders);
581
+ propagation.inject(context.active(), headers, {
582
+ set: (carrier, key, value) => {
583
+ carrier.set(key, value);
584
+ }
585
+ });
586
+ return fetchImpl(input, { ...init, headers });
587
+ };
588
+ };
589
+ const shutdownTelemetry = async () => {
590
+ if (sdk) {
591
+ await sdk.shutdown();
592
+ }
385
593
  };
386
594
  export {
387
- ot as buildSamplingContextFromAttributes,
388
- K as createIgnoreMatcher,
389
- bt as createPropagatingFetch,
390
- tt as createSpanNameMatcher,
391
- Tt as createTelemetryRuntime,
392
- et as defaultIgnoreConfig,
393
- Ht as injectTraceHeaders,
394
- lt as loadTelemetryConfigFromEnv,
395
- it as matchSamplingRule,
396
- at as resolveSamplingRatio,
397
- Y as resolveTelemetryConfig,
398
- vt as shutdownTelemetry,
399
- xt as startTelemetry,
400
- Dt as updateTelemetryConfig
595
+ buildSamplingContextFromAttributes,
596
+ createIgnoreMatcher,
597
+ createPropagatingFetch,
598
+ createSpanNameMatcher,
599
+ createTelemetryRuntime,
600
+ defaultIgnoreConfig,
601
+ injectTraceHeaders,
602
+ loadTelemetryConfigFromEnv,
603
+ matchSamplingRule,
604
+ resolveSamplingRatio,
605
+ resolveTelemetryConfig,
606
+ shutdownTelemetry,
607
+ startTelemetry,
608
+ updateTelemetryConfig
401
609
  };