veryfront 0.1.911 → 0.1.912
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/esm/deno.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,yBAAyB,CAAC;AAcjC,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAChF,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;AAEpE,MAAM,WAAW,uBAAuB;IACtC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA8TD,wBAAgB,OAAO,CACrB,IAAI,EAAE,MAAM,EACZ,KAAK,SAAI,EACT,UAAU,CAAC,EAAE,gBAAgB,EAC7B,OAAO,CAAC,EAAE,uBAAuB,GAChC,IAAI,CAON;AAED,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,gBAAgB,EAC7B,OAAO,CAAC,EAAE,uBAAuB,GAChC,IAAI,CAON;AAED,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,gBAAgB,EAC7B,OAAO,CAAC,EAAE,uBAAuB,GAChC,IAAI,CAaN;AAED,eAAO,MAAM,OAAO;;;;uBAIO,OAAO,CAAC,IAAI,CAAC;uBAGnB,IAAI;CAWxB,CAAC"}
|
package/esm/src/metrics/index.js
CHANGED
|
@@ -13,11 +13,18 @@
|
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
import "../../_dnt.polyfills.js";
|
|
16
|
+
import * as dntShim from "../../_dnt.shims.js";
|
|
16
17
|
import { getGlobalMetricsAPI, } from "../observability/tracing/api-shim.js";
|
|
17
18
|
import { getCurrentRequestContext } from "../platform/adapters/fs/veryfront/request-context.js";
|
|
18
19
|
const counters = new Map();
|
|
19
20
|
const histograms = new Map();
|
|
20
21
|
const gauges = new Map();
|
|
22
|
+
const directQueue = [];
|
|
23
|
+
const directCounterTotals = new Map();
|
|
24
|
+
let directFlushTimer = null;
|
|
25
|
+
const DIRECT_FLUSH_DELAY_MS = 1_000;
|
|
26
|
+
const DIRECT_MAX_BATCH_SIZE = 100;
|
|
27
|
+
const HISTOGRAM_BOUNDS = [0, 10, 50, 100, 250, 500, 1_000, 2_500, 5_000, 10_000];
|
|
21
28
|
function getMeter() {
|
|
22
29
|
return getGlobalMetricsAPI()?.getMeter("veryfront.project.metrics");
|
|
23
30
|
}
|
|
@@ -88,14 +95,227 @@ function getGauge(name, options) {
|
|
|
88
95
|
gauges.set(name, gauge);
|
|
89
96
|
return gauge;
|
|
90
97
|
}
|
|
98
|
+
function readEnv(name) {
|
|
99
|
+
try {
|
|
100
|
+
return dntShim.Deno.env.get(name);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function resolveOtlpMetricsUrl() {
|
|
107
|
+
if (readEnv("OTEL_METRICS_ENABLED") !== "true")
|
|
108
|
+
return null;
|
|
109
|
+
const endpoint = readEnv("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT") ??
|
|
110
|
+
readEnv("OTEL_EXPORTER_OTLP_ENDPOINT");
|
|
111
|
+
if (!endpoint)
|
|
112
|
+
return null;
|
|
113
|
+
const trimmed = endpoint.replace(/\/$/, "");
|
|
114
|
+
return trimmed.endsWith("/v1/metrics") ? trimmed : `${trimmed}/v1/metrics`;
|
|
115
|
+
}
|
|
116
|
+
function parseHeaders(headerInput) {
|
|
117
|
+
if (!headerInput)
|
|
118
|
+
return {};
|
|
119
|
+
if (headerInput.startsWith("Basic "))
|
|
120
|
+
return { Authorization: headerInput };
|
|
121
|
+
if (headerInput.startsWith("Authorization=")) {
|
|
122
|
+
return { Authorization: headerInput.slice("Authorization=".length) };
|
|
123
|
+
}
|
|
124
|
+
const result = {};
|
|
125
|
+
for (const part of headerInput.split(",")) {
|
|
126
|
+
const [key, ...valueParts] = part.split("=");
|
|
127
|
+
if (key && valueParts.length > 0) {
|
|
128
|
+
result[key.trim()] = valueParts.join("=").trim();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
function toOtlpValue(value) {
|
|
134
|
+
if (typeof value === "boolean")
|
|
135
|
+
return { boolValue: value };
|
|
136
|
+
if (typeof value === "number")
|
|
137
|
+
return { doubleValue: value };
|
|
138
|
+
return { stringValue: String(value) };
|
|
139
|
+
}
|
|
140
|
+
function toOtlpAttributes(attributes) {
|
|
141
|
+
return Object.entries(attributes).map(([key, value]) => ({
|
|
142
|
+
key,
|
|
143
|
+
value: toOtlpValue(value),
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
function getUnixNanoTimestamp() {
|
|
147
|
+
return String(BigInt(Date.now()) * 1000000n);
|
|
148
|
+
}
|
|
149
|
+
function buildHistogramBuckets(value) {
|
|
150
|
+
const counts = new Array(HISTOGRAM_BOUNDS.length + 1).fill(0);
|
|
151
|
+
const bucketIndex = HISTOGRAM_BOUNDS.findIndex((bound) => value <= bound);
|
|
152
|
+
counts[bucketIndex === -1 ? counts.length - 1 : bucketIndex] = 1;
|
|
153
|
+
return counts;
|
|
154
|
+
}
|
|
155
|
+
function buildDirectMetric(sample) {
|
|
156
|
+
const attributes = toOtlpAttributes(sample.attributes);
|
|
157
|
+
if (sample.kind === "counter") {
|
|
158
|
+
const key = `${sample.name}:${attributesKey(sample.attributes)}`;
|
|
159
|
+
const total = directCounterTotals.get(key) ?? {
|
|
160
|
+
value: 0,
|
|
161
|
+
startTimeUnixNano: sample.timestampUnixNano,
|
|
162
|
+
};
|
|
163
|
+
total.value += sample.value;
|
|
164
|
+
directCounterTotals.set(key, total);
|
|
165
|
+
return {
|
|
166
|
+
name: sample.name,
|
|
167
|
+
sum: {
|
|
168
|
+
dataPoints: [{
|
|
169
|
+
attributes,
|
|
170
|
+
startTimeUnixNano: total.startTimeUnixNano,
|
|
171
|
+
timeUnixNano: sample.timestampUnixNano,
|
|
172
|
+
asDouble: total.value,
|
|
173
|
+
}],
|
|
174
|
+
aggregationTemporality: 2,
|
|
175
|
+
isMonotonic: true,
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (sample.kind === "histogram") {
|
|
180
|
+
return {
|
|
181
|
+
name: sample.name,
|
|
182
|
+
histogram: {
|
|
183
|
+
dataPoints: [{
|
|
184
|
+
attributes,
|
|
185
|
+
startTimeUnixNano: sample.timestampUnixNano,
|
|
186
|
+
timeUnixNano: sample.timestampUnixNano,
|
|
187
|
+
count: 1,
|
|
188
|
+
sum: sample.value,
|
|
189
|
+
explicitBounds: HISTOGRAM_BOUNDS,
|
|
190
|
+
bucketCounts: buildHistogramBuckets(sample.value),
|
|
191
|
+
}],
|
|
192
|
+
aggregationTemporality: 1,
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
name: sample.name,
|
|
198
|
+
gauge: {
|
|
199
|
+
dataPoints: [{
|
|
200
|
+
attributes,
|
|
201
|
+
timeUnixNano: sample.timestampUnixNano,
|
|
202
|
+
asDouble: sample.value,
|
|
203
|
+
}],
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function buildDirectOtlpBody(samples) {
|
|
208
|
+
return {
|
|
209
|
+
resourceMetrics: [{
|
|
210
|
+
resource: {
|
|
211
|
+
attributes: toOtlpAttributes({
|
|
212
|
+
"service.name": readEnv("OTEL_SERVICE_NAME") ?? "veryfront",
|
|
213
|
+
"service.version": readEnv("VERYFRONT_VERSION") ??
|
|
214
|
+
readEnv("RELEASE_VERSION") ??
|
|
215
|
+
"unknown",
|
|
216
|
+
}),
|
|
217
|
+
},
|
|
218
|
+
scopeMetrics: [{
|
|
219
|
+
scope: {
|
|
220
|
+
name: "veryfront.project.metrics",
|
|
221
|
+
},
|
|
222
|
+
metrics: samples.map(buildDirectMetric),
|
|
223
|
+
}],
|
|
224
|
+
}],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function logDirectExportFailure(error) {
|
|
228
|
+
if (readEnv("VERYFRONT_DEBUG") !== "1")
|
|
229
|
+
return;
|
|
230
|
+
console.warn("[metrics] direct OTLP export failed", error);
|
|
231
|
+
}
|
|
232
|
+
async function flushDirectMetrics() {
|
|
233
|
+
if (directFlushTimer) {
|
|
234
|
+
clearTimeout(directFlushTimer);
|
|
235
|
+
directFlushTimer = null;
|
|
236
|
+
}
|
|
237
|
+
const url = resolveOtlpMetricsUrl();
|
|
238
|
+
if (!url || directQueue.length === 0) {
|
|
239
|
+
directQueue.length = 0;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const batch = directQueue.splice(0, DIRECT_MAX_BATCH_SIZE);
|
|
243
|
+
try {
|
|
244
|
+
const response = await fetch(url, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: {
|
|
247
|
+
...parseHeaders(readEnv("OTEL_EXPORTER_OTLP_HEADERS")),
|
|
248
|
+
"Content-Type": "application/json",
|
|
249
|
+
},
|
|
250
|
+
body: JSON.stringify(buildDirectOtlpBody(batch)),
|
|
251
|
+
});
|
|
252
|
+
if (!response.ok) {
|
|
253
|
+
logDirectExportFailure(`HTTP ${response.status}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
logDirectExportFailure(error);
|
|
258
|
+
}
|
|
259
|
+
if (directQueue.length > 0) {
|
|
260
|
+
scheduleDirectFlush();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function scheduleDirectFlush() {
|
|
264
|
+
if (directFlushTimer || resolveOtlpMetricsUrl() === null)
|
|
265
|
+
return;
|
|
266
|
+
directFlushTimer = dntShim.setTimeout(() => {
|
|
267
|
+
void flushDirectMetrics();
|
|
268
|
+
}, DIRECT_FLUSH_DELAY_MS);
|
|
269
|
+
try {
|
|
270
|
+
if (typeof directFlushTimer === "number") {
|
|
271
|
+
dntShim.Deno.unrefTimer(directFlushTimer);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
directFlushTimer.unref?.();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
// Some runtimes do not expose unref support; exporting still works there.
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function enqueueDirectMetric(kind, name, value, attributes) {
|
|
282
|
+
if (resolveOtlpMetricsUrl() === null)
|
|
283
|
+
return;
|
|
284
|
+
directQueue.push({
|
|
285
|
+
kind,
|
|
286
|
+
name,
|
|
287
|
+
value,
|
|
288
|
+
attributes,
|
|
289
|
+
timestampUnixNano: getUnixNanoTimestamp(),
|
|
290
|
+
});
|
|
291
|
+
if (directQueue.length >= DIRECT_MAX_BATCH_SIZE) {
|
|
292
|
+
void flushDirectMetrics();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
scheduleDirectFlush();
|
|
296
|
+
}
|
|
91
297
|
export function counter(name, value = 1, attributes, options) {
|
|
92
|
-
|
|
298
|
+
const normalizedAttributes = normalizeAttributes(attributes);
|
|
299
|
+
if (resolveOtlpMetricsUrl() === null) {
|
|
300
|
+
getCounter(name, options)?.add(value, normalizedAttributes);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
enqueueDirectMetric("counter", name, value, normalizedAttributes);
|
|
93
304
|
}
|
|
94
305
|
export function histogram(name, value, attributes, options) {
|
|
95
|
-
|
|
306
|
+
const normalizedAttributes = normalizeAttributes(attributes);
|
|
307
|
+
if (resolveOtlpMetricsUrl() === null) {
|
|
308
|
+
getHistogram(name, options)?.record(value, normalizedAttributes);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
enqueueDirectMetric("histogram", name, value, normalizedAttributes);
|
|
96
312
|
}
|
|
97
313
|
export function gauge(name, value, attributes, options) {
|
|
98
314
|
const normalizedAttributes = normalizeAttributes(attributes);
|
|
315
|
+
if (resolveOtlpMetricsUrl() !== null) {
|
|
316
|
+
enqueueDirectMetric("gauge", name, value, normalizedAttributes);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
99
319
|
const target = getGauge(name, options);
|
|
100
320
|
if (!target)
|
|
101
321
|
return;
|
|
@@ -108,9 +328,18 @@ export const metrics = {
|
|
|
108
328
|
counter,
|
|
109
329
|
histogram,
|
|
110
330
|
gauge,
|
|
331
|
+
async __flushForTests() {
|
|
332
|
+
await flushDirectMetrics();
|
|
333
|
+
},
|
|
111
334
|
__resetForTests() {
|
|
112
335
|
counters.clear();
|
|
113
336
|
histograms.clear();
|
|
114
337
|
gauges.clear();
|
|
338
|
+
directQueue.length = 0;
|
|
339
|
+
directCounterTotals.clear();
|
|
340
|
+
if (directFlushTimer) {
|
|
341
|
+
clearTimeout(directFlushTimer);
|
|
342
|
+
directFlushTimer = null;
|
|
343
|
+
}
|
|
115
344
|
},
|
|
116
345
|
};
|