http-log-replay 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # 🚦 Traffic Mirror (http-log-replay)
2
+
3
+ > **Regressions No More.** Record production traffic and replay it against your changes to verify correctness with zero effort.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/http-log-replay.svg)](https://www.npmjs.com/package/http-log-replay)
6
+ [![License: ISC](https://img.shields.io/badge/License-ISC-yellow.svg)](https://opensource.org/licenses/ISC)
7
+
8
+ **Traffic Mirror** is a powerful regression testing tool designed for modern engineering teams. It allows you to **record** HTTP traffic from a live environment (like Production) and **replay** it against two different target environments (e.g., Stable vs. Canary) to instantly detect regressions, bugs, or side-effects.
9
+
10
+ ---
11
+
12
+ ## πŸ“‹ Table of Contents
13
+
14
+ - [🌟 Features](#-features)
15
+ - [πŸš€ Quick Start (Zero Setup)](#-quick-start-zero-setup)
16
+ - [πŸ’» Usage Guide: Web UI](#-usage-guide-web-ui)
17
+ - [πŸ–₯️ Usage Guide: CLI](#️-usage-guide-cli)
18
+ - [🐳 Usage Guide: Docker](#-usage-guide-docker)
19
+ - [πŸ› οΈ Development & Contribution](#️-development--contribution)
20
+ - [πŸ“¦ Maintenance](#-maintenance)
21
+
22
+ ---
23
+
24
+ ## 🌟 Features
25
+
26
+ - **πŸ›‘οΈ Zero-Config Recording**: Acts as a transparent HTTP proxy to capture real requests.
27
+ - **⚑ High-Performance Replay**: Replay thousands of requests in parallel with customizable concurrency.
28
+ - **πŸ” Intelligent Diffing**: compares JSON responses and ignores dynamic fields (like timestamps, UUIDs) that cause false positives.
29
+ - **πŸ“Š Rich Reporting**: Generates detailed HTML reports showing exactly what broke.
30
+ - **πŸ”Œ Swagger Integration**: Auto-generate test traffic from your OpenAPI definitions if you don't have live traffic.
31
+ - **πŸ—οΈ Two Modes**: Full interactive **Web UI** for debugging and a lightweight **CLI** for CI/CD pipelines.
32
+
33
+ ---
34
+
35
+ ## πŸš€ Quick Start (Zero Setup)
36
+
37
+ You don't need to install anything if you have Node.js (v18+) installed. Just run:
38
+
39
+ ```bash
40
+ npx http-log-replay ui --port 4200
41
+ ```
42
+
43
+ This will launch the **Traffic Mirror Dashboard** at [http://localhost:4200](http://localhost:4200).
44
+
45
+ ---
46
+
47
+ ## πŸ’» Usage Guide: Web UI
48
+
49
+ The Web UI is the best way to get started. It guides you through the entire workflow in three simple tabs.
50
+
51
+ ### 1. πŸ”΄ Record / Generate
52
+ - **Manual Mode**: Start the proxy, point your application/client to it, and use your app normally. Requests are saved to a file.
53
+ - **Auto-Generate**: Upload an OpenAPI/Swagger JSON file to automatically generate realistic traffic patterns.
54
+
55
+ ### 2. ▢️ Replay
56
+ - Configure your **Primary** (Stable) and **Secondary** (Test) environments.
57
+ - Set **Concurrency** to speed up large suites.
58
+ - Add **Ignore Fields** (e.g., `createdAt`, `traceId`) to filter out noise in the comparison.
59
+ - Click **Replay & Compare**.
60
+
61
+ ### 3. πŸ“„ Report
62
+ - Instantly view the results.
63
+ - **Green**: Exact match.
64
+ - **Red**: Mismatch (click to expand the JSON diff).
65
+
66
+ ---
67
+
68
+ ## πŸ–₯️ Usage Guide: CLI
69
+
70
+ Perfect for **CI/CD pipelines** or headless environments.
71
+
72
+ ### 1. Record Traffic
73
+ Start a recording proxy on port `3000` forwarding to your real API at `localhost:8080`.
74
+
75
+ ```bash
76
+ npx http-log-replay record --target http://localhost:8080 --port 3000 --out traffic.jsonl
77
+ ```
78
+
79
+ ### 2. Auto-Generate from Swagger
80
+ Generate traffic without manual clicking.
81
+
82
+ ```bash
83
+ npx http-log-replay generate --file ./openapi.json --target http://localhost:3000
84
+ ```
85
+
86
+ ### 3. Replay & Verify
87
+ Replay recorded traffic against two environments and generate an HTML report.
88
+
89
+ ```bash
90
+ npx http-log-replay replay \
91
+ --log traffic.jsonl \
92
+ --primary http://prod-api.com \
93
+ --secondary http://staging-api.com \
94
+ --report report.html \
95
+ --ignore "timestamp,id"
96
+ ```
97
+
98
+ ---
99
+
100
+ ## 🐳 Usage Guide: Docker
101
+
102
+ We provide optimized Docker images for both the UI and CLI.
103
+
104
+ ### Prerequisites
105
+ - Docker & Docker Compose installed.
106
+
107
+ ### Option A: Complete Environment (Recommended)
108
+ Use the included `docker-compose.yml` to run everything.
109
+
110
+ **Start the GUI:**
111
+ ```bash
112
+ docker-compose up gui
113
+ ```
114
+ > Access at [http://localhost:4200](http://localhost:4200). Data is persisted to your host folder.
115
+
116
+ **Run CLI Commands:**
117
+ ```bash
118
+ docker-compose run cli record --target http://host.docker.internal:8080 ...
119
+ ```
120
+
121
+ ### Option B: Manual Docker Run
122
+
123
+ **UI Image:**
124
+ ```bash
125
+ docker run -p 4200:4200 -v $(pwd):/app traffic-mirror-gui
126
+ ```
127
+
128
+ **CLI Image:**
129
+ ```bash
130
+ docker run -v $(pwd):/app traffic-mirror-cli --help
131
+ ```
132
+
133
+ ---
134
+
135
+ ## πŸ› οΈ Development & Contribution
136
+
137
+ We welcome contributions! Here is how to run the project locally for development.
138
+
139
+ ### 1. Setup
140
+ ```bash
141
+ git clone https://github.com/your-username/http-log-replay.git
142
+ cd http-log-replay
143
+ npm install
144
+ ```
145
+
146
+ ### 2. Build the UI
147
+ The UI is built with Angular. You must build it before running the app.
148
+ ```bash
149
+ cd ui
150
+ npm install
151
+ npm run build
152
+ cd ..
153
+ ```
154
+
155
+ ### 3. Run Locally (Dev Mode)
156
+ ```bash
157
+ # Start the full app (Frontend + Backend)
158
+ node index.js ui --port 4200
159
+ ```
160
+
161
+ ### 4. Testing & Code Quality
162
+ We use **Jest** for testing and **ESLint** for code quality.
163
+
164
+ ```bash
165
+ # Run Unit Tests
166
+ npm test
167
+
168
+ # Lint Code
169
+ npm run lint
170
+
171
+ # Format Code
172
+ npm run format
173
+ ```
174
+
175
+ ---
176
+
177
+ ## πŸ“¦ Maintenance
178
+
179
+ ### Publishing to NPM
180
+ 1. **Bump Version**: Update `version` in `package.json`.
181
+ 2. **Build UI**: The `prepublishOnly` script will automatically build the Angular UI.
182
+ 3. **Publish**:
183
+ ```bash
184
+ npm publish
185
+ ```
186
+
187
+ ### File Structure
188
+ - `index.js`: CLI entry point.
189
+ - `recorder.js`: Proxy logic.
190
+ - `replayer.js`: Replay & Diff logic.
191
+ - `server.js`: Express server for the UI.
192
+ - `ui/`: Angular frontend source code.
193
+ - `tests/`: Jest unit tests.
194
+
195
+ ---
196
+
197
+ _Built with ❀️ by the Traffic Mirror Team._
@@ -0,0 +1,19 @@
1
+ module.exports = {
2
+ apps: [
3
+ {
4
+ name: 'traffic-mirror-ui',
5
+ script: './index.js',
6
+ args: 'ui --port 4200',
7
+ instances: 4, // Keep 1 instance to avoid port conflicts with the recorder
8
+ autorestart: true,
9
+ watch: false,
10
+ max_memory_restart: '1G',
11
+ env: {
12
+ NODE_ENV: 'development',
13
+ },
14
+ env_production: {
15
+ NODE_ENV: 'production',
16
+ },
17
+ },
18
+ ],
19
+ };
package/index.js ADDED
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-console */
3
+ const { Command } = require('commander');
4
+ const recorder = require('./recorder');
5
+ const replayAndDiff = require('./replayer');
6
+ const startServer = require('./server');
7
+ const generator = require('./traffic-generator');
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('traffic-mirror')
13
+ .description('Record and Replay HTTP traffic to detect regressions')
14
+ .version('1.0.0');
15
+
16
+ // Command: Record
17
+ program
18
+ .command('record')
19
+ .description('Start a proxy to record traffic to a JSONL file')
20
+ .requiredOption('-t, --target <url>', 'The target URL to proxy to (e.g., http://localhost:8080)')
21
+ .option('-p, --port <number>', 'Port to listen on', '3000')
22
+ .option('-o, --out <file>', 'Output file for logs', 'traffic.jsonl')
23
+ .action((options) => {
24
+ recorder.start(options.target, options.port, options.out);
25
+ });
26
+
27
+ // Command: Replay
28
+ program
29
+ .command('replay')
30
+ .description('Replay logs against two environments and diff the results')
31
+ .requiredOption('-l, --log <file>', 'The log file to replay')
32
+ .requiredOption('-a, --primary <url>', 'Primary environment URL (e.g., Stable)')
33
+ .requiredOption('-b, --secondary <url>', 'Secondary environment URL (e.g., Staging)')
34
+ .option('-r, --report <file>', 'Path to save HTML report', 'report.html')
35
+ .option('-i, --ignore <items>', 'Comma separated list of JSON fields to ignore', (val) =>
36
+ val.split(',')
37
+ )
38
+ .option('-x, --exclude-endpoints <items>', 'List of URL paths to skip', (val) => val.split(',')) // <--- NEW FLAG
39
+ .option('-c, --concurrency <number>', 'Number of concurrent requests', (val) => parseInt(val, 10), 5) // Default 5
40
+ .option('--auth <token>', 'Inject Authorization header (e.g. "Bearer eyJhb...")')
41
+ .action((options) => {
42
+ const injectedHeaders = options.auth ? { Authorization: options.auth } : {};
43
+ // Pass injectedHeaders to the replayer
44
+ replayAndDiff(
45
+ options.log,
46
+ options.primary,
47
+ options.secondary,
48
+ options.report,
49
+ options.ignore || [],
50
+ injectedHeaders,
51
+ options.excludeEndpoints || [],
52
+ () => { }, // Empty callback for CLI
53
+ options.concurrency || 1
54
+ );
55
+ });
56
+
57
+ program
58
+ .command('ui')
59
+ .description('Start the Web Interface')
60
+ .option('-p, --port <number>', 'Port for the UI', '4200')
61
+ .action((options) => {
62
+ startServer(options.port);
63
+ });
64
+
65
+ program
66
+ .command('generate')
67
+ .description('Auto-generate traffic from Swagger file')
68
+ .option('-t, --target <url>', 'Proxy URL', 'http://localhost:3000')
69
+ .option('-f, --file <path>', 'Swagger file path', './full_documentation.json')
70
+ .option('-x, --exclude <items>', 'Comma separated list of endpoints to exclude', (val) =>
71
+ val.split(',')
72
+ ) // <--- NEW OPTION
73
+ .option('-s, --source <url>', 'Source Server URL', 'http://localhost:1338')
74
+ .action(async (options) => {
75
+ try {
76
+ console.log('πŸš€ Starting Traffic Generation...');
77
+
78
+ // Pass options.exclude (or empty array) as 3rd arg
79
+ await generator.run(
80
+ options.target,
81
+ options.file,
82
+ options.exclude || [],
83
+ (log) => {
84
+ console.log(log.message);
85
+ },
86
+ options.source
87
+ );
88
+
89
+ console.log('βœ… Done.');
90
+ } catch (e) {
91
+ console.error('❌ Error:', e.message);
92
+ }
93
+ });
94
+
95
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "http-log-replay",
3
+ "version": "1.0.0",
4
+ "description": "Traffic Mirror is a regression testing tool that records HTTP traffic from a live environment and replays it against two different environments (e.g., Stable vs. Canary) to detect differences in responses.",
5
+ "author": "Xhani Manolis Trungu",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "traffic-mirror": "./index.js"
9
+ },
10
+ "files": [
11
+ "*.js",
12
+ "ui/dist"
13
+ ],
14
+ "scripts": {
15
+ "test": "jest tests/",
16
+ "lint": "eslint .",
17
+ "format": "prettier --write .",
18
+ "start:pm2": "pm2 start ecosystem.config.js",
19
+ "prepublishOnly": "cd ui && npm install && npm run build"
20
+ },
21
+ "keywords": [
22
+ "http-log",
23
+ "http-log-replay",
24
+ "traffic-replay",
25
+ "regression-testing",
26
+ "api-regression",
27
+ "api-testing"
28
+ ],
29
+ "license": "ISC",
30
+ "type": "commonjs",
31
+ "dependencies": {
32
+ "axios": "^1.13.2",
33
+ "colors": "^1.4.0",
34
+ "commander": "^14.0.2",
35
+ "cors": "^2.8.5",
36
+ "deep-diff": "^1.0.2",
37
+ "diff": "^8.0.2",
38
+ "express": "^5.2.1",
39
+ "http-proxy": "^1.18.1",
40
+ "open": "^11.0.0",
41
+ "readline": "^1.3.0",
42
+ "socket.io": "^4.8.1"
43
+ },
44
+ "devDependencies": {
45
+ "eslint": "^9.39.2",
46
+ "eslint-config-prettier": "^10.1.8",
47
+ "jest": "^30.2.0",
48
+ "pm2": "^6.0.14",
49
+ "prettier": "^3.7.4"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/xhani-manolis-trungu/traffic-mirror"
54
+ }
55
+ }
package/recorder.js ADDED
@@ -0,0 +1,114 @@
1
+ /* eslint-disable no-console */
2
+ const http = require('http');
3
+ const httpProxy = require('http-proxy');
4
+ const fs = require('fs');
5
+
6
+ class Recorder {
7
+ constructor() {
8
+ this.server = null;
9
+ this.proxy = httpProxy.createProxyServer({});
10
+
11
+ // 1. Handle Proxy Errors (Critical for preventing crashes)
12
+ this.proxy.on('error', (err, req, res) => {
13
+ console.error(`❌ Proxy Error [${req.url}]:`, err.message);
14
+ if (!res.headersSent) {
15
+ res.writeHead(502, { 'Content-Type': 'application/json' });
16
+ }
17
+ res.end(JSON.stringify({ error: 'Proxy Request Failed', details: err.message }));
18
+ });
19
+ }
20
+
21
+ start(target, port, outputFile, onLog = () => { }) {
22
+ if (this.server) throw new Error('Recorder already running');
23
+
24
+ console.log(`πŸ“ Recording to file: ${outputFile}`);
25
+ const stream = fs.createWriteStream(outputFile, { flags: 'a' });
26
+
27
+ // Handle file write errors
28
+ stream.on('error', (err) => {
29
+ console.error('❌ File Write Error:', err.message);
30
+ });
31
+
32
+ this.server = http.createServer((req, res) => {
33
+ // 2. Capture Request Body safely
34
+ // We use a separate array but we DO NOT attach 'data' listeners directly
35
+ // unless we plan to buffer it for the proxy.
36
+ // For simplicity, let's rely on the response capture primarily.
37
+
38
+ const reqChunks = [];
39
+ // We tap into the stream without consuming it destructively if possible,
40
+ // but the safest way with http-proxy is often just to listen to the proxy events.
41
+ // HOWEVER, for your specific "Request Stealing" fix:
42
+ req.on('data', (chunk) => reqChunks.push(chunk));
43
+
44
+ // --- Response Capture Logic ---
45
+ const originalWrite = res.write;
46
+ const originalEnd = res.end;
47
+ const resChunks = [];
48
+
49
+ res.write = function (chunk, ...args) {
50
+ if (chunk) resChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
51
+ return originalWrite.apply(res, [chunk, ...args]);
52
+ };
53
+
54
+ res.end = function (chunk, ...args) {
55
+ if (chunk) resChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
56
+
57
+ // WRAP IN TRY/CATCH to prevent silent crashes
58
+ try {
59
+ const reqBody = Buffer.concat(reqChunks).toString();
60
+ const resBody = Buffer.concat(resChunks).toString('utf8');
61
+
62
+ const logData = {
63
+ timestamp: new Date().toISOString(),
64
+ method: req.method,
65
+ url: req.url,
66
+ status: res.statusCode,
67
+ requestBody: reqBody || '',
68
+ responseBody: resBody || '',
69
+ // requestHeaders: req.headers, // Uncomment if needed (verbose)
70
+ };
71
+
72
+ const jsonLine = JSON.stringify(logData) + '\n';
73
+
74
+ // 3. Write and Log
75
+ stream.write(jsonLine);
76
+ onLog(logData);
77
+
78
+ // Debug Log to prove it worked
79
+ process.stdout.write('.'); // Prints a dot for every logged request
80
+ } catch (e) {
81
+ console.error('\n❌ Error generating log:', e.message);
82
+ }
83
+
84
+ return originalEnd.apply(res, [chunk, ...args]);
85
+ };
86
+
87
+ // 4. Force Plain Text (Disable Gzip)
88
+ delete req.headers['accept-encoding'];
89
+
90
+ // 5. Forward to Target
91
+ // We must set 'buffer' to the request stream if we consumed it,
92
+ // but since we are just tapping 'data' without pausing, http-proxy might miss it.
93
+ // A simple workaround for node streams in this context:
94
+ this.proxy.web(req, res, { target });
95
+ });
96
+
97
+ this.server.listen(port, () => {
98
+ console.log(`\n⏺️ Recorder listening on port ${port}`);
99
+ console.log(`➑️ Forwarding to ${target}`);
100
+ });
101
+
102
+ return `Recorder started`;
103
+ }
104
+
105
+ stop() {
106
+ if (this.server) {
107
+ this.server.close();
108
+ this.server = null;
109
+ return 'Recorder stopped';
110
+ }
111
+ }
112
+ }
113
+
114
+ module.exports = new Recorder();