Skip to main content

Storage exporter

The MastraStorageExporter persists traces to your configured storage backend, making them accessible through Studio. It requires no external services.

note

MastraStorageExporter was previously called DefaultExporter. The original DefaultExporter class is still exported from @mastra/observability for backward compatibility, but it's deprecated. New code should use MastraStorageExporter.

Production Observability

Observability data can quickly overwhelm general-purpose databases in production. For high-traffic applications, route the observability storage domain to ClickHouse through composite storage. See Production Recommendations for details.

Configuration
Direct link to Configuration

Prerequisites
Direct link to Prerequisites

  1. Storage Backend: Configure a storage provider (libSQL, PostgreSQL, etc.)
  2. Studio: Install for viewing traces locally

Basic Setup
Direct link to Basic Setup

src/mastra/index.ts
import { Mastra } from '@mastra/core'
import { Observability, MastraStorageExporter } from '@mastra/observability'
import { LibSQLStore } from '@mastra/libsql'

export const mastra = new Mastra({
storage: new LibSQLStore({
id: 'mastra-storage',
url: 'file:./mastra.db', // Required for trace persistence
}),
observability: new Observability({
configs: {
local: {
serviceName: 'my-service',
exporters: [new MastraStorageExporter()],
},
},
}),
})

Include MastraStorageExporter in your observability configuration:

import { Mastra } from '@mastra/core'
import {
Observability,
MastraStorageExporter,
MastraPlatformExporter,
SensitiveDataFilter,
} from '@mastra/observability'
import { LibSQLStore } from '@mastra/libsql'

export const mastra = new Mastra({
storage: new LibSQLStore({
id: 'mastra-storage',
url: 'file:./mastra.db',
}),
observability: new Observability({
configs: {
default: {
serviceName: 'mastra',
exporters: [
new MastraStorageExporter(), // Persists observability events to Mastra Storage
new MastraPlatformExporter(), // Sends observability events to Mastra platform (requires MASTRA_PLATFORM_ACCESS_TOKEN)
],
spanOutputProcessors: [new SensitiveDataFilter()],
},
},
}),
})

Export to storage and Platform
Direct link to Export to storage and Platform

On Mastra Platform, a nonempty MASTRA_DEPLOYMENT_ID disables MastraStorageExporter and the deprecated DefaultExporter by default. Other exporters remain enabled. The opt-in below applies only to MastraStorageExporter. Deprecated DefaultExporter stays disabled on Platform even with the flag set to true. Migrate to MastraStorageExporter to use this option.

Local storage remains blocked even with the opt-in

retainStorageExporterOnPlatform: true doesn't enable in-memory, local libSQL, or DuckDB observability writes on Platform. When the observability domain reports local persistence, the built-in storage exporter stays disabled: it won't persist traces, logs, metrics, scores, or feedback to that database. Other exporters continue to receive events.

With updated adapters and retainStorageExporterOnPlatform: true, the behavior on Platform is:

