func (c *controllerAPI) AppLog(ctx context.Context, w http.ResponseWriter, req *http.Request) { ctx, cancel := context.WithCancel(ctx) opts := logagg.LogOpts{ Follow: req.FormValue("follow") == "true", JobID: req.FormValue("job_id"), } if vals, ok := req.Form["process_type"]; ok && len(vals) > 0 { opts.ProcessType = &vals[len(vals)-1] } if streamTypeVals := req.FormValue("stream_types"); streamTypeVals != "" { streamTypes := strings.Split(streamTypeVals, ",") opts.StreamTypes = make([]logagg.StreamType, len(streamTypes)) for i, typ := range streamTypes { opts.StreamTypes[i] = logagg.StreamType(typ) } } if strLines := req.FormValue("lines"); strLines != "" { lines, err := strconv.Atoi(req.FormValue("lines")) if err != nil { respondWithError(w, err) return } opts.Lines = &lines } rc, err := c.logaggc.GetLog(c.getApp(ctx).ID, &opts) if err != nil { respondWithError(w, err) return } if cn, ok := w.(http.CloseNotifier); ok { ch := cn.CloseNotify() go func() { select { case <-ch: rc.Close() case <-ctx.Done(): } }() } defer cancel() defer rc.Close() if !strings.Contains(req.Header.Get("Accept"), "text/event-stream") { w.Header().Set("Content-Type", "text/plain") w.WriteHeader(200) // Send headers right away if following if wf, ok := w.(http.Flusher); ok && opts.Follow { wf.Flush() } fw := httphelper.FlushWriter{Writer: w, Enabled: opts.Follow} io.Copy(fw, rc) return } ch := make(chan *ct.SSELogChunk) l, _ := ctxhelper.LoggerFromContext(ctx) s := sse.NewStream(w, ch, l) defer s.Close() s.Serve() msgc := make(chan *json.RawMessage) go func() { defer close(msgc) dec := json.NewDecoder(rc) for { var m json.RawMessage if err := dec.Decode(&m); err != nil { if err != io.EOF { l.Error("decoding logagg stream", err) } return } msgc <- &m } }() for { select { case m := <-msgc: if m == nil { ch <- &ct.SSELogChunk{Event: "eof"} return } // write to sse select { case ch <- &ct.SSELogChunk{Event: "message", Data: *m}: case <-s.Done: return case <-ctx.Done(): return } case <-s.Done: return case <-ctx.Done(): return } } }
func (a *aggregatorAPI) GetLog(ctx context.Context, w http.ResponseWriter, req *http.Request) { ctx, cancel := context.WithCancel(ctx) if cn, ok := w.(http.CloseNotifier); ok { ch := cn.CloseNotify() go func() { select { case <-ch: cancel() case <-ctx.Done(): } }() } defer cancel() params, _ := ctxhelper.ParamsFromContext(ctx) follow := false if strFollow := req.FormValue("follow"); strFollow == "true" { follow = true } var ( backlog bool lines int err error ) if strLines := req.FormValue("lines"); strLines != "" { if lines, err = strconv.Atoi(strLines); err != nil { httphelper.ValidationError(w, "lines", err.Error()) return } if lines < 0 || lines > 10000 { httphelper.ValidationError(w, "lines", "lines must be an integer between 0 and 10000") return } backlog = lines > 0 } filters := make(filterSlice, 0) if jobID := req.FormValue("job_id"); jobID != "" { filters = append(filters, filterJobID(jobID)) } if processTypeVals, ok := req.Form["process_type"]; ok && len(processTypeVals) > 0 { val := processTypeVals[len(processTypeVals)-1] filters = append(filters, filterProcessType(val)) } if streamTypeVals := req.FormValue("stream_types"); streamTypeVals != "" { vals := strings.Split(streamTypeVals, ",") streamTypes := make([]logagg.StreamType, len(vals)) for i, typ := range vals { streamTypes[i] = logagg.StreamType(typ) } filters = append(filters, filterStreamType(streamTypes...)) } iter := &Iterator{ id: params.ByName("channel_id"), follow: follow, backlog: backlog, lines: lines, filter: filters, donec: ctx.Done(), } writeMessages(ctx, w, iter.Scan(a.agg)) }