Receives Qlik Sense Log4Net UDP messages, parses them, and shows a live, terminal UI with aggregated stats per subsystem.
Blog post: qs-log-scanner: open source tool for exploring Qlik Sense logs
- Listens on a UDP socket (configurable host/port).
- Parses semicolon-separated Log4Net UDP messages.
- Aggregates counts per
(source, subsystem). - Tracks sender IPs and log levels seen per subsystem.
- Tracks search term matches (message content + subsystem).
- Renders a live table in the terminal (Ink) with keyboard shortcuts.
- Exports the current aggregated stats to CSV.
- Node.js
>= 20
npm installEdit config/default.yaml:
udp:
port: 9999
host: "192.168.3.250"
receiveBufferSize: 8388608
maxQueueSize: 10000
display:
refreshInterval: 5000
maxMessagePreview: 100
autoRefresh: true
autoRefreshInterval: 5000
logging:
level: info
logToFile: true
logFilePath: ./logs/scanner.logNotes:
udp.hostis the interface to bind to. Use0.0.0.0to listen on all interfaces.receiveBufferSizeincreases the UDP receive buffer to better handle bursts.maxQueueSizeis the maximum number of messages buffered in-process; when exceeded, messages are dropped.display.autoRefreshanddisplay.autoRefreshIntervalcontrol the table refresh.display.refreshIntervalanddisplay.maxMessagePrevieware present inconfig/default.yamlbut are not currently used by the UI.
npm startHot reload during development:
npm run devThe app is keyboard-driven.
h: toggle helpa: add search termr: remove search terms: show/hide search termsi: show/hide local IP addressese: export CSV (prompts for filename)x: clear statst: toggle auto-refresho: change sort (cyclessource↑ → source↓ → subsystem↑ → subsystem↓ → count↑ → count↓ → ip↑ → ip↓)↑/↓: scroll tablePageUp/PageDown: scroll a pageEnter: refresh nowq: quit
The stats table aggregates by (source, subsystem) and shows:
- Source: parsed from field
0with/stripped and lowercased - Subsystem: field
6 - Count: number of messages received for that
(source, subsystem) - IP: sender IPs (UI shows last octet(s); full IPs are written to CSV)
- Level: distinct log levels seen for the subsystem
- Matches: search terms that matched for that subsystem
Export writes one row per (source, subsystem) with these columns:
Source,Subsystem,Count,Sender IPs,Log Levels,Search Matches
Lists are joined with "; " inside the CSV field. If you don’t provide a filename, it uses qs-log-scanner-<timestamp>.csv in the current working directory.
CSV export is the only export supported in the UI.
Messages are expected to be semicolon-separated, with at least 7 fields:
| Index | Field |
|---|---|
| 0 | Source (e.g. /qseow-root/) |
| 1 | Log row number |
| 2 | ISO timestamp |
| 3 | Local timestamp |
| 4 | Log level |
| 5 | Host |
| 6 | Subsystem |
| 7 | Windows user |
| 8+ | Message content (joined back with ;) |
UDP can drop packets under load. This tool also has an internal queue; if the queue fills up, it will drop messages.
When the app detects backlog/drops, the Statistics panel shows:
Queue: <n> | Dropped: <n>
If Dropped is non-zero, some messages were not processed.
- Verify
udp.hostandudp.portinconfig/default.yaml. - Ensure firewalls allow inbound UDP on the chosen port.
- Confirm Qlik Sense is configured to send UDP logs to this host/port.
Ink requires a TTY for keyboard input. Run the app in a normal terminal (not via a pipeline or a process that doesn’t provide a real stdin).
MIT. See LICENSE.