Container events are Ham Lab's own record, not Docker's short memory
docker events is a live stream with a small in-memory backlog: ask for thirty days and you get a few minutes, and a daemon restart loses the lot. That is why Dockhand kept its own copy. A watcher now follows the stream and appends lifecycle events to a ring beside the config, flushed every thirty seconds, so the question of what happened to a container last week has an answer. Health-check exec events are excluded — three per probe, thousands a day, and the health panel says it better. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
b3e1c495f0
commit
a70f3e2ed2
@@ -439,65 +439,6 @@ func (s *Server) dockerHealth(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// dockerEvents is what has happened to one container lately — started, died, killed, the
|
||||
// health checks going bad. Asked of Docker on demand rather than kept: Docker already has
|
||||
// the history, and a copy of it would only go stale.
|
||||
func (s *Server) dockerEvents(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
if !dockerRef.MatchString(id) {
|
||||
jsonErr(w, 400, "bad container")
|
||||
return
|
||||
}
|
||||
since := r.URL.Query().Get("since")
|
||||
if !regexp.MustCompile(`^[0-9]+[mhd]$`).MatchString(since) {
|
||||
since = "7d"
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
out, err := dockerRun(ctx, "events", "--since", since, "--until", "0s",
|
||||
"--filter", "container="+id, "--format", "{{json .}}")
|
||||
if err != nil {
|
||||
jsonErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
events := []map[string]any{}
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||||
if !strings.HasPrefix(line, "{") {
|
||||
continue
|
||||
}
|
||||
var e struct {
|
||||
Action string `json:"Action"`
|
||||
Time int64 `json:"time"`
|
||||
TimeNano int64 `json:"timeNano"`
|
||||
Actor map[string]any `json:"Actor"`
|
||||
}
|
||||
if json.Unmarshal([]byte(line), &e) != nil {
|
||||
continue
|
||||
}
|
||||
m := map[string]any{"action": e.Action, "t": e.Time * 1000}
|
||||
if e.TimeNano > 0 {
|
||||
m["t"] = e.TimeNano / 1e6
|
||||
}
|
||||
if at, ok := e.Actor["Attributes"].(map[string]any); ok {
|
||||
// the interesting ones: why it stopped, and what the health check said
|
||||
for _, k := range []string{"exitCode", "signal", "name", "image"} {
|
||||
if v, ok := at[k]; ok && v != "" {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
events = append(events, m)
|
||||
}
|
||||
// newest first, and bounded: a flapping container can produce thousands
|
||||
for i, j := 0, len(events)-1; i < j; i, j = i+1, j-1 {
|
||||
events[i], events[j] = events[j], events[i]
|
||||
}
|
||||
if len(events) > 400 {
|
||||
events = events[:400]
|
||||
}
|
||||
jsonOK(w, 200, map[string]any{"events": events, "since": since})
|
||||
}
|
||||
|
||||
// dockerExec opens a shell inside a container, as a terminal session like any other — so
|
||||
// it reattaches, resizes and scrolls back the same way, and works on a managed machine
|
||||
// through the same proxy.
|
||||
|
||||
Reference in New Issue
Block a user