@zudojs/observability 1.1.0 → 1.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.
|
@@ -143,6 +143,35 @@ function buildResourceAttributes(config) {
|
|
|
143
143
|
...(config.resource ?? {}),
|
|
144
144
|
};
|
|
145
145
|
}
|
|
146
|
+
/** Shortest interval between two repeat queue-overflow reports. */
|
|
147
|
+
const DROP_REPORT_INTERVAL_MS = 60_000;
|
|
148
|
+
/**
|
|
149
|
+
* Rate-limits a processor's `onDrop` callback.
|
|
150
|
+
*
|
|
151
|
+
* `onDrop` fires once per record the bounded queue refuses, on the
|
|
152
|
+
* synchronous `logger.info()` path, and the report below allocates an
|
|
153
|
+
* `Error` (so, a stack capture) and re-enters the caller's `onError` —
|
|
154
|
+
* which usually writes to the sink that is already stalled. The bounded
|
|
155
|
+
* queue is the control that makes a stalled exporter survivable; reporting
|
|
156
|
+
* every drop turned it into a second, louder failure.
|
|
157
|
+
*
|
|
158
|
+
* Handled the same way an over-cardinality metric is a few lines below: the
|
|
159
|
+
* first occurrence is reported immediately, then at most one per interval.
|
|
160
|
+
* The count handed to `report` is the processor's running total, so
|
|
161
|
+
* whoever does get notified still sees how many records were lost.
|
|
162
|
+
*/
|
|
163
|
+
function throttleDropReports(report) {
|
|
164
|
+
let lastReportedAt;
|
|
165
|
+
return (dropped) => {
|
|
166
|
+
const at = Date.now();
|
|
167
|
+
if (lastReportedAt !== undefined &&
|
|
168
|
+
at - lastReportedAt < DROP_REPORT_INTERVAL_MS) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
lastReportedAt = at;
|
|
172
|
+
report(dropped);
|
|
173
|
+
};
|
|
174
|
+
}
|
|
146
175
|
function buildPipeline(config) {
|
|
147
176
|
const useConsole = config.useConsoleExporters ?? true;
|
|
148
177
|
/* ── Logging ─────────────────────────────────────────────────────────── */
|
|
@@ -153,7 +182,7 @@ function buildPipeline(config) {
|
|
|
153
182
|
batchSize: config.logBatchSize,
|
|
154
183
|
flushIntervalMs: config.logFlushIntervalMs,
|
|
155
184
|
onError: config.onError,
|
|
156
|
-
onDrop: (dropped) => config.onError?.(new Error(`Dropped ${dropped} log records: queue full`), "BatchLogProcessor"),
|
|
185
|
+
onDrop: throttleDropReports((dropped) => config.onError?.(new Error(`Dropped ${dropped} log records: queue full`), "BatchLogProcessor")),
|
|
157
186
|
});
|
|
158
187
|
// Redaction is applied by the logger, so every transport and exporter
|
|
159
188
|
// downstream sees redacted records — configuring it and not wiring it here
|
|
@@ -179,7 +208,7 @@ function buildPipeline(config) {
|
|
|
179
208
|
new BatchSpanProcessor({
|
|
180
209
|
exporter: ownSpanExporter(),
|
|
181
210
|
onError: config.onError,
|
|
182
|
-
onDrop: (dropped) => config.onError?.(new Error(`Dropped ${dropped} spans: queue full`), "BatchSpanProcessor"),
|
|
211
|
+
onDrop: throttleDropReports((dropped) => config.onError?.(new Error(`Dropped ${dropped} spans: queue full`), "BatchSpanProcessor")),
|
|
183
212
|
}),
|
|
184
213
|
];
|
|
185
214
|
// When the caller supplied their own processors, they own the exporter's
|
|
@@ -6,6 +6,23 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { SpanKind, SpanStatus } from "../../types.js";
|
|
8
8
|
import { createSpanContext, createChildSpanContext, } from "./spanContext.type.js";
|
|
9
|
+
/**
|
|
10
|
+
* Stores one attribute on a bag whose keys come from instrumentation.
|
|
11
|
+
*
|
|
12
|
+
* `bag[key] = value` invokes the inherited `__proto__` setter for that one
|
|
13
|
+
* key: the attribute never lands, and an object value replaces the bag's
|
|
14
|
+
* prototype. A poisoned prototype then defeats `maxAttributes`, because the
|
|
15
|
+
* cap is only applied to keys the bag does not already answer for. Matches
|
|
16
|
+
* the same guard in `redaction.core.ts`.
|
|
17
|
+
*/
|
|
18
|
+
function defineAttribute(target, key, value) {
|
|
19
|
+
Object.defineProperty(target, key, {
|
|
20
|
+
value,
|
|
21
|
+
enumerable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
configurable: true,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
9
26
|
/** Defaults matching the OpenTelemetry specification. */
|
|
10
27
|
const DEFAULT_LIMITS = {
|
|
11
28
|
maxAttributes: 128,
|
|
@@ -81,12 +98,12 @@ export class DefaultSpan {
|
|
|
81
98
|
setAttribute(key, value) {
|
|
82
99
|
if (this.ended || !this.recording)
|
|
83
100
|
return;
|
|
84
|
-
if (!(
|
|
101
|
+
if (!Object.prototype.hasOwnProperty.call(this.attributes, key) &&
|
|
85
102
|
Object.keys(this.attributes).length >= this.limits.maxAttributes) {
|
|
86
103
|
this.droppedAttributes++;
|
|
87
104
|
return;
|
|
88
105
|
}
|
|
89
|
-
this.attributes
|
|
106
|
+
defineAttribute(this.attributes, key, this.sanitize(key, value));
|
|
90
107
|
}
|
|
91
108
|
addEvent(name, attributes) {
|
|
92
109
|
if (this.ended || !this.recording)
|
|
@@ -104,7 +121,7 @@ export class DefaultSpan {
|
|
|
104
121
|
this.droppedAttributes++;
|
|
105
122
|
continue;
|
|
106
123
|
}
|
|
107
|
-
eventAttributes
|
|
124
|
+
defineAttribute(eventAttributes, key, this.sanitize(key, value));
|
|
108
125
|
kept++;
|
|
109
126
|
}
|
|
110
127
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/observability",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Structured logging, metrics, tracing, context propagation, and exporters for Zudojs applications.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"!dist/.tsbuildinfo"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@zudojs/errors": "1.
|
|
29
|
+
"@zudojs/errors": "1.2.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"typescript": "7.0.2",
|