@0xchain/telemetry 1.1.0-beta.2 → 1.1.0-beta.20

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,98 +1,609 @@
1
- var S = Object.defineProperty;
2
- var f = (n, e, t) => e in n ? S(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t;
3
- var m = (n, e, t) => f(n, typeof e != "symbol" ? e + "" : e, t);
4
- import { NodeSDK as g } from "@opentelemetry/sdk-node";
5
- import { OTLPTraceExporter as E } from "@opentelemetry/exporter-trace-otlp-http";
6
- import { resourceFromAttributes as I } from "@opentelemetry/resources";
7
- import { ATTR_SERVICE_VERSION as T, ATTR_SERVICE_NAME as R } from "@opentelemetry/semantic-conventions";
8
- import { TraceIdRatioBasedSampler as _, AlwaysOnSampler as x, SamplingDecision as l, BatchSpanProcessor as w } from "@opentelemetry/sdk-trace-base";
9
- import { trace as A, TraceFlags as u } from "@opentelemetry/api";
10
- import { getNodeAutoInstrumentations as N } from "@opentelemetry/auto-instrumentations-node";
11
- import { UndiciInstrumentation as P } from "@opentelemetry/instrumentation-undici";
12
- const y = "deployment.environment.name";
13
- class D {
14
- constructor(e) {
15
- m(this, "defaultSampler");
16
- m(this, "inspectionSampler");
17
- this.defaultSampler = new _(e), this.inspectionSampler = new x();
18
- }
19
- shouldSample(e, t, r, s, o, i) {
20
- if (o["http.request.header.x-inspection"] === "true" || o["x-inspection"] === "true")
21
- return this.inspectionSampler.shouldSample(e, t, r, s, o, i);
22
- const a = A.getSpanContext(e);
23
- return a ? (a.traceFlags & u.SAMPLED) === u.SAMPLED ? { decision: l.RECORD_AND_SAMPLED } : { decision: l.NOT_RECORD } : this.defaultSampler.shouldSample(e, t, r, s, o, i);
24
- }
25
- toString() {
26
- return "InspectionSampler";
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;
27
18
  }
28
- }
29
- const O = [
30
- "/health",
31
- "/metrics",
32
- "/_next/static",
33
- "/favicon.ico",
34
- "/__nextjs_",
35
- "/.well-known"
36
- ], d = (n, e) => n.some((t) => e.includes(t));
37
- function M(n) {
38
- const e = [...O, ...n];
19
+ return typeof value === "string" ? value : void 0;
20
+ };
21
+ const getPathname = (url) => {
22
+ if (!url) return "";
23
+ try {
24
+ if (url.startsWith("http://") || url.startsWith("https://")) {
25
+ return new URL(url).pathname;
26
+ }
27
+ return new URL(url, "http://localhost").pathname;
28
+ } catch {
29
+ return url.split("?")[0] || url;
30
+ }
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();
39
38
  return [
40
- N({
41
- // 禁用可能导致问题的检测器
42
- "@opentelemetry/instrumentation-grpc": {
43
- enabled: !1
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()
50
+ };
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;
66
+ },
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
+ }
44
77
  },
45
- "@opentelemetry/instrumentation-http": {
46
- enabled: !0,
47
- startIncomingSpanHook: (t) => "headers" in t && t.headers["x-inspection"] === "true" ? {
48
- "http.request.header.x-inspection": "true",
49
- "x-inspection": "true"
50
- } : {},
51
- ignoreIncomingRequestHook: (t) => {
52
- const r = t.url || "";
53
- return d(e, r);
54
- },
55
- ignoreOutgoingRequestHook: (t) => {
56
- const r = typeof t == "string" ? t : t.path || "";
57
- return d(e, r);
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);
58
90
  }
91
+ },
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);
97
+ },
98
+ ignoreOutgoingRequestHook: (request) => {
99
+ const url = typeof request === "string" ? request : request.path || "";
100
+ return shouldIgnore(getPathname(url));
59
101
  }
60
102
  }),
61
- new P()
103
+ new UndiciInstrumentation()
62
104
  ];
63
105
  }
64
- let c;
65
- function j(n) {
66
- const {
67
- serviceName: e,
68
- serviceVersion: t,
69
- environment: r,
70
- ignorePaths: s = [],
71
- inspectionRatio: o = 0.01,
72
- ...i
73
- } = n, p = I({
74
- [R]: e || "ichaingo-seo",
75
- [T]: t || "1.0.0",
76
- [y]: r || "development"
77
- }), a = new E(i), h = new w(a, {
78
- scheduledDelayMillis: 1e3,
79
- // 1秒延迟
80
- maxQueueSize: 2048,
81
- maxExportBatchSize: 512
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, [
139
+ "http.response.status_code",
140
+ "http.status_code",
141
+ "telemetry.response.status_code"
142
+ ]);
143
+ const inspectionValue = pickAttribute(attributes, [
144
+ "telemetry.request.inspection",
145
+ "http.request.header.x-inspection",
146
+ "x-inspection"
147
+ ]);
148
+ const inspection = inspectionValue === true || inspectionValue === "true" || inspectionValue === "1" || inspectionValue === 1;
149
+ return {
150
+ path: toStringValue(pathValue),
151
+ method: toStringValue(methodValue),
152
+ userType: toStringValue(userTypeValue),
153
+ statusCode: toNumberValue(statusValue),
154
+ inspection
155
+ };
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 = {};
233
+ return {
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
+ }
243
+ }
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 };
253
+ }
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
262
+ );
263
+ return getSamplerForRatio(ratio).shouldSample(context2, traceId, spanName, spanKind, attributes, links);
264
+ },
265
+ toString: () => "RuleBasedSampler"
266
+ };
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 ?? []]);
309
+ return {
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
314
+ };
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
345
+ } : void 0
346
+ };
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 } },
366
+ { name: "error", ratio: 1, when: { minStatusCode: 500 } }
367
+ ];
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] }
388
+ });
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] }
397
+ });
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);
408
+ return {
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
423
+ };
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);
431
+ return {
432
+ getConfig: () => current,
433
+ updateConfig: (partial) => {
434
+ current = normalizeResolvedConfig({
435
+ ...current,
436
+ ...partial,
437
+ ignore: mergeIgnoreConfig(current.ignore, partial.ignore)
438
+ });
439
+ }
440
+ };
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") {
464
+ try {
465
+ return new URL(url).pathname;
466
+ } catch {
467
+ return url.split("?")[0];
468
+ }
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()
494
+ };
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
554
+ })
555
+ ];
556
+ sdk = new NodeSDK({
557
+ serviceName: config.serviceName,
558
+ resource,
559
+ spanProcessors,
560
+ sampler: createRuleBasedSampler(runtime),
561
+ instrumentations: getInstrumentations(config)
82
562
  });
83
- c = new g({
84
- serviceName: e,
85
- resource: p,
86
- spanProcessors: [h],
87
- sampler: new D(o),
88
- // traceExporter,
89
- instrumentations: M(s)
90
- }), c.start(), console.log("OpenTelemetry started:", JSON.stringify(n, null, 2));
91
- }
92
- function q() {
93
- c && c.shutdown();
94
- }
563
+ try {
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
+ }
593
+ };
95
594
  export {
96
- q as shutdownTelemetry,
97
- j as startTelemetry
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
98
609
  };