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:
cedricandClaude Opus 5.5 committed 2026-09-28 16:52:05 +02:00
1 parent aad0fb7c16
commit 085095c46f
12 files changed
+974 -10

No files matched your search

+12 -1
View File
@@ -35,6 +35,9 @@ type Entry struct {
Source string // sender IP address
Proto string // udp or tcp
HostIP string // original host value when it was an IP resolved through DNS
SourceType string // "syslog" or "docker"
Extra map[string]string // additional fields (docker: container, image, …)
}
// Record returns the entry in the shape stored in VictoriaLogs and returned by the API.
@@ -65,6 +68,14 @@ func (e *Entry) Record() map[string]string {
if e.HostIP != "" {
r["host_ip"] = e.HostIP
}
if e.SourceType != "" {
r["source_type"] = e.SourceType
}
for k, v := range e.Extra {
if v != "" {
r[k] = v
}
}
return r
}
@@ -81,7 +92,7 @@ func ParseSyslog(raw []byte, source, proto string, now time.Time) *Entry {
if !utf8.ValidString(s) {
s = strings.ToValidUTF8(s, "\uFFFD")
}
e := &Entry{Time: now, Received: now, Host: source, Source: source, Proto: proto}
e := &Entry{Time: now, Received: now, Host: source, Source: source, Proto: proto, SourceType: "syslog"}
e.setPri(13) // user.notice, the RFC 3164 default
if len(s) > 2 && s[0] == '<' {