@mastra/core
Version:
1 lines • 55.9 kB
Source Map (JSON)
{"version":3,"file":"observability-BzV5axz0.cjs","names":["getOrCreateSpan","EntityType"],"sources":["../src/observability/types/core.ts","../src/observability/types/metrics.ts","../src/observability/no-op.ts","../src/observability/context-factory.ts","../src/observability/context.ts","../src/observability/rag-ingestion.ts"],"sourcesContent":["/**\n * Top-level observability infrastructure types.\n *\n * This file contains the core observability interfaces for managing instances,\n * exporters, bridges, configuration, event bus, and the context mixin.\n *\n * For tracing-specific types (spans, span types, attributes, etc.), see tracing.ts.\n */\nimport type { IMastraLogger } from '../../logger';\nimport type { Mastra } from '../../mastra';\nimport type { RequestContext } from '../../request-context';\nimport type { ClientObservabilityProxy } from './client';\nimport type { FeedbackEvent, FeedbackInput } from './feedback';\nimport type { LoggerContext, LogEvent } from './logging';\nimport type { MetricsContext, MetricEvent } from './metrics';\nimport type { ScoreEvent, ScoreInput } from './scores';\nimport type {\n AnySpan,\n AnyExportedSpan,\n RecordedTrace,\n CreateSpanOptions,\n EntityType,\n ExportedSpan,\n Span,\n SpanIds,\n SpanOutputProcessor,\n SpanType,\n StartSpanOptions,\n TracingContext,\n TracingEvent,\n} from './tracing';\n\n// ============================================================================\n// ObservabilityContext\n// ============================================================================\n\n/**\n * Canonical observability correlation and execution context.\n * These fields can travel alongside observability signals without being encoded in labels.\n */\nexport interface CorrelationContext {\n /**\n * @deprecated Use the signal's top-level `traceId` instead.\n */\n traceId?: string;\n /**\n * @deprecated Use the signal's top-level `spanId` instead.\n */\n spanId?: string;\n entityType?: EntityType;\n entityId?: string;\n entityName?: string;\n entityVersionId?: string;\n parentEntityType?: EntityType;\n parentEntityId?: string;\n parentEntityName?: string;\n parentEntityVersionId?: string;\n rootEntityType?: EntityType;\n rootEntityId?: string;\n rootEntityName?: string;\n rootEntityVersionId?: string;\n userId?: string;\n organizationId?: string;\n resourceId?: string;\n runId?: string;\n sessionId?: string;\n threadId?: string;\n requestId?: string;\n environment?: string;\n source?: string;\n serviceName?: string;\n experimentId?: string;\n tags?: string[];\n}\n\n/**\n * Mixin interface that provides unified observability access.\n * All execution contexts (tools, workflow steps, processors) extend this\n * to gain access to tracing, logging, and metrics.\n *\n * ## Naming conventions\n *\n * `tracingContext` is the **source** — it represents your position in the span tree.\n * Creating a child span produces a new `tracingContext` with that child as `currentSpan`.\n *\n * `loggerVNext` and `metrics` are **derived** — they are rebuilt from the current span so that\n * log entries and metric data points are automatically correlated to the active trace:\n *\n * ```\n * tracingContext → create child span → new tracingContext\n * → new loggerVNext (correlated to child span)\n * → new metrics (tagged with child span metadata)\n * ```\n *\n * The short names (`tracing`, `loggerVNext`, `metrics`) read naturally at **usage sites**:\n * `tracing.createSpan()`, `loggerVNext.info()`, `metrics.record()`.\n *\n * `loggerVNext` uses the VNext suffix to distinguish from the existing `logger: IMastraLogger`\n * infrastructure logger used throughout the codebase (e.g. `MastraPrimitives.logger`).\n *\n * The `tracingContext` alias is preferred at **forwarding sites** where the \"Context\"\n * suffix clarifies that a structural context object is being passed, not a subsystem.\n */\nexport interface ObservabilityContext {\n /** Tracing context for span creation and tree navigation. */\n tracing: TracingContext;\n\n /** Logger derived from the current span — log entries are trace-correlated. Uses VNext suffix to avoid conflict with IMastraLogger. */\n loggerVNext: LoggerContext;\n\n /** Metrics derived from the current span — data points are span-tagged. */\n metrics: MetricsContext;\n\n /**\n * Alias for `tracing`. Preferred at forwarding sites where the \"Context\" suffix\n * clarifies that a structural context object is being passed between functions.\n */\n tracingContext: TracingContext;\n}\n\n// ============================================================================\n// Shared Scorer Types\n// ============================================================================\n\n/** Where a registered definition came from. */\nexport type DefinitionSource = 'code' | 'stored' | 'fs';\n\n/** What kind of scoring flow produced the score. */\nexport type ScorerScoreSource = 'live' | 'trace' | 'experiment';\n\n/** How the scorer interpreted the target data. */\nexport type ScorerTargetScope = 'span' | 'trajectory';\n\n/** Execution style for a scorer step. */\nexport type ScorerStepType = 'function' | 'prompt';\n\n// ============================================================================\n// ObservabilityEventBus\n// ============================================================================\n\n/**\n * Generic event bus interface for observability events.\n * Implementations handle buffering, batching, and delivery to exporters.\n */\nexport interface ObservabilityEventBus<TEvent> {\n /** Emit an event to the bus */\n emit(event: TEvent): void;\n\n /** Subscribe to events. Returns unsubscribe function. */\n subscribe(handler: (event: TEvent) => void): () => void;\n\n /** Flush any buffered events */\n flush(): Promise<void>;\n\n /** Shutdown the bus and release resources */\n shutdown(): Promise<void>;\n}\n\n/**\n * Union of all observability event types.\n * Used by the unified ObservabilityBus that handles all signals.\n */\nexport type ObservabilityEvent = TracingEvent | LogEvent | MetricEvent | ScoreEvent | FeedbackEvent;\n\n/** Signal whose event was dropped by the observability exporter pipeline. */\nexport type ObservabilityDropSignal = 'tracing' | 'log' | 'metric' | 'score' | 'feedback';\n\n/** Reason an observability event was dropped by the exporter pipeline. */\nexport type ObservabilityDropReason = 'unsupported-storage' | 'retry-exhausted';\n\n/** Sanitized error details for observability drop events. */\nexport interface ObservabilityDropError {\n id?: string;\n domain?: string;\n message: string;\n}\n\n/**\n * Structured event emitted when the exporter pipeline drops observability events.\n */\nexport interface ObservabilityDropEvent {\n type: 'drop';\n signal: ObservabilityDropSignal;\n reason: ObservabilityDropReason;\n count: number;\n timestamp: Date;\n exporterName: string;\n storageName?: string;\n error?: ObservabilityDropError;\n}\n\n// ============================================================================\n// ObservabilityInstance\n// ============================================================================\n\n/**\n * Primary interface for Observability\n */\nexport interface ObservabilityInstance {\n /**\n * Get current configuration\n */\n getConfig(): Readonly<ObservabilityInstanceConfig>;\n\n /**\n * Get all exporters\n */\n getExporters(): readonly ObservabilityExporter[];\n\n /**\n * Get all span output processors\n */\n getSpanOutputProcessors(): readonly SpanOutputProcessor[];\n\n /**\n * Get the logger instance (for exporters and other components)\n */\n getLogger(): IMastraLogger;\n\n /**\n * Get the bridge instance if configured\n */\n getBridge(): ObservabilityBridge | undefined;\n\n /**\n * Start a new span of a specific SpanType\n */\n startSpan<TType extends SpanType>(options: StartSpanOptions<TType>): Span<TType>;\n\n /**\n * Rebuild a span from exported data for lifecycle operations.\n * Used by durable execution engines (e.g., Inngest) to end/update spans\n * that were created in a previous durable operation.\n *\n * @param cached - The exported span data to rebuild from\n * @returns A span that can have end()/update()/error() called on it\n */\n rebuildSpan<TType extends SpanType>(cached: ExportedSpan<TType>): Span<TType>;\n\n /**\n * Force flush any buffered/queued spans from all exporters and the bridge\n * without shutting down the observability instance.\n *\n * This is useful in serverless environments (like Vercel's fluid compute) where\n * you need to ensure all spans are exported before the runtime instance is\n * terminated, while keeping the observability system active for future requests.\n *\n * Unlike shutdown(), flush() does not release resources or prevent future tracing.\n */\n flush(): Promise<void>;\n\n /**\n * Shutdown tracing and clean up resources\n */\n shutdown(): Promise<void>;\n\n /**\n * Override setLogger to add tracing specific initialization log\n */\n __setLogger(logger: IMastraLogger): void;\n\n /**\n * Get a LoggerContext for this instance, optionally correlated to a span.\n * When a span is provided, the returned logger automatically includes\n * the span's traceId, spanId, and other metadata in every log entry.\n * Returns no-op context when logging is not configured on this instance.\n *\n * @param span - Optional span to correlate logs with\n */\n getLoggerContext?(span?: AnySpan): LoggerContext;\n\n /**\n * Get a MetricsContext for this instance, optionally tagged from a span.\n * When a span is provided, the returned metrics context automatically\n * includes the span's metadata as labels/tags on emitted data points.\n * Returns no-op context when metrics are not configured on this instance.\n *\n * @param span - Optional span to derive metric tags from\n */\n getMetricsContext?(span?: AnySpan): MetricsContext;\n\n /**\n * Register an additional exporter to this instance at runtime.\n * Duplicate registrations (same instance) are silently ignored.\n *\n * @param exporter - The exporter to register\n */\n registerExporter?(exporter: ObservabilityExporter): void;\n\n /**\n * Returns the deployment environment propagated from the parent Mastra\n * instance (resolved from `Mastra` config `environment` or `process.env.NODE_ENV`).\n * Used by spans as a fallback when `metadata.environment` isn't set on a\n * specific span.\n */\n getMastraEnvironment?(): string | undefined;\n\n /**\n * Internal hook used by the parent `Observability` entrypoint to push the\n * resolved Mastra-level environment into this instance during\n * `setMastraContext`. Implementations should store the value for later reads\n * via `getMastraEnvironment()`.\n */\n __setMastraEnvironment?(environment: string | undefined): void;\n}\n\n// ============================================================================\n// ObservabilityEntrypoint\n// ============================================================================\n\nexport interface ObservabilityEntrypoint {\n flush(): Promise<void>;\n\n shutdown(): Promise<void>;\n\n setMastraContext(options: { mastra: Mastra }): void;\n\n setLogger(options: { logger: IMastraLogger }): void;\n\n getSelectedInstance(options: ConfigSelectorOptions): ObservabilityInstance | undefined;\n\n /**\n * Load a persisted trace as a hydrated RecordedTrace object.\n * Returns null when storage is unavailable or the trace does not exist.\n */\n getRecordedTrace?(args: { traceId: string }): Promise<RecordedTrace | null>;\n\n /**\n * Add a score to a persisted trace or span without hydrating a RecordedTrace.\n * Useful for durable executions that persist only identifiers across serialization boundaries.\n *\n * `traceId` anchors the scored target when available.\n * Include `spanId` when the score is about a specific span.\n * Include `correlationContext` to emit immediately from live span/trace state\n * without rehydrating the target from storage first.\n */\n addScore?(args: {\n traceId?: string;\n spanId?: string;\n correlationContext?: CorrelationContext;\n score: ScoreInput;\n }): Promise<void>;\n\n /**\n * Add feedback to a persisted trace or span without hydrating a RecordedTrace.\n * Useful for durable executions that persist only identifiers across serialization boundaries.\n *\n * `traceId` anchors the feedback target when available.\n * Include `spanId` when the feedback is about a specific span.\n * Include `correlationContext` to emit immediately from live span/trace state\n * without rehydrating the target from storage first.\n */\n addFeedback?(args: {\n traceId?: string;\n spanId?: string;\n correlationContext?: CorrelationContext;\n feedback: FeedbackInput;\n }): Promise<void>;\n\n /**\n * Returns the proxy responsible for client observability (W3C trace\n * context injection + OTLP/JSON payload reception for spans/logs\n * returned from client-side execution).\n *\n * Returns `undefined` when no implementation is registered (e.g.\n * `NoOpObservability`, or when `@mastra/observability` is not\n * installed). Callers must treat `undefined` as \"no cross-boundary\n * client observability\" and skip inject/receive accordingly.\n */\n getClientObservabilityProxy?(): ClientObservabilityProxy | undefined;\n\n // Registry management methods\n registerInstance(name: string, instance: ObservabilityInstance, isDefault?: boolean): void;\n getInstance(name: string): ObservabilityInstance | undefined;\n getDefaultInstance(): ObservabilityInstance | undefined;\n listInstances(): ReadonlyMap<string, ObservabilityInstance>;\n unregisterInstance(name: string): boolean;\n hasInstance(name: string): boolean;\n setConfigSelector(selector: ConfigSelector): void;\n clear(): void;\n}\n\n// ============================================================================\n// Sampling Strategy\n// ============================================================================\n\n/**\n * Sampling strategy types\n */\nexport enum SamplingStrategyType {\n ALWAYS = 'always',\n NEVER = 'never',\n RATIO = 'ratio',\n CUSTOM = 'custom',\n}\n\n/**\n * Sampling strategy configuration\n */\nexport type SamplingStrategy =\n | { type: SamplingStrategyType.ALWAYS }\n | { type: SamplingStrategyType.NEVER }\n | { type: SamplingStrategyType.RATIO; probability: number }\n | { type: SamplingStrategyType.CUSTOM; sampler: (options?: CustomSamplerOptions) => boolean };\n\n/**\n * Options passed when using a custom sampler strategy\n */\nexport interface CustomSamplerOptions {\n requestContext?: RequestContext;\n metadata?: Record<string, any>;\n}\n\n// ============================================================================\n// Serialization Options\n// ============================================================================\n\n/**\n * Options for controlling serialization of span data.\n * These options control how input, output, and attributes are cleaned before export.\n */\nexport interface SerializationOptions {\n /**\n * Maximum length for string values\n * @default 1024\n */\n maxStringLength?: number;\n /**\n * Maximum depth for nested objects\n * @default 6\n */\n maxDepth?: number;\n /**\n * Maximum number of items in arrays\n * @default 50\n */\n maxArrayLength?: number;\n /**\n * Maximum number of keys in objects\n * @default 50\n */\n maxObjectKeys?: number;\n}\n\n// ============================================================================\n// Registry Config\n// ============================================================================\n\n/**\n * Configuration for a single observability instance\n */\nexport interface ObservabilityInstanceConfig {\n /** Unique identifier for this config in the observability registry */\n name: string;\n /** Service name for observability */\n serviceName: string;\n /** Sampling strategy - controls whether tracing is collected (defaults to ALWAYS) */\n sampling?: SamplingStrategy;\n /** Custom exporters */\n exporters?: ObservabilityExporter[];\n /** Custom processors */\n spanOutputProcessors?: SpanOutputProcessor[];\n /** OpenTelemetry bridge for integration with existing OTEL infrastructure */\n bridge?: ObservabilityBridge;\n /** Set to `true` if you want to see spans internal to the operation of mastra */\n includeInternalSpans?: boolean;\n /**\n * Span types to exclude from export. Spans of these types are silently dropped\n * before reaching exporters. This is useful for reducing noise and costs in\n * observability platforms that charge per-span (e.g., Langfuse).\n *\n * @example\n * ```typescript\n * excludeSpanTypes: [SpanType.MODEL_CHUNK, SpanType.MODEL_STEP]\n * ```\n */\n excludeSpanTypes?: SpanType[];\n /**\n * Filter function to control which spans are exported. Return `true` to keep\n * the span, `false` to drop it. This runs after `excludeSpanTypes` and\n * `spanOutputProcessors`, giving you access to the final exported span data\n * for fine-grained filtering by type, attributes, entity, metadata, or any\n * combination.\n *\n * @example\n * ```typescript\n * spanFilter: (span) => {\n * // Drop all model chunks\n * if (span.type === SpanType.MODEL_CHUNK) return false;\n * // Only keep tool calls that failed\n * if (span.type === SpanType.TOOL_CALL && span.attributes?.success) return false;\n * return true;\n * }\n * ```\n */\n spanFilter?: (span: AnyExportedSpan) => boolean;\n /**\n * RequestContext keys to automatically extract as metadata for all spans\n * created with this observability configuration.\n * Supports dot notation for nested values.\n */\n requestContextKeys?: string[];\n /**\n * Options for controlling serialization of span data (input/output/attributes).\n * Use these to customize truncation limits for large payloads.\n */\n serializationOptions?: SerializationOptions;\n}\n\n/**\n * Complete Observability registry configuration\n */\nexport interface ObservabilityRegistryConfig {\n /** Enables default exporters, with sampling: always, and sensitive data filtering */\n default?: {\n enabled?: boolean;\n };\n /** Map of tracing instance names to their configurations or pre-instantiated instances */\n configs?: Record<string, Omit<ObservabilityInstanceConfig, 'name'> | ObservabilityInstance>;\n /** Optional selector function to choose which tracing instance to use */\n configSelector?: ConfigSelector;\n}\n\n// ============================================================================\n// Config Selector\n// ============================================================================\n\n/**\n * Options passed when using a custom tracing config selector\n */\nexport interface ConfigSelectorOptions {\n /** Request Context */\n requestContext?: RequestContext;\n}\n\n/**\n * Function to select which tracing instance to use for a given span\n * Returns the name of the tracing instance, or undefined to use default\n */\nexport type ConfigSelector = (\n options: ConfigSelectorOptions,\n availableConfigs: ReadonlyMap<string, ObservabilityInstance>,\n) => string | undefined;\n\n// ============================================================================\n// Exporter and Bridge Interfaces\n// ============================================================================\n\nexport interface InitExporterOptions {\n mastra?: Mastra;\n config?: ObservabilityInstanceConfig;\n emitDropEvent?: (event: ObservabilityDropEvent) => void;\n}\n\nexport interface InitBridgeOptions {\n mastra?: Mastra;\n config?: ObservabilityInstanceConfig;\n}\n\n/**\n * Shared Observability event interface for Exporters & Bridges\n */\nexport interface ObservabilityEvents {\n /** Handle tracing events */\n onTracingEvent?(event: TracingEvent): void | Promise<void>;\n\n /** Handle log events */\n onLogEvent?(event: LogEvent): void | Promise<void>;\n\n /** Handle metric events */\n onMetricEvent?(event: MetricEvent): void | Promise<void>;\n\n /** Handle score events */\n onScoreEvent?(event: ScoreEvent): void | Promise<void>;\n\n /** Handle feedback events */\n onFeedbackEvent?(event: FeedbackEvent): void | Promise<void>;\n\n /** Handle exporter pipeline droppedEvent */\n onDroppedEvent?(event: ObservabilityDropEvent): void | Promise<void>;\n\n /** Export tracing events */\n exportTracingEvent(event: TracingEvent): Promise<void>;\n}\n\n/**\n * Interface for tracing exporters\n */\nexport interface ObservabilityExporter extends ObservabilityEvents {\n /** Exporter name */\n name: string;\n\n /** Initialize exporter with tracing configuration and/or access to Mastra */\n init?(options: InitExporterOptions): void;\n\n /** Sets logger instance on the exporter. */\n __setLogger?(logger: IMastraLogger): void;\n\n addScoreToTrace?({\n traceId,\n spanId,\n score,\n reason,\n scorerName,\n metadata,\n }: {\n traceId: string;\n spanId?: string;\n score: number;\n reason?: string;\n scorerName: string;\n metadata?: Record<string, any>;\n }): Promise<void>;\n\n /**\n * Force flush any buffered/queued spans without shutting down the exporter.\n * This is useful in serverless environments where you need to ensure spans\n * are exported before the runtime instance is terminated, while keeping\n * the exporter active for future requests.\n *\n * Unlike shutdown(), flush() does not release resources or prevent future exports.\n */\n flush(): Promise<void>;\n\n /** Shutdown exporter */\n shutdown(): Promise<void>;\n}\n\n/**\n * Interface for observability bridges\n */\nexport interface ObservabilityBridge extends ObservabilityEvents {\n /** Bridge name */\n name: string;\n\n /** Initialize bridge with observability configuration and/or access to Mastra */\n init?(options: InitBridgeOptions): void;\n\n /** Sets logger instance on the bridge */\n __setLogger?(logger: IMastraLogger): void;\n\n /**\n * Execute an async function within the tracing context of a Mastra span.\n * This enables auto-instrumented operations (HTTP, DB) to have correct parent spans\n * in the external tracing system (e.g., OpenTelemetry, DataDog, etc.).\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The async function to execute within the span context\n * @returns The result of the function execution\n */\n executeInContext?<T>(spanId: string, fn: () => Promise<T>): Promise<T>;\n\n /**\n * Execute a synchronous function within the tracing context of a Mastra span.\n * This enables auto-instrumented operations (HTTP, DB) to have correct parent spans\n * in the external tracing system (e.g., OpenTelemetry, DataDog, etc.).\n *\n * @param spanId - The ID of the Mastra span to use as context\n * @param fn - The synchronous function to execute within the span context\n * @returns The result of the function execution\n */\n executeInContextSync?<T>(spanId: string, fn: () => T): T;\n\n /**\n * Create a span in the bridge's tracing system.\n * Called during Mastra span construction to get bridge-generated identifiers.\n *\n * @param options - Span creation options from Mastra\n * @returns Span identifiers (spanId, traceId, parentSpanId) from bridge, or undefined if creation fails\n */\n createSpan(options: CreateSpanOptions<SpanType>): SpanIds | undefined;\n\n /**\n * Release any state the bridge holds for a span that ended but will not be\n * exported. Bridges keep per-span state from `createSpan()` until the span\n * ends; span-end events are only emitted for spans that survive export\n * filtering, so without this the state for a span dropped by\n * `excludeSpanTypes`, a `spanFilter`, or a span output processor is never\n * freed.\n *\n * The bridge should only drop its bookkeeping here. It must not end/finish\n * the underlying span, which would export data the user filtered out.\n *\n * @param spanId - The ID of the Mastra span whose bridge state can be dropped\n * @param traceId - The trace the span belonged to, for per-trace bridge state\n */\n releaseSpan?(spanId: string, traceId: string): void;\n\n /**\n * Force flush any buffered/queued spans without shutting down the bridge.\n * This is useful in serverless environments where you need to ensure spans\n * are exported before the runtime instance is terminated, while keeping\n * the bridge active for future requests.\n *\n * Unlike shutdown(), flush() does not release resources or prevent future exports.\n */\n flush(): Promise<void>;\n\n /** Shutdown bridge and cleanup resources */\n shutdown(): Promise<void>;\n}\n","import type { CorrelationContext } from './core';\n\n// ============================================================================\n// Metric Type\n// ============================================================================\n\n/**\n * @deprecated MetricType is no longer stored. All metrics are raw events\n * with aggregation determined at query time.\n */\nexport type MetricType = 'counter' | 'gauge' | 'histogram';\n\n// ============================================================================\n// MetricsContext (API Interface)\n// ============================================================================\n\n/**\n * MetricsContext - API for emitting metrics.\n * Use `emit()` to record a metric observation.\n */\nexport interface MetricEmitOptions {\n /** Canonical model/cost context for this specific metric row */\n costContext?: CostContext;\n}\n\nexport interface MetricsContext {\n /** Emit a metric observation. */\n emit(name: string, value: number, labels?: Record<string, string>, options?: MetricEmitOptions): void;\n\n /** @deprecated Use `emit()` instead. */\n counter(name: string): Counter;\n /** @deprecated Use `emit()` instead. */\n gauge(name: string): Gauge;\n /** @deprecated Use `emit()` instead. */\n histogram(name: string): Histogram;\n}\n\n/** @deprecated Use MetricsContext.emit() instead. */\nexport interface Counter {\n add(value: number, additionalLabels?: Record<string, string>): void;\n}\n\n/** @deprecated Use MetricsContext.emit() instead. */\nexport interface Gauge {\n set(value: number, additionalLabels?: Record<string, string>): void;\n}\n\n/** @deprecated Use MetricsContext.emit() instead. */\nexport interface Histogram {\n record(value: number, additionalLabels?: Record<string, string>): void;\n}\n\n// ============================================================================\n// ExportedMetric (Event Bus Transport)\n// ============================================================================\n\n/**\n * Typed context used for cost estimations.\n */\nexport interface CostContext {\n provider?: string;\n model?: string;\n estimatedCost?: number;\n costUnit?: string;\n costMetadata?: Record<string, unknown>;\n}\n\n/**\n * Metric data transported via the event bus.\n * Represents a single metric observation.\n * Must be JSON-serializable (Date serializes via toJSON()).\n *\n * Descriptive correlation metadata travels in `correlationContext`.\n * Signal identity stays on the top-level `traceId` / `spanId` fields.\n * pricing/model fields travel in `costContext`.\n */\nexport interface ExportedMetric {\n /** Unique identifier for this metric event, generated at emission time */\n metricId: string;\n\n /** When the metric was recorded */\n timestamp: Date;\n\n /** Trace associated with this metric (undefined = not tied to a trace) */\n traceId?: string;\n\n /** Specific span associated with this metric */\n spanId?: string;\n\n /** Metric name (e.g., mastra_agent_duration_ms) */\n name: string;\n\n /** Metric value (single observation) */\n value: number;\n\n /** Metric labels for dimensional filtering */\n labels: Record<string, string>;\n\n /** Context for correlation to traces */\n correlationContext?: CorrelationContext;\n\n /** Context for cost estimation */\n costContext?: CostContext;\n\n /**\n * User-defined metadata.\n * This is reserved for non-canonical metadata that does not belong\n * in record context or cost context.\n */\n metadata?: Record<string, unknown>;\n}\n\n// ============================================================================\n// MetricEvent (Event Bus Event)\n// ============================================================================\n\n/** Metric event emitted to the ObservabilityBus */\nexport interface MetricEvent {\n type: 'metric';\n metric: ExportedMetric;\n}\n\n// ============================================================================\n// Cardinality Protection\n// ============================================================================\n\n/**\n * Default labels to block from metrics to prevent cardinality explosion.\n * These are high-cardinality fields that should not be used as metric labels.\n */\nexport const DEFAULT_BLOCKED_LABELS = [\n 'trace_id',\n 'span_id',\n 'run_id',\n 'request_id',\n 'user_id',\n 'resource_id',\n 'session_id',\n 'thread_id',\n] as const;\n\n/** Cardinality protection configuration */\nexport interface CardinalityConfig {\n /**\n * Labels to block from metrics. **Replaces** the default list entirely —\n * DEFAULT_BLOCKED_LABELS are NOT merged in when this is set.\n *\n * - `undefined` (default) → uses DEFAULT_BLOCKED_LABELS\n * - `[]` → disables label blocking (allows all labels through)\n * - `['x', 'y']` → blocks only x and y; defaults like trace_id are allowed\n *\n * To extend the defaults, spread them into your list:\n * ```ts\n * blockedLabels: [...DEFAULT_BLOCKED_LABELS, 'my_custom_label']\n * ```\n */\n blockedLabels?: string[];\n\n /**\n * Whether to block UUID-like values in labels.\n * @default true\n */\n blockUUIDs?: boolean;\n}\n\n/** Metrics-specific configuration */\nexport interface MetricsConfig {\n /** Whether metrics are enabled */\n enabled?: boolean;\n /** Cardinality protection settings */\n cardinality?: CardinalityConfig;\n}\n","import type { Mastra } from '..';\nimport type { IMastraLogger } from '../logger';\nimport type {\n CorrelationContext,\n ConfigSelector,\n ConfigSelectorOptions,\n Counter,\n FeedbackInput,\n Gauge,\n Histogram,\n LoggerContext,\n MetricsContext,\n ObservabilityEntrypoint,\n ObservabilityInstance,\n RecordedTrace,\n ScoreInput,\n TracingContext,\n} from './types';\n\n// ============================================================================\n// No-Op Metric Instruments\n// ============================================================================\n\nconst noOpCounter: Counter = {\n add() {},\n};\n\nconst noOpGauge: Gauge = {\n set() {},\n};\n\nconst noOpHistogram: Histogram = {\n record() {},\n};\n\n// ============================================================================\n// No-Op TracingContext\n// ============================================================================\n\n/**\n * No-op tracing context used when observability is not configured.\n */\nexport const noOpTracingContext: TracingContext = {\n currentSpan: undefined,\n};\n\n// ============================================================================\n// No-Op LoggerContext\n// ============================================================================\n\n/**\n * No-op logger context that silently discards all log calls.\n * Used when observability is not configured.\n */\nexport const noOpLoggerContext: LoggerContext = {\n debug() {},\n info() {},\n warn() {},\n error() {},\n fatal() {},\n};\n\n// ============================================================================\n// No-Op MetricsContext\n// ============================================================================\n\n/**\n * No-op metrics context that silently discards all metric operations.\n * Used when observability is not configured.\n */\nexport const noOpMetricsContext: MetricsContext = {\n emit() {},\n counter() {\n return noOpCounter;\n },\n gauge() {\n return noOpGauge;\n },\n histogram() {\n return noOpHistogram;\n },\n};\n\n// ============================================================================\n// No-Op Observability\n// ============================================================================\n\n/** No-op observability entrypoint that silently discards all operations. */\nexport class NoOpObservability implements ObservabilityEntrypoint {\n setMastraContext(_options: { mastra: Mastra }): void {\n return;\n }\n\n setLogger(_options: { logger: IMastraLogger }): void {\n return;\n }\n\n getSelectedInstance(_options: ConfigSelectorOptions): ObservabilityInstance | undefined {\n return;\n }\n\n async getRecordedTrace(_args: { traceId: string }): Promise<RecordedTrace | null> {\n return null;\n }\n\n async addScore(_args: {\n traceId?: string;\n spanId?: string;\n correlationContext?: CorrelationContext;\n score: ScoreInput;\n }): Promise<void> {\n return;\n }\n\n async addFeedback(_args: {\n traceId?: string;\n spanId?: string;\n correlationContext?: CorrelationContext;\n feedback: FeedbackInput;\n }): Promise<void> {\n return;\n }\n\n registerInstance(_name: string, _instance: ObservabilityInstance, _isDefault = false): void {\n return;\n }\n\n getInstance(_name: string): ObservabilityInstance | undefined {\n return;\n }\n\n getDefaultInstance(): ObservabilityInstance | undefined {\n return;\n }\n\n listInstances(): ReadonlyMap<string, ObservabilityInstance> {\n return new Map();\n }\n\n unregisterInstance(_name: string): boolean {\n return false;\n }\n\n hasInstance(_name: string): boolean {\n return false;\n }\n\n setConfigSelector(_selector: ConfigSelector): void {\n return;\n }\n\n clear(): void {\n return;\n }\n\n async flush(): Promise<void> {\n return;\n }\n\n async shutdown(): Promise<void> {\n return;\n }\n}\n","// packages/core/src/observability/context-factory.ts\n\nimport { noOpLoggerContext, noOpMetricsContext, noOpTracingContext } from './no-op';\nimport type { LoggerContext, MetricsContext, ObservabilityContext, TracingContext } from './types';\n\n// ============================================================================\n// Context Derivation\n// ============================================================================\n\n/**\n * Derives a LoggerContext from the current span's ObservabilityInstance.\n * Falls back to no-op when there is no span or the instance doesn't support logging.\n */\nfunction deriveLoggerContext(tracing: TracingContext): LoggerContext {\n const span = tracing.currentSpan;\n return span?.observabilityInstance?.getLoggerContext?.(span) ?? noOpLoggerContext;\n}\n\n/**\n * Derives a MetricsContext from the current span's ObservabilityInstance.\n * Falls back to no-op when there is no span or the instance doesn't support metrics.\n */\nfunction deriveMetricsContext(tracing: TracingContext): MetricsContext {\n const span = tracing.currentSpan;\n return span?.observabilityInstance?.getMetricsContext?.(span) ?? noOpMetricsContext;\n}\n\n// ============================================================================\n// Context Factory\n// ============================================================================\n\n/**\n * Creates an observability context with real or no-op implementations for\n * tracing, logging, and metrics.\n *\n * When a TracingContext with a current span is provided, the logger and metrics\n * contexts are derived from the span's ObservabilityInstance so that log entries\n * and metric data points are automatically correlated to the active trace.\n *\n * @param tracingContext - TracingContext with current span, or undefined for no-op\n * @returns ObservabilityContext with all three signals (tracing, logger, metrics)\n */\nexport function createObservabilityContext(tracingContext?: TracingContext): ObservabilityContext {\n const tracing = tracingContext ?? noOpTracingContext;\n\n return {\n tracing,\n loggerVNext: deriveLoggerContext(tracing),\n metrics: deriveMetricsContext(tracing),\n tracingContext: tracing, // alias — preferred at forwarding sites\n };\n}\n\n/**\n * Resolves a partial observability context (from execute params) into a\n * complete ObservabilityContext with no-op defaults for any missing fields.\n *\n * Explicitly provided logger/metrics contexts are preserved (e.g. when set\n * upstream). When missing, they are derived from the tracing context's span,\n * following the same derivation logic as createObservabilityContext().\n *\n * @param partial - Partial context from ExecuteFunctionParams\n * @returns Complete ObservabilityContext\n */\nexport function resolveObservabilityContext(partial: Partial<ObservabilityContext>): ObservabilityContext {\n const tracing = partial.tracing ?? partial.tracingContext ?? noOpTracingContext;\n\n return {\n tracing,\n loggerVNext: partial.loggerVNext ?? deriveLoggerContext(tracing),\n metrics: partial.metrics ?? deriveMetricsContext(tracing),\n tracingContext: tracing, // alias — preferred at forwarding sites\n };\n}\n","/**\n * Tracing Context Integration\n *\n * This module provides automatic tracing context propagation throughout Mastra's execution contexts.\n * It uses JavaScript Proxies to transparently wrap Mastra, Agent, and Workflow instances so that\n * tracing context is automatically injected without requiring manual passing by users.\n */\n\nimport type { MastraPrimitives } from '../action';\nimport type { Agent } from '../agent';\nimport type { Mastra } from '../mastra';\nimport type { Workflow } from '../workflows';\nimport { createObservabilityContext } from './context-factory';\nimport type { TracingContext, AnySpan } from './types';\n\nconst AGENT_GETTERS = ['getAgent', 'getAgentById'];\nconst AGENT_METHODS_TO_WRAP = ['generate', 'stream', 'generateLegacy', 'streamLegacy'];\n\nconst WORKFLOW_GETTERS = ['getWorkflow', 'getWorkflowById'];\nconst WORKFLOW_METHODS_TO_WRAP = ['execute', 'createRun', 'createRun'];\n\n/**\n * Helper function to detect NoOp spans to avoid unnecessary wrapping\n */\nfunction isNoOpSpan(span: AnySpan): boolean {\n // Check if this is a NoOp span implementation\n return span.constructor.name === 'NoOpSpan' || (span as any).__isNoOp === true;\n}\n\n/**\n * Checks to see if a passed object is an actual instance of Mastra\n * (for the purposes of wrapping it for Tracing)\n */\nexport function isMastra<T extends Mastra | (Mastra & MastraPrimitives) | MastraPrimitives>(mastra: T): boolean {\n const hasAgentGetters = AGENT_GETTERS.every(method => typeof (mastra as any)?.[method] === 'function');\n const hasWorkflowGetters = WORKFLOW_GETTERS.every(method => typeof (mastra as any)?.[method] === 'function');\n\n return hasAgentGetters && hasWorkflowGetters;\n}\n\n/**\n * Creates a tracing-aware Mastra proxy that automatically injects\n * tracing context into agent and workflow method calls\n */\nexport function wrapMastra<T extends Mastra | (Mastra & MastraPrimitives) | MastraPrimitives>(\n mastra: T,\n tracingContext: TracingContext,\n): T {\n // Don't wrap if no current span or if using NoOp span\n if (!tracingContext.currentSpan || isNoOpSpan(tracingContext.currentSpan)) {\n return mastra;\n }\n\n // Check if this object has the methods we want to wrap - if not, return as is\n if (!isMastra(mastra)) {\n return mastra;\n }\n\n try {\n return new Proxy(mastra, {\n get(target, prop) {\n try {\n if (AGENT_GETTERS.includes(prop as string)) {\n return (...args: any[]) => {\n const agent = (target as any)[prop](...args);\n return wrapAgent(agent, tracingContext);\n };\n }\n\n // Wrap workflow getters\n if (WORKFLOW_GETTERS.includes(prop as string)) {\n return (...args: any[]) => {\n const workflow = (target as any)[prop](...args);\n return wrapWorkflow(workflow, tracingContext);\n };\n }\n\n // Pass through all other methods unchanged - bind functions to preserve 'this' context\n const value = (target as any)[prop];\n return typeof value === 'function' ? value.bind(target) : value;\n } catch (error) {\n console.warn('Tracing: Failed to wrap method, falling back to original', error);\n const value = (target as any)[prop];\n return typeof value === 'function' ? value.bind(target) : value;\n }\n },\n });\n } catch (error) {\n console.warn('Tracing: Failed to create proxy, using original Mastra instance', error);\n return mastra;\n }\n}\n\n/**\n * Creates a tracing-aware Agent proxy that automatically injects\n * tracing context into generation method calls\n */\nfunction wrapAgent<T extends Agent>(agent: T, tracingContext: TracingContext): T {\n // Don't wrap if no current span or if using NoOp span\n if (!tracingContext.currentSpan || isNoOpSpan(tracingContext.currentSpan)) {\n return agent;\n }\n\n try {\n return new Proxy(agent, {\n get(target, prop) {\n try {\n if (AGENT_METHODS_TO_WRAP.includes(prop as string)) {\n return (input: any, options: any = {}) => {\n return (target as any)[prop](input, {\n ...options,\n ...createObservabilityContext(tracingContext),\n });\n };\n }\n\n // Bind functions to preserve 'this' context for private member access\n const value = (target as any)[prop];\n return typeof value === 'function' ? value.bind(target) : value;\n } catch (error) {\n console.warn('Tracing: Failed to wrap agent method, falling back to original', error);\n const value = (target as any)[prop];\n return typeof value === 'function' ? value.bind(target) : value;\n }\n },\n });\n } catch (error) {\n console.warn('Tracing: Failed to create agent proxy, using original instance', error);\n return agent;\n }\n}\n\n/**\n * Creates a tracing-aware Workflow proxy that automatically injects\n * tracing context into execution method calls\n */\nfunction wrapWorkflow<T extends Workflow>(workflow: T, tracingContext: TracingContext): T {\n // Don't wrap if no current span or if using NoOp span\n if (!tracingContext.currentSpan || isNoOpSpan(tracingContext.currentSpan)) {\n return workflow;\n }\n\n try {\n return new Proxy(workflow, {\n get(target, prop) {\n try {\n // Wrap workflow execution methods with tracing context\n if (WORKFLOW_METHODS_TO_WRAP.includes(prop as string)) {\n // Handle createRun and createRun methods differently\n if (prop === 'createRun' || prop === 'createRun') {\n return async (options: any = {}) => {\n const run = await (target as any)[prop](options);\n return run ? wrapRun(run, tracingContext) : run;\n };\n }\n\n // Handle other methods like execute\n return (input: any, options: any = {}) => {\n return (target as any)[prop](input, {\n ...options,\n ...createObservabilityContext(tracingContext),\n });\n };\n }\n\n // Bind functions to preserve 'this' context for private member access\n const value = (target as any)[prop];\n return typeof value === 'function' ? value.bind(target) : value;\n } catch (error) {\n console.warn('Tracing: Failed to wrap workflow method, falling back to original', error);\n const value = (target as any)[prop];\n return typeof value === 'function' ? value.bind(target) : value;\n }\n },\n });\n } catch (error) {\n console.warn('Tracing: Failed to create workflow proxy, using original instance', error);\n return workflow;\n }\n}\n\n/**\n * Creates a tracing-aware Run proxy that automatically injects\n * tracing context into start method calls\n */\nfunction wrapRun<T extends object>(run: T, tracingContext: TracingContext): T {\n // Don't wrap if no current span or if using NoOp span\n if (!tracingContext.currentSpan || isNoOpSpan(tracingContext.currentSpan)) {\n return run;\n }\n\n try {\n return new Proxy(run, {\n get(target, prop) {\n try {\n if (prop === 'start') {\n return (startOptions: any = {}) => {\n return (target as any).start({\n ...startOptions,\n ...createObservabilityContext(startOptions.tracingContext ?? tracingContext),\n });\n };\n }\n\n // Pass through all other properties and methods unchanged\n const value = (target as any)[prop];\n return typeof value === 'function' ? value.bind(target) : value;\n } catch (error) {\n console.warn('Tracing: Failed to wrap run method, falling back to original', error);\n const value = (target as any)[prop];\n return typeof value === 'function' ? value.bind(target) : value;\n }\n },\n });\n } catch (error) {\n console.warn('Tracing: Failed to create run proxy, using original instance', error);\n return run;\n }\n}\n","/**\n * RAG ingestion helpers.\n *\n * Provides a thin wrapper around `getOrCreateSpan` for starting a\n * `RAG_INGESTION` root span without reaching into observability internals.\n *\n * Two surfaces:\n * - `startRagIngestion(...)` — manual: returns `{ span, observabilityContext }`,\n * caller is responsible for `.end()` / `.error()`.\n * - `withRagIngestion(opts, fn)` — scoped: runs `fn(observabilityContext)`,\n * automatically attaches the return value as the span's `output` and\n * routes thrown errors to `span.error(...)`.\n *\n * ## Observability data\n *\n * Mastra emits raw span data (start/end timestamps, attributes, input,\n * output) — exporters and downstream consumers do their own aggregation.\n * The shapes below are designed to make the common derivations cheap:\n *\n * - **Duration**: every span has start/end, so per-operation latency\n * falls out for free.\n * - **Embedding cost**: `RAG_EMBEDDING` spans (and only `RAG_EMBEDDING`\n * spans) expose `attributes.usage` using the same `UsageStats` shape as\n * `MODEL_GENERATION`, so any existing LLM cost-extraction pipeline that\n * parses `usage.inputTokens` handles embeddings uniformly. Cost\n * dimensions are `{model, provider, mode}` (mode is `'ingest'` or\n * `'query'`). Token counts are deliberately NOT duplicated on the\n * `RAG_INGESTION` root — aggregating at the root would double-count\n * when summing child spans. Mirrors how `AGENT_RUN` does not carry\n * aggregated `MODEL_GENERATION` usage.\n * - **Vector store throughput**: `RAG_VECTOR_OPERATION` spans carry\n * `{operation, store, indexName}` as attributes; result counts live on\n * `output` (e.g. `output.returned`, `output.vectorCount`).\n * - **Ingestion roll-ups**: `RAG_INGESTION` (root) carries\n * `{vectorStore, indexName, embeddingModel, embeddingProvider}` as\n * attributes and aggregate `usage` summed across child embed calls.\n */\n\nimport { createObservabilityContext } from './context-factory';\nimport { EntityType, SpanType } from './types';\nimport type { GetOrCreateSpanOptions, ObservabilityContext, RagIngestionAttributes, Span } from './types';\nimport { getOrCreateSpan } from './utils';\n\nexport type StartRagIngestionOptions = Omit<GetOrCreateSpanOptions<SpanType.RAG_INGESTION>, 'type' | 'entityType'>;\n\nexport interface StartRagIngestionResult {\n /**\n * The RAG_INGESTION span. May be undefined if observability is disabled\n * or no Mastra instance / parent span is available.\n */\n span: Span<SpanType.RAG_INGESTION> | undefined;\n /**\n * Full observability context to thread through chunk / embed / upsert calls.\n * Always defined; falls back to no-op when `span` is undefined.\n */\n observabilityContext: ObservabilityContext;\n}\n\n/**\n * Start a `RAG_INGESTION` root span. Caller is responsible for closing it\n * via `result.span?.end(...)` or `result.span?.error(...)`.\n *\n * Prefer `withRagIngestion` for the common try/catch/end flow.\n *\n * @example\n * ```ts\n * const { span, observabilityContext } = startRagIngestion({\n * mastra,\n * name: 'docs ingestion',\n * attributes: { vectorStore: 'pgvector', indexName: 'docs' },\n * });\n * try {\n * const chunks = await doc.chunk({ observabilityContext });\n * // ...\n * span?.end({ output: { chunkCount: chunks.length } });\n * } catch (err) {\n * span?.error({ error: err as Error });\n * throw err;\n * }\n * ```\n */\nexport function startRagIngestion(options: StartRagIngestionOptions): StartRagIngestionResult {\n const span = getOrCreateSpan<SpanType.RAG_INGESTION>({\n ...options,\n entityType: EntityType.RAG_INGESTION,\n type: SpanType.RAG_INGESTION,\n });\n\n const observabilityContext = createObservabilityContext(span ? { currentSpan: span } : undefined);\n\n return { span, observabilityContext };\n}\n\n/**\n * Run an async function inside a `RAG_INGESTION` root span.\n *\n * The callback receives an `ObservabilityContext` to thread into chunk,\n * embed, and vector-store calls. The return value is attached to the span\n * as `output`. Thrown errors are recorded via `span.error(...)` and\n * re-thrown.\n *\n * @example\n * ```ts\n * await withRagIngestion(\n * {\n * mastra,\n * name: 'docs ingestion',\n *