veryfront 0.1.911 → 0.1.913
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;AAuWD,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,259 @@ 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 resolveInternalMetricsUrl() {
|
|
117
|
+
if (readEnv("OTEL_METRICS_ENABLED") !== "true")
|
|
118
|
+
return null;
|
|
119
|
+
const apiBaseUrl = readEnv("VERYFRONT_API_BASE_URL") ?? readEnv("VERYFRONT_API_URL");
|
|
120
|
+
const username = readEnv("VERYFRONT_API_INTERNAL_USER");
|
|
121
|
+
const password = readEnv("VERYFRONT_API_INTERNAL_PASS");
|
|
122
|
+
if (!apiBaseUrl || !username || !password)
|
|
123
|
+
return null;
|
|
124
|
+
return `${apiBaseUrl.replace(/\/$/, "")}/internal/metrics/otlp/v1/metrics`;
|
|
125
|
+
}
|
|
126
|
+
function buildBasicAuth(username, password) {
|
|
127
|
+
const credentials = `${username}:${password}`;
|
|
128
|
+
return `Basic ${globalThis.btoa(credentials)}`;
|
|
129
|
+
}
|
|
130
|
+
function parseHeaders(headerInput) {
|
|
131
|
+
if (!headerInput)
|
|
132
|
+
return {};
|
|
133
|
+
if (headerInput.startsWith("Basic "))
|
|
134
|
+
return { Authorization: headerInput };
|
|
135
|
+
if (headerInput.startsWith("Authorization=")) {
|
|
136
|
+
return { Authorization: headerInput.slice("Authorization=".length) };
|
|
137
|
+
}
|
|
138
|
+
const result = {};
|
|
139
|
+
for (const part of headerInput.split(",")) {
|
|
140
|
+
const [key, ...valueParts] = part.split("=");
|
|
141
|
+
if (key && valueParts.length > 0) {
|
|
142
|
+
result[key.trim()] = valueParts.join("=").trim();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
function resolveDirectMetricsTarget() {
|
|
148
|
+
const internalUrl = resolveInternalMetricsUrl();
|
|
149
|
+
if (internalUrl) {
|
|
150
|
+
return {
|
|
151
|
+
url: internalUrl,
|
|
152
|
+
headers: {
|
|
153
|
+
Authorization: buildBasicAuth(readEnv("VERYFRONT_API_INTERNAL_USER") ?? "", readEnv("VERYFRONT_API_INTERNAL_PASS") ?? ""),
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const otlpUrl = resolveOtlpMetricsUrl();
|
|
158
|
+
if (!otlpUrl)
|
|
159
|
+
return null;
|
|
160
|
+
return {
|
|
161
|
+
url: otlpUrl,
|
|
162
|
+
headers: parseHeaders(readEnv("OTEL_EXPORTER_OTLP_HEADERS")),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function toOtlpValue(value) {
|
|
166
|
+
if (typeof value === "boolean")
|
|
167
|
+
return { boolValue: value };
|
|
168
|
+
if (typeof value === "number")
|
|
169
|
+
return { doubleValue: value };
|
|
170
|
+
return { stringValue: String(value) };
|
|
171
|
+
}
|
|
172
|
+
function toOtlpAttributes(attributes) {
|
|
173
|
+
return Object.entries(attributes).map(([key, value]) => ({
|
|
174
|
+
key,
|
|
175
|
+
value: toOtlpValue(value),
|
|
176
|
+
}));
|
|
177
|
+
}
|
|
178
|
+
function getUnixNanoTimestamp() {
|
|
179
|
+
return String(BigInt(Date.now()) * 1000000n);
|
|
180
|
+
}
|
|
181
|
+
function buildHistogramBuckets(value) {
|
|
182
|
+
const counts = new Array(HISTOGRAM_BOUNDS.length + 1).fill(0);
|
|
183
|
+
const bucketIndex = HISTOGRAM_BOUNDS.findIndex((bound) => value <= bound);
|
|
184
|
+
counts[bucketIndex === -1 ? counts.length - 1 : bucketIndex] = 1;
|
|
185
|
+
return counts;
|
|
186
|
+
}
|
|
187
|
+
function buildDirectMetric(sample) {
|
|
188
|
+
const attributes = toOtlpAttributes(sample.attributes);
|
|
189
|
+
if (sample.kind === "counter") {
|
|
190
|
+
const key = `${sample.name}:${attributesKey(sample.attributes)}`;
|
|
191
|
+
const total = directCounterTotals.get(key) ?? {
|
|
192
|
+
value: 0,
|
|
193
|
+
startTimeUnixNano: sample.timestampUnixNano,
|
|
194
|
+
};
|
|
195
|
+
total.value += sample.value;
|
|
196
|
+
directCounterTotals.set(key, total);
|
|
197
|
+
return {
|
|
198
|
+
name: sample.name,
|
|
199
|
+
sum: {
|
|
200
|
+
dataPoints: [{
|
|
201
|
+
attributes,
|
|
202
|
+
startTimeUnixNano: total.startTimeUnixNano,
|
|
203
|
+
timeUnixNano: sample.timestampUnixNano,
|
|
204
|
+
asDouble: total.value,
|
|
205
|
+
}],
|
|
206
|
+
aggregationTemporality: 2,
|
|
207
|
+
isMonotonic: true,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (sample.kind === "histogram") {
|
|
212
|
+
return {
|
|
213
|
+
name: sample.name,
|
|
214
|
+
histogram: {
|
|
215
|
+
dataPoints: [{
|
|
216
|
+
attributes,
|
|
217
|
+
startTimeUnixNano: sample.timestampUnixNano,
|
|
218
|
+
timeUnixNano: sample.timestampUnixNano,
|
|
219
|
+
count: 1,
|
|
220
|
+
sum: sample.value,
|
|
221
|
+
explicitBounds: HISTOGRAM_BOUNDS,
|
|
222
|
+
bucketCounts: buildHistogramBuckets(sample.value),
|
|
223
|
+
}],
|
|
224
|
+
aggregationTemporality: 1,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
name: sample.name,
|
|
230
|
+
gauge: {
|
|
231
|
+
dataPoints: [{
|
|
232
|
+
attributes,
|
|
233
|
+
timeUnixNano: sample.timestampUnixNano,
|
|
234
|
+
asDouble: sample.value,
|
|
235
|
+
}],
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function buildDirectOtlpBody(samples) {
|
|
240
|
+
return {
|
|
241
|
+
resourceMetrics: [{
|
|
242
|
+
resource: {
|
|
243
|
+
attributes: toOtlpAttributes({
|
|
244
|
+
"service.name": readEnv("OTEL_SERVICE_NAME") ?? "veryfront",
|
|
245
|
+
"service.version": readEnv("VERYFRONT_VERSION") ??
|
|
246
|
+
readEnv("RELEASE_VERSION") ??
|
|
247
|
+
"unknown",
|
|
248
|
+
}),
|
|
249
|
+
},
|
|
250
|
+
scopeMetrics: [{
|
|
251
|
+
scope: {
|
|
252
|
+
name: "veryfront.project.metrics",
|
|
253
|
+
},
|
|
254
|
+
metrics: samples.map(buildDirectMetric),
|
|
255
|
+
}],
|
|
256
|
+
}],
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function logDirectExportFailure(error) {
|
|
260
|
+
if (readEnv("VERYFRONT_DEBUG") !== "1")
|
|
261
|
+
return;
|
|
262
|
+
console.warn("[metrics] direct OTLP export failed", error);
|
|
263
|
+
}
|
|
264
|
+
async function flushDirectMetrics() {
|
|
265
|
+
if (directFlushTimer) {
|
|
266
|
+
clearTimeout(directFlushTimer);
|
|
267
|
+
directFlushTimer = null;
|
|
268
|
+
}
|
|
269
|
+
const target = resolveDirectMetricsTarget();
|
|
270
|
+
if (!target || directQueue.length === 0) {
|
|
271
|
+
directQueue.length = 0;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const batch = directQueue.splice(0, DIRECT_MAX_BATCH_SIZE);
|
|
275
|
+
try {
|
|
276
|
+
const response = await fetch(target.url, {
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: {
|
|
279
|
+
...target.headers,
|
|
280
|
+
"Content-Type": "application/json",
|
|
281
|
+
},
|
|
282
|
+
body: JSON.stringify(buildDirectOtlpBody(batch)),
|
|
283
|
+
});
|
|
284
|
+
if (!response.ok) {
|
|
285
|
+
logDirectExportFailure(`HTTP ${response.status}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (error) {
|
|
289
|
+
logDirectExportFailure(error);
|
|
290
|
+
}
|
|
291
|
+
if (directQueue.length > 0) {
|
|
292
|
+
scheduleDirectFlush();
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function scheduleDirectFlush() {
|
|
296
|
+
if (directFlushTimer || resolveDirectMetricsTarget() === null)
|
|
297
|
+
return;
|
|
298
|
+
directFlushTimer = dntShim.setTimeout(() => {
|
|
299
|
+
void flushDirectMetrics();
|
|
300
|
+
}, DIRECT_FLUSH_DELAY_MS);
|
|
301
|
+
try {
|
|
302
|
+
if (typeof directFlushTimer === "number") {
|
|
303
|
+
dntShim.Deno.unrefTimer(directFlushTimer);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
directFlushTimer.unref?.();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
// Some runtimes do not expose unref support; exporting still works there.
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function enqueueDirectMetric(kind, name, value, attributes) {
|
|
314
|
+
if (resolveDirectMetricsTarget() === null)
|
|
315
|
+
return;
|
|
316
|
+
directQueue.push({
|
|
317
|
+
kind,
|
|
318
|
+
name,
|
|
319
|
+
value,
|
|
320
|
+
attributes,
|
|
321
|
+
timestampUnixNano: getUnixNanoTimestamp(),
|
|
322
|
+
});
|
|
323
|
+
if (directQueue.length >= DIRECT_MAX_BATCH_SIZE) {
|
|
324
|
+
void flushDirectMetrics();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
scheduleDirectFlush();
|
|
328
|
+
}
|
|
91
329
|
export function counter(name, value = 1, attributes, options) {
|
|
92
|
-
|
|
330
|
+
const normalizedAttributes = normalizeAttributes(attributes);
|
|
331
|
+
if (resolveDirectMetricsTarget() === null) {
|
|
332
|
+
getCounter(name, options)?.add(value, normalizedAttributes);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
enqueueDirectMetric("counter", name, value, normalizedAttributes);
|
|
93
336
|
}
|
|
94
337
|
export function histogram(name, value, attributes, options) {
|
|
95
|
-
|
|
338
|
+
const normalizedAttributes = normalizeAttributes(attributes);
|
|
339
|
+
if (resolveDirectMetricsTarget() === null) {
|
|
340
|
+
getHistogram(name, options)?.record(value, normalizedAttributes);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
enqueueDirectMetric("histogram", name, value, normalizedAttributes);
|
|
96
344
|
}
|
|
97
345
|
export function gauge(name, value, attributes, options) {
|
|
98
346
|
const normalizedAttributes = normalizeAttributes(attributes);
|
|
347
|
+
if (resolveDirectMetricsTarget() !== null) {
|
|
348
|
+
enqueueDirectMetric("gauge", name, value, normalizedAttributes);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
99
351
|
const target = getGauge(name, options);
|
|
100
352
|
if (!target)
|
|
101
353
|
return;
|
|
@@ -108,9 +360,18 @@ export const metrics = {
|
|
|
108
360
|
counter,
|
|
109
361
|
histogram,
|
|
110
362
|
gauge,
|
|
363
|
+
async __flushForTests() {
|
|
364
|
+
await flushDirectMetrics();
|
|
365
|
+
},
|
|
111
366
|
__resetForTests() {
|
|
112
367
|
counters.clear();
|
|
113
368
|
histograms.clear();
|
|
114
369
|
gauges.clear();
|
|
370
|
+
directQueue.length = 0;
|
|
371
|
+
directCounterTotals.clear();
|
|
372
|
+
if (directFlushTimer) {
|
|
373
|
+
clearTimeout(directFlushTimer);
|
|
374
|
+
directFlushTimer = null;
|
|
375
|
+
}
|
|
115
376
|
},
|
|
116
377
|
};
|