@hotmeshio/hotmesh
Version:
Permanent-Memory Workflows & AI Agents
307 lines (306 loc) • 11.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TelemetryService = void 0;
const package_json_1 = __importDefault(require("../../package.json"));
const mapper_1 = require("../mapper");
const stream_1 = require("../../types/stream");
const telemetry_1 = require("../../types/telemetry");
const enums_1 = require("../../modules/enums");
class TelemetryService {
constructor(appId, config, metadata, context) {
this.leg = 1;
this.appId = appId;
this.config = config;
this.metadata = metadata;
this.context = context;
}
/**
* too chatty for production; only output traces, jobs, triggers and workers
*/
shouldCreateSpan() {
return (enums_1.HMSH_TELEMETRY === 'debug' ||
this.config?.type === 'trigger' ||
this.config?.type === 'worker');
}
static createNoopSpan(traceId, spanId) {
// A no-op span that returns the given spanContext and ignores all operations.
return {
spanContext() {
return {
traceId,
spanId,
isRemote: true,
traceFlags: 1,
};
},
addEvent(_name, _attributesOrStartTime, _startTime) {
return this;
},
setAttribute(_key, _value) {
return this;
},
setAttributes(_attributes) {
return this;
},
setStatus(_status) {
return this;
},
updateName(_name) {
return this;
},
end(_endTime) {
// no-op
},
isRecording() {
return false;
},
recordException(_exception, _time) {
// no-op
},
};
}
getJobParentSpanId() {
return this.context.metadata.spn;
}
getActivityParentSpanId(leg) {
if (leg === 1) {
return this.context[this.config.parent].output?.metadata?.l2s;
}
else {
return this.context['$self'].output?.metadata?.l1s;
}
}
getTraceId() {
return this.context.metadata.trc;
}
startJobSpan() {
const spanName = `JOB/${this.appId}/${this.config.subscribes}/1`;
const traceId = this.getTraceId();
const spanId = this.getJobParentSpanId();
const attributes = this.getSpanAttrs(1);
const span = this.startSpan(traceId, spanId, spanName, attributes);
this.jobSpan = span;
this.setTelemetryContext(span, 1);
return this;
}
/**
* Traces an activity.
* @private
*/
static async traceActivity(appId, attributes, activityId, traceId, spanId, index = 0) {
const spanName = `TRACE/${appId}/${activityId}/${index}`;
const tracer = telemetry_1.trace.getTracer(package_json_1.default.name, package_json_1.default.version);
const restoredSpanContext = {
traceId,
spanId,
isRemote: true,
traceFlags: 1,
};
const parentContext = telemetry_1.trace.setSpanContext(telemetry_1.context.active(), restoredSpanContext);
return telemetry_1.context.with(parentContext, () => {
const span = tracer.startSpan(spanName, {
kind: telemetry_1.SpanKind.CLIENT,
attributes,
});
span.setAttributes(attributes);
span.end();
return true;
});
}
startActivitySpan(leg = this.leg) {
const spanName = `${this.config.type.toUpperCase()}/${this.appId}/${this.metadata.aid}/${leg}`;
const traceId = this.getTraceId();
const spanId = this.getActivityParentSpanId(leg);
const attributes = this.getSpanAttrs(leg);
const span = this.startSpan(traceId, spanId, spanName, attributes);
this.span = span;
this.setTelemetryContext(span, leg);
return this;
}
startStreamSpan(data, role) {
let type;
if (role === stream_1.StreamRole.SYSTEM) {
type = 'SYSTEM';
}
else if (role === stream_1.StreamRole.WORKER) {
type = 'EXECUTE';
}
else if (data.type === stream_1.StreamDataType.RESULT ||
data.type === stream_1.StreamDataType.RESPONSE) {
type = 'FANIN'; //re-entering engine router (from worker router)
}
else {
type = 'FANOUT'; //exiting engine router (to worker router)
}
// `EXECUTE` refers to the 'worker router' NOT the 'worker activity' run by the 'engine router'
// (Regardless, it's worker-related, so it matters and will be traced)
// `SYSTEM` refers to catastrophic errors, which are always traced
if (this.shouldCreateSpan() || type === 'EXECUTE' || type === 'SYSTEM') {
const topic = data.metadata.topic ? `/${data.metadata.topic}` : '';
const spanName = `${type}/${this.appId}/${data.metadata.aid}${topic}`;
const attributes = this.getStreamSpanAttrs(data);
this.span = this.startSpan(data.metadata.trc, data.metadata.spn, spanName, attributes, true);
}
else {
this.traceId = data.metadata.trc;
this.spanId = data.metadata.spn;
this.span = TelemetryService.createNoopSpan(data.metadata.trc, data.metadata.spn);
}
return this;
}
startSpan(traceId, spanId, spanName, attributes, bCreate = false) {
this.traceId = traceId;
this.spanId = spanId;
if (bCreate || this.shouldCreateSpan()) {
const tracer = telemetry_1.trace.getTracer(package_json_1.default.name, package_json_1.default.version);
const parentContext = this.getParentSpanContext();
const span = tracer.startSpan(spanName, { kind: telemetry_1.SpanKind.CLIENT, attributes, root: !parentContext }, parentContext);
return span;
}
return TelemetryService.createNoopSpan(traceId, spanId);
}
mapActivityAttributes() {
if (this.config.telemetry && this.span) {
const telemetryAtts = new mapper_1.MapperService(this.config.telemetry, this.context).mapRules();
const namespacedAtts = {
...Object.keys(telemetryAtts).reduce((result, key) => {
if (['string', 'boolean', 'number'].includes(typeof telemetryAtts[key])) {
result[`app.activity.data.${key}`] = telemetryAtts[key];
}
return result;
}, {}),
};
this.span.setAttributes(namespacedAtts);
}
}
setActivityAttributes(attributes) {
this.span?.setAttributes(attributes);
}
setStreamAttributes(attributes) {
this.span?.setAttributes(attributes);
}
setJobAttributes(attributes) {
this.jobSpan?.setAttributes(attributes);
}
endJobSpan() {
this.endSpan(this.jobSpan);
}
endActivitySpan() {
this.endSpan(this.span);
}
endStreamSpan() {
this.endSpan(this.span);
}
endSpan(span) {
// For a no-op span, end() does nothing anyway
span && span.end();
}
getParentSpanContext() {
if (this.traceId && this.spanId) {
const restoredSpanContext = {
traceId: this.traceId,
spanId: this.spanId,
isRemote: true,
traceFlags: 1,
};
const parentContext = telemetry_1.trace.setSpanContext(telemetry_1.context.active(), restoredSpanContext);
return parentContext;
}
}
getSpanAttrs(leg) {
return {
...Object.keys(this.context.metadata).reduce((result, key) => {
if (key !== 'trc') {
result[`app.job.${key}`] = this.context.metadata[key];
}
return result;
}, {}),
...Object.keys(this.metadata).reduce((result, key) => {
result[`app.activity.${key}`] = this.metadata[key];
return result;
}, {}),
'app.activity.leg': leg,
};
}
getStreamSpanAttrs(input) {
return {
...Object.keys(input.metadata).reduce((result, key) => {
if (key !== 'trc' && key !== 'spn') {
result[`app.stream.${key}`] = input.metadata[key];
}
return result;
}, {}),
};
}
setTelemetryContext(span, leg) {
// Even if span is no-op, we still set context so that callers remain unaware
if (!this.context.metadata.trc) {
this.context.metadata.trc = span.spanContext().traceId;
}
if (!this.context['$self'].output.metadata) {
this.context['$self'].output.metadata = {};
}
// Echo the parent's or the newly created span's spanId
// This ensures the caller sees a consistent chain
if (leg === 1) {
this.context['$self'].output.metadata.l1s = span.spanContext().spanId;
}
else {
this.context['$self'].output.metadata.l2s = span.spanContext().spanId;
}
}
setActivityError(message) {
this.span?.setStatus({ code: telemetry_1.SpanStatusCode.ERROR, message });
}
setStreamError(message) {
this.span?.setStatus({ code: telemetry_1.SpanStatusCode.ERROR, message });
}
static addTargetTelemetryPaths(consumes, config, metadata, leg) {
if (leg === 1) {
if (!(config.parent in consumes)) {
consumes[config.parent] = [];
}
consumes[config.parent].push(`${config.parent}/output/metadata/l2s`);
}
else {
if (!(metadata.aid in consumes)) {
consumes[metadata.aid] = [];
}
consumes[metadata.aid].push(`${metadata.aid}/output/metadata/l1s`);
}
}
static bindJobTelemetryToState(state, config, context) {
if (config.type === 'trigger') {
state['metadata/trc'] = context.metadata.trc;
}
}
static bindActivityTelemetryToState(state, config, metadata, context, leg) {
if (config.type === 'trigger') {
state[`${metadata.aid}/output/metadata/l1s`] =
context['$self'].output.metadata.l1s;
state[`${metadata.aid}/output/metadata/l2s`] =
context['$self'].output.metadata.l2s;
}
else if (config.type === 'hook' && leg === 1) {
state[`${metadata.aid}/output/metadata/l1s`] =
context['$self'].output.metadata.l1s;
state[`${metadata.aid}/output/metadata/l2s`] =
context['$self'].output.metadata.l1s;
}
else if (config.type === 'signal' && leg === 1) {
state[`${metadata.aid}/output/metadata/l1s`] =
context['$self'].output.metadata.l1s;
state[`${metadata.aid}/output/metadata/l2s`] =
context['$self'].output.metadata.l1s;
}
else {
const target = `l${leg}s`;
state[`${metadata.aid}/output/metadata/${target}`] =
context['$self'].output.metadata[target];
}
}
}
exports.TelemetryService = TelemetryService;