Traffic observability web UI for GOST tunnel/proxy nodes. Consumes records published by GOST nodes via Redis Pub/Sub and stores them in MongoDB for querying and live streaming.
# Pull
docker pull gogost/inspector
# Run (mount your config)
docker run -v $PWD/inspector.yaml:/etc/inspector.yaml -p 18000:18000 gogost/inspector
# Build locally
docker build -t gogost/inspector .# Build web frontend
npm --prefix web run build
# Build Go binary
go build
# Run with config
./inspector -C inspector.yaml
# Open http://localhost:18000addr: :18000
requireClientID: false
noFilterClientID: "*"
db:
mongo:
uri: mongodb://localhost:27017
db: gost
timeout: 10s
maxPoolSize: 100
debug: false
redis:
addr: localhost:6379
db: 0
username:
password:
poolSize: 10
debug: false
log:
level: info # debug, info, warn, error
format: json # json or text
output: # file path, empty = stderr
rotation:
maxSize: 100 # MB per file
maxAge: 0 # days to retain
maxBackups: 0 # files to retain
localTime: false
compress: false
profiling:
addr: # pprof listen address (e.g. :6060)Config is loaded from -C <path>, ./inspector.yaml, or /etc/inspector.yaml.
| Path | Default | Description |
|---|---|---|
addr |
:8000 |
HTTP listen address |
requireClientID |
false |
Reject query/tail requests without client_id |
noFilterClientID |
— | Value for client_id that bypasses filtering (e.g. "*" for admin view) |
db.mongo.uri |
— | MongoDB connection URI |
db.mongo.db |
— | MongoDB database name |
db.mongo.timeout |
10s |
MongoDB operation timeout |
db.mongo.maxPoolSize |
100 |
MongoDB connection pool size |
db.mongo.debug |
false |
Log all MongoDB queries |
db.redis.addr |
— | Redis address |
db.redis.db |
0 |
Redis database number |
db.redis.username |
— | Redis username (ACL) |
db.redis.password |
— | Redis password |
db.redis.poolSize |
10 |
Redis connection pool size |
db.redis.debug |
false |
Log all Redis commands |
log.level |
info |
Log level: debug, info, warn, error |
log.format |
json |
Log format: json or text |
log.output |
— | Log output file path; empty = stderr |
log.rotation.maxSize |
100 |
Rotate when log reaches this many MB |
log.rotation.maxAge |
0 |
Days to retain old log files (0 = forever) |
log.rotation.maxBackups |
0 |
Max old log files to retain (0 = all) |
log.rotation.localTime |
false |
Use local time in rotated filenames |
log.rotation.compress |
false |
Compress rotated files with gzip |
profiling.addr |
— | pprof listen address (e.g. :6060); disabled if empty |
Inspector is the consumer side of a three-component pipeline:
GOST node → HTTP POST → gost-plugins recorder → MongoDB (persistence)
→ Redis Pub/Sub (live stream) → Inspector
Inspector does not receive traffic directly from GOST nodes. The gost-plugins/recorder plugin is a standalone HTTP service that sits between GOST and the databases. Each GOST node is configured to post traffic records to the recorder plugin, which then fans them out to MongoDB (for historical queries) and Redis Pub/Sub (for live streaming via WebSocket).
# Start the recorder plugin (separate process)
gost-plugins recorder \
--addr :8000 \
--mongo.uri mongodb://localhost:27017 \
--mongo.db gost \
--redis.addr localhost:6379| Flag | Default | Description |
|---|---|---|
--addr |
:8000 |
HTTP listen address |
--mongo.uri |
— | MongoDB URI (e.g. mongodb://127.0.0.1:27017); disabled if empty |
--mongo.db |
gost |
MongoDB database name |
--loki.url |
— | Loki push URL (e.g. http://localhost:3100/loki/api/v1/push); disabled if empty |
--loki.id |
gost |
Loki tenant ID (sent as X-Scope-OrgId header) |
--redis.addr |
— | Redis address; disabled if empty |
--redis.db |
0 |
Redis database number |
--redis.username |
— | Redis username (ACL) |
--redis.password |
— | Redis password |
--timeout |
10s |
Connection timeout for MongoDB and Loki |
Recorder configuration has two levels in GOST YAML:
1. Top-level recorders — defines the backend that connects to the gost-plugins recorder HTTP service:
recorders:
- name: recorder-0
http:
url: http://127.0.0.1:8000 # gost-plugins recorder address
timeout: 1s2. Service-level recorders — references the named recorder with a record pattern (sibling of handler, not nested under it):
services:
- name: my-service
addr: :8080
recorders:
- name: recorder-0
record: recorder.service.handler
metadata:
http.body: true # capture HTTP request/response bodies
http.maxBodySize: 512 # max body size per record (KB)
handler:
type: http
listener:
type: tcpThe record field controls what events are captured. Common values:
| Value | Records |
|---|---|
recorder.service.handler |
Handler-level events (connection start, end, error) |
recorder.service.handler.tcp |
TCP-level I/O events |
recorder.service.handler.tcp.connector |
Connector-level events |
The clientID field in the GOST service config (if set) flows through to Inspector as a filterable field — use different clientID values per node or per service to distinguish traffic sources in the Inspector UI.
- MongoDB — stores historical records (Inspector queries this directly)
- Redis — Pub/Sub channel for live record streaming (Inspector subscribes to
gost:pubsub:recorder:<clientID>) - gost-plugins recorder — bridges GOST nodes to MongoDB and Redis
- MongoDB + Redis
gost-plugins recorder- GOST nodes (with recorder plugin config)
inspector
| Method | Path | Description |
|---|---|---|
| GET | /liveness |
Health check |
| GET | /api/records/query |
Query historical records |
| GET | /api/records/:id |
Single record detail (includes full req/res bodies) |
| WebSocket | /api/records/tail |
Stream live records |
| Param | Type | Description |
|---|---|---|
client_id |
string | Filter by client identifier |
service |
string | Filter by service name |
sid |
string | Filter by session ID |
type |
string | Filter by traffic type: http, websocket, tls, dns |
start |
int64 | Start time (Unix timestamp) |
end |
int64 | End time (Unix timestamp) |
before |
string | Cursor — hex ObjectID, returns records older than this ID |
after |
string | Cursor — hex ObjectID, returns records newer than this ID |
limit |
int | Max records (default 100, max 200) |
Response includes data.before / data.after cursor hex strings for pagination.
| Param | Type | Description |
|---|---|---|
client_id |
string | Client identifier (required if requireClientID is set) |
type |
string | Filter by traffic type: http, websocket, tls, dns |
service |
string | Filter by service name |
sid |
string | Filter by session ID |
- Go backend — Gin HTTP server serving both API and embedded SPA
- React + TypeScript + Tailwind frontend — separate views for HTTP, WebSocket, TLS, and DNS traffic
- MongoDB — persistent record storage (collection:
recorders) - Redis Pub/Sub — live record streaming
The web UI is embedded into the Go binary via embed.FS — a single static binary with no external assets.
The recorders collection expects the following indexes, managed by scripts/recorders-indexes.js:
// 30-day TTL — removes records older than 30 days
db.recorders.createIndex({ time: -1 }, { expireAfterSeconds: 2592000 })
// Filter by type, sorted by time descending
db.recorders.createIndex({ type: 1, _id: -1 })
// Filter by service + type, sorted by time descending
db.recorders.createIndex({ service: 1, type: 1, _id: -1 })
// Filter by client + type, sorted by time descending
db.recorders.createIndex({ clientid: 1, type: 1, _id: -1 })
// Filter by session + type, sorted by time descending
db.recorders.createIndex({ sid: 1, type: 1, _id: -1 })Run the script to reconcile:
mongosh "mongodb://<host>:<port>/<db>" scripts/recorders-indexes.js