Example #1
0
func (r *Runner) getBuildLog(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	id := ps.ByName("build")
	b := &Build{}
	if err := r.db.View(func(tx *bolt.Tx) error {
		v := tx.Bucket(dbBucket).Get([]byte(id))
		if err := json.Unmarshal(v, b); err != nil {
			return fmt.Errorf("could not decode build %s: %s", v, err)
		}
		return nil
	}); err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	if b.Finished() {
		if b.Version == BuildVersion1 {
			http.Redirect(w, req, b.LogURL, http.StatusMovedPermanently)
			return
		}
		if strings.Contains(req.Header.Get("Accept"), "text/event-stream") {
			if err := serveBuildLogStream(b, w); err != nil {
				http.Error(w, err.Error(), 500)
			}
			return
		}
		http.ServeFile(w, req, path.Join(args.AssetsDir, "build-log.html"))
		return
	}
	t, err := tail.TailFile(b.LogFile, tail.Config{Follow: true, MustExist: true})
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	if cn, ok := w.(http.CloseNotifier); ok {
		go func() {
			<-cn.CloseNotify()
			t.Stop()
		}()
	} else {
		defer t.Stop()
	}
	flush := func() {
		if fw, ok := w.(http.Flusher); ok {
			fw.Flush()
		}
	}
	w.Header().Set("Content-Type", textPlain)
	w.WriteHeader(http.StatusOK)
	flush()
	for line := range t.Lines {
		if _, err := io.WriteString(w, line.Text+"\n"); err != nil {
			log.Printf("serveBuildLog write error: %s\n", err)
			return
		}
		flush()
		if strings.HasPrefix(line.Text, "build finished") {
			return
		}
	}
}
Example #2
0
func tailFile(filename string, config tail.Config, done chan bool) {
	defer func() { done <- true }()
	t, err := tail.TailFile(filename, config)
	if err != nil {
		fmt.Println(err)
		return
	}
	for line := range t.Lines {
		fmt.Println(line.Text)
	}
	err = t.Wait()
	if err != nil {
		fmt.Println(err)
	}
}
Example #3
0
func getBuildLogStream(b *Build, ch chan string) (stream.Stream, error) {
	stream := stream.New()

	// if the build hasn't finished, tail the log from disk
	if !b.Finished() {
		t, err := tail.TailFile(b.LogFile, tail.Config{Follow: true, MustExist: true})
		if err != nil {
			return nil, err
		}
		go func() {
			defer t.Stop()
			defer close(ch)
			for {
				select {
				case line, ok := <-t.Lines:
					if !ok {
						stream.Error = t.Err()
						return
					}
					select {
					case ch <- line.Text:
					case <-stream.StopCh:
						return
					}
					if strings.HasPrefix(line.Text, "build finished") {
						return
					}
				case <-stream.StopCh:
					return
				}
			}
		}()
		return stream, nil
	}

	// get the multipart log from S3 and serve just the "build.log" file
	res, err := http.Get(b.LogURL)
	if err != nil {
		return nil, err
	}
	if res.StatusCode != http.StatusOK {
		res.Body.Close()
		return nil, fmt.Errorf("unexpected status %d getting build log", res.StatusCode)
	}
	_, params, err := mime.ParseMediaType(res.Header.Get("Content-Type"))
	if err != nil {
		res.Body.Close()
		return nil, err
	}
	go func() {
		defer res.Body.Close()
		defer close(ch)

		mr := multipart.NewReader(res.Body, params["boundary"])
		for {
			select {
			case <-stream.StopCh:
				return
			default:
			}

			p, err := mr.NextPart()
			if err != nil {
				stream.Error = err
				return
			}
			if p.FileName() != "build.log" {
				continue
			}
			s := bufio.NewScanner(p)
			for s.Scan() {
				select {
				case ch <- s.Text():
				case <-stream.StopCh:
					return
				}
			}
			return
		}
	}()
	return stream, nil
}
Example #4
0
// Read old log lines from a logfile.
func (l *Log) Read(lines int, follow bool, ch chan Data, done chan struct{}) error {
	name := l.l.Filename

	var seek int64
	if lines == 0 {
		f, err := os.Open(name)
		defer f.Close()
		if err != nil {
			return err
		}
		if seek, err = f.Seek(0, os.SEEK_END); err != nil {
			return err
		}
	} else if lines == -1 {
		// return all lines
		dir := filepath.Dir(name)
		files, err := ioutil.ReadDir(dir)
		if err != nil {
			return err
		}
		basename := filepath.Base(name)
		ext := filepath.Ext(basename)
		id := strings.TrimSuffix(basename, ext)
		for _, f := range files {
			if !(strings.HasPrefix(f.Name(), id+"-") && strings.HasSuffix(f.Name(), ext)) {
				continue
			}

			t, err := tail.TailFile(filepath.Join(dir, f.Name()), tail.Config{
				Logger: tail.DiscardingLogger,
			})
			if err != nil {
				return err
			}
			for line := range t.Lines {
				data := Data{}
				if err := json.Unmarshal([]byte(line.Text), &data); err != nil {
					return err
				}
				ch <- data
			}
		}
	}

	t, err := tail.TailFile(name, tail.Config{
		Follow: follow,
		ReOpen: follow,
		Logger: tail.DiscardingLogger,
		Location: &tail.SeekInfo{
			Offset: seek,
			Whence: os.SEEK_SET,
		},
	})
	if err != nil {
		return err
	}
	defer t.Stop()
	closed := l.closed
outer:
	for {
		select {
		case line, ok := <-t.Lines:
			if !ok {
				break outer
			}
			data := Data{}
			if err := json.Unmarshal([]byte(line.Text), &data); err != nil {
				return err
			}
			ch <- data
		case <-done:
			break outer
		case <-closed:
			// StopAtEOF will wait until the log hits an EOF before closing
			// t.Lines
			go t.StopAtEOF()
			// make sure that we don't hit this path again
			closed = nil
		}
	}
	close(ch) // send a close event so we know everything was read
	return nil
}