DEV Community

Cover image for I Got Tired of "It Works on My Machine" So I Built a Time-Travel Debugger
Ujwal Vanjare
Ujwal Vanjare

Posted on

I Got Tired of "It Works on My Machine" So I Built a Time-Travel Debugger

Last year, I spent three hours debugging a checkout bug that only happened in production. The external payment API was returning a slightly different response format than what we saw in staging. By the time I figured it out, I'd burned half my day and my patience.

That was the last straw.

I started building Timetracer that weekend. Now, when something breaks in production, I can capture the exact request, complete with every external API call, database query, and cache operation, and replay it on my laptop. No more guessing. No more "can you add some logging and redeploy?"

This post is about why I built it, how it works, and why you might want to use it too.


The Debugging Problem Nobody Talks About

Here's a scenario you've probably lived through:

A customer reports that their order failed. You check the logs. You see the error. You try to reproduce it locally. But your local setup hits a different database, a sandbox payment API, and your Redis is empty. The bug doesn't happen.

You add more logging. Redeploy. Wait for it to happen again. Check the new logs. Still can't reproduce it. Repeat.

Or maybe you're working on a feature that integrates with a third-party API, but that API is rate-limited, or requires special credentials, or just goes down at random times. Every time you run your code, you're making real API calls. Testing becomes painful.

Or you're trying to demo something to your team, but the staging environment is down. Or slow. Or someone else is testing migrations on it.

These problems have something in common: your code depends on things outside your control, and that makes debugging and development unpredictable.


What If You Could Record Everything?

The idea behind Timetracer is simple: when your API handles a request, record everything that happens. Not just the request and response, but every external call your code makes.

  • That HTTP call to Stripe? Recorded.
  • That SELECT query to Postgres? Recorded.
  • That Redis GET? Recorded.

All of it goes into a JSON file we call a "cassette" (borrowing the term from VCR.py, which does something similar but only for HTTP).

Later, when you want to debug, you load that cassette and replay the request. Timetracer intercepts all the external calls and returns the recorded responses instead of making real ones.

Same input. Same external responses. Same bug. But now it's on your laptop, where you can step through the code, add breakpoints, and actually figure out what went wrong.


How It Actually Works

Let's get into the technical stuff.

Setup

If you're using FastAPI, adding Timetracer is two lines:

from fastapi import FastAPI
from timetracer.integrations.fastapi import auto_setup

app = auto_setup(FastAPI())
Enter fullscreen mode Exit fullscreen mode

That's it. The auto_setup function adds middleware that handles recording and replaying.

For Flask, it's similar:

from flask import Flask
from timetracer.integrations.flask import auto_setup

app = auto_setup(Flask(__name__))
Enter fullscreen mode Exit fullscreen mode

You control the behavior with environment variables:

# Record mode - captures everything
export TIMETRACER_MODE=record
export TIMETRACER_DIR=./cassettes

python app.py
Enter fullscreen mode Exit fullscreen mode

Now make some requests to your app. Each request creates a cassette file.

What's In a Cassette?

Here's a simplified version of what gets saved:

