@blamejs/core 0.7.102 → 0.7.104
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/CHANGELOG.md +4 -0
- package/index.js +2 -0
- package/lib/audit.js +1 -0
- package/lib/dsr.js +953 -0
- package/lib/guard-mime.js +3 -2
- package/lib/middleware/headers.js +3 -2
- package/lib/middleware/index.js +8 -2
- package/lib/middleware/span-http-server.js +243 -0
- package/lib/middleware/trace-log-correlation.js +134 -0
- package/lib/middleware/trace-propagate.js +9 -0
- package/lib/observability-otlp-exporter.js +351 -0
- package/lib/observability-tracer.js +395 -0
- package/lib/observability.js +230 -6
- package/lib/safe-buffer.js +15 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/lib/guard-mime.js
CHANGED
|
@@ -36,6 +36,7 @@ var lazyRequire = require("./lazy-require");
|
|
|
36
36
|
var gateContract = require("./gate-contract");
|
|
37
37
|
var C = require("./constants");
|
|
38
38
|
var numericBounds = require("./numeric-bounds");
|
|
39
|
+
var safeBuffer = require("./safe-buffer");
|
|
39
40
|
var { GuardMimeError } = require("./framework-error");
|
|
40
41
|
|
|
41
42
|
var observability = lazyRequire(function () { return require("./observability"); });
|
|
@@ -48,8 +49,8 @@ var _err = GuardMimeError.factory;
|
|
|
48
49
|
// 127 octets per token.
|
|
49
50
|
var TOKEN_RE = /^[A-Za-z0-9][A-Za-z0-9!#$&\-^_.+]{0,126}$/;
|
|
50
51
|
|
|
51
|
-
// Parameter token (RFC 7231 §3.1.1.1): tchar set.
|
|
52
|
-
var PARAM_TOKEN_RE =
|
|
52
|
+
// Parameter token (RFC 7231 §3.1.1.1): tchar set per RFC 7230.
|
|
53
|
+
var PARAM_TOKEN_RE = safeBuffer.RFC7230_TCHAR_RE;
|
|
53
54
|
|
|
54
55
|
// Quoted-string body (between double quotes) per RFC 7230 §3.2.6.
|
|
55
56
|
var QUOTED_STRING_BODY_RE = /^[\t\x20-\x7e]*$/; // allow:raw-byte-literal — printable ASCII range
|
|
@@ -34,12 +34,13 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
var lazyRequire = require("../lazy-require");
|
|
37
|
+
var safeBuffer = require("../safe-buffer");
|
|
37
38
|
|
|
38
39
|
var observability = lazyRequire(function () { return require("../observability"); });
|
|
39
40
|
void observability;
|
|
40
41
|
|
|
41
|
-
// RFC 9110 §5.1 token grammar — tchar set.
|
|
42
|
-
var TOKEN_RE =
|
|
42
|
+
// RFC 9110 §5.1 token grammar — tchar set per RFC 7230.
|
|
43
|
+
var TOKEN_RE = safeBuffer.RFC7230_TCHAR_RE;
|
|
43
44
|
|
|
44
45
|
var DEPRECATED_TRUST_HEADERS = Object.freeze([
|
|
45
46
|
"x-forwarded-for",
|
package/lib/middleware/index.js
CHANGED
|
@@ -45,7 +45,9 @@ var requireContentType = require("./require-content-type");
|
|
|
45
45
|
var requireMethods = require("./require-methods");
|
|
46
46
|
var securityHeaders = require("./security-headers");
|
|
47
47
|
var securityTxt = require("./security-txt");
|
|
48
|
+
var spanHttpServer = require("./span-http-server");
|
|
48
49
|
var sse = require("./sse");
|
|
50
|
+
var traceLogCorrelation = require("./trace-log-correlation");
|
|
49
51
|
var tracePropagate = require("./trace-propagate");
|
|
50
52
|
var tusUpload = require("./tus-upload");
|
|
51
53
|
var webAppManifest = require("./web-app-manifest");
|
|
@@ -81,7 +83,9 @@ module.exports = {
|
|
|
81
83
|
dpop: dpop.create,
|
|
82
84
|
hostAllowlist: hostAllowlist.create,
|
|
83
85
|
networkAllowlist: networkAllowlist.create,
|
|
84
|
-
|
|
86
|
+
spanHttpServer: spanHttpServer.create,
|
|
87
|
+
traceLogCorrelation: traceLogCorrelation.create,
|
|
88
|
+
tracePropagate: tracePropagate.create,
|
|
85
89
|
tusUpload: tusUpload.create,
|
|
86
90
|
webAppManifest: webAppManifest.create,
|
|
87
91
|
|
|
@@ -114,7 +118,9 @@ module.exports = {
|
|
|
114
118
|
dpop: dpop,
|
|
115
119
|
hostAllowlist: hostAllowlist,
|
|
116
120
|
networkAllowlist: networkAllowlist,
|
|
117
|
-
|
|
121
|
+
spanHttpServer: spanHttpServer,
|
|
122
|
+
traceLogCorrelation: traceLogCorrelation,
|
|
123
|
+
tracePropagate: tracePropagate,
|
|
118
124
|
tusUpload: tusUpload,
|
|
119
125
|
webAppManifest: webAppManifest,
|
|
120
126
|
},
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* spanHttpServer middleware — auto-creates a root span per HTTP
|
|
4
|
+
* request, populates OTel SEMCONV.HTTP_* attributes, attaches
|
|
5
|
+
* the span to req.span, and ends the span on response close.
|
|
6
|
+
*
|
|
7
|
+
* var tracer = b.observability.tracer.create({ service: "checkout" });
|
|
8
|
+
* var exporter = b.observability.otlpExporter.create({
|
|
9
|
+
* endpoint: "https://collector.example.com/v1/traces",
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* router.use(b.middleware.tracePropagate()); // populates req.trace
|
|
13
|
+
* router.use(b.middleware.spanHttpServer({
|
|
14
|
+
* tracer: tracer,
|
|
15
|
+
* onEnd: exporter.queue,
|
|
16
|
+
* ignorePaths: ["/healthz", /^\/static/],
|
|
17
|
+
* captureRequestHeaders: ["user-agent", "x-tenant-id"],
|
|
18
|
+
* captureResponseHeaders: ["content-type", "content-length"],
|
|
19
|
+
* }));
|
|
20
|
+
*
|
|
21
|
+
* app.get("/checkout", function (req, res) {
|
|
22
|
+
* // req.span is the active root server span
|
|
23
|
+
* req.span.setAttribute("checkout.cart_size", cart.items.length);
|
|
24
|
+
* var childSpan = tracer.startChildOf(req.span, "db.query");
|
|
25
|
+
* // ... do query work
|
|
26
|
+
* childSpan.end();
|
|
27
|
+
* res.json({ ok: true });
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* Span attributes auto-populated per OTel HTTP-server semconv:
|
|
31
|
+
* - http.request.method, http.route (when available)
|
|
32
|
+
* - url.scheme, url.path, url.query
|
|
33
|
+
* - server.address, client.address
|
|
34
|
+
* - user_agent.original
|
|
35
|
+
* - http.response.status_code (set when response writeHead fires)
|
|
36
|
+
*
|
|
37
|
+
* Span kind: "server".
|
|
38
|
+
*
|
|
39
|
+
* Skip paths: opts.ignorePaths accepts an array of strings (exact match)
|
|
40
|
+
* or RegExp instances. Use this to keep healthz / static-asset routes
|
|
41
|
+
* out of the span volume.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
var lazyRequire = require("../lazy-require");
|
|
45
|
+
var requestHelpers = require("../request-helpers");
|
|
46
|
+
var validateOpts = require("../validate-opts");
|
|
47
|
+
var { defineClass } = require("../framework-error");
|
|
48
|
+
|
|
49
|
+
var SpanHttpError = defineClass("SpanHttpError", { alwaysPermanent: true });
|
|
50
|
+
|
|
51
|
+
var observability = lazyRequire(function () { return require("../observability"); });
|
|
52
|
+
|
|
53
|
+
function _shouldIgnore(path, ignorePaths) {
|
|
54
|
+
if (!ignorePaths || !Array.isArray(ignorePaths)) return false;
|
|
55
|
+
for (var i = 0; i < ignorePaths.length; i++) {
|
|
56
|
+
var rule = ignorePaths[i];
|
|
57
|
+
if (typeof rule === "string" && rule === path) return true;
|
|
58
|
+
if (rule instanceof RegExp && rule.test(path)) return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function _splitUrl(url) {
|
|
64
|
+
if (typeof url !== "string" || url.length === 0) return { path: "/", query: null };
|
|
65
|
+
var qIdx = url.indexOf("?");
|
|
66
|
+
if (qIdx === -1) return { path: url, query: null };
|
|
67
|
+
return { path: url.slice(0, qIdx), query: url.slice(qIdx + 1) };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function _scheme(req) {
|
|
71
|
+
var x = req.headers && (req.headers["x-forwarded-proto"] || "");
|
|
72
|
+
if (typeof x === "string" && x.length > 0) {
|
|
73
|
+
var first = x.split(",")[0].trim().toLowerCase();
|
|
74
|
+
if (first === "http" || first === "https") return first;
|
|
75
|
+
}
|
|
76
|
+
return (req.socket && req.socket.encrypted) ? "https" : "http";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _serverAddress(req) {
|
|
80
|
+
var hostHeader = req.headers && (req.headers["x-forwarded-host"] || req.headers.host);
|
|
81
|
+
if (typeof hostHeader === "string" && hostHeader.length > 0) {
|
|
82
|
+
return hostHeader.split(",")[0].trim();
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function _captureHeaderAttrs(req, captureList, prefix) {
|
|
88
|
+
if (!Array.isArray(captureList) || captureList.length === 0) return {};
|
|
89
|
+
var out = Object.create(null);
|
|
90
|
+
for (var i = 0; i < captureList.length; i++) {
|
|
91
|
+
var name = String(captureList[i] || "").toLowerCase();
|
|
92
|
+
if (!name) continue;
|
|
93
|
+
var v = req.headers && req.headers[name];
|
|
94
|
+
if (v === undefined) continue;
|
|
95
|
+
if (Array.isArray(v)) v = v.join(", ");
|
|
96
|
+
out[prefix + "." + name] = String(v);
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function _captureResponseHeaderAttrs(res, captureList, prefix) {
|
|
102
|
+
if (!Array.isArray(captureList) || captureList.length === 0) return {};
|
|
103
|
+
var out = Object.create(null);
|
|
104
|
+
for (var i = 0; i < captureList.length; i++) {
|
|
105
|
+
var name = String(captureList[i] || "").toLowerCase();
|
|
106
|
+
if (!name) continue;
|
|
107
|
+
var v;
|
|
108
|
+
try { v = res.getHeader(name); } catch (_e) { continue; }
|
|
109
|
+
if (v === undefined || v === null) continue;
|
|
110
|
+
if (Array.isArray(v)) v = v.join(", ");
|
|
111
|
+
out[prefix + "." + name] = String(v);
|
|
112
|
+
}
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function create(opts) {
|
|
117
|
+
validateOpts.requireObject(opts, "middleware.spanHttpServer", SpanHttpError);
|
|
118
|
+
validateOpts(opts, [
|
|
119
|
+
"tracer", "onEnd", "ignorePaths",
|
|
120
|
+
"captureRequestHeaders", "captureResponseHeaders",
|
|
121
|
+
"spanNameFn", "audit",
|
|
122
|
+
], "middleware.spanHttpServer");
|
|
123
|
+
|
|
124
|
+
if (!opts.tracer || typeof opts.tracer.start !== "function") {
|
|
125
|
+
throw new SpanHttpError("span-http/bad-tracer",
|
|
126
|
+
"middleware.spanHttpServer: tracer must be a b.observability.tracer.create() instance");
|
|
127
|
+
}
|
|
128
|
+
validateOpts.optionalFunction(opts.onEnd,
|
|
129
|
+
"middleware.spanHttpServer: onEnd", SpanHttpError, "span-http/bad-opts");
|
|
130
|
+
validateOpts.optionalFunction(opts.spanNameFn,
|
|
131
|
+
"middleware.spanHttpServer: spanNameFn", SpanHttpError, "span-http/bad-opts");
|
|
132
|
+
|
|
133
|
+
var tracer = opts.tracer;
|
|
134
|
+
var onEnd = opts.onEnd || null;
|
|
135
|
+
var ignorePaths = opts.ignorePaths || null;
|
|
136
|
+
var captureReqHeaders = opts.captureRequestHeaders || null;
|
|
137
|
+
var captureResHeaders = opts.captureResponseHeaders || null;
|
|
138
|
+
var spanNameFn = opts.spanNameFn || null;
|
|
139
|
+
var auditOn = opts.audit !== false;
|
|
140
|
+
|
|
141
|
+
return function spanHttpServerMiddleware(req, res, next) {
|
|
142
|
+
var SEMCONV = observability().SEMCONV;
|
|
143
|
+
var url = _splitUrl(req.url || "/");
|
|
144
|
+
if (_shouldIgnore(url.path, ignorePaths)) return next();
|
|
145
|
+
|
|
146
|
+
var spanName;
|
|
147
|
+
if (typeof spanNameFn === "function") {
|
|
148
|
+
try { spanName = String(spanNameFn(req)); }
|
|
149
|
+
catch (_e) { spanName = "http.server.request"; }
|
|
150
|
+
} else {
|
|
151
|
+
spanName = (req.method ? req.method.toUpperCase() + " " : "") + (url.path || "/");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
var traceId = req.trace && req.trace.traceId;
|
|
155
|
+
var parentId = req.trace && req.trace.parentId;
|
|
156
|
+
var sampled = !req.trace || req.trace.sampled !== false;
|
|
157
|
+
|
|
158
|
+
var span = tracer.start(spanName, {
|
|
159
|
+
traceId: traceId,
|
|
160
|
+
parentId: parentId,
|
|
161
|
+
sampled: sampled,
|
|
162
|
+
kind: "server",
|
|
163
|
+
attributes: Object.assign({},
|
|
164
|
+
{
|
|
165
|
+
[SEMCONV.HTTP_REQUEST_METHOD]: (req.method || "").toUpperCase(),
|
|
166
|
+
[SEMCONV.URL_SCHEME]: _scheme(req),
|
|
167
|
+
[SEMCONV.URL_PATH]: url.path,
|
|
168
|
+
},
|
|
169
|
+
url.query !== null ? { [SEMCONV.URL_QUERY]: url.query } : {},
|
|
170
|
+
(function () {
|
|
171
|
+
var serverAddr = _serverAddress(req);
|
|
172
|
+
return serverAddr ? { [SEMCONV.SERVER_ADDRESS]: serverAddr } : {};
|
|
173
|
+
})(),
|
|
174
|
+
(function () {
|
|
175
|
+
var clientAddr = requestHelpers.clientIp(req);
|
|
176
|
+
return clientAddr ? { [SEMCONV.CLIENT_ADDRESS]: clientAddr } : {};
|
|
177
|
+
})(),
|
|
178
|
+
(function () {
|
|
179
|
+
var ua = req.headers && req.headers["user-agent"];
|
|
180
|
+
return ua ? { [SEMCONV.USER_AGENT_ORIGINAL]: String(ua) } : {};
|
|
181
|
+
})(),
|
|
182
|
+
_captureHeaderAttrs(req, captureReqHeaders, "http.request.header")),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
req.span = span;
|
|
186
|
+
|
|
187
|
+
var ended = false;
|
|
188
|
+
function _finish(err) {
|
|
189
|
+
if (ended) return;
|
|
190
|
+
ended = true;
|
|
191
|
+
try {
|
|
192
|
+
var status = res.statusCode;
|
|
193
|
+
if (typeof status === "number") {
|
|
194
|
+
span.setAttribute(SEMCONV.HTTP_RESPONSE_STATUS_CODE, status);
|
|
195
|
+
if (status >= 500) {
|
|
196
|
+
span.setStatus("error", "HTTP " + status);
|
|
197
|
+
} else if (status >= 400) {
|
|
198
|
+
// Per OTel semconv: client errors don't auto-set error status
|
|
199
|
+
// (they're "expected" failures). Operators that want to flag
|
|
200
|
+
// 4xx as errors call span.setStatus("error", ...) themselves.
|
|
201
|
+
span.setStatus("ok");
|
|
202
|
+
} else {
|
|
203
|
+
span.setStatus("ok");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (err) span.recordException(err);
|
|
207
|
+
var resHeaders = _captureResponseHeaderAttrs(res, captureResHeaders, "http.response.header");
|
|
208
|
+
var resHeaderKeys = Object.keys(resHeaders);
|
|
209
|
+
for (var i = 0; i < resHeaderKeys.length; i++) {
|
|
210
|
+
span.setAttribute(resHeaderKeys[i], resHeaders[resHeaderKeys[i]]);
|
|
211
|
+
}
|
|
212
|
+
if (req.route && req.route.path) {
|
|
213
|
+
span.setAttribute(SEMCONV.HTTP_ROUTE, String(req.route.path));
|
|
214
|
+
}
|
|
215
|
+
} catch (_e) { /* drop-silent — observability sink */ }
|
|
216
|
+
try { span.end(); }
|
|
217
|
+
catch (_e) { /* drop-silent */ }
|
|
218
|
+
if (typeof onEnd === "function") {
|
|
219
|
+
try { onEnd(span.toJSON()); }
|
|
220
|
+
catch (_e) { /* operator hook — drop-silent */ }
|
|
221
|
+
}
|
|
222
|
+
if (auditOn) {
|
|
223
|
+
try {
|
|
224
|
+
observability().safeEvent("middleware.spanHttpServer.complete", 1, {
|
|
225
|
+
kind: "server",
|
|
226
|
+
sampled: span.sampled ? "1" : "0",
|
|
227
|
+
});
|
|
228
|
+
} catch (_e) { /* drop-silent */ }
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
res.on("finish", function () { _finish(null); });
|
|
233
|
+
res.on("close", function () { _finish(null); });
|
|
234
|
+
res.on("error", function (e) { _finish(e); });
|
|
235
|
+
|
|
236
|
+
return next();
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
create: create,
|
|
242
|
+
SpanHttpError: SpanHttpError,
|
|
243
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* trace-log-correlation middleware — wraps the operator's b.log
|
|
4
|
+
* instance for the request lifetime so every log() / info() / warn()
|
|
5
|
+
* / error() / debug() call inside the handler auto-includes the
|
|
6
|
+
* canonical trace_id + span_id (and tenant context from W3C Baggage
|
|
7
|
+
* when present).
|
|
8
|
+
*
|
|
9
|
+
* var log = b.log.boot("api");
|
|
10
|
+
* router.use(b.middleware.tracePropagate());
|
|
11
|
+
* router.use(b.middleware.traceLogCorrelation({
|
|
12
|
+
* logger: log,
|
|
13
|
+
* reqField: "log", // attaches as req.log
|
|
14
|
+
* }));
|
|
15
|
+
*
|
|
16
|
+
* app.get("/widgets", function (req, res) {
|
|
17
|
+
* // req.log is the wrapped logger; every emission carries
|
|
18
|
+
* // trace_id + span_id + (optional) baggage attributes
|
|
19
|
+
* req.log.info("loading widgets");
|
|
20
|
+
* // → { ..., trace_id: "abc...", span_id: "def...",
|
|
21
|
+
* // baggage: { tenant: "acme" } }
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* The wrapper is a thin adapter: it does not change log levels,
|
|
25
|
+
* sinks, or the b.log API surface. Logs pass through to the
|
|
26
|
+
* wrapped logger with the trace fields injected via the meta-object
|
|
27
|
+
* second argument.
|
|
28
|
+
*
|
|
29
|
+
* When no req.trace is present (unusual — operators typically mount
|
|
30
|
+
* tracePropagate first), the wrapper is a no-op pass-through; logs
|
|
31
|
+
* still flow but without correlation fields.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
var lazyRequire = require("../lazy-require");
|
|
35
|
+
var validateOpts = require("../validate-opts");
|
|
36
|
+
var { defineClass } = require("../framework-error");
|
|
37
|
+
|
|
38
|
+
var TraceLogError = defineClass("TraceLogError", { alwaysPermanent: true });
|
|
39
|
+
|
|
40
|
+
var observability = lazyRequire(function () { return require("../observability"); });
|
|
41
|
+
|
|
42
|
+
var LOG_LEVELS = ["debug", "info", "warn", "error", "fatal"];
|
|
43
|
+
|
|
44
|
+
function _baggageToObject(entries) {
|
|
45
|
+
if (!Array.isArray(entries) || entries.length === 0) return null;
|
|
46
|
+
var out = Object.create(null);
|
|
47
|
+
for (var i = 0; i < entries.length; i++) {
|
|
48
|
+
out[entries[i].key] = entries[i].value;
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function _wrapLogger(baseLogger, req, opts) {
|
|
54
|
+
if (!baseLogger || typeof baseLogger !== "object") return baseLogger;
|
|
55
|
+
var wrapped = Object.create(null);
|
|
56
|
+
// Preserve any non-level properties the operator put on the
|
|
57
|
+
// logger (e.g. boot context, child-logger metadata).
|
|
58
|
+
var keys = Object.keys(baseLogger);
|
|
59
|
+
for (var i = 0; i < keys.length; i++) {
|
|
60
|
+
if (LOG_LEVELS.indexOf(keys[i]) === -1) wrapped[keys[i]] = baseLogger[keys[i]];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function _enrichMeta(meta) {
|
|
64
|
+
var enriched = Object.assign({}, meta || {});
|
|
65
|
+
if (req && req.trace) {
|
|
66
|
+
enriched.trace_id = req.trace.traceId;
|
|
67
|
+
// span_id prefers the active span (set by spanHttpServer) over
|
|
68
|
+
// the trace context's parentId
|
|
69
|
+
if (req.span && typeof req.span.spanId === "string") {
|
|
70
|
+
enriched.span_id = req.span.spanId;
|
|
71
|
+
} else if (typeof req.trace.parentId === "string") {
|
|
72
|
+
enriched.span_id = req.trace.parentId;
|
|
73
|
+
}
|
|
74
|
+
if (opts.includeBaggage !== false) {
|
|
75
|
+
var bg = _baggageToObject(req.trace.tracestate);
|
|
76
|
+
// tracestate is vendor-trace data; baggage is operator data.
|
|
77
|
+
// Operators usually want baggage in logs, not tracestate.
|
|
78
|
+
// We don't have a separate req.baggage today; keep this as
|
|
79
|
+
// the path for when tracePropagate exposes it. For now,
|
|
80
|
+
// emit the resolved tracestate shape under "trace_state".
|
|
81
|
+
if (bg) enriched.trace_state = bg;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return enriched;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Bind each level on the underlying logger so it emits with the
|
|
88
|
+
// enriched meta. We don't replace the underlying logger's bound
|
|
89
|
+
// emitter shape — it still receives meta as the second argument.
|
|
90
|
+
for (var li = 0; li < LOG_LEVELS.length; li++) {
|
|
91
|
+
(function (lvl) {
|
|
92
|
+
if (typeof baseLogger[lvl] !== "function") return;
|
|
93
|
+
wrapped[lvl] = function (msg, meta) {
|
|
94
|
+
try { return baseLogger[lvl](msg, _enrichMeta(meta)); }
|
|
95
|
+
catch (_e) { /* drop-silent — log sink */ }
|
|
96
|
+
};
|
|
97
|
+
})(LOG_LEVELS[li]);
|
|
98
|
+
}
|
|
99
|
+
// Pass through anything else the logger might expose (boot, child, etc.)
|
|
100
|
+
if (typeof baseLogger.boot === "function") wrapped.boot = baseLogger.boot.bind(baseLogger);
|
|
101
|
+
if (typeof baseLogger.child === "function") wrapped.child = baseLogger.child.bind(baseLogger);
|
|
102
|
+
return wrapped;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function create(opts) {
|
|
106
|
+
validateOpts.requireObject(opts, "middleware.traceLogCorrelation", TraceLogError);
|
|
107
|
+
validateOpts(opts, [
|
|
108
|
+
"logger", "reqField", "includeBaggage", "audit",
|
|
109
|
+
], "middleware.traceLogCorrelation");
|
|
110
|
+
|
|
111
|
+
if (!opts.logger || typeof opts.logger !== "object") {
|
|
112
|
+
throw new TraceLogError("trace-log/bad-logger",
|
|
113
|
+
"middleware.traceLogCorrelation: logger must be a b.log instance");
|
|
114
|
+
}
|
|
115
|
+
var reqField = opts.reqField || "log";
|
|
116
|
+
if (typeof reqField !== "string" || reqField.length === 0) {
|
|
117
|
+
throw new TraceLogError("trace-log/bad-reqfield",
|
|
118
|
+
"middleware.traceLogCorrelation: reqField must be a non-empty string");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return function traceLogCorrelationMiddleware(req, res, next) {
|
|
122
|
+
req[reqField] = _wrapLogger(opts.logger, req, opts);
|
|
123
|
+
void observability; // touch lazyRequire so the dep is captured
|
|
124
|
+
return next();
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
module.exports = {
|
|
129
|
+
create: create,
|
|
130
|
+
TraceLogError: TraceLogError,
|
|
131
|
+
// exported for tests
|
|
132
|
+
_wrapLogger: _wrapLogger,
|
|
133
|
+
LOG_LEVELS: LOG_LEVELS,
|
|
134
|
+
};
|
|
@@ -61,12 +61,17 @@ function create(opts) {
|
|
|
61
61
|
var tc = observability().traceContext;
|
|
62
62
|
var inbound = req.headers && req.headers.traceparent;
|
|
63
63
|
var parsed = (typeof inbound === "string") ? tc.parse(inbound) : null;
|
|
64
|
+
var inboundTracestate = req.headers && req.headers.tracestate;
|
|
65
|
+
var tracestateEntries = (typeof inboundTracestate === "string")
|
|
66
|
+
? tc.parseTracestate(inboundTracestate)
|
|
67
|
+
: null;
|
|
64
68
|
if (parsed) {
|
|
65
69
|
req.trace = {
|
|
66
70
|
traceId: parsed.traceId,
|
|
67
71
|
parentId: parsed.parentId,
|
|
68
72
|
sampled: parsed.sampled,
|
|
69
73
|
hadUpstream: true,
|
|
74
|
+
tracestate: tracestateEntries || [],
|
|
70
75
|
};
|
|
71
76
|
} else if (generateIfMissing) {
|
|
72
77
|
req.trace = {
|
|
@@ -74,6 +79,7 @@ function create(opts) {
|
|
|
74
79
|
parentId: tc.newParentId(),
|
|
75
80
|
sampled: true,
|
|
76
81
|
hadUpstream: false,
|
|
82
|
+
tracestate: [],
|
|
77
83
|
};
|
|
78
84
|
if (auditOnMissing && auditOn) {
|
|
79
85
|
try {
|
|
@@ -95,6 +101,9 @@ function create(opts) {
|
|
|
95
101
|
parentId: req.trace.parentId,
|
|
96
102
|
sampled: req.trace.sampled,
|
|
97
103
|
}));
|
|
104
|
+
if (req.trace.tracestate && req.trace.tracestate.length > 0) {
|
|
105
|
+
res.setHeader("tracestate", tc.buildTracestate(req.trace.tracestate));
|
|
106
|
+
}
|
|
98
107
|
} catch (_e) { /* drop-silent — header set best-effort */ }
|
|
99
108
|
}
|
|
100
109
|
return next();
|