func appLog(w http.ResponseWriter, r *http.Request, t *auth.Token) error { var err error var lines int if l := r.URL.Query().Get("lines"); l != "" { lines, err = strconv.Atoi(l) if err != nil { msg := `Parameter "lines" must be an integer.` return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } } else { return &errors.HTTP{Code: http.StatusBadRequest, Message: `Parameter "lines" is mandatory.`} } w.Header().Set("Content-Type", "application/json") source := r.URL.Query().Get("source") u, err := t.User() if err != nil { return err } appName := r.URL.Query().Get(":app") extra := []interface{}{ "app=" + appName, fmt.Sprintf("lines=%d", lines), } if source != "" { extra = append(extra, "source="+source) } if r.URL.Query().Get("follow") == "1" { extra = append(extra, "follow=1") } rec.Log(u.Email, "app-log", extra...) a, err := getApp(appName, u) if err != nil { return err } logs, err := a.LastLogs(lines, source) if err != nil { return err } encoder := json.NewEncoder(w) err = encoder.Encode(logs) if err != nil { return err } // TODO(fss): write an automated test for this code. if r.URL.Query().Get("follow") == "1" { l := app.NewLogListener(&a) defer l.Close() for log := range l.C { err := encoder.Encode([]app.Applog{log}) if err != nil { break } } } return nil }
func (s *S) TestLogStreamTrackerShutdown(c *check.C) { l, err := app.NewLogListener(&app.App{Name: "myapp"}, app.Applog{}) c.Assert(err, check.IsNil) logTracker.add(l) logTracker.Shutdown() select { case <-l.ListenChan(): case <-time.After(5 * time.Second): c.Fatal("timed out waiting for channel to close") } }
// title: app log // path: /apps/{app}/log // method: GET // produce: application/x-json-stream // responses: // 200: Ok // 400: Invalid data // 401: Unauthorized // 404: App not found func appLog(w http.ResponseWriter, r *http.Request, t auth.Token) error { var err error var lines int if l := r.URL.Query().Get("lines"); l != "" { lines, err = strconv.Atoi(l) if err != nil { msg := `Parameter "lines" must be an integer.` return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } } else { return &errors.HTTP{Code: http.StatusBadRequest, Message: `Parameter "lines" is mandatory.`} } w.Header().Set("Content-Type", "application/x-json-stream") source := r.URL.Query().Get("source") unit := r.URL.Query().Get("unit") follow := r.URL.Query().Get("follow") appName := r.URL.Query().Get(":app") filterLog := app.Applog{Source: source, Unit: unit} a, err := getAppFromContext(appName, r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppReadLog, contextsForApp(&a)..., ) if !allowed { return permission.ErrUnauthorized } logs, err := a.LastLogs(lines, filterLog) if err != nil { return err } encoder := json.NewEncoder(w) err = encoder.Encode(logs) if err != nil { return err } if follow != "1" { return nil } var closeChan <-chan bool if notifier, ok := w.(http.CloseNotifier); ok { closeChan = notifier.CloseNotify() } else { closeChan = make(chan bool) } l, err := app.NewLogListener(&a, filterLog) if err != nil { return err } logTracker.add(l) defer func() { logTracker.remove(l) l.Close() }() logChan := l.ListenChan() for { var logMsg app.Applog select { case <-closeChan: return nil case logMsg = <-logChan: } if logMsg == (app.Applog{}) { break } err := encoder.Encode([]app.Applog{logMsg}) if err != nil { break } } return nil }
func appLog(w http.ResponseWriter, r *http.Request, t auth.Token) error { var err error var lines int if l := r.URL.Query().Get("lines"); l != "" { lines, err = strconv.Atoi(l) if err != nil { msg := `Parameter "lines" must be an integer.` return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } } else { return &errors.HTTP{Code: http.StatusBadRequest, Message: `Parameter "lines" is mandatory.`} } w.Header().Set("Content-Type", "application/json") source := r.URL.Query().Get("source") unit := r.URL.Query().Get("unit") follow := r.URL.Query().Get("follow") u, err := t.User() if err != nil { return err } appName := r.URL.Query().Get(":app") extra := []interface{}{ "app=" + appName, fmt.Sprintf("lines=%d", lines), } if source != "" { extra = append(extra, "source="+source) } if follow == "1" { extra = append(extra, "follow=1") } if unit != "" { extra = append(extra, "unit="+unit) } rec.Log(u.Email, "app-log", extra...) filterLog := app.Applog{Source: source, Unit: unit} a, err := getAppFromContext(appName, u, r) if err != nil { return err } logs, err := a.LastLogs(lines, filterLog) if err != nil { return err } encoder := json.NewEncoder(w) err = encoder.Encode(logs) if err != nil { return err } if follow == "1" { l, err := app.NewLogListener(&a, filterLog) if err != nil { return err } logTracker.add(l) defer func() { logTracker.remove(l) l.Close() }() for log := range l.C { err := encoder.Encode([]app.Applog{log}) if err != nil { break } } } return nil }
func appLog(w http.ResponseWriter, r *http.Request, t auth.Token) error { var err error var lines int if l := r.URL.Query().Get("lines"); l != "" { lines, err = strconv.Atoi(l) if err != nil { msg := `Parameter "lines" must be an integer.` return &errors.HTTP{Code: http.StatusBadRequest, Message: msg} } } else { return &errors.HTTP{Code: http.StatusBadRequest, Message: `Parameter "lines" is mandatory.`} } w.Header().Set("Content-Type", "application/json") source := r.URL.Query().Get("source") unit := r.URL.Query().Get("unit") follow := r.URL.Query().Get("follow") u, err := t.User() if err != nil { return err } appName := r.URL.Query().Get(":app") extra := []interface{}{ "app=" + appName, fmt.Sprintf("lines=%d", lines), } if source != "" { extra = append(extra, "source="+source) } if follow == "1" { extra = append(extra, "follow=1") } if unit != "" { extra = append(extra, "unit="+unit) } rec.Log(u.Email, "app-log", extra...) filterLog := app.Applog{Source: source, Unit: unit} a, err := getAppFromContext(appName, r) if err != nil { return err } allowed := permission.Check(t, permission.PermAppReadLog, append(permission.Contexts(permission.CtxTeam, a.Teams), permission.Context(permission.CtxApp, a.Name), permission.Context(permission.CtxPool, a.Pool), )..., ) if !allowed { return permission.ErrUnauthorized } logs, err := a.LastLogs(lines, filterLog) if err != nil { return err } encoder := json.NewEncoder(w) err = encoder.Encode(logs) if err != nil { return err } if follow != "1" { return nil } var closeChan <-chan bool if notifier, ok := w.(http.CloseNotifier); ok { closeChan = notifier.CloseNotify() } else { closeChan = make(chan bool) } l, err := app.NewLogListener(&a, filterLog) if err != nil { return err } logTracker.add(l) defer func() { logTracker.remove(l) l.Close() }() logChan := l.ListenChan() for { var logMsg app.Applog select { case <-closeChan: return nil case logMsg = <-logChan: } if logMsg == (app.Applog{}) { break } err := encoder.Encode([]app.Applog{logMsg}) if err != nil { break } } return nil }