- Go 99.7%
- Makefile 0.3%
|
Some checks failed
build / build (push) Failing after 5m34s
Maintain snapshots incrementally on append, index distinct field values, and reduce redundant TUI work so rendering stays constant as the buffer grows. Add grouping by structured log field with menu and keyboard shortcuts. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| cmd/iwatch | ||
| internal | ||
| .gitignore | ||
| AGENTS.md | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| README.md | ||
iwatch
iwatch ist eine interaktive Dev-Log-TUI fuer Build- und Entwicklungs-Workflows. Sie kombiniert automatische Command-Erkennung, einen Hauptprozess-Runner und eine Oberflaeche zum Lesen, Filtern und Durchsuchen von Prozess-Output.
Features
- Auto-Mode mit Erkennung von
package.json-Scripts undMakefile-Targets - Manueller Rebuild des aktiven Commands per
r - TUI mit Log-View, Bottom-Query und Events-Pane fuer Systemmeldungen
- Zusaetzliche LogStreams fuer Dateien und Hintergrundprozesse, pro Preset aktivierbar
- Scrollen per Cursor, Live-Filter, logfmt-verstaendige Feldabfragen und Highlight-Regeln
- Selektierbare Logzeilen mit Detailansicht fuer geparste Felder und pretty printed JSON-Werte
- Benannte Filter-Presets mit OR-Klauseln, Highlighting, Logfeld-Anzeige und Zeitformaten
- Konfigurierbarer Ringbuffer, standardmaessig
1_000_000Zeilen - Beenden per
qoderEsc+Esc+Esc
Installation
go mod tidy
go build ./cmd/iwatch
Usage
iwatch
iwatch --command npm:dev
iwatch --buffer-lines 200000
iwatch --config ./.iwatch.config.json
Keybindings
r: aktives Command stoppen und neu starten?: Keymap-Hilfe oeffnen oder schliessenl: Streams-Pane oeffnen oder schliessen/: Bottom-Query oeffnenf: Bottom-Query oeffnenw: Events-Pane oeffnen (Streams, Automations, Config)v: Logfeld-Menue oeffnen, per Tippen filtern und erkannte logfmt-Felder ein-/ausblendenF: Live-Feldfilter oeffnen, Felder waehlen und per Contains-Wert filternb: Gruppierungsfeld waehlen (Standard:component),/.: Gruppierungswert vor/zurueck durchschalten (inkl. alle Werte)g: Fullscreen-Config-Editor oeffnen[/]oderleft/right: zwischen konfigurierten Presets umschaltentab: Fokus zwischen offenen Panes wechselnup/down,j/k: scrollen oder Listen bewegenpgup/pgdown,ctrl+u/ctrl+d: seitenweise scrollenhome/end,G: an Anfang oder Ende springenenter: ans Log-Ende springen oder in der Auswahl die Detailansicht oeffnenenterim Streams-Pane: ausgewaehlten on-demand Stream starten oder laufenden Stream stoppenoim Streams-Pane: Ausgabe eines einzelnen Streams modal anzeigent: Logbuffer leeren und wieder ans Log-Ende springenenterzweimal schnell: visuellen Trenner einfuegenn/N: zum naechsten oder vorherigen sichtbaren TrefferS: Split-Richtung wechselnescoderenterin der Detailansicht: zurueck zur Logansichtesc: Bottom-Query schliesseny: Share/Copy der aktuellen Zeile (Agent-friendly Snippet)Y: Share/Copy der aktuellen Zeile inkl. Kontext (+/- 20 Zeilen)O: erkannte Vite-URL im Browser oeffnen (wenn verfuegbar)q: App beendenescdreimal: App beenden
Query Syntax
- Freitext wie
heartbeatmatcht case-insensitive gegen die gesamte Logzeile. key=value-Filter wielevel=infooderlua-manager.resource=thread-examplematchen gegen logfmt-aehnliche Felder in der Zeile.- Mehrere Tokens werden mit
ANDkombiniert, zum Beispiellevel=info heartbeatodermsg=heartbeat level=info. - Feldwerte matchen case-insensitive als Substring, also passt
msg=heartauch aufmsg="thread example heartbeat".
Die Bottom-Query wirkt zusaetzlich auf das aktive Preset. Strukturierte OR-Filter werden in der Config-Datei ueber Presets gepflegt.
Der Live-Feldfilter unter F wirkt nur fuer die laufende Sitzung. Mehrere gesetzte Feldfilter werden mit AND kombiniert und matchen case-insensitive per Contains gegen erkannte logfmt-Felder.
Die Gruppierung filtert live nach einem strukturierten Feld (Default component, konfigurierbar als ui.logView.groupField). , und . schalten exakte Werte dieses Feldes durch (inkl. „alle“). Das ist getrennt vom Contains-Feldfilter unter F und kombiniert sich per AND mit Preset, Query und Feldfiltern.
Structured Logging (logfmt + JSON Lines)
iwatch erkennt strukturierte Felder sowohl aus logfmt-aehnlichen key=value Tokens als auch aus JSON Lines (eine JSON pro Zeile).\n\nBeispiele:\n\n- logfmt:\n - level=INFO msg=\"hello\" request_id=abc\n- JSON Lines:\n - {\"level\":\"INFO\",\"msg\":\"hello\",\"http\":{\"port\":8080}}\n\nNested JSON wird als dot.path flach gemacht (z.B. http.port=8080) und ist dann im Filter nutzbar.
Dev-Flow: Go Backend + Vite (Port Handshake)
Wenn du Go-Backend und Vite getrennt startest, kann iwatch den Backend-Port/URL aus den Logs erkennen und diese Info an den Vite-Devserver als ENV weitergeben.\n\nEmpfohlen:\n\n- Backend loggt structured url=... oder port=... (logfmt oder JSON Lines)\n- Vite wird als Stream mit role: \"vite\" konfiguriert, Backend als role: \"backend\"\n\nMinimal-Beispiel fuer streams:\n\njson\n{\n \"streams\": [\n {\n \"id\": \"backend\",\n \"title\": \"Go backend\",\n \"type\": \"process\",\n \"role\": \"backend\",\n \"cmd\": \"go run ./cmd/server\",\n \"autoStart\": true\n },\n {\n \"id\": \"vite\",\n \"title\": \"Vite dev\",\n \"type\": \"process\",\n \"role\": \"vite\",\n \"cmd\": \"npm run dev\",\n \"autoStart\": false\n }\n ]\n}\n\n\nSobald iwatch eine Backend-URL erkennt, setzt es beim Vite-Stream:\n\n- BACKEND_URL\n- BACKEND_PORT (wenn ableitbar)\n\nund startet den Vite-Stream (on-demand) automatisch.\n\nWenn iwatch die Vite-URL erkennt, kannst du sie mit O direkt oeffnen.
Config Editor
goeffnet einen Fullscreen-Editor fuer Presets, Filter, Highlighting, sichtbare Logfelder und Darstellungsoptionen.up/downoderj/kbewegt die Auswahl.pgup/pgdownoderctrl+u/ctrl+dbewegt sich seitenweise.entertoggelt oder fuehrt die aktuelle Aktion aus.ebearbeitet Textfelder wie Preset-ID, Clauses, Highlight-Regeln oder sichtbare Logfelder.afuegt je nach Bereich Presets, OR-Clauses oder Highlight-Regeln hinzu.dloescht den aktuellen Preset-/Clause-/Rule-Eintrag.ydupliziert das aktive Preset.qbeendet die App direkt,escverlaesst den Editor.- Speichern schreibt projektlokal nach
./.iwatch/config.json, bestehende./.iwatch.config.jsonwird weiterverwendet.
Config
Geladene Config-Dateien in Prioritaetsreihenfolge:
~/.iwatch/config.json./.iwatch/config.json./.iwatch.config.json
CLI-Flags haben Vorrang vor Config-Werten.
Beispiel:
{
"bufferLines": 1000000,
"defaultCommand": "npm:dev",
"commands": [
{
"id": "go:test",
"title": "go test",
"cmd": "go test ./...",
"source": "config"
}
],
"streams": [
{
"id": "app-log",
"title": "App log",
"type": "file",
"source": "./logs/app.log"
},
{
"id": "worker",
"title": "Worker",
"type": "process",
"cmd": "npm run worker",
"cwd": "./",
"autoStart": false
}
],
"highlightRules": [
{
"id": "errors",
"pattern": "(?i)error|failed",
"style": "error",
"priority": 100
},
{
"id": "warn",
"pattern": "(?i)warn",
"style": "warn",
"priority": 50
}
],
"ui": {
"openPanes": ["log", "events"],
"splitDirection": "vertical",
"focusPane": "log",
"activePreset": "ops",
"presets": [
{
"id": "ops",
"title": "Ops",
"streams": ["app-log", "worker"],
"clauses": [
{
"conditions": [
{ "field": "level", "value": "error" }
]
},
{
"conditions": [
{ "value": "panic" }
]
}
],
"highlightRules": [
{
"id": "panic",
"pattern": "(?i)panic|fatal",
"style": "error",
"priority": 100
}
]
},
{
"id": "heartbeat",
"title": "Heartbeat",
"clauses": [
{
"conditions": [
{ "field": "msg", "value": "heartbeat" },
{ "field": "level", "value": "info" }
]
}
]
}
],
"logView": {
"visibleFields": ["level", "msg", "lua-manager.resource"],
"hiddenFields": ["debug"],
"showRawMessage": true,
"showSource": false,
"showTimestamp": true,
"timeFormat": "date-short",
"wrapMode": "field",
"palette": "default"
}
}
}
Preset-Struktur
ui.presets[*].clausesist eine OR-Liste.clauses[*].conditionsinnerhalb einer Clause werden mit AND kombiniert.- Bedingungen mit
fieldmatchen gegen erkannte logfmt-Felder. - Bedingungen ohne
fieldmatchen als Freitext gegen die komplette Logzeile. ui.presets[*].streamsaktiviert gezielt Stream-IDs fuer dieses Preset. Wenn kein Preset ueberhauptstreamsdefiniert, gelten alle konfigurierten Streams als aktiv;autoStart: falsebleibt dabei on-demand.
LogStreams
- Root-
streamsdefiniert zusaetzliche Eingaben fuer die globale LogView. type: "file"liestsourceals Datei- oder Glob-Pfad. Beim Start springt der Stream ans aktuelle Dateiende und liest nur neue Zeilen. Truncate, Delete oder Recreate werden toleriert; nach Cleanup wird wieder ab Dateiende weitergelesen.type: "process"startetcmdmit optionalemcwdals Hintergrundprozess.autoStart: falsemacht den Stream on-demand startbar im Streams-Pane.- Nur Streams, die im aktiven Preset genannt sind und laufen, schreiben in den globalen Buffer. Dadurch wirken Bottom-Query, globale Suche, Feldfilter, Highlighting und Detailansicht unveraendert auch auf Stream-Zeilen.
Log View
timeFormat:time,date-short,relativeBeidate-shortundrelativewird keine separate Zeitspalte gerendert; die feste Zeitspalte ist nur fuertimesichtbar.wrapMode:off,simple,fieldpalette:default,contrast,ocean,forest,emberIndefaultwird die Zeit dunkelgrau, der logfmt-Key hellgrau und der Wert weiss gerendert.visibleFields: steuert die bevorzugte Reihenfolge erkannter logfmt-Felder; neue Felder werden automatisch angehaengthiddenFields: blendet erkannte logfmt-Felder aus; das Feld-Menue schreibt diese Liste erst nach explizitem Speichern in die ConfiggroupField: Standard-Gruppierungsfeld fuerbund,/.(Default:component)- Root-
highlightRulesbleiben als Rueckwaertskompatibilitaets-Fallback aktiv, wenn ein Preset keine eigenen Regeln hat