@omote/core 0.10.5 → 0.10.6
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 +76 -34
- package/dist/chunk-3FILA2CD.mjs +785 -0
- package/dist/chunk-3FILA2CD.mjs.map +1 -0
- package/dist/chunk-5WIOGMJA.mjs +785 -0
- package/dist/chunk-5WIOGMJA.mjs.map +1 -0
- package/dist/chunk-NWZMIQK4.mjs +782 -0
- package/dist/chunk-NWZMIQK4.mjs.map +1 -0
- package/dist/chunk-WW4XAUJ3.mjs +208 -0
- package/dist/chunk-WW4XAUJ3.mjs.map +1 -0
- package/dist/index.d.mts +84 -79
- package/dist/index.d.ts +84 -79
- package/dist/index.js +514 -406
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +233 -199
- package/dist/index.mjs.map +1 -1
- package/dist/logging/index.js +5 -0
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs +1 -1
- package/dist/otlp-2BML6FIK.mjs +7 -0
- package/dist/otlp-2BML6FIK.mjs.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
// src/logging/types.ts
|
|
2
|
+
var LOG_LEVEL_PRIORITY = {
|
|
3
|
+
error: 0,
|
|
4
|
+
warn: 1,
|
|
5
|
+
info: 2,
|
|
6
|
+
debug: 3,
|
|
7
|
+
trace: 4,
|
|
8
|
+
verbose: 5
|
|
9
|
+
};
|
|
10
|
+
var DEFAULT_LOGGING_CONFIG = {
|
|
11
|
+
level: "info",
|
|
12
|
+
enabled: true,
|
|
13
|
+
format: "pretty",
|
|
14
|
+
timestamps: true,
|
|
15
|
+
includeModule: true
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/logging/formatters.ts
|
|
19
|
+
var COLORS = {
|
|
20
|
+
reset: "\x1B[0m",
|
|
21
|
+
red: "\x1B[31m",
|
|
22
|
+
yellow: "\x1B[33m",
|
|
23
|
+
blue: "\x1B[34m",
|
|
24
|
+
cyan: "\x1B[36m",
|
|
25
|
+
gray: "\x1B[90m",
|
|
26
|
+
white: "\x1B[37m",
|
|
27
|
+
magenta: "\x1B[35m"
|
|
28
|
+
};
|
|
29
|
+
var LEVEL_COLORS = {
|
|
30
|
+
error: COLORS.red,
|
|
31
|
+
warn: COLORS.yellow,
|
|
32
|
+
info: COLORS.blue,
|
|
33
|
+
debug: COLORS.cyan,
|
|
34
|
+
trace: COLORS.magenta,
|
|
35
|
+
verbose: COLORS.gray
|
|
36
|
+
};
|
|
37
|
+
var LEVEL_NAMES = {
|
|
38
|
+
error: "ERROR ",
|
|
39
|
+
warn: "WARN ",
|
|
40
|
+
info: "INFO ",
|
|
41
|
+
debug: "DEBUG ",
|
|
42
|
+
trace: "TRACE ",
|
|
43
|
+
verbose: "VERBOSE"
|
|
44
|
+
};
|
|
45
|
+
var isBrowser = typeof window !== "undefined";
|
|
46
|
+
function formatTimestamp(timestamp) {
|
|
47
|
+
const date = new Date(timestamp);
|
|
48
|
+
return date.toISOString().substring(11, 23);
|
|
49
|
+
}
|
|
50
|
+
function safeStringify(data) {
|
|
51
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
52
|
+
return JSON.stringify(data, (key, value) => {
|
|
53
|
+
if (typeof value === "object" && value !== null) {
|
|
54
|
+
if (seen.has(value)) {
|
|
55
|
+
return "[Circular]";
|
|
56
|
+
}
|
|
57
|
+
seen.add(value);
|
|
58
|
+
}
|
|
59
|
+
if (value instanceof Error) {
|
|
60
|
+
return {
|
|
61
|
+
name: value.name,
|
|
62
|
+
message: value.message,
|
|
63
|
+
stack: value.stack
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (value instanceof Float32Array || value instanceof Int16Array) {
|
|
67
|
+
return `${value.constructor.name}(${value.length})`;
|
|
68
|
+
}
|
|
69
|
+
if (ArrayBuffer.isView(value)) {
|
|
70
|
+
return `${value.constructor.name}(${value.byteLength})`;
|
|
71
|
+
}
|
|
72
|
+
return value;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
var jsonFormatter = (entry) => {
|
|
76
|
+
const output = {
|
|
77
|
+
timestamp: entry.timestamp,
|
|
78
|
+
level: entry.level,
|
|
79
|
+
module: entry.module,
|
|
80
|
+
message: entry.message
|
|
81
|
+
};
|
|
82
|
+
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
83
|
+
output.data = entry.data;
|
|
84
|
+
}
|
|
85
|
+
if (entry.traceId) output.traceId = entry.traceId;
|
|
86
|
+
if (entry.spanId) output.spanId = entry.spanId;
|
|
87
|
+
if (entry.error) {
|
|
88
|
+
output.error = {
|
|
89
|
+
name: entry.error.name,
|
|
90
|
+
message: entry.error.message,
|
|
91
|
+
stack: entry.error.stack
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return safeStringify(output);
|
|
95
|
+
};
|
|
96
|
+
var prettyFormatter = (entry) => {
|
|
97
|
+
const time = formatTimestamp(entry.timestamp);
|
|
98
|
+
const level = LEVEL_NAMES[entry.level];
|
|
99
|
+
const module = entry.module;
|
|
100
|
+
const message = entry.message;
|
|
101
|
+
let output;
|
|
102
|
+
if (isBrowser) {
|
|
103
|
+
output = `${time} ${level} [${module}] ${message}`;
|
|
104
|
+
} else {
|
|
105
|
+
const color = LEVEL_COLORS[entry.level];
|
|
106
|
+
output = `${COLORS.gray}${time}${COLORS.reset} ${color}${level}${COLORS.reset} ${COLORS.cyan}[${module}]${COLORS.reset} ${message}`;
|
|
107
|
+
}
|
|
108
|
+
if (entry.traceId) {
|
|
109
|
+
output += ` [trace:${entry.traceId.slice(0, 8)}]`;
|
|
110
|
+
}
|
|
111
|
+
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
112
|
+
const dataStr = safeStringify(entry.data);
|
|
113
|
+
if (dataStr.length > 80) {
|
|
114
|
+
output += "\n " + JSON.stringify(entry.data, null, 2).replace(/\n/g, "\n ");
|
|
115
|
+
} else {
|
|
116
|
+
output += " " + dataStr;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (entry.error) {
|
|
120
|
+
output += `
|
|
121
|
+
${entry.error.name}: ${entry.error.message}`;
|
|
122
|
+
if (entry.error.stack) {
|
|
123
|
+
const stackLines = entry.error.stack.split("\n").slice(1, 4);
|
|
124
|
+
output += "\n " + stackLines.join("\n ");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return output;
|
|
128
|
+
};
|
|
129
|
+
function getFormatter(format) {
|
|
130
|
+
return format === "json" ? jsonFormatter : prettyFormatter;
|
|
131
|
+
}
|
|
132
|
+
function createBrowserConsoleArgs(entry) {
|
|
133
|
+
const time = formatTimestamp(entry.timestamp);
|
|
134
|
+
const level = entry.level.toUpperCase().padEnd(7);
|
|
135
|
+
const module = entry.module;
|
|
136
|
+
const message = entry.message;
|
|
137
|
+
const styles = {
|
|
138
|
+
time: "color: gray;",
|
|
139
|
+
error: "color: red; font-weight: bold;",
|
|
140
|
+
warn: "color: orange; font-weight: bold;",
|
|
141
|
+
info: "color: blue;",
|
|
142
|
+
debug: "color: cyan;",
|
|
143
|
+
trace: "color: magenta;",
|
|
144
|
+
verbose: "color: gray;",
|
|
145
|
+
module: "color: teal; font-weight: bold;",
|
|
146
|
+
message: "color: inherit;"
|
|
147
|
+
};
|
|
148
|
+
let formatStr = "%c%s %c%s %c[%s]%c %s";
|
|
149
|
+
const args = [
|
|
150
|
+
styles.time,
|
|
151
|
+
time,
|
|
152
|
+
styles[entry.level],
|
|
153
|
+
level,
|
|
154
|
+
styles.module,
|
|
155
|
+
module,
|
|
156
|
+
styles.message,
|
|
157
|
+
message
|
|
158
|
+
];
|
|
159
|
+
if (entry.data && Object.keys(entry.data).length > 0) {
|
|
160
|
+
formatStr += " %o";
|
|
161
|
+
args.push(entry.data);
|
|
162
|
+
}
|
|
163
|
+
return [formatStr, ...args];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/telemetry/exporters/console.ts
|
|
167
|
+
var ConsoleExporter = class {
|
|
168
|
+
constructor(options = {}) {
|
|
169
|
+
this.enabled = options.enabled ?? true;
|
|
170
|
+
this.prefix = options.prefix ?? "[Omote Telemetry]";
|
|
171
|
+
}
|
|
172
|
+
exportSpan(span) {
|
|
173
|
+
if (!this.enabled) return;
|
|
174
|
+
const statusIcon = span.status === "ok" ? "\u2713" : "\u2717";
|
|
175
|
+
const statusColor = span.status === "ok" ? "color: green" : "color: red";
|
|
176
|
+
console.groupCollapsed(
|
|
177
|
+
`%c${this.prefix} %c${statusIcon} ${span.name} %c(${span.durationMs.toFixed(2)}ms)`,
|
|
178
|
+
"color: gray",
|
|
179
|
+
statusColor,
|
|
180
|
+
"color: gray"
|
|
181
|
+
);
|
|
182
|
+
console.log("Trace ID:", span.traceId);
|
|
183
|
+
console.log("Span ID:", span.spanId);
|
|
184
|
+
if (span.parentSpanId) {
|
|
185
|
+
console.log("Parent Span ID:", span.parentSpanId);
|
|
186
|
+
}
|
|
187
|
+
console.log("Duration:", `${span.durationMs.toFixed(2)}ms`);
|
|
188
|
+
console.log("Status:", span.status);
|
|
189
|
+
if (Object.keys(span.attributes).length > 0) {
|
|
190
|
+
console.log("Attributes:", span.attributes);
|
|
191
|
+
}
|
|
192
|
+
if (span.error) {
|
|
193
|
+
console.error("Error:", span.error);
|
|
194
|
+
}
|
|
195
|
+
console.groupEnd();
|
|
196
|
+
}
|
|
197
|
+
exportMetric(metric) {
|
|
198
|
+
if (!this.enabled) return;
|
|
199
|
+
const typeIcon = metric.type === "counter" ? "\u2191" : "\u{1F4CA}";
|
|
200
|
+
console.log(
|
|
201
|
+
`%c${this.prefix} %c${typeIcon} ${metric.name}: %c${metric.value}`,
|
|
202
|
+
"color: gray",
|
|
203
|
+
"color: blue",
|
|
204
|
+
"color: black; font-weight: bold",
|
|
205
|
+
metric.attributes
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
async flush() {
|
|
209
|
+
}
|
|
210
|
+
async shutdown() {
|
|
211
|
+
this.enabled = false;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// src/logging/Clock.ts
|
|
216
|
+
var defaultClock = {
|
|
217
|
+
now: () => performance.now(),
|
|
218
|
+
timestamp: () => Date.now()
|
|
219
|
+
};
|
|
220
|
+
var activeClock = defaultClock;
|
|
221
|
+
function configureClock(clock) {
|
|
222
|
+
activeClock = clock;
|
|
223
|
+
}
|
|
224
|
+
function getClock() {
|
|
225
|
+
return activeClock;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/telemetry/OmoteTelemetry.ts
|
|
229
|
+
var DEFAULT_HISTOGRAM_BUCKETS = [1, 5, 10, 25, 50, 100, 250, 500, 1e3, 2500, 5e3, 1e4, 3e4, 6e4];
|
|
230
|
+
function generateId(length = 16) {
|
|
231
|
+
const bytes = new Uint8Array(length);
|
|
232
|
+
crypto.getRandomValues(bytes);
|
|
233
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
234
|
+
}
|
|
235
|
+
var globalTelemetry = null;
|
|
236
|
+
function configureTelemetry(config) {
|
|
237
|
+
if (globalTelemetry) {
|
|
238
|
+
globalTelemetry.shutdown();
|
|
239
|
+
}
|
|
240
|
+
globalTelemetry = new OmoteTelemetry(config);
|
|
241
|
+
return globalTelemetry;
|
|
242
|
+
}
|
|
243
|
+
function getTelemetry() {
|
|
244
|
+
return globalTelemetry;
|
|
245
|
+
}
|
|
246
|
+
var OmoteTelemetry = class {
|
|
247
|
+
constructor(config) {
|
|
248
|
+
this.exporter = null;
|
|
249
|
+
this.exporterReady = null;
|
|
250
|
+
this.activeTraceId = null;
|
|
251
|
+
this.metricsIntervalId = null;
|
|
252
|
+
// Span stack for log-to-span correlation
|
|
253
|
+
this.spanStack = [];
|
|
254
|
+
// Metric accumulators
|
|
255
|
+
this.counters = /* @__PURE__ */ new Map();
|
|
256
|
+
this.histograms = /* @__PURE__ */ new Map();
|
|
257
|
+
this.config = {
|
|
258
|
+
enabled: config.enabled ?? false,
|
|
259
|
+
serviceName: config.serviceName ?? "omote-sdk",
|
|
260
|
+
serviceVersion: config.serviceVersion ?? "0.1.0",
|
|
261
|
+
exporter: config.exporter ?? "none",
|
|
262
|
+
exporterConfig: config.exporterConfig,
|
|
263
|
+
sampling: config.sampling ?? { ratio: 1, alwaysSampleErrors: true },
|
|
264
|
+
metricsEnabled: config.metricsEnabled ?? true,
|
|
265
|
+
metricsIntervalMs: config.metricsIntervalMs ?? 6e4
|
|
266
|
+
};
|
|
267
|
+
if (this.config.enabled) {
|
|
268
|
+
if (config.customExporter) {
|
|
269
|
+
this.exporter = config.customExporter;
|
|
270
|
+
} else {
|
|
271
|
+
this.exporterReady = this.initExporter();
|
|
272
|
+
}
|
|
273
|
+
this.startMetricsCollection();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Initialize the configured exporter
|
|
278
|
+
*/
|
|
279
|
+
async initExporter() {
|
|
280
|
+
if (this.config.customExporter) {
|
|
281
|
+
this.exporter = this.config.customExporter;
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
switch (this.config.exporter) {
|
|
285
|
+
case "console":
|
|
286
|
+
this.exporter = new ConsoleExporter({ enabled: true });
|
|
287
|
+
break;
|
|
288
|
+
case "otlp":
|
|
289
|
+
if (!this.config.exporterConfig) {
|
|
290
|
+
console.warn("[Telemetry] OTLP exporter requires exporterConfig with endpoint");
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
{
|
|
294
|
+
const { OTLPExporter } = await import("./otlp-2BML6FIK.mjs");
|
|
295
|
+
this.exporter = new OTLPExporter(
|
|
296
|
+
this.config.exporterConfig,
|
|
297
|
+
this.config.serviceName,
|
|
298
|
+
this.config.serviceVersion
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
break;
|
|
302
|
+
case "none":
|
|
303
|
+
default:
|
|
304
|
+
this.exporter = null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Start periodic metrics collection
|
|
309
|
+
*/
|
|
310
|
+
startMetricsCollection() {
|
|
311
|
+
if (!this.config.metricsEnabled || !this.exporter) return;
|
|
312
|
+
this.metricsIntervalId = setInterval(() => {
|
|
313
|
+
this.flushMetrics();
|
|
314
|
+
}, this.config.metricsIntervalMs);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Check if this operation should be sampled
|
|
318
|
+
*/
|
|
319
|
+
shouldSample(isError = false) {
|
|
320
|
+
if (!this.config.enabled) return false;
|
|
321
|
+
const sampling = this.config.sampling;
|
|
322
|
+
if (isError && sampling.alwaysSampleErrors) return true;
|
|
323
|
+
const ratio = sampling.ratio ?? 1;
|
|
324
|
+
return Math.random() < ratio;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Start a new span
|
|
328
|
+
*
|
|
329
|
+
* @example
|
|
330
|
+
* ```typescript
|
|
331
|
+
* const span = telemetry.startSpan('Wav2Vec2.infer', {
|
|
332
|
+
* 'inference.input_samples': samples.length,
|
|
333
|
+
* 'model.backend': 'webgpu',
|
|
334
|
+
* });
|
|
335
|
+
*
|
|
336
|
+
* try {
|
|
337
|
+
* const result = await doInference();
|
|
338
|
+
* span.setAttributes({ 'inference.output_frames': result.frames });
|
|
339
|
+
* span.end();
|
|
340
|
+
* } catch (error) {
|
|
341
|
+
* span.endWithError(error);
|
|
342
|
+
* }
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
startSpan(name, attributes = {}, parentContext) {
|
|
346
|
+
const traceId = parentContext?.traceId ?? this.activeTraceId ?? generateId(16);
|
|
347
|
+
const spanId = generateId(8);
|
|
348
|
+
const parentSpanId = parentContext?.spanId;
|
|
349
|
+
const startTime = getClock().now();
|
|
350
|
+
const epochMs = Date.now();
|
|
351
|
+
if (!parentContext && !this.activeTraceId) {
|
|
352
|
+
this.activeTraceId = traceId;
|
|
353
|
+
}
|
|
354
|
+
let spanAttributes = { ...attributes };
|
|
355
|
+
let ended = false;
|
|
356
|
+
let sampled = this.shouldSample();
|
|
357
|
+
const context = { traceId, spanId, parentSpanId };
|
|
358
|
+
this.spanStack.push({ traceId, spanId });
|
|
359
|
+
const endSpan = (status, error) => {
|
|
360
|
+
if (ended) return;
|
|
361
|
+
ended = true;
|
|
362
|
+
const idx = this.spanStack.findIndex((s) => s.spanId === spanId);
|
|
363
|
+
if (idx !== -1) this.spanStack.splice(idx, 1);
|
|
364
|
+
const endTime = getClock().now();
|
|
365
|
+
const durationMs = endTime - startTime;
|
|
366
|
+
const endEpochMs = epochMs + (endTime - startTime);
|
|
367
|
+
if (status === "error" && !sampled) {
|
|
368
|
+
sampled = this.shouldSample(true);
|
|
369
|
+
}
|
|
370
|
+
if (!sampled || !this.exporter) return;
|
|
371
|
+
const spanData = {
|
|
372
|
+
name,
|
|
373
|
+
traceId,
|
|
374
|
+
spanId,
|
|
375
|
+
parentSpanId,
|
|
376
|
+
startTime,
|
|
377
|
+
endTime,
|
|
378
|
+
durationMs,
|
|
379
|
+
epochMs,
|
|
380
|
+
endEpochMs,
|
|
381
|
+
status,
|
|
382
|
+
attributes: spanAttributes,
|
|
383
|
+
error
|
|
384
|
+
};
|
|
385
|
+
this.exporter.exportSpan(spanData);
|
|
386
|
+
if (!parentSpanId && this.activeTraceId === traceId) {
|
|
387
|
+
this.activeTraceId = null;
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
return {
|
|
391
|
+
end: () => endSpan("ok"),
|
|
392
|
+
endWithError: (error) => endSpan("error", error),
|
|
393
|
+
setAttributes: (attrs) => {
|
|
394
|
+
spanAttributes = { ...spanAttributes, ...attrs };
|
|
395
|
+
},
|
|
396
|
+
getContext: () => context
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Wrap an async function with a span
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* ```typescript
|
|
404
|
+
* const result = await telemetry.withSpan('Model.load', async (span) => {
|
|
405
|
+
* const model = await loadModel();
|
|
406
|
+
* span.setAttributes({ 'model.size_bytes': model.size });
|
|
407
|
+
* return model;
|
|
408
|
+
* });
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
async withSpan(name, fn, attributes = {}, parentContext) {
|
|
412
|
+
const span = this.startSpan(name, attributes, parentContext);
|
|
413
|
+
try {
|
|
414
|
+
const result = await fn(span);
|
|
415
|
+
span.end();
|
|
416
|
+
return result;
|
|
417
|
+
} catch (error) {
|
|
418
|
+
span.endWithError(error);
|
|
419
|
+
throw error;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Increment a counter metric
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* ```typescript
|
|
427
|
+
* telemetry.incrementCounter('omote.inference.total', 1, {
|
|
428
|
+
* model: 'wav2vec2',
|
|
429
|
+
* backend: 'webgpu',
|
|
430
|
+
* status: 'success',
|
|
431
|
+
* });
|
|
432
|
+
* ```
|
|
433
|
+
*/
|
|
434
|
+
incrementCounter(name, value = 1, attributes = {}) {
|
|
435
|
+
if (!this.config.enabled || !this.config.metricsEnabled) return;
|
|
436
|
+
const key = this.getMetricKey(name, attributes);
|
|
437
|
+
const existing = this.counters.get(key);
|
|
438
|
+
if (existing) {
|
|
439
|
+
existing.value += value;
|
|
440
|
+
} else {
|
|
441
|
+
this.counters.set(key, { value, attributes });
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Record a histogram value
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```typescript
|
|
449
|
+
* telemetry.recordHistogram('omote.inference.latency', durationMs, {
|
|
450
|
+
* model: 'wav2vec2',
|
|
451
|
+
* backend: 'webgpu',
|
|
452
|
+
* });
|
|
453
|
+
* ```
|
|
454
|
+
*/
|
|
455
|
+
recordHistogram(name, value, attributes = {}, bucketBoundaries = DEFAULT_HISTOGRAM_BUCKETS) {
|
|
456
|
+
if (!this.config.enabled || !this.config.metricsEnabled) return;
|
|
457
|
+
const key = this.getMetricKey(name, attributes);
|
|
458
|
+
const existing = this.histograms.get(key);
|
|
459
|
+
if (existing) {
|
|
460
|
+
existing.count++;
|
|
461
|
+
existing.sum += value;
|
|
462
|
+
if (value < existing.min) existing.min = value;
|
|
463
|
+
if (value > existing.max) existing.max = value;
|
|
464
|
+
let placed = false;
|
|
465
|
+
for (let i = 0; i < existing.bucketBoundaries.length; i++) {
|
|
466
|
+
if (value <= existing.bucketBoundaries[i]) {
|
|
467
|
+
existing.bucketCounts[i]++;
|
|
468
|
+
placed = true;
|
|
469
|
+
break;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (!placed) existing.bucketCounts[existing.bucketCounts.length - 1]++;
|
|
473
|
+
} else {
|
|
474
|
+
const bucketCounts = new Array(bucketBoundaries.length + 1).fill(0);
|
|
475
|
+
let placed = false;
|
|
476
|
+
for (let i = 0; i < bucketBoundaries.length; i++) {
|
|
477
|
+
if (value <= bucketBoundaries[i]) {
|
|
478
|
+
bucketCounts[i]++;
|
|
479
|
+
placed = true;
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (!placed) bucketCounts[bucketCounts.length - 1]++;
|
|
484
|
+
this.histograms.set(key, { count: 1, sum: value, min: value, max: value, bucketBoundaries, bucketCounts, attributes });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Generate unique key for metric with attributes
|
|
489
|
+
*/
|
|
490
|
+
getMetricKey(name, attributes) {
|
|
491
|
+
const keys = Object.keys(attributes);
|
|
492
|
+
if (keys.length === 0) return name;
|
|
493
|
+
const sortedAttrs = Object.entries(attributes).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}=${v}`).join(",");
|
|
494
|
+
return `${name}|${sortedAttrs}`;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Flush accumulated metrics to exporter
|
|
498
|
+
*/
|
|
499
|
+
flushMetrics() {
|
|
500
|
+
if (!this.exporter) return;
|
|
501
|
+
const timestamp = Date.now();
|
|
502
|
+
for (const [key, data] of this.counters) {
|
|
503
|
+
if (data.value === 0) continue;
|
|
504
|
+
const name = key.split("|")[0];
|
|
505
|
+
const metric = {
|
|
506
|
+
name,
|
|
507
|
+
type: "counter",
|
|
508
|
+
value: data.value,
|
|
509
|
+
attributes: data.attributes,
|
|
510
|
+
timestamp
|
|
511
|
+
};
|
|
512
|
+
this.exporter.exportMetric(metric);
|
|
513
|
+
data.value = 0;
|
|
514
|
+
}
|
|
515
|
+
for (const [key, data] of this.histograms) {
|
|
516
|
+
const name = key.split("|")[0];
|
|
517
|
+
if (data.count === 0) continue;
|
|
518
|
+
const avg = data.sum / data.count;
|
|
519
|
+
const metric = {
|
|
520
|
+
name,
|
|
521
|
+
type: "histogram",
|
|
522
|
+
value: avg,
|
|
523
|
+
attributes: {
|
|
524
|
+
...data.attributes,
|
|
525
|
+
count: data.count,
|
|
526
|
+
sum: data.sum,
|
|
527
|
+
min: data.min,
|
|
528
|
+
max: data.max
|
|
529
|
+
},
|
|
530
|
+
timestamp,
|
|
531
|
+
histogramData: {
|
|
532
|
+
count: data.count,
|
|
533
|
+
sum: data.sum,
|
|
534
|
+
min: data.min,
|
|
535
|
+
max: data.max,
|
|
536
|
+
bucketBoundaries: [...data.bucketBoundaries],
|
|
537
|
+
bucketCounts: [...data.bucketCounts]
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
this.exporter.exportMetric(metric);
|
|
541
|
+
data.count = 0;
|
|
542
|
+
data.sum = 0;
|
|
543
|
+
data.min = Infinity;
|
|
544
|
+
data.max = -Infinity;
|
|
545
|
+
data.bucketCounts.fill(0);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Force flush all pending data
|
|
550
|
+
*/
|
|
551
|
+
async flush() {
|
|
552
|
+
if (this.exporterReady) await this.exporterReady;
|
|
553
|
+
this.flushMetrics();
|
|
554
|
+
await this.exporter?.flush();
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Shutdown telemetry
|
|
558
|
+
*/
|
|
559
|
+
async shutdown() {
|
|
560
|
+
if (this.metricsIntervalId) {
|
|
561
|
+
clearInterval(this.metricsIntervalId);
|
|
562
|
+
this.metricsIntervalId = null;
|
|
563
|
+
}
|
|
564
|
+
await this.flush();
|
|
565
|
+
await this.exporter?.shutdown();
|
|
566
|
+
this.exporter = null;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Check if telemetry is enabled
|
|
570
|
+
*/
|
|
571
|
+
isEnabled() {
|
|
572
|
+
return this.config.enabled;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Get current configuration
|
|
576
|
+
*/
|
|
577
|
+
getConfig() {
|
|
578
|
+
return { ...this.config };
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Get the active span context for log-to-span correlation.
|
|
582
|
+
* Returns the most recent (top of stack) active span, or null if none.
|
|
583
|
+
*/
|
|
584
|
+
getActiveContext() {
|
|
585
|
+
return this.spanStack.length > 0 ? this.spanStack[this.spanStack.length - 1] : null;
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
|
|
589
|
+
// src/logging/Logger.ts
|
|
590
|
+
var isBrowser2 = typeof window !== "undefined";
|
|
591
|
+
var globalConfig = { ...DEFAULT_LOGGING_CONFIG };
|
|
592
|
+
function configureLogging(config) {
|
|
593
|
+
globalConfig = { ...globalConfig, ...config };
|
|
594
|
+
}
|
|
595
|
+
function getLoggingConfig() {
|
|
596
|
+
return { ...globalConfig };
|
|
597
|
+
}
|
|
598
|
+
function resetLoggingConfig() {
|
|
599
|
+
globalConfig = { ...DEFAULT_LOGGING_CONFIG };
|
|
600
|
+
}
|
|
601
|
+
function setLogLevel(level) {
|
|
602
|
+
globalConfig.level = level;
|
|
603
|
+
}
|
|
604
|
+
function setLoggingEnabled(enabled) {
|
|
605
|
+
globalConfig.enabled = enabled;
|
|
606
|
+
}
|
|
607
|
+
var consoleSink = (entry) => {
|
|
608
|
+
const consoleMethod = entry.level === "error" ? "error" : entry.level === "warn" ? "warn" : "log";
|
|
609
|
+
if (globalConfig.format === "pretty" && isBrowser2) {
|
|
610
|
+
const args = createBrowserConsoleArgs(entry);
|
|
611
|
+
console[consoleMethod](...args);
|
|
612
|
+
} else {
|
|
613
|
+
const formatter = getFormatter(globalConfig.format);
|
|
614
|
+
const formatted = formatter(entry);
|
|
615
|
+
console[consoleMethod](formatted);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
function getActiveSink() {
|
|
619
|
+
return globalConfig.sink || consoleSink;
|
|
620
|
+
}
|
|
621
|
+
function shouldLog(level) {
|
|
622
|
+
if (!globalConfig.enabled) return false;
|
|
623
|
+
return LOG_LEVEL_PRIORITY[level] <= LOG_LEVEL_PRIORITY[globalConfig.level];
|
|
624
|
+
}
|
|
625
|
+
var Logger = class _Logger {
|
|
626
|
+
constructor(module) {
|
|
627
|
+
this.module = module;
|
|
628
|
+
}
|
|
629
|
+
log(level, message, data) {
|
|
630
|
+
if (!shouldLog(level)) return;
|
|
631
|
+
const entry = {
|
|
632
|
+
timestamp: getClock().timestamp(),
|
|
633
|
+
level,
|
|
634
|
+
module: this.module,
|
|
635
|
+
message,
|
|
636
|
+
data
|
|
637
|
+
};
|
|
638
|
+
const ctx = getTelemetry()?.getActiveContext();
|
|
639
|
+
if (ctx) {
|
|
640
|
+
entry.traceId = ctx.traceId;
|
|
641
|
+
entry.spanId = ctx.spanId;
|
|
642
|
+
}
|
|
643
|
+
if (data?.error instanceof Error) {
|
|
644
|
+
entry.error = data.error;
|
|
645
|
+
const { error, ...rest } = data;
|
|
646
|
+
entry.data = Object.keys(rest).length > 0 ? rest : void 0;
|
|
647
|
+
}
|
|
648
|
+
getActiveSink()(entry);
|
|
649
|
+
}
|
|
650
|
+
error(message, data) {
|
|
651
|
+
this.log("error", message, data);
|
|
652
|
+
}
|
|
653
|
+
warn(message, data) {
|
|
654
|
+
this.log("warn", message, data);
|
|
655
|
+
}
|
|
656
|
+
info(message, data) {
|
|
657
|
+
this.log("info", message, data);
|
|
658
|
+
}
|
|
659
|
+
debug(message, data) {
|
|
660
|
+
this.log("debug", message, data);
|
|
661
|
+
}
|
|
662
|
+
trace(message, data) {
|
|
663
|
+
this.log("trace", message, data);
|
|
664
|
+
}
|
|
665
|
+
verbose(message, data) {
|
|
666
|
+
this.log("verbose", message, data);
|
|
667
|
+
}
|
|
668
|
+
child(subModule) {
|
|
669
|
+
return new _Logger(`${this.module}.${subModule}`);
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
var loggerCache = /* @__PURE__ */ new Map();
|
|
673
|
+
function createLogger(module) {
|
|
674
|
+
let logger = loggerCache.get(module);
|
|
675
|
+
if (!logger) {
|
|
676
|
+
logger = new Logger(module);
|
|
677
|
+
loggerCache.set(module, logger);
|
|
678
|
+
}
|
|
679
|
+
return logger;
|
|
680
|
+
}
|
|
681
|
+
function clearLoggerCache() {
|
|
682
|
+
loggerCache.clear();
|
|
683
|
+
}
|
|
684
|
+
var noopLogger = {
|
|
685
|
+
module: "noop",
|
|
686
|
+
error: () => {
|
|
687
|
+
},
|
|
688
|
+
warn: () => {
|
|
689
|
+
},
|
|
690
|
+
info: () => {
|
|
691
|
+
},
|
|
692
|
+
debug: () => {
|
|
693
|
+
},
|
|
694
|
+
trace: () => {
|
|
695
|
+
},
|
|
696
|
+
verbose: () => {
|
|
697
|
+
},
|
|
698
|
+
child: () => noopLogger
|
|
699
|
+
};
|
|
700
|
+
function getNoopLogger() {
|
|
701
|
+
return noopLogger;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// src/logging/ErrorCodes.ts
|
|
705
|
+
var ErrorCodes = {
|
|
706
|
+
// ── Inference ──────────────────────────────────────────────────────────
|
|
707
|
+
/** Model failed to load (file not found, corrupted, unsupported format) */
|
|
708
|
+
INF_LOAD_FAILED: "OMOTE_INF_001",
|
|
709
|
+
/** ORT session poisoned after WebGPU device loss — must reload tab */
|
|
710
|
+
INF_SESSION_POISON: "OMOTE_INF_002",
|
|
711
|
+
/** Inference exceeded timeout threshold */
|
|
712
|
+
INF_TIMEOUT: "OMOTE_INF_003",
|
|
713
|
+
/** Out-of-memory during inference or model loading */
|
|
714
|
+
INF_OOM: "OMOTE_INF_004",
|
|
715
|
+
/** WebGPU unavailable, fell back to WASM */
|
|
716
|
+
INF_WEBGPU_FALLBACK: "OMOTE_INF_005",
|
|
717
|
+
/** Input tensor shape does not match model expectations */
|
|
718
|
+
INF_SHAPE_MISMATCH: "OMOTE_INF_006",
|
|
719
|
+
// ── Audio ──────────────────────────────────────────────────────────────
|
|
720
|
+
/** AudioContext creation or resume failed */
|
|
721
|
+
AUD_CONTEXT_FAILED: "OMOTE_AUD_001",
|
|
722
|
+
/** Gap detected in audio scheduling (buffer underrun) */
|
|
723
|
+
AUD_SCHEDULE_GAP: "OMOTE_AUD_002",
|
|
724
|
+
/** Audio buffer decoding failed */
|
|
725
|
+
AUD_DECODE_FAILED: "OMOTE_AUD_003",
|
|
726
|
+
// ── Speech ─────────────────────────────────────────────────────────────
|
|
727
|
+
/** Voice activity detection error */
|
|
728
|
+
SPH_VAD_ERROR: "OMOTE_SPH_001",
|
|
729
|
+
/** Automatic speech recognition error */
|
|
730
|
+
SPH_ASR_ERROR: "OMOTE_SPH_002",
|
|
731
|
+
/** Microphone access denied or unavailable */
|
|
732
|
+
SPH_MIC_DENIED: "OMOTE_SPH_003",
|
|
733
|
+
// ── TTS ────────────────────────────────────────────────────────────────
|
|
734
|
+
/** TTS synthesis failed */
|
|
735
|
+
TTS_SYNTH_FAILED: "OMOTE_TTS_001",
|
|
736
|
+
/** TTS streaming error (chunk delivery failure) */
|
|
737
|
+
TTS_STREAM_ERROR: "OMOTE_TTS_002",
|
|
738
|
+
/** Phonemizer (eSpeak-NG WASM) ran out of memory */
|
|
739
|
+
TTS_PHONEMIZER_OOM: "OMOTE_TTS_003",
|
|
740
|
+
// ── Pipeline ───────────────────────────────────────────────────────────
|
|
741
|
+
/** Invalid state transition in pipeline state machine */
|
|
742
|
+
PIP_STATE_ERROR: "OMOTE_PIP_001",
|
|
743
|
+
/** Pipeline operation aborted (user interrupt or signal) */
|
|
744
|
+
PIP_ABORT: "OMOTE_PIP_002",
|
|
745
|
+
// ── Cache ──────────────────────────────────────────────────────────────
|
|
746
|
+
/** IndexedDB storage quota exceeded */
|
|
747
|
+
CAC_QUOTA_EXCEEDED: "OMOTE_CAC_001",
|
|
748
|
+
/** Cache entry evicted (LRU or manual) */
|
|
749
|
+
CAC_EVICTION: "OMOTE_CAC_002",
|
|
750
|
+
/** Cached model is stale (version mismatch) */
|
|
751
|
+
CAC_STALE: "OMOTE_CAC_003",
|
|
752
|
+
// ── Network ────────────────────────────────────────────────────────────
|
|
753
|
+
/** HTTP fetch failed (model download, CDN) */
|
|
754
|
+
NET_FETCH_FAILED: "OMOTE_NET_001",
|
|
755
|
+
/** Network request timed out */
|
|
756
|
+
NET_TIMEOUT: "OMOTE_NET_002",
|
|
757
|
+
/** WebSocket connection error */
|
|
758
|
+
NET_WEBSOCKET_ERROR: "OMOTE_NET_003"
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
export {
|
|
762
|
+
LOG_LEVEL_PRIORITY,
|
|
763
|
+
DEFAULT_LOGGING_CONFIG,
|
|
764
|
+
jsonFormatter,
|
|
765
|
+
prettyFormatter,
|
|
766
|
+
getFormatter,
|
|
767
|
+
ConsoleExporter,
|
|
768
|
+
defaultClock,
|
|
769
|
+
configureClock,
|
|
770
|
+
getClock,
|
|
771
|
+
configureTelemetry,
|
|
772
|
+
getTelemetry,
|
|
773
|
+
OmoteTelemetry,
|
|
774
|
+
configureLogging,
|
|
775
|
+
getLoggingConfig,
|
|
776
|
+
resetLoggingConfig,
|
|
777
|
+
setLogLevel,
|
|
778
|
+
setLoggingEnabled,
|
|
779
|
+
createLogger,
|
|
780
|
+
clearLoggerCache,
|
|
781
|
+
noopLogger,
|
|
782
|
+
getNoopLogger,
|
|
783
|
+
ErrorCodes
|
|
784
|
+
};
|
|
785
|
+
//# sourceMappingURL=chunk-5WIOGMJA.mjs.map
|