@astroscope/opentelemetry 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,15 +1,9 @@
1
1
  import {
2
2
  recordFetchRequestDuration
3
- } from "./chunk-DPYEL3WF.js";
3
+ } from "./chunk-JU6FJKLT.js";
4
4
 
5
5
  // src/fetch.ts
6
- import {
7
- SpanKind,
8
- SpanStatusCode,
9
- context,
10
- propagation,
11
- trace
12
- } from "@opentelemetry/api";
6
+ import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
13
7
  var LIB_NAME = "@astroscope/opentelemetry";
14
8
  function instrumentFetch() {
15
9
  const originalFetch = globalThis.fetch;
@@ -1,5 +1,5 @@
1
1
  // src/metrics.ts
2
- import { metrics, ValueType } from "@opentelemetry/api";
2
+ import { ValueType, metrics } from "@opentelemetry/api";
3
3
  var LIB_NAME = "@astroscope/opentelemetry";
4
4
  var httpRequestDuration = null;
5
5
  var httpActiveRequests = null;
@@ -50,7 +50,6 @@ function getActionDuration() {
50
50
  return actionDuration;
51
51
  }
52
52
  function recordHttpRequestStart(attributes) {
53
- const startTime = performance.now();
54
53
  getHttpActiveRequests().add(1, {
55
54
  "http.request.method": attributes.method,
56
55
  "http.route": attributes.route
@@ -2,16 +2,10 @@ import {
2
2
  recordActionDuration,
3
3
  recordHttpRequestDuration,
4
4
  recordHttpRequestStart
5
- } from "./chunk-DPYEL3WF.js";
5
+ } from "./chunk-JU6FJKLT.js";
6
6
 
7
7
  // src/middleware.ts
8
- import {
9
- SpanKind,
10
- SpanStatusCode,
11
- context,
12
- propagation,
13
- trace
14
- } from "@opentelemetry/api";
8
+ import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
15
9
  import { RPCType, setRPCMetadata } from "@opentelemetry/core";
16
10
  var LIB_NAME = "@astroscope/opentelemetry";
17
11
  var ACTIONS_PREFIX = "/_actions/";
@@ -29,11 +23,10 @@ function shouldExclude(ctx, exclude) {
29
23
  if (typeof exclude === "function") {
30
24
  return exclude(ctx);
31
25
  }
32
- const path = ctx.url.pathname;
33
- return exclude.some((pattern) => matchesPattern(path, pattern));
26
+ return exclude.some((pattern) => matchesPattern(ctx.url.pathname, pattern));
34
27
  }
35
28
  function getClientIp(request) {
36
- return request.headers.get("x-forwarded-for")?.split(",")[0].trim() ?? request.headers.get("x-real-ip") ?? request.headers.get("cf-connecting-ip") ?? // Cloudflare
29
+ return request.headers.get("x-forwarded-for")?.split(",")[0].trim() ?? request.headers.get("x-real-ip") ?? request.headers.get("cf-connecting-ip") ?? // no native ip address access in Astro available
37
30
  void 0;
38
31
  }
39
32
  function createOpenTelemetryMiddleware(options = {}) {
@@ -42,21 +35,18 @@ function createOpenTelemetryMiddleware(options = {}) {
42
35
  if (shouldExclude(ctx, options.exclude)) {
43
36
  return next();
44
37
  }
38
+ const startTime = performance.now();
45
39
  const { request, url } = ctx;
46
- const input = {
47
- traceparent: request.headers.get("traceparent"),
48
- tracestate: request.headers.get("tracestate")
49
- };
40
+ const input = { traceparent: request.headers.get("traceparent"), tracestate: request.headers.get("tracestate") };
50
41
  const parentContext = propagation.extract(context.active(), input);
51
- const clientIp = getClientIp(request);
52
42
  const contentLength = request.headers.get("content-length");
43
+ const clientIp = getClientIp(request);
53
44
  const spanOptions = {
54
45
  attributes: {
55
46
  "http.request.method": request.method,
56
47
  "url.full": request.url,
57
48
  "url.path": url.pathname,
58
49
  "url.query": url.search.slice(1),
59
- // Remove leading "?"
60
50
  "url.scheme": url.protocol.replace(":", ""),
61
51
  "server.address": url.hostname,
62
52
  "server.port": url.port ? parseInt(url.port) : url.protocol === "https:" ? 443 : 80,
@@ -72,76 +62,66 @@ function createOpenTelemetryMiddleware(options = {}) {
72
62
  const span = tracer.startSpan(spanName, spanOptions, parentContext);
73
63
  const spanContext = trace.setSpan(parentContext, span);
74
64
  const rpcMetadata = { type: RPCType.HTTP, span };
75
- const startTime = performance.now();
76
65
  const endActiveRequest = recordHttpRequestStart({ method: request.method, route: url.pathname });
77
- return context.with(
78
- setRPCMetadata(spanContext, rpcMetadata),
79
- async () => {
80
- const finalize = (status, responseSize) => {
81
- span.setAttribute("http.response.status_code", status);
82
- span.setAttribute("http.response.body.size", responseSize);
83
- if (status >= 400) {
84
- span.setStatus({
85
- code: SpanStatusCode.ERROR,
86
- message: `HTTP ${status}`
87
- });
88
- } else {
89
- span.setStatus({ code: SpanStatusCode.OK });
90
- }
91
- span.end();
92
- endActiveRequest();
93
- const duration = performance.now() - startTime;
94
- recordHttpRequestDuration(
95
- { method: request.method, route: url.pathname, status },
96
- duration
97
- );
98
- if (isAction) {
99
- recordActionDuration({ name: actionName, status }, duration);
100
- }
101
- };
102
- try {
103
- const response = await next();
104
- if (!response.body) {
105
- finalize(response.status, 0);
106
- return response;
107
- }
108
- const [measureStream, clientStream] = response.body.tee();
109
- let responseSize = 0;
110
- (async () => {
111
- const reader = measureStream.getReader();
112
- try {
113
- while (true) {
114
- const { done, value } = await reader.read();
115
- if (done) break;
116
- responseSize += value.length;
117
- }
118
- } finally {
119
- finalize(response.status, responseSize);
120
- }
121
- })();
122
- return new Response(clientStream, {
123
- status: response.status,
124
- headers: response.headers
125
- });
126
- } catch (e) {
66
+ return context.with(setRPCMetadata(spanContext, rpcMetadata), async () => {
67
+ const finalize = (status, responseSize) => {
68
+ span.setAttribute("http.response.status_code", status);
69
+ span.setAttribute("http.response.body.size", responseSize);
70
+ if (status >= 400) {
127
71
  span.setStatus({
128
72
  code: SpanStatusCode.ERROR,
129
- message: e instanceof Error ? e.message : "Unknown error"
73
+ message: `HTTP ${status}`
130
74
  });
131
- span.end();
132
- endActiveRequest();
133
- const duration = performance.now() - startTime;
134
- recordHttpRequestDuration(
135
- { method: request.method, route: url.pathname, status: 500 },
136
- duration
137
- );
138
- if (isAction) {
139
- recordActionDuration({ name: actionName, status: 500 }, duration);
75
+ } else {
76
+ span.setStatus({ code: SpanStatusCode.OK });
77
+ }
78
+ span.end();
79
+ endActiveRequest();
80
+ const duration = performance.now() - startTime;
81
+ recordHttpRequestDuration({ method: request.method, route: url.pathname, status }, duration);
82
+ if (isAction) {
83
+ recordActionDuration({ name: actionName, status }, duration);
84
+ }
85
+ };
86
+ try {
87
+ const response = await next();
88
+ if (!response.body) {
89
+ finalize(response.status, 0);
90
+ return response;
91
+ }
92
+ const [measureStream, clientStream] = response.body.tee();
93
+ let responseSize = 0;
94
+ (async () => {
95
+ const reader = measureStream.getReader();
96
+ try {
97
+ while (true) {
98
+ const { done, value } = await reader.read();
99
+ if (done) break;
100
+ responseSize += value.length;
101
+ }
102
+ } finally {
103
+ finalize(response.status, responseSize);
140
104
  }
141
- throw e;
105
+ })();
106
+ return new Response(clientStream, {
107
+ status: response.status,
108
+ headers: response.headers
109
+ });
110
+ } catch (e) {
111
+ span.setStatus({
112
+ code: SpanStatusCode.ERROR,
113
+ message: e instanceof Error ? e.message : "Unknown error"
114
+ });
115
+ span.end();
116
+ endActiveRequest();
117
+ const duration = performance.now() - startTime;
118
+ recordHttpRequestDuration({ method: request.method, route: url.pathname, status: 500 }, duration);
119
+ if (isAction) {
120
+ recordActionDuration({ name: actionName, status: 500 }, duration);
142
121
  }
122
+ throw e;
143
123
  }
144
- );
124
+ });
145
125
  };
146
126
  }
147
127
 
@@ -0,0 +1,7 @@
1
+ import {
2
+ instrumentFetch
3
+ } from "./chunk-45TY3U7U.js";
4
+ import "./chunk-JU6FJKLT.js";
5
+ export {
6
+ instrumentFetch
7
+ };
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createOpenTelemetryMiddleware
3
- } from "./chunk-7RBJ7XB3.js";
3
+ } from "./chunk-PCCKGEEG.js";
4
4
  import {
5
5
  instrumentFetch
6
- } from "./chunk-BQFWPPEO.js";
7
- import "./chunk-DPYEL3WF.js";
6
+ } from "./chunk-45TY3U7U.js";
7
+ import "./chunk-JU6FJKLT.js";
8
8
 
9
9
  // src/integration.ts
10
10
  import fs from "fs";
@@ -18,10 +18,7 @@ var DEV_EXCLUDES = [
18
18
  { prefix: "/src/" },
19
19
  { prefix: "/node_modules/" }
20
20
  ];
21
- var ASTRO_STATIC_EXCLUDES = [
22
- { prefix: "/_astro/" },
23
- { prefix: "/_image" }
24
- ];
21
+ var ASTRO_STATIC_EXCLUDES = [{ prefix: "/_astro/" }, { prefix: "/_image" }];
25
22
  var STATIC_EXCLUDES = [
26
23
  { exact: "/favicon.ico" },
27
24
  { exact: "/robots.txt" },
@@ -31,26 +28,13 @@ var STATIC_EXCLUDES = [
31
28
  { exact: "/manifest.webmanifest" },
32
29
  { prefix: "/.well-known/" }
33
30
  ];
34
- var RECOMMENDED_EXCLUDES = [
35
- ...DEV_EXCLUDES,
36
- ...ASTRO_STATIC_EXCLUDES,
37
- ...STATIC_EXCLUDES
38
- ];
31
+ var RECOMMENDED_EXCLUDES = [...DEV_EXCLUDES, ...ASTRO_STATIC_EXCLUDES, ...STATIC_EXCLUDES];
39
32
 
40
33
  // src/integration.ts
41
34
  var VIRTUAL_MODULE_ID = "virtual:@astroscope/opentelemetry/config";
42
- var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
35
+ var RESOLVED_VIRTUAL_MODULE_ID = `\0${VIRTUAL_MODULE_ID}`;
43
36
  function serializeExcludePatterns(patterns) {
44
- const items = patterns.map((p) => {
45
- if ("pattern" in p) {
46
- return `{ pattern: ${p.pattern.toString()} }`;
47
- } else if ("prefix" in p) {
48
- return `{ prefix: ${JSON.stringify(p.prefix)} }`;
49
- } else {
50
- return `{ exact: ${JSON.stringify(p.exact)} }`;
51
- }
52
- });
53
- return `[${items.join(", ")}]`;
37
+ return `[${patterns.map((p) => "pattern" in p ? `{ pattern: ${p.pattern.toString()} }` : JSON.stringify(p)).join(", ")}]`;
54
38
  }
55
39
  function opentelemetry(options = {}) {
56
40
  const httpConfig = options.instrumentations?.http ?? {
@@ -64,12 +48,7 @@ function opentelemetry(options = {}) {
64
48
  return {
65
49
  name: "@astroscope/opentelemetry",
66
50
  hooks: {
67
- "astro:config:setup": ({
68
- command,
69
- updateConfig,
70
- logger,
71
- addMiddleware
72
- }) => {
51
+ "astro:config:setup": ({ command, updateConfig, logger, addMiddleware }) => {
73
52
  isBuild = command === "build";
74
53
  if (httpConfig.enabled) {
75
54
  addMiddleware({
@@ -93,7 +72,6 @@ function opentelemetry(options = {}) {
93
72
  }
94
73
  }
95
74
  },
96
- // Only add fetch instrumentation plugin if enabled
97
75
  ...fetchConfig.enabled ? [
98
76
  {
99
77
  name: "@astroscope/opentelemetry/fetch",
@@ -101,7 +79,7 @@ function opentelemetry(options = {}) {
101
79
  if (isBuild) return;
102
80
  server.httpServer?.once("listening", async () => {
103
81
  try {
104
- const { instrumentFetch: instrumentFetch2 } = await import("./fetch-RERXGIJA.js");
82
+ const { instrumentFetch: instrumentFetch2 } = await import("./fetch-Z2DBQPE2.js");
105
83
  instrumentFetch2();
106
84
  logger.info("fetch instrumentation enabled");
107
85
  } catch (error) {
@@ -118,15 +96,14 @@ function opentelemetry(options = {}) {
118
96
  if (!outDir) return;
119
97
  const entryPath = path.join(outDir, "entry.mjs");
120
98
  if (!fs.existsSync(entryPath)) return;
121
- let content = fs.readFileSync(entryPath, "utf-8");
122
- const instrumentCode = `import { instrumentFetch } from '@astroscope/opentelemetry';
99
+ const content = fs.readFileSync(entryPath, "utf-8");
100
+ fs.writeFileSync(
101
+ entryPath,
102
+ `import { instrumentFetch } from '@astroscope/opentelemetry';
123
103
  instrumentFetch();
124
- `;
125
- content = instrumentCode + content;
126
- fs.writeFileSync(entryPath, content);
127
- logger.info(
128
- "injected fetch instrumentation into entry.mjs"
104
+ ${content}`
129
105
  );
106
+ logger.info("injected fetch instrumentation into entry.mjs");
130
107
  }
131
108
  }
132
109
  ] : []
@@ -1,13 +1,11 @@
1
1
  import {
2
2
  createOpenTelemetryMiddleware
3
- } from "./chunk-7RBJ7XB3.js";
4
- import "./chunk-DPYEL3WF.js";
3
+ } from "./chunk-PCCKGEEG.js";
4
+ import "./chunk-JU6FJKLT.js";
5
5
 
6
6
  // src/middleware-entrypoint.ts
7
7
  import { excludePatterns } from "virtual:@astroscope/opentelemetry/config";
8
- var onRequest = createOpenTelemetryMiddleware({
9
- exclude: excludePatterns
10
- });
8
+ var onRequest = createOpenTelemetryMiddleware({ exclude: excludePatterns });
11
9
  export {
12
10
  onRequest
13
11
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astroscope/opentelemetry",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenTelemetry tracing middleware for Astro SSR",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,14 +55,14 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "@opentelemetry/api": "^1.9.0",
58
- "@opentelemetry/core": "^1.30.0",
58
+ "@opentelemetry/core": "^2.2.0",
59
59
  "astro": "^5.1.0",
60
60
  "tsup": "^8.5.1",
61
61
  "typescript": "^5.9.3"
62
62
  },
63
63
  "peerDependencies": {
64
64
  "@opentelemetry/api": "^1.0.0",
65
- "@opentelemetry/core": "^1.0.0",
65
+ "@opentelemetry/core": "^2.0.0",
66
66
  "astro": "^5.0.0"
67
67
  }
68
68
  }
@@ -1,146 +0,0 @@
1
- import {
2
- recordHttpRequestDuration,
3
- recordHttpRequestStart
4
- } from "./chunk-UPNRPRAW.js";
5
-
6
- // src/middleware.ts
7
- import {
8
- SpanKind,
9
- SpanStatusCode,
10
- context,
11
- propagation,
12
- trace
13
- } from "@opentelemetry/api";
14
- import { RPCType, setRPCMetadata } from "@opentelemetry/core";
15
- var LIB_NAME = "@astroscope/opentelemetry";
16
- var ACTIONS_PREFIX = "/_actions/";
17
- function matchesPattern(path, pattern) {
18
- if ("pattern" in pattern) {
19
- return pattern.pattern.test(path);
20
- }
21
- if ("prefix" in pattern) {
22
- return path.startsWith(pattern.prefix);
23
- }
24
- return path === pattern.exact;
25
- }
26
- function shouldExclude(ctx, exclude) {
27
- if (!exclude) return false;
28
- if (typeof exclude === "function") {
29
- return exclude(ctx);
30
- }
31
- const path = ctx.url.pathname;
32
- return exclude.some((pattern) => matchesPattern(path, pattern));
33
- }
34
- function getClientIp(request) {
35
- return request.headers.get("x-forwarded-for")?.split(",")[0].trim() ?? request.headers.get("x-real-ip") ?? request.headers.get("cf-connecting-ip") ?? // Cloudflare
36
- void 0;
37
- }
38
- function createOpenTelemetryMiddleware(options = {}) {
39
- const tracer = trace.getTracer(LIB_NAME);
40
- return async (ctx, next) => {
41
- if (shouldExclude(ctx, options.exclude)) {
42
- return next();
43
- }
44
- const { request, url } = ctx;
45
- const input = {
46
- traceparent: request.headers.get("traceparent"),
47
- tracestate: request.headers.get("tracestate")
48
- };
49
- const parentContext = propagation.extract(context.active(), input);
50
- const clientIp = getClientIp(request);
51
- const contentLength = request.headers.get("content-length");
52
- const spanOptions = {
53
- attributes: {
54
- "http.request.method": request.method,
55
- "url.full": request.url,
56
- "url.path": url.pathname,
57
- "url.query": url.search.slice(1),
58
- // Remove leading "?"
59
- "url.scheme": url.protocol.replace(":", ""),
60
- "server.address": url.hostname,
61
- "server.port": url.port ? parseInt(url.port) : url.protocol === "https:" ? 443 : 80,
62
- "user_agent.original": request.headers.get("user-agent") ?? "",
63
- ...contentLength && { "http.request.body.size": parseInt(contentLength) },
64
- ...clientIp && { "client.address": clientIp }
65
- },
66
- kind: SpanKind.SERVER
67
- };
68
- const isAction = url.pathname.startsWith(ACTIONS_PREFIX);
69
- const actionName = url.pathname.slice(ACTIONS_PREFIX.length).replace(/\/$/, "");
70
- const spanName = isAction ? `ACTION ${actionName}` : `${request.method} ${url.pathname}`;
71
- const span = tracer.startSpan(spanName, spanOptions, parentContext);
72
- const spanContext = trace.setSpan(parentContext, span);
73
- const rpcMetadata = { type: RPCType.HTTP, span };
74
- const metricsEnabled = options.metrics ?? false;
75
- const startTime = metricsEnabled ? performance.now() : 0;
76
- const endActiveRequest = metricsEnabled ? recordHttpRequestStart({ method: request.method, route: url.pathname }) : void 0;
77
- return context.with(
78
- setRPCMetadata(spanContext, rpcMetadata),
79
- async () => {
80
- const finalize = (status, responseSize) => {
81
- span.setAttribute("http.response.status_code", status);
82
- span.setAttribute("http.response.body.size", responseSize);
83
- if (status >= 400) {
84
- span.setStatus({
85
- code: SpanStatusCode.ERROR,
86
- message: `HTTP ${status}`
87
- });
88
- } else {
89
- span.setStatus({ code: SpanStatusCode.OK });
90
- }
91
- span.end();
92
- if (metricsEnabled) {
93
- endActiveRequest?.();
94
- recordHttpRequestDuration(
95
- { method: request.method, route: url.pathname, status },
96
- performance.now() - startTime
97
- );
98
- }
99
- };
100
- try {
101
- const response = await next();
102
- if (!response.body) {
103
- finalize(response.status, 0);
104
- return response;
105
- }
106
- const [measureStream, clientStream] = response.body.tee();
107
- let responseSize = 0;
108
- (async () => {
109
- const reader = measureStream.getReader();
110
- try {
111
- while (true) {
112
- const { done, value } = await reader.read();
113
- if (done) break;
114
- responseSize += value.length;
115
- }
116
- } finally {
117
- finalize(response.status, responseSize);
118
- }
119
- })();
120
- return new Response(clientStream, {
121
- status: response.status,
122
- headers: response.headers
123
- });
124
- } catch (e) {
125
- span.setStatus({
126
- code: SpanStatusCode.ERROR,
127
- message: e instanceof Error ? e.message : "Unknown error"
128
- });
129
- span.end();
130
- if (metricsEnabled) {
131
- endActiveRequest?.();
132
- recordHttpRequestDuration(
133
- { method: request.method, route: url.pathname, status: 500 },
134
- performance.now() - startTime
135
- );
136
- }
137
- throw e;
138
- }
139
- }
140
- );
141
- };
142
- }
143
-
144
- export {
145
- createOpenTelemetryMiddleware
146
- };
@@ -1,89 +0,0 @@
1
- import {
2
- recordFetchRequestDuration
3
- } from "./chunk-DPYEL3WF.js";
4
-
5
- // src/fetch.ts
6
- import {
7
- SpanKind,
8
- SpanStatusCode,
9
- context,
10
- propagation,
11
- trace
12
- } from "@opentelemetry/api";
13
- var LIB_NAME = "@astroscope/opentelemetry";
14
- var metricsEnabled = false;
15
- function instrumentFetch(options) {
16
- metricsEnabled = options?.metrics ?? false;
17
- const originalFetch = globalThis.fetch;
18
- async function instrumentedFetch(input, init) {
19
- const tracer = trace.getTracer(LIB_NAME);
20
- const activeContext = context.active();
21
- const request = new Request(input, init);
22
- const url = new URL(request.url);
23
- const span = tracer.startSpan(
24
- `FETCH ${request.method}`,
25
- {
26
- kind: SpanKind.CLIENT,
27
- attributes: {
28
- "http.request.method": request.method,
29
- "url.full": request.url,
30
- "url.path": url.pathname,
31
- "url.scheme": url.protocol.replace(":", ""),
32
- "server.address": url.hostname,
33
- "server.port": url.port ? parseInt(url.port) : url.protocol === "https:" ? 443 : 80
34
- }
35
- },
36
- activeContext
37
- );
38
- const headers = new Headers(request.headers);
39
- const carrier = {};
40
- propagation.inject(trace.setSpan(activeContext, span), carrier);
41
- for (const [key, value] of Object.entries(carrier)) {
42
- headers.set(key, value);
43
- }
44
- const startTime = metricsEnabled ? performance.now() : 0;
45
- try {
46
- const response = await originalFetch(request.url, {
47
- ...init,
48
- method: request.method,
49
- headers,
50
- body: request.body
51
- });
52
- span.setAttribute("http.response.status_code", response.status);
53
- if (response.status >= 400) {
54
- span.setStatus({
55
- code: SpanStatusCode.ERROR,
56
- message: `HTTP ${response.status}`
57
- });
58
- } else {
59
- span.setStatus({ code: SpanStatusCode.OK });
60
- }
61
- span.end();
62
- if (metricsEnabled) {
63
- recordFetchRequestDuration(
64
- { method: request.method, host: url.hostname, status: response.status },
65
- performance.now() - startTime
66
- );
67
- }
68
- return response;
69
- } catch (error) {
70
- span.setStatus({
71
- code: SpanStatusCode.ERROR,
72
- message: error instanceof Error ? error.message : "Unknown error"
73
- });
74
- span.end();
75
- if (metricsEnabled) {
76
- recordFetchRequestDuration(
77
- { method: request.method, host: url.hostname, status: 0 },
78
- performance.now() - startTime
79
- );
80
- }
81
- throw error;
82
- }
83
- }
84
- globalThis.fetch = Object.assign(instrumentedFetch, originalFetch);
85
- }
86
-
87
- export {
88
- instrumentFetch
89
- };
@@ -1,70 +0,0 @@
1
- // src/fetch.ts
2
- import {
3
- SpanKind,
4
- SpanStatusCode,
5
- context,
6
- propagation,
7
- trace
8
- } from "@opentelemetry/api";
9
- var LIB_NAME = "@astroscope/opentelemetry";
10
- function instrumentFetch() {
11
- const originalFetch = globalThis.fetch;
12
- async function instrumentedFetch(input, init) {
13
- const tracer = trace.getTracer(LIB_NAME);
14
- const activeContext = context.active();
15
- const request = new Request(input, init);
16
- const url = new URL(request.url);
17
- const span = tracer.startSpan(
18
- `FETCH ${request.method}`,
19
- {
20
- kind: SpanKind.CLIENT,
21
- attributes: {
22
- "http.request.method": request.method,
23
- "url.full": request.url,
24
- "url.path": url.pathname,
25
- "url.scheme": url.protocol.replace(":", ""),
26
- "server.address": url.hostname,
27
- "server.port": url.port ? parseInt(url.port) : url.protocol === "https:" ? 443 : 80
28
- }
29
- },
30
- activeContext
31
- );
32
- const headers = new Headers(request.headers);
33
- const carrier = {};
34
- propagation.inject(trace.setSpan(activeContext, span), carrier);
35
- for (const [key, value] of Object.entries(carrier)) {
36
- headers.set(key, value);
37
- }
38
- try {
39
- const response = await originalFetch(request.url, {
40
- ...init,
41
- method: request.method,
42
- headers,
43
- body: request.body
44
- });
45
- span.setAttribute("http.response.status_code", response.status);
46
- if (response.status >= 400) {
47
- span.setStatus({
48
- code: SpanStatusCode.ERROR,
49
- message: `HTTP ${response.status}`
50
- });
51
- } else {
52
- span.setStatus({ code: SpanStatusCode.OK });
53
- }
54
- span.end();
55
- return response;
56
- } catch (error) {
57
- span.setStatus({
58
- code: SpanStatusCode.ERROR,
59
- message: error instanceof Error ? error.message : "Unknown error"
60
- });
61
- span.end();
62
- throw error;
63
- }
64
- }
65
- globalThis.fetch = Object.assign(instrumentedFetch, originalFetch);
66
- }
67
-
68
- export {
69
- instrumentFetch
70
- };