package logs import ( "bufio" "context" "fmt" "io" "os" "os/signal" "sync" "github.com/docker/docker/api/types" containerTypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/filters" dockerClient "github.com/docker/docker/client" ) type TailOpts struct { AppName string Services []string StdErr bool Since string Buffer *[]string ToBuffer bool Filters filters.Args } // TailLogs gathers logs for the given app with optional service names to be // filtered on. These logs can be printed to os.Stdout or gathered to a buffer. func TailLogs( cl *dockerClient.Client, opts TailOpts, ) error { sigIntCh := make(chan os.Signal, 1) signal.Notify(sigIntCh, os.Interrupt) defer signal.Stop(sigIntCh) services, err := cl.ServiceList( context.Background(), types.ServiceListOptions{Filters: opts.Filters}, ) if err != nil { return err } errCh := make(chan error) waitCh := make(chan struct{}) go func() { var wg sync.WaitGroup for _, service := range services { wg.Add(1) go func(serviceID string) { tail := "50" if opts.ToBuffer { // NOTE(d1): more logs from before deployment when analysing via file tail = "150" } logs, err := cl.ServiceLogs(context.Background(), serviceID, containerTypes.LogsOptions{ ShowStderr: true, ShowStdout: !opts.StdErr, Since: opts.Since, Until: "", Timestamps: true, Follow: true, Tail: tail, Details: false, }) if err == nil { defer logs.Close() if opts.ToBuffer { buf := bufio.NewScanner(logs) for buf.Scan() { line := fmt.Sprintf("%s: %s", service.Spec.Name, buf.Text()) *opts.Buffer = append(*opts.Buffer, line) } logs.Close() return } if _, err = io.Copy(os.Stdout, logs); err != nil && err != io.EOF { errCh <- fmt.Errorf("tailLogs: unable to copy buffer: %s", err) } } }(service.ID) } wg.Wait() close(waitCh) }() select { case <-waitCh: return nil case <-sigIntCh: return nil case err := <-errCh: return err } return nil }