Monstr is a full-stack STORJ log monitoring platform. The asynchronous FastAPI backend tails multiple log files, persists new lines into SQLite, and exposes a REST API. The React + TypeScript frontend renders the stored data today and is prepared for WebSocket streaming in the future.
- Async log tailing across multiple files defined at startup
- Automatic database bootstrap and periodic data retention cleanup
- REST API for querying stored log, reputation, and transfer data (served under
/api) - REST API for adding sensors to Home Assistant
- Production build of the client served directly by the Python backend
- Prepared to run in Docker (multi-stage Dockerfile included)
- Modern React stack (Vite, TypeScript, Zustand) with testing via Vitest and Testing Library
server/– FastAPI service, background workers, database models, and testsclient/– Vite-powered React SPA with routing, services, and state store
- Python 3.11+
- Node.js 18+
- SQLite (bundled with standard Python installations)
PowerShell (Windows)
cd server
python -m venv .vent.monstr
.\.vent.monstr\Scripts\activate
pip install -r requirements.txtBash (macOS / Linux / WSL)
cd server
python3 -m venv .vent.monstr
source .vent.monstr/bin/activate
pip install -r requirements.txtDeactivate the environment at any time with deactivate.
Important: run the CLI from the repository root (
monstr/), not from theserver/subdirectory. If you just installed dependencies insideserver/, executecd ..first.
PowerShell (Windows):
# from the project root with the virtual environment still activated
python -m server.src.cli --source myNode:.\testdata\node.log --source otherNode:C:\path\to\node2.logBash (macOS / Linux):
# from the project root with the virtual environment activated
python -m server.src.cli --source myNode:./testdata/node.log --source otherNode:/path/to/node2.logNode specifications follow the NAME:PATH pattern so each database record references the logical node name instead of the filesystem path. Below is a compact reference for the CLI, environment overrides, and examples showing how to run the server.
Basic shape for starting the service:
PowerShell (Windows):
# from the project root with the virtual environment activated
python -m server.src.cli --source myNode:.\testdata\node.log --source otherNode:C:\path\to\node2.logBash (macOS / Linux / WSL):
# from the project root with the virtual environment activated
python -m server.src.cli --source myNode:./testdata/node.log --source otherNode:/path/to/node2.logSupported CLI flags
-
--source NAME:SPEC(repeatable) — Declare a log source in the preferred sequence. UseNAME:PATHfor local log files orNAME:HOST:PORTfor remote TCP sources. Repeat the flag to declare multiple sources; their declared order is preserved at startup. Append|http://localhost:14002(or another HTTP(S) URL) to associate a nodeapi endpoint with the source.Implementation note: you can use the companion project
hwmland/tailsenderas a lightweight remote sender that tails a file and forwards appended lines to Monstr over TCP. Configure a tailsender instance on the remote host and point Monstr at it with--source name:host:port. -
--host HOST— Bind the API server to the specified host (default:127.0.0.1). Setting--host 0.0.0.0(or--host ::) makes the API listen on all network interfaces so the server becomes reachable from other machines on the network. Use this when running inside a container or when exposing the API to other hosts. Beware that binding to all interfaces exposes the API to your network; secure the host appropriately (firewall, auth) if used in production. -
--port PORT— Bind the API server to the specified port (default:8000). -
--log-level LEVEL— Override the API root logger level (e.g.info,debug). This sets the overall verbosity for the server. -
--log NAME:LEVEL(repeatable) — Per-logger override inLOGGER:LEVELform. These take precedence over theMONSTR_LOG_OVERRIDESenvironment variable and are useful to enable fine-grained debug output (for example--log api.call:DEBUG).
Environment variables
MONSTR_LOG_OVERRIDES— Comma-separatedLOGGER:LEVELpairs (e.g.root:INFO,services.cleanup:WARNING). The CLI--logflag takes precedence for any logger it names.
Examples
PowerShell (Windows) example that sets two nodes and enables the request-finish debug messages emitted by the api.call logger:
python -m server.src.cli \
--source Hashnode:.\testdata\hash.log|http://localhost:14002 \
--source Blobnode:.\testdata\blob.log \
--host 0.0.0.0 \
--port 8000 \
--log-level info \
--log api.call:DEBUGBash (macOS / Linux) equivalent:
python -m server.src.cli \
--source Hashnode:./testdata/hash.log|http://localhost:14002 \
--source Blobnode:./testdata/blob.log \
--host 0.0.0.0 \
--port 8000 \
--log-level info \
--log api.call:DEBUGMonstr includes a small request-finish middleware that can emit a concise DEBUG message whenever an HTTP request completes. The message contains the client address, HTTP method, full path (including query), response status code and duration in milliseconds.
This behavior is controlled solely by the api.call logger. To enable the messages, set api.call to DEBUG using any of the supported mechanisms (environment/CLI/admin API). For example, use the admin API to set the logger at runtime:
POST /api/admin/loggers
Content-Type: application/json
{ "name": "api.call", "level": "DEBUG" }To disable, set the logger back to INFO (or your preferred level):
POST /api/admin/loggers
Content-Type: application/json
{ "name": "api.call", "level": "INFO" }Alternatively, set the logger at startup with MONSTR_LOG_OVERRIDES or the
CLI --log flag (CLI wins over environment values).
Notes:
- The middleware is always registered but checks the
api.calllogger's level for DEBUG each request, so toggles take effect immediately. - We chose logger-level gating instead of a separate runtime setting so you can use existing logging controls to manage verbosity without adding another configuration surface.
-
OpenAPI docs: once running, the backend exposes the OpenAPI UI at
http://<host>:<port>/api/docs(defaulthttp://127.0.0.1:8000/api/docs). -
Frontend SPA: if
client/distexists (a production build of the client), FastAPI will serve the compiled SPA at the root path/(for examplehttp://127.0.0.1:8000/). -
Overall status API: the server exposes a lightweight health/status endpoint at
/api/overall-statuswhich returns a compact JSON summary of the running service (configured source count, last-processed timestamps, processing lag, database connectivity and similar high-level metrics). This endpoint is intentionally small and stable so it can be polled frequently by external systems. It's provided primarily as a convenience for Home Assistant users — you can create a simple REST sensor in Home Assistant to surface Monstr's health into dashboards and automations.Example Home Assistant REST sensor configuration:
rest:
resource: http://pve1.internal:9898/api/overall-status
method: POST
headers:
User-Agent: Home Assistant
Content-Type: application/json
payload: '{ "nodes": [ ] }'
scan_interval: 60
sensor:
- name: "STORJ Download Speed"
value_template: "{{ (float(value_json.total.minute1.downloadSpeed) / 1000000) }}"
unit_of_measurement: "Mbps"
- name: "STORJ Upload Speed"
value_template: "{{ value_json.total.minute1.uploadSpeed / 1000000 }}"
unit_of_measurement: "Mbps"API output: /api/overall-status (click to expand)
The /api/overall-status endpoint returns an OverallStatusResponse containing a total summary and a nodes mapping keyed by node name. Each value follows the NodeOverallMetrics schema and contains reputation aggregates and short transfer windows. The request body is OverallStatusRequest (JSON { "nodes": [...] }); omit or send an empty list to request all nodes.
Response shape (serialized field names shown):
total(object): aggregateNodeOverallMetricsacross selected nodes.nodes(object): mapping where each property name is a node identifier and the value is aNodeOverallMetricsobject for that node.
NodeOverallMetrics fields:
node(string)minOnline,minAudit,minSuspension(float) — minimum reputation scores observed across satellitesavgOnline,avgAudit,avgSuspension(float) — simple averages of reputation scoresminute1,minute3,minute5(objects) —TransferWindowMetricsfor 1/3/5 minute windowscurrentMonthPayout(object) — optional payout summary for the current month gathered from each node's nodeapi. Fields:estimatedPayout,heldBackPayout,downloadPayout,repairPayout,diskPayout,totalHeldPayout(all numeric or null, inUSD).
TransferWindowMetrics fields (per window):
downloadSize,uploadSize(int) — total bytes transferred in the windowdownloadCount,uploadCount(int) — successful operation countsdownloadCountTotal,uploadCountTotal(int) — total attempted operations (including failures)downloadSuccessRate,uploadSuccessRate(float 0..1) — success ratiosdownloadSpeed,uploadSpeed(float) — computed speeds; implementation reports bytes/sec converted to bits/sec (bytes/window_seconds * 8)
Example payload (matches current implementation):
{
"total": {
"node": "total",
"minOnline": 0.98,
"minAudit": 0.95,
"minSuspension": 0.0,
"avgOnline": 0.993,
"avgAudit": 0.976,
"avgSuspension": 0.0,
"minute1": {
"downloadSize": 1234567,
"uploadSize": 234567,
"downloadCount": 12,
"uploadCount": 3,
"downloadCountTotal": 13,
"uploadCountTotal": 4,
"downloadSuccessRate": 0.9230769230769231,
"uploadSuccessRate": 0.75,
"downloadSpeed": 164608.93333333335,
"uploadSpeed": 31274.666666666668
},
"minute3": {
/* ... */
},
"minute5": {
/* ... */
},
"currentMonthPayout": {
"estimatedPayout": 123.45,
"heldBackPayout": 10.0,
"totalHeldPayout": 75.25,
"downloadPayout": 45.0,
"repairPayout": 2.5,
"diskPayout": 65.95
}
},
"nodes": {
"hashnode": {
"node": "hashnode",
"minOnline": 0.99,
"minAudit": 0.97,
"minSuspension": 0.0,
"avgOnline": 0.995,
"avgAudit": 0.98,
"avgSuspension": 0.0,
"minute1": {
"downloadSize": 1234567,
"uploadSize": 234567,
"downloadCount": 12,
"uploadCount": 3,
"downloadCountTotal": 13,
"uploadCountTotal": 4,
"downloadSuccessRate": 0.923,
"uploadSuccessRate": 0.75,
"downloadSpeed": 164608.9,
"uploadSpeed": 31274.7
},
"minute3": {
/* ... */
},
"minute5": {
/* ... */
},
"currentMonthPayout": {
"estimatedPayout": 123.45,
"heldBackPayout": 10.0,
"totalHeldPayout": 75.25,
"downloadPayout": 45.0,
"repairPayout": 2.5,
"diskPayout": 65.95
}
}
}
}Notes:
- The
downloadSpeed/uploadSpeedvalues are in bits per second as computed by the implementation (bytes/window_seconds * 8). Convert to Mbps in Home Assistant withvalue_json.total.minute1.downloadSpeed / 1000000. - Use
value_templateto extract a single numeric value for a sensor andjson_attributes_path: "$"to expose the rest of the payload as attributes.
Tip: always run the CLI from the repository root so relative paths in NAME:PATH pairs are resolved consistently.
PowerShell (Windows):
pytestBash (macOS / Linux / WSL):
pytestPowerShell (Windows):
cd client
npm install
npm run buildBash (macOS / Linux / WSL):
cd client
npm install
npm run buildThe compiled assets land in client/dist. On the next backend start, FastAPI will serve those files automatically.
PowerShell (Windows):
npm run dev # launches Vite dev server on http://127.0.0.1:5173
npm test # runs VitestBash (macOS / Linux / WSL):
npm run dev # launches Vite dev server on http://127.0.0.1:5173
npm test # runs VitestDuring development the backend API remains available at http://127.0.0.1:8000/api.
The repository includes a multi-stage Dockerfile that builds the Vite client and bundles it alongside the FastAPI server inside a slim Python runtime.
PowerShell (Windows):
docker build -t monstr .Bash (macOS / Linux / WSL):
docker build -t monstr .Run the container with sample logs mounted (example binds port 8000 and mounts a testdata directory):
PowerShell (Windows):
docker run
-p 8000:8000 \
-e MONSTR_SOURCES="hashnode:/logs/hash.log,blobnode:/logs/blob.log" \
-v ${PWD}\testdata:/logs:ro \
monstr:latestBash (macOS / Linux / WSL):
docker run -p 8000:8000 \
-e MONSTR_SOURCES="hashnode:/logs/hash.log,blobnode:/logs/blob.log" \
-v ${PWD}/testdata:/logs:ro \
monstr:latestDocker Compose example (service runs the CLI and sets logger overrides):
services:
monstr:
image: ghcr.io/hwmland/monstr:latest
ports:
- "8000:8000"
environment:
MONSTR_SOURCES="hashnode:/logs/hash.log|http://localhost:14002,blobnode:/logs/blob.log"
MONSTR_LOG_OVERRIDES="root:INFO,services.cleanup:WARNING"
volumes:
- ./testdata:/logs:ro- The log parsing logic lives in
server/src/services/log_monitor.pyand can be extended to support additional formats. - The cleanup/retention settings are configurable in the server configuration; see
server/src/config.py. - Frontend charting uses Recharts and has a centralized time-format preference stored in localStorage under the key
pref_time_24h.
Contributions and issues are welcome — open a PR or file an issue with a reproducible example.