package adminapi import ( "context" "net/http" "strconv" ) // LogsView is one of a node's logs, as the node sent it. type LogsView struct { Node string `json:"node"` Source string `json:"source"` Lines []string `json:"lines"` // Sources are every log the node can show: "agent", and one per engine // running there. Sources []string `json:"sources"` Error string `json:"error,omitempty"` } // maxLogLines bounds a request. The agent keeps two thousand lines of its own // and a few hundred per engine, so more would only be refused further down. const maxLogLines = 2000 // NodeLogs asks a node for the recent lines of one of its logs. // // Exported because the cluster page renders it server-side, the same as // Nodes. func (a *API) NodeLogs(ctx context.Context, node, source string, lines int) LogsView { if source == "" { source = "agent" } v := LogsView{Node: node, Source: source} lr := a.ctl.LogReader() if lr == nil { v.Error = "this control plane cannot read node logs" return v } if lines <= 0 { lines = 300 } out, sources, err := lr.Logs(ctx, node, source, min(lines, maxLogLines)) v.Lines, v.Sources = out, sources if err != nil { v.Error = err.Error() } if len(v.Sources) == 0 { v.Sources = []string{"agent"} } return v } func (a *API) nodeLogs(w http.ResponseWriter, r *http.Request) { lines, _ := strconv.Atoi(r.URL.Query().Get("lines")) v := a.NodeLogs(r.Context(), r.PathValue("id"), r.URL.Query().Get("source"), lines) code := http.StatusOK if v.Error != "" && len(v.Lines) == 0 { code = http.StatusBadGateway } writeJSON(w, code, v) }