|
1 | 1 | # Timetracer |
2 | 2 |
|
3 | | -**Time-travel debugging for FastAPI and Flask** - Record API requests, replay with mocked dependencies. |
| 3 | +**Time-travel debugging for FastAPI and Flask** — Record API requests, replay with mocked dependencies. |
4 | 4 |
|
5 | 5 | [](https://github.com/usv240/timetracer/actions) |
6 | 6 | [](https://www.python.org/downloads/) |
|
9 | 9 |
|
10 | 10 | --- |
11 | 11 |
|
12 | | -## What is it? |
| 12 | +## What is Timetracer? |
13 | 13 |
|
14 | | -Timetracer records real API request executions into portable **cassettes** and replays them locally by mocking dependency calls (HTTP, database, Redis). |
| 14 | +Timetracer captures real API requests into portable **cassettes** and replays them with mocked dependencies (HTTP calls, database queries, Redis commands). |
15 | 15 |
|
16 | | -*Record once. Replay anywhere. Debug faster.* |
| 16 | +> Record once. Replay anywhere. Debug faster. |
17 | 17 |
|
18 | | -**Use cases:** |
19 | | -- Reproduce production/staging bugs quickly |
20 | | -- Build regression tests from real traffic |
21 | | -- Run offline demos |
22 | | -- Detect performance regressions |
23 | | -- Diff behavior between runs |
| 18 | +**Common use cases:** |
| 19 | + |
| 20 | +- Reproduce production bugs locally without access to external services |
| 21 | +- Build regression tests from real traffic patterns |
| 22 | +- Run demos offline with pre-recorded data |
| 23 | +- Detect performance regressions between releases |
| 24 | +- Compare behavior between different code versions |
| 25 | + |
| 26 | +--- |
24 | 27 |
|
25 | 28 | ## Installation |
26 | 29 |
|
27 | 30 | ```bash |
28 | | -# Core + all plugins |
29 | 31 | pip install timetracer[all] |
30 | | - |
31 | | -# Or just what you need |
32 | | -pip install timetracer[fastapi,httpx] # FastAPI + HTTP |
33 | | -pip install timetracer[flask,httpx] # Flask + HTTP |
34 | | -pip install timetracer[sqlalchemy] # Database |
35 | | -pip install timetracer[redis] # Redis |
36 | | -pip install timetracer[s3] # S3 storage |
37 | 32 | ``` |
38 | 33 |
|
39 | | -## Quickstart (3 Steps) |
40 | | - |
41 | | -### Step 1: Install |
| 34 | +Or install only what you need: |
42 | 35 |
|
43 | 36 | ```bash |
44 | | -pip install timetracer[fastapi,httpx] |
| 37 | +pip install timetracer[fastapi,httpx] # FastAPI + HTTP |
| 38 | +pip install timetracer[flask,requests] # Flask + HTTP |
45 | 39 | ``` |
46 | 40 |
|
47 | | -### Step 2: Add 4 Lines to Your App |
| 41 | +--- |
| 42 | + |
| 43 | +## Quick Start |
| 44 | + |
| 45 | +### FastAPI |
48 | 46 |
|
49 | 47 | ```python |
50 | 48 | from fastapi import FastAPI |
51 | | -from timetracer.config import TraceConfig |
52 | | -from timetracer.integrations.fastapi import TimeTraceMiddleware |
53 | | -from timetracer.plugins import enable_httpx |
| 49 | +from timetracer.integrations.fastapi import auto_setup |
54 | 50 |
|
55 | | -app = FastAPI() |
| 51 | +app = auto_setup(FastAPI()) |
56 | 52 |
|
57 | | -# Add these 2 lines to enable Timetracer |
58 | | -config = TraceConfig.from_env() |
59 | | -app.add_middleware(TimeTraceMiddleware, config=config) |
| 53 | +@app.get("/users/{user_id}") |
| 54 | +async def get_user(user_id: int): |
| 55 | + async with httpx.AsyncClient() as client: |
| 56 | + return (await client.get(f"https://api.example.com/users/{user_id}")).json() |
| 57 | +``` |
60 | 58 |
|
61 | | -# Optional: Enable httpx tracking |
62 | | -enable_httpx() |
| 59 | +### Flask |
63 | 60 |
|
64 | | -@app.post("/checkout") |
65 | | -async def checkout(): |
66 | | - async with httpx.AsyncClient() as client: |
67 | | - response = await client.get("https://api.example.com/data") |
68 | | - return {"status": "ok"} |
| 61 | +```python |
| 62 | +from flask import Flask |
| 63 | +from timetracer.integrations.flask import auto_setup |
| 64 | + |
| 65 | +app = auto_setup(Flask(__name__)) |
69 | 66 | ``` |
70 | 67 |
|
71 | | -### Step 3: Record and Replay |
| 68 | +### Record and Replay |
72 | 69 |
|
73 | 70 | ```bash |
74 | | -# Record requests |
| 71 | +# Record mode - captures all external calls |
75 | 72 | TIMETRACER_MODE=record uvicorn app:app |
76 | | -curl -X POST http://localhost:8000/checkout |
77 | | - |
78 | | -# Check cassettes |
79 | | -ls ./cassettes/ |
80 | | -# Output: 2026-01-16/POST__checkout__abc123.json |
| 73 | +curl http://localhost:8000/users/123 |
81 | 74 |
|
82 | | -# Replay (mocked HTTP calls) |
| 75 | +# Replay mode - mocks external calls from cassette |
83 | 76 | TIMETRACER_MODE=replay \ |
84 | | - TIMETRACER_CASSETTE=./cassettes/2026-01-16/POST__checkout__abc123.json \ |
| 77 | + TIMETRACER_CASSETTE=./cassettes/2026-01-16/GET__users_{user_id}__abc.json \ |
85 | 78 | uvicorn app:app |
86 | 79 | ``` |
87 | 80 |
|
88 | | -**Example Output:** |
89 | | - |
90 | | -``` |
91 | | -# Recording |
92 | | -TIMETRACER [OK] recorded POST /checkout id=abc123 status=200 total=523ms deps=http.client:1 |
93 | | - cassette: ./cassettes/2026-01-16/POST__checkout__abc123.json |
94 | | -
|
95 | | -# Replaying |
96 | | -TIMETRACER replay POST /checkout mocked=1 matched=OK runtime=45ms recorded=523ms |
97 | | -``` |
98 | | - |
99 | | -That's it! External HTTP calls are now mocked from the recorded cassette. |
100 | | - |
101 | 81 | --- |
102 | 82 |
|
103 | | -## Flask Integration |
104 | | - |
105 | | -```python |
106 | | -from flask import Flask |
107 | | -from timetracer.integrations.flask import init_app |
108 | | -from timetracer.config import TraceConfig |
| 83 | +## Manual Setup |
109 | 84 |
|
110 | | -app = Flask(__name__) |
111 | | -init_app(app, TraceConfig.from_env()) |
112 | | -``` |
| 85 | +For more control over configuration: |
113 | 86 |
|
114 | | -## Features |
| 87 | +```python |
| 88 | +import httpx |
| 89 | +from fastapi import FastAPI |
| 90 | +from timetracer import TraceConfig |
| 91 | +from timetracer.integrations.fastapi import TimeTraceMiddleware |
| 92 | +from timetracer.plugins import enable_httpx |
115 | 93 |
|
116 | | -### Frameworks |
117 | | -- **FastAPI** - Full middleware support |
118 | | -- **Flask** - WSGI middleware support |
| 94 | +app = FastAPI() |
119 | 95 |
|
120 | | -### Plugins |
121 | | -- **httpx** - Async/sync HTTP client |
122 | | -- **requests** - requests library |
123 | | -- **SQLAlchemy** - Database queries |
124 | | -- **Redis** - Redis commands |
| 96 | +config = TraceConfig( |
| 97 | + mode="record", |
| 98 | + cassette_dir="./my-cassettes", |
| 99 | + errors_only=True, |
| 100 | +) |
| 101 | +app.add_middleware(TimeTraceMiddleware, config=config) |
125 | 102 |
|
126 | | -### Storage |
127 | | -- **Local filesystem** - Default |
128 | | -- **S3** - AWS S3 and S3-compatible (MinIO) |
| 103 | +enable_httpx() |
| 104 | +``` |
129 | 105 |
|
130 | | -### Analysis and Tools |
131 | | -- **Diff engine** - Compare cassettes |
132 | | -- **HTML timeline** - Visualize execution |
133 | | -- **Hybrid replay** - Mock some deps, keep others live |
134 | | -- **Search and Index** - Find cassettes by endpoint, status, date |
| 106 | +--- |
135 | 107 |
|
136 | 108 | ## Configuration |
137 | 109 |
|
| 110 | +All settings are controlled via environment variables: |
| 111 | + |
138 | 112 | | Variable | Description | Default | |
139 | 113 | |----------|-------------|---------| |
140 | 114 | | `TIMETRACER_MODE` | `off`, `record`, `replay` | `off` | |
141 | | -| `TIMETRACER_DIR` | Cassette directory | `./cassettes` | |
142 | | -| `TIMETRACER_CASSETTE` | Replay cassette path | - | |
143 | | -| `TIMETRACER_SAMPLE_RATE` | Record fraction (0-1) | `1.0` | |
144 | | -| `TIMETRACER_ERRORS_ONLY` | Only record errors | `false` | |
145 | | -| `TIMETRACER_MOCK_PLUGINS` | Plugins to mock | all | |
146 | | -| `TIMETRACER_LIVE_PLUGINS` | Plugins to keep live | none | |
147 | | - |
148 | | -## CLI |
| 115 | +| `TIMETRACER_DIR` | Cassette storage directory | `./cassettes` | |
| 116 | +| `TIMETRACER_CASSETTE` | Path to cassette file (replay mode) | — | |
| 117 | +| `TIMETRACER_SAMPLE_RATE` | Fraction of requests to record (0-1) | `1.0` | |
| 118 | +| `TIMETRACER_ERRORS_ONLY` | Only record error responses | `false` | |
| 119 | +| `TIMETRACER_MOCK_PLUGINS` | Plugins to mock during replay | all | |
| 120 | +| `TIMETRACER_LIVE_PLUGINS` | Plugins to keep live during replay | none | |
149 | 121 |
|
150 | | -```bash |
151 | | -# List cassettes |
152 | | -timetracer list --dir ./cassettes |
| 122 | +--- |
153 | 123 |
|
154 | | -# Show cassette details |
155 | | -timetracer show ./cassettes/.../POST__checkout.json --events |
| 124 | +## Features |
156 | 125 |
|
157 | | -# Diff two cassettes |
158 | | -timetracer diff --a cassette1.json --b cassette2.json |
| 126 | +| Category | Supported | |
| 127 | +|----------|-----------| |
| 128 | +| **Frameworks** | FastAPI, Flask | |
| 129 | +| **HTTP Clients** | httpx, requests | |
| 130 | +| **Databases** | SQLAlchemy | |
| 131 | +| **Cache** | Redis | |
| 132 | +| **Storage** | Local filesystem, AWS S3 | |
| 133 | +| **Tools** | CLI, diff engine, HTML timeline | |
159 | 134 |
|
160 | | -# Generate timeline |
161 | | -timetracer timeline ./cassettes/POST__checkout.json --open |
| 135 | +--- |
162 | 136 |
|
163 | | -# Search cassettes |
164 | | -timetracer search --endpoint /checkout --method POST --errors |
165 | | -timetracer index --dir ./cassettes |
| 137 | +## CLI |
166 | 138 |
|
167 | | -# S3 operations |
168 | | -timetracer s3 upload ./cassettes/ -b my-bucket |
169 | | -timetracer s3 sync up -d ./cassettes -b my-bucket |
| 139 | +```bash |
| 140 | +timetracer list --dir ./cassettes # List all cassettes |
| 141 | +timetracer show ./cassettes/GET__users.json # Show cassette details |
| 142 | +timetracer diff --a old.json --b new.json # Compare two cassettes |
| 143 | +timetracer timeline ./cassettes/GET__users.json --open # Generate timeline |
170 | 144 | ``` |
171 | 145 |
|
| 146 | +--- |
| 147 | + |
172 | 148 | ## Security |
173 | 149 |
|
174 | | -By default, Timetracer: |
175 | | -- Removes `Authorization`, `Cookie`, `Set-Cookie` headers |
176 | | -- Masks sensitive body keys (`password`, `token`, etc.) |
177 | | -- Captures bodies only on errors by default |
178 | | -- Enforces size limits (64KB default) |
| 150 | +Timetracer automatically protects sensitive data: |
| 151 | + |
| 152 | +- Removes `Authorization`, `Cookie`, and `Set-Cookie` headers |
| 153 | +- Masks sensitive fields like `password`, `token`, `api_key` in request/response bodies |
| 154 | +- Enforces a 64KB body size limit to prevent large data captures |
| 155 | + |
| 156 | +--- |
179 | 157 |
|
180 | 158 | ## Documentation |
181 | 159 |
|
182 | | -- [Quickstart](docs/quickstart.md) |
183 | | -- [Configuration](docs/configuration.md) |
184 | | -- [Plugins](docs/plugins.md) |
| 160 | +- [Quick Start Guide](docs/quickstart.md) |
| 161 | +- [Configuration Reference](docs/configuration.md) |
| 162 | +- [Plugin Guide](docs/plugins.md) |
185 | 163 | - [Flask Integration](docs/flask.md) |
186 | | -- [SQLAlchemy Plugin](docs/sqlalchemy.md) |
187 | | -- [S3 Storage](docs/s3-storage.md) |
188 | | -- [Security](docs/security.md) |
189 | | - |
190 | | -## Roadmap |
| 164 | +- [Security Best Practices](docs/security.md) |
191 | 165 |
|
192 | | -- [x] v0.1-v0.3: Core, FastAPI, httpx, requests, diff, timeline |
193 | | -- [x] v1.0: Hybrid replay, schema v1.0, CI/CD, docs |
194 | | -- [x] v2.0: SQLAlchemy, Redis, Flask, S3 store |
195 | | -- [x] v2.1: Cassette search/index |
| 166 | +--- |
196 | 167 |
|
197 | 168 | ## Contributing |
198 | 169 |
|
199 | | -Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md). |
| 170 | +Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. |
200 | 171 |
|
201 | 172 | ## License |
202 | 173 |
|
203 | | -MIT - see [LICENSE](LICENSE). |
| 174 | +MIT License. See [LICENSE](LICENSE) for details. |
0 commit comments