@zudojs/observability 1.0.0 → 1.0.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.
package/README.md CHANGED
@@ -98,9 +98,11 @@ obs.logger.info("login", {
98
98
  ```
99
99
 
100
100
  Traversal handles the shapes secrets actually arrive in: arrays, nested
101
- objects, and cyclic graphs (a request object in a log context becomes
102
- `[CIRCULAR]` rather than a stack overflow). Class instances — `Error`, `Date`,
103
- `Map` — are left intact instead of being flattened to `{}`.
101
+ objects, instances of your own classes (a DTO carrying a `password` field is
102
+ redacted and keeps its prototype), and cyclic graphs (a request object in a log
103
+ context becomes `[CIRCULAR]` rather than a stack overflow). Built-ins —
104
+ `Error`, `Date`, `Map`, `Set`, typed arrays, `URL` — are left intact instead of
105
+ being flattened to `{}`.
104
106
 
105
107
  `redactObject`, `redactValue` and `createStructureRedactor` are exported for
106
108
  use outside the logger.
@@ -8,8 +8,11 @@
8
8
  * (`headers: [{ authorization: "Bearer …" }]`);
9
9
  * - traversal is cycle-aware and depth-capped, because a request object in
10
10
  * a log context is a graph, not a tree;
11
- * - class instances are not walked as plain objects, so an `Error` or a
12
- * `Date` survives instead of collapsing to `{}`;
11
+ * - built-ins such as `Error`, `Date`, `Map` and `Set` are left intact
12
+ * instead of collapsing to `{}`, while an instance of a user-defined
13
+ * class is walked like a plain object — its own fields are what an
14
+ * exporter serializes, so a DTO carrying a `password` must not slip
15
+ * past the field list;
13
16
  * - matching is substring-based by default, so `userPassword` and
14
17
  * `x-api-key` are caught, not just the exact names in the list.
15
18
  */
@@ -8,8 +8,11 @@
8
8
  * (`headers: [{ authorization: "Bearer …" }]`);
9
9
  * - traversal is cycle-aware and depth-capped, because a request object in
10
10
  * a log context is a graph, not a tree;
11
- * - class instances are not walked as plain objects, so an `Error` or a
12
- * `Date` survives instead of collapsing to `{}`;
11
+ * - built-ins such as `Error`, `Date`, `Map` and `Set` are left intact
12
+ * instead of collapsing to `{}`, while an instance of a user-defined
13
+ * class is walked like a plain object — its own fields are what an
14
+ * exporter serializes, so a DTO carrying a `password` must not slip
15
+ * past the field list;
13
16
  * - matching is substring-based by default, so `userPassword` and
14
17
  * `x-api-key` are caught, not just the exact names in the list.
15
18
  */
@@ -145,10 +148,48 @@ function isPlainContainer(value) {
145
148
  if (Array.isArray(value))
146
149
  return false;
147
150
  const prototype = Object.getPrototypeOf(value);
148
- // Walk plain objects and null-prototype bags; leave Error, Date, Map, Set,
149
- // Buffer and every other class instance intact.
150
151
  return prototype === Object.prototype || prototype === null;
151
152
  }
153
+ /**
154
+ * Built-ins whose contents are not key/value fields: they are left intact
155
+ * rather than walked, because rebuilding them field by field would collapse
156
+ * a `Date` or a `Map` to `{}`.
157
+ */
158
+ function isOpaqueBuiltin(value) {
159
+ return (value instanceof Date ||
160
+ value instanceof RegExp ||
161
+ value instanceof Error ||
162
+ value instanceof Map ||
163
+ value instanceof Set ||
164
+ value instanceof WeakMap ||
165
+ value instanceof WeakSet ||
166
+ value instanceof Promise ||
167
+ value instanceof ArrayBuffer ||
168
+ ArrayBuffer.isView(value) ||
169
+ value instanceof Number ||
170
+ value instanceof String ||
171
+ value instanceof Boolean ||
172
+ (typeof URL !== "undefined" && value instanceof URL) ||
173
+ (typeof URLSearchParams !== "undefined" && value instanceof URLSearchParams));
174
+ }
175
+ /**
176
+ * True for an instance of a user-defined class (a DTO, a request model).
177
+ *
178
+ * Its own enumerable fields are exactly what `JSON.stringify` — and so every
179
+ * exporter — writes out, so a `password` field on a class instance leaked
180
+ * through a redactor that only walked plain objects. Such an instance is
181
+ * walked like a plain object and rebuilt on the same prototype, so
182
+ * `instanceof` and any `toJSON` it defines survive.
183
+ */
184
+ function isWalkableInstance(value) {
185
+ if (value === null || typeof value !== "object")
186
+ return false;
187
+ if (Array.isArray(value) || isPlainContainer(value))
188
+ return false;
189
+ if (isOpaqueBuiltin(value))
190
+ return false;
191
+ return Object.keys(value).length > 0;
192
+ }
152
193
  function walk(value, compiled, depth, seen) {
153
194
  if (depth > compiled.maxDepth)
154
195
  return MAX_DEPTH_MARKER;
@@ -160,11 +201,11 @@ function walk(value, compiled, depth, seen) {
160
201
  seen.delete(value);
161
202
  return result;
162
203
  }
163
- if (isPlainContainer(value)) {
204
+ if (isPlainContainer(value) || isWalkableInstance(value)) {
164
205
  if (seen.has(value))
165
206
  return CIRCULAR_MARKER;
166
207
  seen.add(value);
167
- const result = {};
208
+ const result = Object.create(Object.getPrototypeOf(value));
168
209
  for (const [key, entry] of Object.entries(value)) {
169
210
  const redacted = redactField(key, entry, compiled);
170
211
  // A field the matcher replaced is done — never walk into it, or a
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "@zudojs/observability",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Structured logging, metrics, tracing, context propagation, and exporters for Zudojs applications.",
5
5
  "license": "MIT",
6
+ "author": {
7
+ "name": "Oluwayemi Oyinlola",
8
+ "url": "https://github.com/oyinlola-tech"
9
+ },
6
10
  "type": "module",
7
11
  "main": "./dist/index.js",
8
12
  "module": "./dist/index.js",
@@ -22,7 +26,7 @@
22
26
  "!dist/.tsbuildinfo"
23
27
  ],
24
28
  "dependencies": {
25
- "@zudojs/errors": "1.0.0"
29
+ "@zudojs/errors": "1.0.1"
26
30
  },
27
31
  "devDependencies": {
28
32
  "typescript": "7.0.2",