Live web UI for events.log and openhab.log built with Node.js, Express, and Server-Sent Events. Every physical log file line stays visible as its own UI row; continuation lines stay grouped under their timestamped parent entry.
- Initial load of the latest configured lines from
events.logandopenhab.log - Live streaming of new lines via SSE
- Visible per-source file states (
watching,missing,permission-denied,rotated) - Browser-side filters for source, level, and text search
- Pause, clear, auto-scroll, and theme switching
- Stored UI preferences that survive a browser reload
- Bounded browser and server buffers
| Variable | Default | Description |
|---|---|---|
PORT |
9001 |
HTTP port used by the application |
OPENHAB_LOG_DIR |
/var/log/openhab |
Fallback directory for log files |
EVENTS_LOG_PATH |
/var/log/openhab/events.log |
Full path to events.log |
OPENHAB_LOG_PATH |
/var/log/openhab/openhab.log |
Full path to openhab.log |
INITIAL_LINES_PER_FILE |
500 |
Number of latest lines per file included in bootstrap |
MAX_BUFFERED_LINES |
2000 |
Maximum shared server-side ring buffer size |
CLIENT_MAX_RENDERED_LINES |
500 |
Maximum number of lines kept in the browser buffer. Hard-capped at 500: the browser clamps higher values to 500 to stay responsive under live updates, so the accepted range is 100–500. |
MAX_SSE_CLIENTS |
10 |
Maximum number of concurrent SSE stream connections; excess requests receive HTTP 503 |
MAX_SSE_CLIENTS_PER_IP |
3 |
Maximum number of concurrent SSE stream connections per client IP; excess requests receive HTTP 503. Prevents a single client from consuming all global slots. |
POLL_INTERVAL_MS |
1000 |
Interval in milliseconds between file polls for each log source. Lower it for faster live updates on high-volume deployments, raise it to reduce wakeups on low-volume ones. Clamped to the range 100–5000. |
TRUST_PROXY |
false |
Express trust proxy setting. Leave off for direct deployments. Set to the number of proxy hops (e.g. 1) when running behind a reverse proxy so rate limiting keys on the real client IP. Also accepts true, a preset (loopback), or a comma-separated list of IPs/subnets. |
HEALTH_DETAILS |
false |
When off, /api/health returns only { "status": "ok" }. Set to true to include host details (pid, uptime, per-source states, SSE client counts) for local monitoring. Keep it off when the endpoint is reachable through a reverse proxy, so those details are not exposed. |
ALLOWED_HOSTS |
(unset) | Comma-separated allowlist of host names / addresses the app may be reached under, e.g. openhab-pi,openhab-pi.local,192.168.1.10,localhost. When set, requests whose Host header is not on the list get 403 Forbidden host. Entries are matched case-insensitively and the port is ignored (list hosts without a port; list an IPv6 address in bracketed form, e.g. [::1]). Unset → validation is off (default). |
EVENTS_LOG_PATH and OPENHAB_LOG_PATH take precedence over OPENHAB_LOG_DIR.
Per-IP limiting keys on the client IP as seen by the app. Behind a reverse proxy, set TRUST_PROXY (see #65) so the real client IP is used instead of the proxy's.
When the app runs behind a reverse proxy (Nginx, Caddy, Traefik), set TRUST_PROXY so the proxy's X-Forwarded-For header is honored. Without it, rate limiting keys every request on the proxy's IP, letting clients exhaust each other's request budget. Keep it off for direct deployments, otherwise clients can spoof their IP via X-Forwarded-For.
The app has no built-in authentication and relies on the browser's same-origin policy, which DNS rebinding can bypass. Setting ALLOWED_HOSTS to the host names you actually use to reach the viewer closes that gap: a rebinding request still carries the attacker's Host header, which the allowlist rejects with 403. This is a hardening measure, not authentication — for real access control, put the app behind an authenticating reverse proxy.
npm install
npm run buildThe build creates:
dist/
client/
server/
Start locally:
npm run startThe application is then available at http://localhost:9001.
Run the test suite (requires Node.js 22+):
npm testFor development profiling, enable the browser-side instrumentation with ?perf=1 in the URL or by running localStorage.setItem('openhab-log-viewer.perf', '1') in the browser console and then reloading the page.
When enabled, the client records recent bootstrap, filter, render, SSE, reconnect, and visibility-resume measurements in window.__openhabPerf.snapshot(). Bootstrap, reconnect, visibility, and slow filter/render/SSE timings are also written to the browser console. Disable it again with localStorage.removeItem('openhab-log-viewer.perf') or ?perf=0.
After startup, the application automatically loads the latest configured lines from events.log and openhab.log, then connects to the live SSE stream.
| Element | Purpose |
|---|---|
Source |
Filters between both files, only events.log, or only openhab.log |
Level |
Filters by TRACE, DEBUG, INFO, WARN, or ERROR |
Search |
Searches rawLine using a case-insensitive substring match |
Theme |
Switches between Light and Dark; Light is the default |
Order |
Switches between Newest first and Oldest first; newest-first is the default. In Oldest first, the controls panel stays sticky and slightly transparent while scrolling. |
Auto-scroll |
Keeps the view pinned to the newest visible edge of the log list |
Pause UI |
Stops rerendering in the browser only; data is still received |
Clear browser buffer |
Clears the current browser view only; it does not clear the server buffer |
Source, level, search text, theme, order, auto-scroll, and pause state are stored in the browser and restored after a reload.
- Connecting / Reconnecting / Connected show the browser-to-server connection state.
- The Source status section shows the current state of each watched file.
- Typical states:
watching: file is being tailed normallymissing: file was not foundpermission-denied: file exists but cannot be readrotated: log rotation or truncation was detected and the file was reattachederror: another file-related error occurred
- Every physical log file line is rendered as its own visible row.
- Continuation lines stay on their own rows but render under the same log entry with empty metadata columns.
events.logandopenhab.logremain visually distinct.- Newest entries are shown at the top by default, but users can switch back to oldest-first ordering.
- Long content wraps instead of causing endless horizontal scrolling.
Preferred deployment flow:
- Develop the project externally
- Run
npm installandnpm run build - Copy the following artifacts to the target host:
deploy-package/
dist/
client/
server/
package.json
package-lock.json
deploy/
systemd/
openhab-log-viewer.service
README.md
Because the server is bundled to dist/server/index.cjs, the target host only needs Node.js at runtime.
This section is a complete walkthrough for running the viewer as a background service on a Linux host with systemd. It works both for the deploy package described above and for a checkout you built yourself. The bundled unit file deploy/systemd/openhab-log-viewer.service assumes the application lives in /opt/openhab-log-viewer and runs as the openhab user.
-
A Linux host with systemd (the default on Debian, Ubuntu, Raspberry Pi OS, Fedora, etc.).
-
Node.js 22 or newer installed on the target host. Check with
node --version. Because the server is bundled into a singledist/server/index.cjsfile, this is the only runtime dependency — you do not need to runnpm installon the target host. -
The
openhabuser. On a standard openHAB installation it already exists and owns the log files. If it does not exist, create a dedicated service user:sudo useradd --system --no-create-home --shell /usr/sbin/nologin openhab
If you prefer a different user, change the
User=line in the service file in step 3.
Copy the deploy package (or your built project, including at least dist/, package.json, and the deploy/ folder) to the host and place it under /opt/openhab-log-viewer:
# From your build machine
scp -r deploy-package/ user@host:/tmp/openhab-log-viewer
# On the target host
sudo mkdir -p /opt/openhab-log-viewer
sudo cp -r /tmp/openhab-log-viewer/* /opt/openhab-log-viewer/Give the service user ownership of the application directory:
sudo chown -R openhab:openhab /opt/openhab-log-viewerThe service user (openhab by default) must have read access to the files it tails:
sudo -u openhab head -n1 /var/log/openhab/events.log
sudo -u openhab head -n1 /var/log/openhab/openhab.logIf both commands print a line, permissions are fine. On a standard openHAB install the openhab user already owns these files. If you run the viewer under a different user, add that user to the group that owns the logs (often openhab), for example:
sudo usermod -aG openhab youruserA permission-denied source status in the UI almost always means this step is missing.
sudo cp /opt/openhab-log-viewer/deploy/systemd/openhab-log-viewer.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now openhab-log-viewerCheck that it is running and follow its log output:
sudo systemctl status openhab-log-viewer
journalctl -u openhab-log-viewer -fThen open http://<host-ip>:9001 in a browser. With the default PORT=9001, the viewer is reachable on port 9001.
The service file ships with sensible defaults baked in via Environment= lines. To override any setting without editing the unit file, create /etc/default/openhab-log-viewer — it is read automatically through the EnvironmentFile=-/etc/default/openhab-log-viewer directive:
PORT=9001
EVENTS_LOG_PATH=/var/log/openhab/events.log
OPENHAB_LOG_PATH=/var/log/openhab/openhab.log
INITIAL_LINES_PER_FILE=500
MAX_BUFFERED_LINES=10000
CLIENT_MAX_RENDERED_LINES=500
MAX_SSE_CLIENTS=10
MAX_SSE_CLIENTS_PER_IP=3
# Set to the number of proxy hops when running behind a reverse proxy:
# TRUST_PROXY=1
# Restrict which Host headers are accepted (DNS-rebinding mitigation):
# ALLOWED_HOSTS=openhab-pi,192.168.1.10See the Configuration table for every available variable. Apply changes with:
sudo systemctl restart openhab-log-viewerCopy the new dist/ directory over the existing installation, restore ownership, and restart the service:
sudo cp -r /tmp/openhab-log-viewer/dist/* /opt/openhab-log-viewer/dist/
sudo chown -R openhab:openhab /opt/openhab-log-viewer
sudo systemctl restart openhab-log-viewersudo systemctl disable --now openhab-log-viewer
sudo rm /etc/systemd/system/openhab-log-viewer.service
sudo systemctl daemon-reload
sudo rm -rf /opt/openhab-log-viewer
# Optional, if you created a dedicated environment file:
sudo rm -f /etc/default/openhab-log-viewer- The application is intended for home-network use or use behind a reverse proxy.
- Built-in authentication is intentionally not part of the first version.
- If external access is required, add authentication in front of the app via Nginx, Caddy, or Traefik.
