Collect the logs of the local Docker containers
- docker.go follows every running container through the Docker API (events + logs with follow), resumes after a restart from the last position saved in /data/docker-state.json, reads DOCKER_BACKFILL (1h) of history for new containers, strips terminal color codes and guesses the severity from the line (JSON, logfmt, [ERROR], ERROR ...). - Logs carry source_type=docker, container, container_id, image, compose_project, compose_service and stream; host is the Docker host. - Settings > Sources: one switch per container (grouped by compose project), enable/disable all, follow new containers automatically. Choices are saved per compose service in /data/docker.json. - Source filter (syslog / docker) in the filter bar and the live view. - docker-compose: read-only docker-socket-proxy; Logstream and the proxy are labelled logstream.exclude=true and never collected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aad0fb7c16
commit
085095c46f
12 files changed
+974
-10
No files matched your search
@@ -14,7 +14,8 @@ type Filter struct {
|
||||
Range string // 5m, 15m, 1h, 6h, 24h, 7d, 30d; anything else = no time limit
|
||||
Host string
|
||||
App string
|
||||
Severity int // highest severity number included (0 = emerg … 7 = debug), -1 = all
|
||||
Severity int // highest severity number included (0 = emerg … 7 = debug), -1 = all
|
||||
Source string // "syslog", "docker" or "" for all
|
||||
}
|
||||
|
||||
var validRanges = map[string]bool{"5m": true, "15m": true, "1h": true, "6h": true, "24h": true, "7d": true, "30d": true}
|
||||
@@ -27,6 +28,7 @@ func FilterFromRequest(r *http.Request) Filter {
|
||||
Range: q.Get("range"),
|
||||
Host: q.Get("host"),
|
||||
App: q.Get("app"),
|
||||
Source: q.Get("source"),
|
||||
Severity: -1,
|
||||
}
|
||||
if v, err := strconv.Atoi(q.Get("severity")); err == nil && v >= 0 && v <= 7 {
|
||||
@@ -90,6 +92,12 @@ func (f Filter) filterExpr() string {
|
||||
if f.App != "" {
|
||||
parts = append(parts, "app:="+strconv.Quote(f.App))
|
||||
}
|
||||
switch f.Source {
|
||||
case "docker":
|
||||
parts = append(parts, `source_type:="docker"`)
|
||||
case "syslog": // also matches logs stored before source_type existed
|
||||
parts = append(parts, `!(source_type:="docker")`)
|
||||
}
|
||||
if f.Severity >= 0 && f.Severity < 7 {
|
||||
names := make([]string, 0, 8)
|
||||
for i := 0; i <= f.Severity; i++ {
|
||||
@@ -173,6 +181,9 @@ func (m *Matcher) Match(e *Entry) bool {
|
||||
if m.f.App != "" && e.App != m.f.App {
|
||||
return false
|
||||
}
|
||||
if (m.f.Source == "docker") != (e.SourceType == "docker") && m.f.Source != "" {
|
||||
return false
|
||||
}
|
||||
if m.f.Severity >= 0 && e.SevNum > m.f.Severity {
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user