UNPKG

@hotmeshio/hotmesh

Version:

Permanent-Memory Workflows & AI Agents

307 lines (306 loc) 11.7 kB
"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;