UNPKG

@genkit-ai/google-cloud

Version:

Genkit AI framework plugin for Google Cloud Platform including Firestore trace/state store and deployment helpers for Cloud Functions for Firebase.

374 lines (373 loc) 13.7 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var gcpOpenTelemetry_exports = {}; __export(gcpOpenTelemetry_exports, { GcpOpenTelemetry: () => GcpOpenTelemetry, __forceFlushSpansForTesting: () => __forceFlushSpansForTesting, __getMetricExporterForTesting: () => __getMetricExporterForTesting, __getSpanExporterForTesting: () => __getSpanExporterForTesting }); module.exports = __toCommonJS(gcpOpenTelemetry_exports); var import_opentelemetry_cloud_monitoring_exporter = require("@google-cloud/opentelemetry-cloud-monitoring-exporter"); var import_opentelemetry_cloud_trace_exporter = require("@google-cloud/opentelemetry-cloud-trace-exporter"); var import_opentelemetry_resource_util = require("@google-cloud/opentelemetry-resource-util"); var import_api = require("@opentelemetry/api"); var import_auto_instrumentations_node = require("@opentelemetry/auto-instrumentations-node"); var import_instrumentation_pino = require("@opentelemetry/instrumentation-pino"); var import_instrumentation_winston = require("@opentelemetry/instrumentation-winston"); var import_resources = require("@opentelemetry/resources"); var import_sdk_metrics = require("@opentelemetry/sdk-metrics"); var import_sdk_trace_base = require("@opentelemetry/sdk-trace-base"); var import_genkit = require("genkit"); var import_logging = require("genkit/logging"); var import_action = require("./telemetry/action.js"); var import_engagement = require("./telemetry/engagement.js"); var import_feature = require("./telemetry/feature.js"); var import_generate = require("./telemetry/generate.js"); var import_path = require("./telemetry/path.js"); var import_utils = require("./utils.js"); let metricExporter; let spanProcessor; let spanExporter; class GcpOpenTelemetry { config; resource; constructor(config) { this.config = config; this.resource = new import_resources.Resource({ type: "global" }).merge( new import_opentelemetry_resource_util.GcpDetectorSync().detect() ); } /** * Log hook for writing trace and span metadata to log messages in the format * required by GCP. */ gcpTraceLogHook = (span, record) => { const spanContext = span.spanContext(); const isSampled = !!(spanContext.traceFlags & import_api.TraceFlags.SAMPLED); const projectId = this.config.projectId; record["logging.googleapis.com/trace"] ??= `projects/${projectId}/traces/${spanContext.traceId}`; record["logging.googleapis.com/trace_sampled"] ??= isSampled ? "1" : "0"; record["logging.googleapis.com/spanId"] ??= spanContext.spanId; delete record["span_id"]; delete record["trace_id"]; delete record["trace_flags"]; }; async getConfig() { spanProcessor = new import_sdk_trace_base.BatchSpanProcessor(await this.createSpanExporter()); return { resource: this.resource, spanProcessor, sampler: this.config.sampler, instrumentations: this.getInstrumentations(), metricReader: await this.createMetricReader() }; } async createSpanExporter() { spanExporter = new AdjustingTraceExporter( this.shouldExportTraces() ? new import_opentelemetry_cloud_trace_exporter.TraceExporter({ // provided projectId should take precedence over env vars, etc projectId: this.config.projectId, // creds for non-GCP environments, in lieu of using ADC. credentials: this.config.credentials }) : new import_sdk_trace_base.InMemorySpanExporter(), this.config.exportInputAndOutput, this.config.projectId, getErrorHandler( (err) => { return (0, import_utils.tracingDenied)(err); }, await (0, import_utils.tracingDeniedHelpText)() ) ); return spanExporter; } /** * Creates a {MetricReader} for pushing metrics out to GCP via OpenTelemetry. */ async createMetricReader() { metricExporter = await this.buildMetricExporter(); return new import_sdk_metrics.PeriodicExportingMetricReader({ exportIntervalMillis: this.config.metricExportIntervalMillis, exportTimeoutMillis: this.config.metricExportTimeoutMillis, exporter: metricExporter }); } /** Gets all open telemetry instrumentations as configured by the plugin. */ getInstrumentations() { let instrumentations = []; if (this.config.autoInstrumentation) { instrumentations = (0, import_auto_instrumentations_node.getNodeAutoInstrumentations)( this.config.autoInstrumentationConfig ); } return instrumentations.concat(this.getDefaultLoggingInstrumentations()).concat(this.config.instrumentations ?? []); } shouldExportTraces() { return this.config.export && !this.config.disableTraces; } shouldExportMetrics() { return this.config.export && !this.config.disableMetrics; } /** Always configure the Pino and Winston instrumentations */ getDefaultLoggingInstrumentations() { return [ new import_instrumentation_winston.WinstonInstrumentation({ logHook: this.gcpTraceLogHook }), new import_instrumentation_pino.PinoInstrumentation({ logHook: this.gcpTraceLogHook }) ]; } async buildMetricExporter() { const exporter = this.shouldExportMetrics() ? new MetricExporterWrapper( { userAgent: { product: "genkit", version: import_genkit.GENKIT_VERSION }, // provided projectId should take precedence over env vars, etc projectId: this.config.projectId, // creds for non-GCP environments, in lieu of using ADC. credentials: this.config.credentials }, getErrorHandler( (err) => { return (0, import_utils.metricsDenied)(err); }, await (0, import_utils.metricsDeniedHelpText)() ) ) : new import_sdk_metrics.InMemoryMetricExporter(import_sdk_metrics.AggregationTemporality.DELTA); return exporter; } } class MetricExporterWrapper extends import_opentelemetry_cloud_monitoring_exporter.MetricExporter { constructor(options, errorHandler) { super(options); this.errorHandler = errorHandler; } promise = new Promise((resolve) => resolve()); async export(metrics, resultCallback) { await this.promise; this.modifyStartTimes(metrics); this.promise = new Promise((resolve) => { super.export(metrics, (result) => { try { if (this.errorHandler && result.error) { this.errorHandler(result.error); } resultCallback(result); } finally { resolve(); } }); }); } selectAggregation(instrumentType) { if (instrumentType === import_sdk_metrics.InstrumentType.HISTOGRAM) { return new import_sdk_metrics.ExponentialHistogramAggregation(); } return new import_sdk_metrics.DefaultAggregation(); } selectAggregationTemporality(instrumentType) { return import_sdk_metrics.AggregationTemporality.DELTA; } /** * Modify the start times of each data point to ensure no * overlap with previous exports. * * Cloud metrics do not support delta metrics for custom metrics * and will convert any DELTA aggregations to CUMULATIVE ones on * export. There is implicit overlap in the start/end times that * the Metric reader is sending -- the end_time of the previous * export will become the start_time of the current export. The * overlap in times means that only one of those records will * persist and the other will be overwritten. This * method adds a thousandth of a second to ensure discrete export * timeframes. */ modifyStartTimes(metrics) { metrics.scopeMetrics.forEach((scopeMetric) => { scopeMetric.metrics.forEach((metric) => { metric.dataPoints.forEach((dataPoint) => { dataPoint.startTime[1] = dataPoint.startTime[1] + 1e6; }); }); }); } async shutdown() { return await this.forceFlush(); } async forceFlush() { await this.promise; } } class AdjustingTraceExporter { constructor(exporter, logInputAndOutput, projectId, errorHandler) { this.exporter = exporter; this.logInputAndOutput = logInputAndOutput; this.projectId = projectId; this.errorHandler = errorHandler; } export(spans, resultCallback) { this.exporter?.export(this.adjust(spans), (result) => { if (this.errorHandler && result.error) { this.errorHandler(result.error); } resultCallback(result); }); } shutdown() { return this.exporter?.shutdown(); } getExporter() { return this.exporter; } forceFlush() { if (this.exporter?.forceFlush) { return this.exporter.forceFlush(); } return Promise.resolve(); } adjust(spans) { return spans.map((span) => { this.tickTelemetry(span); span = this.redactInputOutput(span); span = this.markErrorSpanAsError(span); span = this.markFailedSpan(span); span = this.markGenkitFeature(span); span = this.markGenkitModel(span); span = this.normalizeLabels(span); return span; }); } tickTelemetry(span) { const attributes = span.attributes; if (!Object.keys(attributes).includes("genkit:type")) { return; } const type = attributes["genkit:type"]; const subtype = attributes["genkit:metadata:subtype"]; const isRoot = !!span.attributes["genkit:isRoot"]; import_path.pathsTelemetry.tick(span, this.logInputAndOutput, this.projectId); if (isRoot) { import_feature.featuresTelemetry.tick(span, this.logInputAndOutput, this.projectId); span.attributes["genkit:rootState"] = span.attributes["genkit:state"]; } else { if (type === "action" && subtype === "model") { import_generate.generateTelemetry.tick(span, this.logInputAndOutput, this.projectId); } if (type === "action" && subtype === "tool") { } if (type === "action" || type === "flow" || type == "flowStep" || type == "util") { import_action.actionTelemetry.tick(span, this.logInputAndOutput, this.projectId); } } if (type === "userEngagement") { import_engagement.engagementTelemetry.tick(span, this.logInputAndOutput, this.projectId); } } redactInputOutput(span) { const hasInput = "genkit:input" in span.attributes; const hasOutput = "genkit:output" in span.attributes; return !hasInput && !hasOutput ? span : { ...span, spanContext: span.spanContext, attributes: { ...span.attributes, "genkit:input": "<redacted>", "genkit:output": "<redacted>" } }; } // This is a workaround for GCP Trace to mark a span with a red // exclamation mark indicating that it is an error. markErrorSpanAsError(span) { return span.status.code !== import_api.SpanStatusCode.ERROR ? span : { ...span, spanContext: span.spanContext, attributes: { ...span.attributes, "/http/status_code": "599" } }; } normalizeLabels(span) { const normalized = {}; for (const [key, value] of Object.entries(span.attributes)) { normalized[key.replace(/\:/g, "/")] = value; } return { ...span, spanContext: span.spanContext, attributes: normalized }; } markFailedSpan(span) { if (span.attributes["genkit:isFailureSource"]) { span.attributes["genkit:failedSpan"] = span.attributes["genkit:name"]; span.attributes["genkit:failedPath"] = span.attributes["genkit:path"]; } return span; } markGenkitFeature(span) { if (span.attributes["genkit:isRoot"] && !!span.attributes["genkit:name"]) { span.attributes["genkit:feature"] = span.attributes["genkit:name"]; } return span; } markGenkitModel(span) { if (span.attributes["genkit:metadata:subtype"] === "model" && !!span.attributes["genkit:name"]) { span.attributes["genkit:model"] = span.attributes["genkit:name"]; } return span; } } function getErrorHandler(shouldLogFn, helpText) { let instructionsLogged = false; return (err) => { const defaultLogger = import_logging.logger.defaultLogger; if (err && shouldLogFn(err)) { if (!instructionsLogged) { instructionsLogged = true; defaultLogger.error( `Unable to send telemetry to Google Cloud: ${err.message} ${helpText} ` ); } } else if (err) { defaultLogger.error(`Unable to send telemetry to Google Cloud: ${err}`); } }; } function __getMetricExporterForTesting() { return metricExporter; } function __getSpanExporterForTesting() { return spanExporter.getExporter(); } function __forceFlushSpansForTesting() { spanProcessor.forceFlush(); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { GcpOpenTelemetry, __forceFlushSpansForTesting, __getMetricExporterForTesting, __getSpanExporterForTesting }); //# sourceMappingURL=gcpOpenTelemetry.js.map