veryfront 0.1.910 → 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
|
}
|
|
@@ -33,10 +40,14 @@ function normalizeAttributes(attributes) {
|
|
|
33
40
|
normalized.project_id = context.projectId;
|
|
34
41
|
if (context?.projectSlug)
|
|
35
42
|
normalized.project_slug = context.projectSlug;
|
|
36
|
-
if (context
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
if (context) {
|
|
44
|
+
const environmentName = context.environmentName ??
|
|
45
|
+
(!context.productionMode ? "preview" : undefined);
|
|
46
|
+
if (environmentName)
|
|
47
|
+
normalized.environment = environmentName;
|
|
48
|
+
if (!context.productionMode)
|
|
49
|
+
normalized.branch = context.branch ?? "main";
|
|
50
|
+
}
|
|
40
51
|
return normalized;
|
|
41
52
|
}
|
|
42
53
|
function attributesKey(attributes) {
|
|
@@ -84,14 +95,227 @@ function getGauge(name, options) {
|
|
|
84
95
|
gauges.set(name, gauge);
|
|
85
96
|
return gauge;
|
|
86
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
|
+
}
|
|
87
297
|
export function counter(name, value = 1, attributes, options) {
|
|
88
|
-
|
|
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);
|
|
89
304
|
}
|
|
90
305
|
export function histogram(name, value, attributes, options) {
|
|
91
|
-
|
|
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);
|
|
92
312
|
}
|
|
93
313
|
export function gauge(name, value, attributes, options) {
|
|
94
314
|
const normalizedAttributes = normalizeAttributes(attributes);
|
|
315
|
+
if (resolveOtlpMetricsUrl() !== null) {
|
|
316
|
+
enqueueDirectMetric("gauge", name, value, normalizedAttributes);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
95
319
|
const target = getGauge(name, options);
|
|
96
320
|
if (!target)
|
|
97
321
|
return;
|
|
@@ -104,9 +328,18 @@ export const metrics = {
|
|
|
104
328
|
counter,
|
|
105
329
|
histogram,
|
|
106
330
|
gauge,
|
|
331
|
+
async __flushForTests() {
|
|
332
|
+
await flushDirectMetrics();
|
|
333
|
+
},
|
|
107
334
|
__resetForTests() {
|
|
108
335
|
counters.clear();
|
|
109
336
|
histograms.clear();
|
|
110
337
|
gauges.clear();
|
|
338
|
+
directQueue.length = 0;
|
|
339
|
+
directCounterTotals.clear();
|
|
340
|
+
if (directFlushTimer) {
|
|
341
|
+
clearTimeout(directFlushTimer);
|
|
342
|
+
directFlushTimer = null;
|
|
343
|
+
}
|
|
111
344
|
},
|
|
112
345
|
};
|