@astroscope/opentelemetry 0.2.1 → 0.2.2
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/README.md +1 -1
- package/dist/{chunk-45TY3U7U.js → chunk-A7VHVQTG.js} +29 -17
- package/dist/{chunk-LT23L3YD.js → chunk-OIBEYPAM.js} +7 -6
- package/dist/{fetch-Z2DBQPE2.js → fetch-PVKAGUFE.js} +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -3
- package/dist/middleware-entrypoint.js +1 -1
- package/package.json +5 -5
- package/dist/chunk-63MOS4WA.js +0 -146
- package/dist/chunk-7RBJ7XB3.js +0 -150
- package/dist/chunk-BQFWPPEO.js +0 -83
- package/dist/chunk-CEPTXEJV.js +0 -89
- package/dist/chunk-DPYEL3WF.js +0 -91
- package/dist/chunk-FEC4ETRL.js +0 -70
- package/dist/chunk-KLGWLEAU.js +0 -123
- package/dist/chunk-OFMO3ZKX.js +0 -124
- package/dist/chunk-PCCKGEEG.js +0 -130
- package/dist/chunk-QIWOBUML.js +0 -89
- package/dist/chunk-QTSNLOSC.js +0 -70
- package/dist/chunk-SNVIK2H6.js +0 -115
- package/dist/chunk-UPNRPRAW.js +0 -72
- package/dist/chunk-VHXZF2FP.js +0 -124
- package/dist/chunk-WTUM3JRA.js +0 -155
- package/dist/fetch-55MNQVLN.js +0 -7
- package/dist/fetch-FUKHXS77.js +0 -6
- package/dist/fetch-JGQFPQSL.js +0 -6
- package/dist/fetch-RERXGIJA.js +0 -7
- package/dist/fetch-SVGRNFLY.js +0 -7
package/README.md
CHANGED
|
@@ -5,20 +5,31 @@ import {
|
|
|
5
5
|
// src/fetch.ts
|
|
6
6
|
import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
7
7
|
var LIB_NAME = "@astroscope/opentelemetry";
|
|
8
|
+
var INSTRUMENTED = /* @__PURE__ */ Symbol.for("@astroscope/opentelemetry/fetch");
|
|
8
9
|
function instrumentFetch() {
|
|
10
|
+
if (globalThis.fetch[INSTRUMENTED]) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
9
13
|
const originalFetch = globalThis.fetch;
|
|
10
14
|
async function instrumentedFetch(input, init) {
|
|
11
15
|
const tracer = trace.getTracer(LIB_NAME);
|
|
12
16
|
const activeContext = context.active();
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
let url;
|
|
18
|
+
let method;
|
|
19
|
+
if (input instanceof Request) {
|
|
20
|
+
url = new URL(input.url);
|
|
21
|
+
method = input.method;
|
|
22
|
+
} else {
|
|
23
|
+
url = new URL(input.toString(), globalThis.location?.href);
|
|
24
|
+
method = init?.method ?? "GET";
|
|
25
|
+
}
|
|
15
26
|
const span = tracer.startSpan(
|
|
16
|
-
`FETCH ${
|
|
27
|
+
`FETCH ${method}`,
|
|
17
28
|
{
|
|
18
29
|
kind: SpanKind.CLIENT,
|
|
19
30
|
attributes: {
|
|
20
|
-
"http.request.method":
|
|
21
|
-
"url.full":
|
|
31
|
+
"http.request.method": method,
|
|
32
|
+
"url.full": url.href,
|
|
22
33
|
"url.path": url.pathname,
|
|
23
34
|
"url.scheme": url.protocol.replace(":", ""),
|
|
24
35
|
"server.address": url.hostname,
|
|
@@ -27,20 +38,23 @@ function instrumentFetch() {
|
|
|
27
38
|
},
|
|
28
39
|
activeContext
|
|
29
40
|
);
|
|
30
|
-
const headers = new Headers(request.headers);
|
|
31
41
|
const carrier = {};
|
|
32
42
|
propagation.inject(trace.setSpan(activeContext, span), carrier);
|
|
43
|
+
const existingHeaders = init?.headers ?? (input instanceof Request ? input.headers : void 0);
|
|
44
|
+
const headers = new Headers(existingHeaders);
|
|
33
45
|
for (const [key, value] of Object.entries(carrier)) {
|
|
34
46
|
headers.set(key, value);
|
|
35
47
|
}
|
|
48
|
+
const hasBody = init?.body !== void 0 || input instanceof Request && input.body !== null;
|
|
49
|
+
const newInit = {
|
|
50
|
+
...init,
|
|
51
|
+
headers,
|
|
52
|
+
// required in node.js 18.13+
|
|
53
|
+
...hasBody && { duplex: "half" }
|
|
54
|
+
};
|
|
36
55
|
const startTime = performance.now();
|
|
37
56
|
try {
|
|
38
|
-
const response = await originalFetch(
|
|
39
|
-
...init,
|
|
40
|
-
method: request.method,
|
|
41
|
-
headers,
|
|
42
|
-
body: request.body
|
|
43
|
-
});
|
|
57
|
+
const response = await originalFetch(input, newInit);
|
|
44
58
|
span.setAttribute("http.response.status_code", response.status);
|
|
45
59
|
if (response.status >= 400) {
|
|
46
60
|
span.setStatus({
|
|
@@ -52,7 +66,7 @@ function instrumentFetch() {
|
|
|
52
66
|
}
|
|
53
67
|
span.end();
|
|
54
68
|
recordFetchRequestDuration(
|
|
55
|
-
{ method
|
|
69
|
+
{ method, host: url.hostname, status: response.status },
|
|
56
70
|
performance.now() - startTime
|
|
57
71
|
);
|
|
58
72
|
return response;
|
|
@@ -62,13 +76,11 @@ function instrumentFetch() {
|
|
|
62
76
|
message: error instanceof Error ? error.message : "Unknown error"
|
|
63
77
|
});
|
|
64
78
|
span.end();
|
|
65
|
-
recordFetchRequestDuration(
|
|
66
|
-
{ method: request.method, host: url.hostname, status: 0 },
|
|
67
|
-
performance.now() - startTime
|
|
68
|
-
);
|
|
79
|
+
recordFetchRequestDuration({ method, host: url.hostname, status: 0 }, performance.now() - startTime);
|
|
69
80
|
throw error;
|
|
70
81
|
}
|
|
71
82
|
}
|
|
83
|
+
instrumentedFetch[INSTRUMENTED] = true;
|
|
72
84
|
globalThis.fetch = Object.assign(instrumentedFetch, originalFetch);
|
|
73
85
|
}
|
|
74
86
|
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
} from "./chunk-JU6FJKLT.js";
|
|
6
6
|
|
|
7
7
|
// src/middleware.ts
|
|
8
|
+
import { shouldExclude } from "@astroscope/excludes";
|
|
8
9
|
import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
|
|
9
10
|
import { RPCType, setRPCMetadata } from "@opentelemetry/core";
|
|
10
|
-
import { shouldExclude } from "@astroscope/excludes";
|
|
11
11
|
var LIB_NAME = "@astroscope/opentelemetry";
|
|
12
12
|
var ACTIONS_PREFIX = "/_actions/";
|
|
13
13
|
function getClientIp(request) {
|
|
@@ -21,7 +21,7 @@ function createOpenTelemetryMiddleware(options = {}) {
|
|
|
21
21
|
return next();
|
|
22
22
|
}
|
|
23
23
|
const startTime = performance.now();
|
|
24
|
-
const { request, url } = ctx;
|
|
24
|
+
const { request, url, routePattern } = ctx;
|
|
25
25
|
const input = { traceparent: request.headers.get("traceparent"), tracestate: request.headers.get("tracestate") };
|
|
26
26
|
const parentContext = propagation.extract(context.active(), input);
|
|
27
27
|
const contentLength = request.headers.get("content-length");
|
|
@@ -29,6 +29,7 @@ function createOpenTelemetryMiddleware(options = {}) {
|
|
|
29
29
|
const spanOptions = {
|
|
30
30
|
attributes: {
|
|
31
31
|
"http.request.method": request.method,
|
|
32
|
+
"http.route": routePattern,
|
|
32
33
|
"url.full": request.url,
|
|
33
34
|
"url.path": url.pathname,
|
|
34
35
|
"url.query": url.search.slice(1),
|
|
@@ -43,11 +44,11 @@ function createOpenTelemetryMiddleware(options = {}) {
|
|
|
43
44
|
};
|
|
44
45
|
const isAction = url.pathname.startsWith(ACTIONS_PREFIX);
|
|
45
46
|
const actionName = url.pathname.slice(ACTIONS_PREFIX.length).replace(/\/$/, "");
|
|
46
|
-
const spanName = isAction ? `ACTION ${actionName}` : `${request.method} ${
|
|
47
|
+
const spanName = isAction ? `ACTION ${actionName}` : `${request.method} ${routePattern}`;
|
|
47
48
|
const span = tracer.startSpan(spanName, spanOptions, parentContext);
|
|
48
49
|
const spanContext = trace.setSpan(parentContext, span);
|
|
49
50
|
const rpcMetadata = { type: RPCType.HTTP, span };
|
|
50
|
-
const endActiveRequest = recordHttpRequestStart({ method: request.method, route:
|
|
51
|
+
const endActiveRequest = recordHttpRequestStart({ method: request.method, route: routePattern });
|
|
51
52
|
return context.with(setRPCMetadata(spanContext, rpcMetadata), async () => {
|
|
52
53
|
const finalize = (status, responseSize) => {
|
|
53
54
|
span.setAttribute("http.response.status_code", status);
|
|
@@ -63,7 +64,7 @@ function createOpenTelemetryMiddleware(options = {}) {
|
|
|
63
64
|
span.end();
|
|
64
65
|
endActiveRequest();
|
|
65
66
|
const duration = performance.now() - startTime;
|
|
66
|
-
recordHttpRequestDuration({ method: request.method, route:
|
|
67
|
+
recordHttpRequestDuration({ method: request.method, route: routePattern, status }, duration);
|
|
67
68
|
if (isAction) {
|
|
68
69
|
recordActionDuration({ name: actionName, status }, duration);
|
|
69
70
|
}
|
|
@@ -100,7 +101,7 @@ function createOpenTelemetryMiddleware(options = {}) {
|
|
|
100
101
|
span.end();
|
|
101
102
|
endActiveRequest();
|
|
102
103
|
const duration = performance.now() - startTime;
|
|
103
|
-
recordHttpRequestDuration({ method: request.method, route:
|
|
104
|
+
recordHttpRequestDuration({ method: request.method, route: routePattern, status: 500 }, duration);
|
|
104
105
|
if (isAction) {
|
|
105
106
|
recordActionDuration({ name: actionName, status: 500 }, duration);
|
|
106
107
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ interface OpenTelemetryMiddlewareOptions {
|
|
|
6
6
|
* Paths to exclude from tracing.
|
|
7
7
|
* Can be an array of patterns or a function that returns true to exclude.
|
|
8
8
|
*/
|
|
9
|
-
exclude?: ExcludePattern[] | ((context: APIContext) => boolean);
|
|
9
|
+
exclude?: ExcludePattern[] | ((context: APIContext) => boolean) | undefined;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -58,16 +58,16 @@ interface OpenTelemetryIntegrationOptions {
|
|
|
58
58
|
*/
|
|
59
59
|
http?: {
|
|
60
60
|
enabled: boolean;
|
|
61
|
-
exclude?: ExcludePattern[];
|
|
62
|
-
};
|
|
61
|
+
exclude?: ExcludePattern[] | undefined;
|
|
62
|
+
} | undefined;
|
|
63
63
|
/**
|
|
64
64
|
* Fetch outgoing request instrumentation.
|
|
65
65
|
* @default { enabled: true }
|
|
66
66
|
*/
|
|
67
67
|
fetch?: {
|
|
68
68
|
enabled: boolean;
|
|
69
|
-
};
|
|
70
|
-
};
|
|
69
|
+
} | undefined;
|
|
70
|
+
} | undefined;
|
|
71
71
|
}
|
|
72
72
|
/**
|
|
73
73
|
* Astro integration for OpenTelemetry instrumentation.
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createOpenTelemetryMiddleware
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OIBEYPAM.js";
|
|
4
4
|
import {
|
|
5
5
|
instrumentFetch
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-A7VHVQTG.js";
|
|
7
7
|
import "./chunk-JU6FJKLT.js";
|
|
8
8
|
|
|
9
9
|
// src/integration.ts
|
|
@@ -55,7 +55,7 @@ function opentelemetry(options = {}) {
|
|
|
55
55
|
if (isBuild) return;
|
|
56
56
|
server.httpServer?.once("listening", async () => {
|
|
57
57
|
try {
|
|
58
|
-
const { instrumentFetch: instrumentFetch2 } = await import("./fetch-
|
|
58
|
+
const { instrumentFetch: instrumentFetch2 } = await import("./fetch-PVKAGUFE.js");
|
|
59
59
|
instrumentFetch2();
|
|
60
60
|
logger.info("fetch instrumentation enabled");
|
|
61
61
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astroscope/opentelemetry",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "OpenTelemetry tracing
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "OpenTelemetry for Astro — tracing, metrics, and component instrumentation that works in dev mode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -55,13 +55,13 @@
|
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@opentelemetry/api": "^1.9.0",
|
|
58
|
-
"@opentelemetry/core": "^2.
|
|
59
|
-
"astro": "^5.
|
|
58
|
+
"@opentelemetry/core": "^2.3.0",
|
|
59
|
+
"astro": "^5.16.9",
|
|
60
60
|
"tsup": "^8.5.1",
|
|
61
61
|
"typescript": "^5.9.3"
|
|
62
62
|
},
|
|
63
63
|
"peerDependencies": {
|
|
64
|
-
"@astroscope/excludes": "^0.1.
|
|
64
|
+
"@astroscope/excludes": "^0.1.2",
|
|
65
65
|
"@opentelemetry/api": "^1.0.0",
|
|
66
66
|
"@opentelemetry/core": "^2.0.0",
|
|
67
67
|
"astro": "^5.0.0"
|
package/dist/chunk-63MOS4WA.js
DELETED
|
@@ -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
|
-
};
|
package/dist/chunk-7RBJ7XB3.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
recordActionDuration,
|
|
3
|
-
recordHttpRequestDuration,
|
|
4
|
-
recordHttpRequestStart
|
|
5
|
-
} from "./chunk-DPYEL3WF.js";
|
|
6
|
-
|
|
7
|
-
// src/middleware.ts
|
|
8
|
-
import {
|
|
9
|
-
SpanKind,
|
|
10
|
-
SpanStatusCode,
|
|
11
|
-
context,
|
|
12
|
-
propagation,
|
|
13
|
-
trace
|
|
14
|
-
} from "@opentelemetry/api";
|
|
15
|
-
import { RPCType, setRPCMetadata } from "@opentelemetry/core";
|
|
16
|
-
var LIB_NAME = "@astroscope/opentelemetry";
|
|
17
|
-
var ACTIONS_PREFIX = "/_actions/";
|
|
18
|
-
function matchesPattern(path, pattern) {
|
|
19
|
-
if ("pattern" in pattern) {
|
|
20
|
-
return pattern.pattern.test(path);
|
|
21
|
-
}
|
|
22
|
-
if ("prefix" in pattern) {
|
|
23
|
-
return path.startsWith(pattern.prefix);
|
|
24
|
-
}
|
|
25
|
-
return path === pattern.exact;
|
|
26
|
-
}
|
|
27
|
-
function shouldExclude(ctx, exclude) {
|
|
28
|
-
if (!exclude) return false;
|
|
29
|
-
if (typeof exclude === "function") {
|
|
30
|
-
return exclude(ctx);
|
|
31
|
-
}
|
|
32
|
-
const path = ctx.url.pathname;
|
|
33
|
-
return exclude.some((pattern) => matchesPattern(path, pattern));
|
|
34
|
-
}
|
|
35
|
-
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
|
|
37
|
-
void 0;
|
|
38
|
-
}
|
|
39
|
-
function createOpenTelemetryMiddleware(options = {}) {
|
|
40
|
-
const tracer = trace.getTracer(LIB_NAME);
|
|
41
|
-
return async (ctx, next) => {
|
|
42
|
-
if (shouldExclude(ctx, options.exclude)) {
|
|
43
|
-
return next();
|
|
44
|
-
}
|
|
45
|
-
const { request, url } = ctx;
|
|
46
|
-
const input = {
|
|
47
|
-
traceparent: request.headers.get("traceparent"),
|
|
48
|
-
tracestate: request.headers.get("tracestate")
|
|
49
|
-
};
|
|
50
|
-
const parentContext = propagation.extract(context.active(), input);
|
|
51
|
-
const clientIp = getClientIp(request);
|
|
52
|
-
const contentLength = request.headers.get("content-length");
|
|
53
|
-
const spanOptions = {
|
|
54
|
-
attributes: {
|
|
55
|
-
"http.request.method": request.method,
|
|
56
|
-
"url.full": request.url,
|
|
57
|
-
"url.path": url.pathname,
|
|
58
|
-
"url.query": url.search.slice(1),
|
|
59
|
-
// Remove leading "?"
|
|
60
|
-
"url.scheme": url.protocol.replace(":", ""),
|
|
61
|
-
"server.address": url.hostname,
|
|
62
|
-
"server.port": url.port ? parseInt(url.port) : url.protocol === "https:" ? 443 : 80,
|
|
63
|
-
"user_agent.original": request.headers.get("user-agent") ?? "",
|
|
64
|
-
...contentLength && { "http.request.body.size": parseInt(contentLength) },
|
|
65
|
-
...clientIp && { "client.address": clientIp }
|
|
66
|
-
},
|
|
67
|
-
kind: SpanKind.SERVER
|
|
68
|
-
};
|
|
69
|
-
const isAction = url.pathname.startsWith(ACTIONS_PREFIX);
|
|
70
|
-
const actionName = url.pathname.slice(ACTIONS_PREFIX.length).replace(/\/$/, "");
|
|
71
|
-
const spanName = isAction ? `ACTION ${actionName}` : `${request.method} ${url.pathname}`;
|
|
72
|
-
const span = tracer.startSpan(spanName, spanOptions, parentContext);
|
|
73
|
-
const spanContext = trace.setSpan(parentContext, span);
|
|
74
|
-
const rpcMetadata = { type: RPCType.HTTP, span };
|
|
75
|
-
const startTime = performance.now();
|
|
76
|
-
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) {
|
|
127
|
-
span.setStatus({
|
|
128
|
-
code: SpanStatusCode.ERROR,
|
|
129
|
-
message: e instanceof Error ? e.message : "Unknown error"
|
|
130
|
-
});
|
|
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);
|
|
140
|
-
}
|
|
141
|
-
throw e;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
);
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export {
|
|
149
|
-
createOpenTelemetryMiddleware
|
|
150
|
-
};
|
package/dist/chunk-BQFWPPEO.js
DELETED
|
@@ -1,83 +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
|
-
function instrumentFetch() {
|
|
15
|
-
const originalFetch = globalThis.fetch;
|
|
16
|
-
async function instrumentedFetch(input, init) {
|
|
17
|
-
const tracer = trace.getTracer(LIB_NAME);
|
|
18
|
-
const activeContext = context.active();
|
|
19
|
-
const request = new Request(input, init);
|
|
20
|
-
const url = new URL(request.url);
|
|
21
|
-
const span = tracer.startSpan(
|
|
22
|
-
`FETCH ${request.method}`,
|
|
23
|
-
{
|
|
24
|
-
kind: SpanKind.CLIENT,
|
|
25
|
-
attributes: {
|
|
26
|
-
"http.request.method": request.method,
|
|
27
|
-
"url.full": request.url,
|
|
28
|
-
"url.path": url.pathname,
|
|
29
|
-
"url.scheme": url.protocol.replace(":", ""),
|
|
30
|
-
"server.address": url.hostname,
|
|
31
|
-
"server.port": url.port ? parseInt(url.port) : url.protocol === "https:" ? 443 : 80
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
activeContext
|
|
35
|
-
);
|
|
36
|
-
const headers = new Headers(request.headers);
|
|
37
|
-
const carrier = {};
|
|
38
|
-
propagation.inject(trace.setSpan(activeContext, span), carrier);
|
|
39
|
-
for (const [key, value] of Object.entries(carrier)) {
|
|
40
|
-
headers.set(key, value);
|
|
41
|
-
}
|
|
42
|
-
const startTime = performance.now();
|
|
43
|
-
try {
|
|
44
|
-
const response = await originalFetch(request.url, {
|
|
45
|
-
...init,
|
|
46
|
-
method: request.method,
|
|
47
|
-
headers,
|
|
48
|
-
body: request.body
|
|
49
|
-
});
|
|
50
|
-
span.setAttribute("http.response.status_code", response.status);
|
|
51
|
-
if (response.status >= 400) {
|
|
52
|
-
span.setStatus({
|
|
53
|
-
code: SpanStatusCode.ERROR,
|
|
54
|
-
message: `HTTP ${response.status}`
|
|
55
|
-
});
|
|
56
|
-
} else {
|
|
57
|
-
span.setStatus({ code: SpanStatusCode.OK });
|
|
58
|
-
}
|
|
59
|
-
span.end();
|
|
60
|
-
recordFetchRequestDuration(
|
|
61
|
-
{ method: request.method, host: url.hostname, status: response.status },
|
|
62
|
-
performance.now() - startTime
|
|
63
|
-
);
|
|
64
|
-
return response;
|
|
65
|
-
} catch (error) {
|
|
66
|
-
span.setStatus({
|
|
67
|
-
code: SpanStatusCode.ERROR,
|
|
68
|
-
message: error instanceof Error ? error.message : "Unknown error"
|
|
69
|
-
});
|
|
70
|
-
span.end();
|
|
71
|
-
recordFetchRequestDuration(
|
|
72
|
-
{ method: request.method, host: url.hostname, status: 0 },
|
|
73
|
-
performance.now() - startTime
|
|
74
|
-
);
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
globalThis.fetch = Object.assign(instrumentedFetch, originalFetch);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export {
|
|
82
|
-
instrumentFetch
|
|
83
|
-
};
|