@gati-framework/runtime
Version:
Gati runtime execution engine for running handler-based applications
133 lines • 4.91 kB
JavaScript
/**
* @module runtime/metrics-client
* @description Metrics and observability client for Gati runtime
*/
import * as api from '@opentelemetry/api';
import { logger } from './logger.js';
/**
* Runtime metrics client implementation
*/
export class RuntimeMetricsClient {
serviceName;
serviceVersion;
tracer;
metrics;
counters = new Map();
gauges = new Map();
histograms = new Map();
constructor(serviceName = 'gati-runtime', serviceVersion = '1.0.0') {
this.serviceName = serviceName;
this.serviceVersion = serviceVersion;
this.tracer = api.trace.getTracer(serviceName, serviceVersion);
// Initialize simple metrics implementation
this.metrics = {
createCounter: (name, help, labelNames = []) => ({
inc: (labels = {}, value = 1) => {
logger.debug({ name, labels, value }, 'Counter incremented');
}
}),
createGauge: (name, help, labelNames = []) => ({
set: (labels = {}, value) => {
logger.debug({ name, labels, value }, 'Gauge set');
}
}),
createHistogram: (name, help, labelNames = [], buckets) => ({
observe: (labels = {}, value) => {
logger.debug({ name, labels, value }, 'Histogram observed');
}
})
};
}
incrementCounter(name, labels = {}, value = 1) {
const key = `${name}_${JSON.stringify(labels)}`;
if (!this.counters.has(key) && this.metrics) {
const labelNames = Object.keys(labels);
this.counters.set(key, this.metrics.createCounter(name, `Counter for ${name}`, labelNames));
}
const counter = this.counters.get(key);
if (counter) {
counter.inc(labels, value);
}
logger.debug({ name, labels, value }, 'Counter incremented');
}
setGauge(name, value, labels = {}) {
const key = `${name}_${JSON.stringify(labels)}`;
if (!this.gauges.has(key) && this.metrics) {
const labelNames = Object.keys(labels);
this.gauges.set(key, this.metrics.createGauge(name, `Gauge for ${name}`, labelNames));
}
const gauge = this.gauges.get(key);
if (gauge) {
gauge.set(labels, value);
}
logger.debug({ name, labels, value }, 'Gauge set');
}
recordHistogram(name, value, labels = {}) {
const key = `${name}_${JSON.stringify(labels)}`;
if (!this.histograms.has(key) && this.metrics) {
const labelNames = Object.keys(labels);
this.histograms.set(key, this.metrics.createHistogram(name, `Histogram for ${name}`, labelNames));
}
const histogram = this.histograms.get(key);
if (histogram) {
histogram.observe(labels, value);
}
logger.debug({ name, labels, value }, 'Histogram recorded');
}
createSpan(name, attributes) {
return this.tracer.startSpan(name, { attributes });
}
async withSpan(name, fn, attributes) {
const span = this.createSpan(name, attributes);
try {
const result = await api.context.with(api.trace.setSpan(api.context.active(), span), async () => await fn(span));
span.setStatus({ code: api.SpanStatusCode.OK });
return result;
}
catch (error) {
span.setStatus({
code: api.SpanStatusCode.ERROR,
message: error instanceof Error ? error.message : 'Unknown error',
});
span.recordException(error);
throw error;
}
finally {
span.end();
}
}
logWithContext(level, message, context) {
const span = api.trace.getActiveSpan();
const traceContext = span ? {
traceId: span.spanContext().traceId,
spanId: span.spanContext().spanId,
} : {};
logger[level]({ ...context, ...traceContext }, message);
}
recordAudit(event, context) {
const auditLog = {
timestamp: new Date().toISOString(),
event,
requestId: context.requestId,
handlerId: context.handlerId,
version: context.version,
userId: context.userId,
action: context.action,
resource: context.resource,
result: context.result,
metadata: context.metadata,
};
logger.info(auditLog, 'Audit event');
// Increment audit counter
this.incrementCounter('audit_events_total', {
event,
action: context.action,
result: context.result,
});
}
}
/**
* Default metrics client instance
*/
export const metricsClient = new RuntimeMetricsClient();
//# sourceMappingURL=metrics-client.js.map