{
  "request": {
    "method": "POST",
    "path": "/checkout",
    "body": {"cart_id": "abc123", "user_id": "user_456"}
  },
  "response": {
    "status": 200,
    "body": {"order_id": "order_789"},
    "duration_ms": 342
  },
  "events": [
    {
      "type": "http.client",
      "method": "POST",
      "url": "https://api.stripe.com/v1/charges",
      "request_body": {"amount": 2999, "currency": "usd"},
      "response_body": {"id": "ch_xxx", "status": "succeeded"},
      "duration_ms": 187
    },
    {
      "type": "db.query",
      "query": "INSERT INTO orders (user_id, total) VALUES (?, ?)",
      "params": ["user_456", 29.99],
      "duration_ms": 12
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Everything is there. The incoming request, the outgoing response, and every dependency call in between.

Replaying

To replay, point Timetracer at the cassette:

export TIMETRACER_MODE=replay
export TIMETRACER_CASSETTE=./cassettes/checkout_abc123.json

python app.py
Enter fullscreen mode Exit fullscreen mode

Now when you hit /checkout with the same request, Timetracer intercepts the Stripe call and the database query. Instead of making real calls, it returns the recorded responses.

Your code runs exactly as it did in production, but locally, without needing Stripe credentials or a production database.


The Plugins

Recording HTTP calls is useful, but real applications do more than HTTP. That's why Timetracer has plugins for common dependencies.

HTTP Clients

We support both httpx (async and sync) and the requests library:

from timetracer.plugins import enable_httpx, enable_requests

enable_httpx()    # For httpx calls
enable_requests() # For requests library calls
Enter fullscreen mode Exit fullscreen mode

Databases

SQLAlchemy queries get captured with their SQL and parameters:

from timetracer.plugins import enable_sqlalchemy

engine = create_engine("postgresql://...")
enable_sqlalchemy(engine)
Enter fullscreen mode Exit fullscreen mode

Caching

Redis commands are recorded too:

from timetracer.plugins import enable_redis

enable_redis()
Enter fullscreen mode Exit fullscreen mode

When you replay, all of these return the recorded values. No real database connections. No real Redis. No real HTTP calls. Just the data from the cassette.


The Dashboard

After using Timetracer for a few weeks, I got tired of opening JSON files to find the cassette I needed. So I built a dashboard.

timetracer serve --dir ./cassettes --open
Enter fullscreen mode Exit fullscreen mode

This starts a local web server with a table of all your cassettes. You can:

  • Sort by time, method, status, or duration
  • Filter by endpoint, HTTP method, or status code
  • Click a row to see full details
  • View the dependency timeline
  • See the raw JSON
  • Replay directly from the browser

Error responses show up with red highlighting, and slow requests (>1 second) get a warning indicator. If a request threw an exception, you see the full Python stack trace.

It's nothing fancy, just a single HTML file with some JavaScript, but it makes browsing through cassettes way faster than grepping through JSON.


Dealing With Sensitive Data

Early on, I realized that cassettes could contain passwords, API keys, and other stuff I definitely didn't want in my Git repo.

So Timetracer automatically redacts sensitive data:

Headers that get stripped:

  • Authorization
  • Cookie
  • Set-Cookie
  • X-API-Key
  • And about 20 others

Body fields that get masked:

  • password, secret, token
  • credit_card, cvv, ssn
  • api_key, private_key
  • And about 100 more patterns

In the latest release (v1.2.0), I added pattern detection for PII, emails, phone numbers, Social Security numbers, credit card numbers. The credit card detection even validates the Luhn checksum to avoid false positives.

A value like user@example.com becomes [REDACTED:EMAIL]. A credit card number becomes [REDACTED:CREDIT_CARD].

The goal is that cassettes should be safe to commit without thinking too hard about what's in them.


Real Use Cases

Debugging Production Bugs

This is the original reason I built it. Something breaks in production, you grab the cassette, replay locally, and debug with full context.

Regression Testing

Once you fix a bug, that cassette becomes a test case. You know exactly what input caused the problem and what the correct behavior should be. Run it against future code changes to make sure the bug doesn't come back.

Offline Development

Working on a flight? At a coffee shop with bad WiFi? If you have cassettes from previous sessions, you can keep developing without hitting real APIs.

Demos

Recording demo scenarios means your demos work even when external services are flaky. No more "let me refresh that" during a presentation.

Performance Analysis

The timeline command generates a waterfall chart showing how long each dependency took:

timetracer timeline ./cassette.json --open
Enter fullscreen mode Exit fullscreen mode

You can see exactly where time is being spent. Is it the database query? The third-party API? The Redis lookup? It's all right there.


What About VCR.py?

VCR.py is great. I've used it for years. But it only records HTTP calls.

In a typical web application, a single API request might:

  1. Query the database for user info
  2. Call an external API for pricing
  3. Check a cache for recent activity
  4. Write to another database
  5. Make another API call

VCR.py captures step 2 and 5. The rest? You're on your own.

Timetracer captures all of it. One cassette has everything.

Also, VCR.py is designed for test suites, you decorate individual test functions. Timetracer is designed as middleware. Every request through your app can be recorded. That makes it useful for production debugging, not just testing.


Getting Started

Install with pip:

pip install timetracer[fastapi,httpx]
Enter fullscreen mode Exit fullscreen mode

Or for Flask:

pip install timetracer[flask,requests]
Enter fullscreen mode Exit fullscreen mode

Add the middleware, set the environment variables, and make some requests. Your first cassettes will be in the directory you specified.

The documentation has more details on configuration, the CLI tools, S3 storage, and all the other features I didn't cover here.

GitHub: https://github.com/usv240/timetracer


What's Next

I'm using Timetracer on my own projects, and I keep finding things to improve. Some ideas on the roadmap:

  • Better support for async database drivers
  • GraphQL request parsing
  • Request diffing between cassettes
  • Maybe a VS Code extension?

If you try it out and have feedback, bugs, feature requests, or just thoughts, I'd love to hear it. Open an issue on GitHub or reach out on LinkedIn.

Thanks for reading. I hope Timetracer saves you some debugging time.


Links:

Top comments (0)