Observability databaseStorage export on Platform
PostgreSQLAllowed
Remote libSQL/Turso (libsql:// or https://)Allowed
Local libSQL (file: or :memory:)Blocked
libSQL embedded replica with a remote sync URLBlocked: it still writes locally
DuckDB, including in-memory databasesBlocked
InMemoryStore, including the fallback when no storage is configuredBlocked

To keep writing to your own remote database alongside Platform observability, set retainStorageExporterOnPlatform to true on the observability config:

import { Observability, MastraStorageExporter, MastraPlatformExporter } from '@mastra/observability'

const observability = new Observability({
configs: {
production: {
serviceName: 'my-service',
retainStorageExporterOnPlatform: true,
exporters: [new MastraStorageExporter(), new MastraPlatformExporter()],
},
},
})

Pass this observability instance to new Mastra() alongside your configured storage provider. The option applies only to this config, including exporters registered later. It doesn't configure a database or add exporters automatically. Other configs keep the default suppression unless they also opt in.

The restriction applies to the observability domain, which can use a different provider from your default application storage. It doesn't disable other storage domains. Outside Platform, local observability storage continues to work.

To enable this guard, upgrade @mastra/core and any @mastra/libsql or @mastra/duckdb adapters alongside @mastra/observability: older or custom adapters without the persistenceLocation capability retain the existing opt-in behavior and aren't checked for local persistence.

A blocked exporter logs a warning during initialization, but the restriction applies even if your logging setup hides that warning. If dual export isn't writing to your database, check the observability provider against the table above and configure a remote database. Retrying or increasing the batch interval won't enable a blocked exporter.

Studio
Direct link to Studio

Access your traces through Studio:

  1. Start Studio
  2. Navigate to Observability
  3. Filter and search your local traces
  4. Inspect detailed span information

Tracing strategies
Direct link to Tracing strategies

MastraStorageExporter automatically selects the optimal tracing strategy based on your storage provider. You can also override this selection if needed.

Available Strategies
Direct link to Available Strategies

StrategyDescriptionUse Case
realtimeProcess each event immediatelyDevelopment, debugging, low traffic
batch-with-updatesBuffer events and batch write with full lifecycle supportLow volume Production
insert-onlyOnly process completed spans, ignore updatesHigh volume Production
event-sourcedWrite one row when a span starts and another when it ends, then collapse them on readHigh volume Production, in-progress traces

Strategy Configuration
Direct link to Strategy Configuration

new MastraStorageExporter({
strategy: 'auto', // Default - let storage provider decide
// or explicitly set:
// strategy: 'realtime' | 'batch-with-updates' | 'insert-only' | 'event-sourced'

// Batching configuration (applies to both batch-with-updates and insert-only)
maxBatchSize: 1000, // Max spans per batch
maxBatchWaitMs: 5000, // Max wait before flushing
maxBufferSize: 10000, // Max spans to buffer
})

Storage provider support
Direct link to Storage provider support

Different storage providers support different tracing strategies. Some providers support observability for production workloads, while others are intended primarily for local development.

If you set the strategy to 'auto', the MastraStorageExporter automatically selects the optimal strategy for the storage provider. If you set an explicit strategy that the storage provider doesn't support, the exporter logs a warning and falls back to the provider's preferred strategy.

Providers with Observability Support
Direct link to Providers with Observability Support

Storage ProviderPreferred StrategySupported StrategiesRecommended Use
ClickHouseinsert-onlyinsert-onlyProduction (high-volume)
PostgresStorebatch-with-updatesbatch-with-updates, insert-onlyProduction (low volume)
PostgresStoreVNextevent-sourcedevent-sourcedProduction (high-volume)
MSSQLbatch-with-updatesbatch-with-updates, insert-onlyProduction (low volume)
MongoDBbatch-with-updatesbatch-with-updates, insert-onlyProduction (low volume)
OracleDBbatch-with-updatesbatch-with-updates, insert-onlyProduction (low volume)
libSQLbatch-with-updatesbatch-with-updates, insert-onlyDefault storage, good for development
note

Under insert-only, only completed spans are persisted, and span start and update events are ignored. A trace therefore becomes visible in Studio only after its root span ends, and filtering traces by status: 'running' returns no results. PostgresStoreVNext uses event-sourced tracing instead, so in-progress traces remain visible while preserving append-only writes.

Providers without Observability Support
Direct link to Providers without Observability Support

The following storage providers don't support the observability domain. If you're using one of these providers and need observability, use composite storage to route observability data to a supported provider:

Strategy Benefits
Direct link to Strategy Benefits

  • realtime: Immediate visibility, best for debugging
  • batch-with-updates: 10-100x throughput improvement, full span lifecycle
  • insert-only: Additional 70% reduction in database operations, perfect for analytics
  • event-sourced: Append-only like insert-only, but in-progress traces stay visible in Studio while a run executes

Production recommendations
Direct link to Production recommendations

Observability data grows quickly in production environments. A single agent interaction can generate hundreds of spans, and high-traffic applications can produce thousands of traces per day. Most general-purpose databases aren't optimized for this write-heavy, append-only workload.

ClickHouse is a columnar database designed for high-volume analytics workloads. It's the recommended choice for production observability because:

  • Optimized for writes: Handles millions of inserts per second
  • Efficient compression: Reduces storage costs for trace data
  • Fast queries: Columnar storage enables quick trace lookups and aggregations
  • Time-series native: Built-in support for time-based data retention and partitioning

Using Composite Storage
Direct link to Using Composite Storage

If you're using a provider without observability support (like Convex or DynamoDB) or want to optimize performance, use composite storage to route observability data to ClickHouse while keeping other data in your primary database.

Batching behavior
Direct link to Batching behavior

Flush Triggers
Direct link to Flush Triggers

For both batch strategies (batch-with-updates and insert-only), traces are flushed to storage when any of these conditions are met:

  1. Size trigger: Buffer reaches maxBatchSize spans
  2. Time trigger: maxBatchWaitMs elapsed since first event
  3. Emergency flush: Buffer approaches maxBufferSize limit
  4. Shutdown: Force flush all pending events

Error handling
Direct link to Error handling

The MastraStorageExporter includes reliable error handling for production use:

  • Retry Logic: Exponential backoff (500ms, 1s, 2s, 4s)
  • Transient Failures: Automatic retry with backoff
  • Persistent Failures: Drop batch after 4 failed attempts
  • Buffer Overflow: Prevent memory issues during storage outages

Dropped observability events
Direct link to Dropped observability events

DefaultExporter emits structured drop events when it can't persist observability data. Register an exporter or bridge with onDroppedEvent to forward these drops to alerting or monitoring.

Events are dropped for these reasons:

  • unsupported-storage: The storage provider doesn't implement the signal type.
  • retry-exhausted: The exporter retried a batch up to maxRetries times and then dropped it.
  • span-not-updatable: Under realtime or batch-with-updates, a span update or end reached storage but didn't apply, because the span was never stored or had already ended. Ended spans don't change.

The following example demonstrates forwarding drop details to a monitoring endpoint:

src/mastra/observability.ts
import { BaseExporter } from '@mastra/observability'
import type { ObservabilityDropEvent, TracingEvent } from '@mastra/core/observability'

class DropAlertExporter extends BaseExporter {
name = 'drop-alerts'

async onDroppedEvent(event: ObservabilityDropEvent) {
await fetch('https://monitoring.example.com/observability-drops', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
count: event.count,
signal: event.signal,
reason: event.reason,
exporterName: event.exporterName,
}),
})
}

protected async _exportTracingEvent(_event: TracingEvent) {}
}

Configuration examples
Direct link to Configuration examples

// Zero config - recommended for most users
new MastraStorageExporter()

// Development override
new MastraStorageExporter({
strategy: 'realtime', // Immediate visibility for debugging
})

// High-throughput production
new MastraStorageExporter({
maxBatchSize: 2000, // Larger batches
maxBatchWaitMs: 10000, // Wait longer to fill batches
maxBufferSize: 50000, // Handle longer outages
})

// Low-latency production
new MastraStorageExporter({
maxBatchSize: 100, // Smaller batches
maxBatchWaitMs: 1000, // Flush quickly
})