If you've worked with WebSockets, you know the pain: something's wrong, and you can't see what's going over the wire.
For browser apps, Chrome DevTools has you covered (Network tab → WS → Messages). But the moment your client is a Node service, a mobile app, a bot, or an IoT device, DevTools can't help. So most of us do this:
ws.on('message', (data) => { console.log('IN', data.toString()); // 🙃 handle(data); }); It works until you have five connections, binary frames, and a terminal full of JSON.
The proxy approach
A cleaner trick: put a proxy between your client and server. The client connects to the proxy, the proxy connects to the real server, and every frame passes through something that can record and show it.
your app ──► proxy (records everything) ──► your WebSocket server Your code doesn't change. Only the URL does.
You can do this with general HTTP tools like mitmproxy, but for wss:// you have to set up a CA certificate and proxy config, and the UI isn't built for message streams. I wanted something I could start in one command, so I built wslog.
Try it in 10 seconds
npx wslog-proxy --demo Open http://localhost:4000. The --demo flag starts a fake chat server with simulated users, so you'll see traffic right away.
Using it with your own app
npx wslog-proxy - Open http://localhost:4000 and paste the URL your app connects to, e.g.
wss://api.example.com/socket - wslog gives you a proxy URL like
ws://127.0.0.1:4000/p/3f9a1c2b - Use that URL in your app:
// before const ws = new WebSocket('wss://api.example.com/socket'); // after (only while debugging) const ws = new WebSocket('ws://127.0.0.1:4000/p/3f9a1c2b'); Path and query are passed through, so .../p/3f9a1c2b/chat?room=1 reaches wss://api.example.com/socket/chat?room=1. Cookies, Authorization headers and the negotiated subprotocol are forwarded too, so auth keeps working.
Tip: load the URL from an env var (WS_URL) and you can switch between direct and proxied without touching code.
The proxy URL is derived from the target and remembered across restarts, so you only set it up once.
What you get
- Live table of every frame: time, direction, size, connection, preview. It stays fast with tens of thousands of rows
- Detail pane with a collapsible JSON tree, raw view, and hex/base64 for binary frames
- Connections sidebar with status, close codes, and a message-rate sparkline
- Search and filters by direction, connection, or target, plus export to JSON
- Multiple servers at once, each with its own proxy URL
Don't leak your tokens
WebSocket traffic is full of secrets. wslog can mask fields before anything is stored:
npx wslog-proxy --mask token,password Masked values never reach disk, the dashboard, or exports, even inside nested JSON or JSON encoded in a string. Handshake headers like cookies and Authorization are forwarded but never stored. And it only listens on 127.0.0.1 by default.
Share a live view
Debugging with a backend teammate? wslog has a read-only share link (token protected) so they can watch the same traffic live instead of you pasting screenshots into Slack.
Keep the traffic
By default messages stay in memory. To keep a session:
npx wslog-proxy --store ./traffic.db # SQLite npx wslog-proxy --store ./traffic.jsonl # JSON lines How it compares
| wslog | Browser DevTools | wscat / websocat | mitmproxy | |
|---|---|---|---|---|
| Non-browser clients | ✅ | ❌ | ✅ (you are the client) | ✅ |
| UI built for WS traffic | ✅ | ✅ | ❌ | partial |
| Search/filter across connections | ✅ | limited | ❌ | ✅ |
| Live share with a teammate | ✅ | ❌ | ❌ | ❌ |
| Setup | one command | none | none | proxy + CA cert |
Wrapping up
wslog is MIT licensed, needs Node 22+, and also runs in Docker. It's v0.1 and I'm building it on my own, so feedback means a lot. Issues, ideas, and ⭐ are all welcome:
👉 https://github.com/hoysengleang/wslog
How do you debug WebSockets today? Let me know in the comments.
