@sentienguard/apm 1.0.14 → 1.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tracing.js +76 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentienguard/apm",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "SentienGuard APM SDK - Minimal, production-safe application performance monitoring",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
package/src/tracing.js CHANGED
@@ -18,6 +18,54 @@ import { SentienGuardTraceSpanExporter } from './traceSpanExporter.js';
18
18
  let sdk = null;
19
19
  let tracingActive = false;
20
20
 
21
+ function setAttr(span, key, value) {
22
+ try {
23
+ if (!span || typeof span.setAttribute !== 'function') return;
24
+ if (value == null) return;
25
+ const v = typeof value === 'string' ? value : String(value);
26
+ if (!v) return;
27
+ span.setAttribute(key, v);
28
+ } catch {
29
+ // ignore
30
+ }
31
+ }
32
+
33
+ function setAttrNumber(span, key, value) {
34
+ try {
35
+ if (!span || typeof span.setAttribute !== 'function') return;
36
+ const n = Number(value);
37
+ if (!Number.isFinite(n)) return;
38
+ span.setAttribute(key, n);
39
+ } catch {
40
+ // ignore
41
+ }
42
+ }
43
+
44
+ function safeUrlFromOutgoingRequest(requestOptions) {
45
+ try {
46
+ if (!requestOptions) return '';
47
+ if (typeof requestOptions === 'string') return requestOptions;
48
+ if (requestOptions instanceof URL) return requestOptions.toString();
49
+ const protocol = requestOptions.protocol || 'http:';
50
+ const hostRaw = requestOptions.hostname || requestOptions.host || '';
51
+ const host = String(hostRaw).trim();
52
+ const path = requestOptions.path || requestOptions.pathname || '/';
53
+ if (!host) return '';
54
+ return `${protocol}//${host}${path}`;
55
+ } catch {
56
+ return '';
57
+ }
58
+ }
59
+
60
+ function safeHostFromIncoming(req) {
61
+ try {
62
+ const h = req?.headers?.host;
63
+ return h ? String(h).split(',')[0].trim() : '';
64
+ } catch {
65
+ return '';
66
+ }
67
+ }
68
+
21
69
  function shouldIgnoreOutgoingHost(hostname) {
22
70
  if (!hostname) return false;
23
71
  const host = hostname.split(':')[0];
@@ -64,6 +112,34 @@ export function startTracing() {
64
112
  ignoreOutgoingRequestHook: (requestOptions) => {
65
113
  const host = requestOptions.hostname || (requestOptions.host ? String(requestOptions.host).split(':')[0] : '');
66
114
  return shouldIgnoreOutgoingHost(host);
115
+ },
116
+ /**
117
+ * Ensure we always attach basic HTTP attributes.
118
+ * This protects us from cases where the underlying instrumentation
119
+ * doesn't populate attributes (common in some Node/Jest/undici combos).
120
+ */
121
+ requestHook: (span, request) => {
122
+ // Incoming request (SERVER)
123
+ if (request && typeof request === 'object' && 'headers' in request && 'method' in request) {
124
+ setAttr(span, 'http.method', request.method);
125
+ setAttr(span, 'http.target', request.url);
126
+ setAttr(span, 'http.host', safeHostFromIncoming(request));
127
+ return;
128
+ }
129
+
130
+ // Outgoing request (CLIENT) — requestOptions
131
+ setAttr(span, 'http.method', request?.method);
132
+ const url = safeUrlFromOutgoingRequest(request);
133
+ if (url) setAttr(span, 'http.url', url);
134
+ const host = request?.hostname || (request?.host ? String(request.host).split(':')[0] : '');
135
+ if (host) setAttr(span, 'net.peer.name', host);
136
+ if (request?.port) setAttrNumber(span, 'net.peer.port', request.port);
137
+ },
138
+ responseHook: (span, response) => {
139
+ // Incoming response (SERVER): ServerResponse
140
+ if (response && typeof response === 'object' && 'statusCode' in response) {
141
+ setAttrNumber(span, 'http.status_code', response.statusCode);
142
+ }
67
143
  }
68
144
  });
69
145