SpacetimeDB Debugging and Troubleshooting Tips: Complete Guide to Logging and Diagnostics

SpacetimeDB ships with a unified, tiered logging architecture that spans from low-level WASM host operations to client-side CLI tools, enabling developers to debug reducers via real-time log streaming, programmatic inspection, and debug builds without exposing sensitive user data.

The clockworklabs/SpacetimeDB repository provides a comprehensive diagnostic system that unifies logging across Rust, TypeScript, and C# modules. Understanding how the database logger, host injection layer, and CLI tools interact allows you to quickly isolate faulty reducers, filter noise in production, and monitor system health.

Understanding the SpacetimeDB Logging Architecture

The platform implements a five-layer logging stack that transports diagnostic messages from your module code to your terminal or test harness. Each layer is designed to work seamlessly across language boundaries.

Core Logging Layers

  • Database Logger: Located in crates/core/src/database_logger.rs, this service stores log records per replica and exposes a tail API. It defines the LogLevel enum and the write routine that transforms a Record struct into a LogEvent.

  • Host-Side Injection: The inject_logs method in crates/core/src/host/module_host.rs is called from generated reducer wrappers, automatically forwarding diagnostics from your business logic to the logger.

  • Console Bridge: Found in crates/core/src/host/instance_env.rs, this layer intercepts WASM syscalls such as console.log and console.debug, mapping them to the appropriate LogLevel before transmission.

  • CLI Log Viewer: The spacetime logs command, implemented in crates/cli/src/subcommands/logs.rs, streams output from the database logger and supports --level, --since, and --follow flags.

  • Programmatic Access: The testing harness in crates/testing/src/modules.rs exposes ModuleHandle::read_log, which calls client.module().database_logger().tail to fetch records directly without CLI overhead.

Essential Debugging Workflows

Emit Logs at the Appropriate Severity Level

Use debug-level logging for transient diagnostics, info for normal events, warn for recoverable anomalies, and error for failures that abort a reducer. The logger automatically prefixes records with the reducer name and timestamp.

Rust Example:

use spacetimedb::{reducer, ReducerContext};

#[reducer]
pub fn transfer(ctx: &ReducerContext, from: u64, to: u64, amount: u32) -> Result<(), String> {
    log::info!("Transfer request: {} → {} ({})", from, to, amount);
    if amount == 0 {
        log::warn!("Zero-amount transfer requested by {}", ctx.sender);
    }
    log::debug!("Reducer invoked by {} with ctx={:?}", ctx.sender, ctx);
    Ok(())
}

TypeScript Example:

import { spacetimedb } from 'spacetimedb/server';

spacetimedb.reducer('processData', { value: t.u32() }, (ctx, { value }) => {
  console.debug(`processData called by ${ctx.sender}, value=${value}`);
  console.log(`Processing ${value}`);
});

C# Example:

using SpacetimeDB;

public static partial class Module
{
    [SpacetimeDB.Reducer]
    public static void ProcessData(ReducerContext ctx, uint value)
    {
        Log.Debug($"ProcessData called by {ctx.Sender}, value={value}");
        Log.Info($"Processing {value}");
    }
}

Build with Debug Mode for Fast Iteration

Compile your Wasm module with debug symbols and no optimizations to speed up the edit-run cycle. This does not affect production builds.

spacetime build --debug

Stream Logs in Real-Time with the CLI

Use the --follow flag to stream new records as they are written, similar to tail -f. Combine with --level to focus on specific severities.


# Stream only warnings and errors

spacetime logs --follow --level warn my-game-db

Filter Logs by Time Windows

When investigating events that occurred at specific times, use --since with an ISO-8601 timestamp to replay the relevant slice.

spacetime logs --since "2024-10-01T12:00:00Z" my-game-db

Access Logs Programmatically in Tests

Avoid race conditions in integration tests by reading logs directly through the client API rather than shelling out to the CLI.

let logs = module_handle.read_log(Some(100)).await; // fetch up to 100 recent lines
println!("Recent logs:\n{logs}");

This method is implemented in crates/testing/src/modules.rs as ModuleHandle::read_log.

Troubleshooting Common Issues

Missing Reducer Logs

If logs do not appear, ensure your reducer function is compiled with the correct attribute. In Rust, verify the #[reducer] macro is present; in TypeScript, ensure the function is registered with spacetimedb.reducer. These macros inject the necessary inject_logs calls.

No Output in Production

By default, the global log level is set to info. To capture debug output in a deployed database, modify standalone.toml to set logs.level = "debug" or use the CLI:

spacetime config set logs.level debug

Interpreting Panic-Level Logs

Panic logs indicate unrecoverable internal errors, such as host-side invariant violations. These bypass all filters and appear regardless of the logs.level setting. Consult the target field in the log record to identify the specific module function that caused the failure.

Summary

  • SpacetimeDB provides a unified logging system across Rust, TypeScript, and C# modules via the database logger in crates/core/src/database_logger.rs.

  • Use spacetime build --debug for rapid development iterations with full symbol information.

  • Stream and filter logs using spacetime logs with --follow, --level, and --since flags.

  • Access logs programmatically in tests via ModuleHandle::read_log to avoid CLI race conditions.

  • Configure production logging levels in standalone.toml or via spacetime config set logs.level.

  • Panic-level messages bypass filters and indicate critical internal errors requiring immediate attention.

Frequently Asked Questions

How do I enable debug logging in a production SpacetimeDB database?

Set the logs.level configuration to "debug" either by editing the standalone.toml configuration file or by running spacetime config set logs.level debug. By default, production environments filter to info level and above to reduce noise.

Why are my reducer logs not appearing in the output?

Ensure your reducer function is properly decorated with the language-specific attribute, such as #[reducer] in Rust or [SpacetimeDB.Reducer] in C#. These macros generate the inject_logs calls found in crates/core/src/host/module_host.rs that bridge your code to the logging system.

What is the difference between spacetime logs --follow and programmatic log access?

The --follow flag provides a human-readable stream suitable for active development monitoring, while programmatic access via ModuleHandle::read_log in crates/testing/src/modules.rs returns structured data directly to your test code, eliminating race conditions between the test execution and CLI polling.

How do I interpret panic-level log messages?

Panic logs indicate unrecoverable internal errors, such as WASM host invariant violations, and bypass all severity filters. Each panic record includes a target field pointing to the specific module function responsible and a stack trace in the message body. These require immediate code review as they represent system-level failures rather than application errors.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →