package journal import ( "bufio" "context" "fmt" "io" "os/exec" "github.com/psviderski/uncloud/pkg/api" ) const ( UnitUncloud = "uncloud" UnitDocker = "docker" UnitCorrosion = "uncloud-corrosion" ) func ValidUnit(unit string) bool { switch unit { case UnitUncloud: case UnitDocker: case UnitCorrosion: default: return false } return true } const journalctl = "journalctl" var commandContext = exec.CommandContext // allow override for test func logs(ctx context.Context, unit string, opts api.ServiceLogsOptions) (io.ReadCloser, func() error, error) { if !ValidUnit(unit) { return nil, nil, fmt.Errorf("journal logs: invalid unit: %s", unit) } args := []string{"-u", unit, "--no-hostname"} args = append(args, "-n") if opts.Tail > -1 { args = append(args, fmt.Sprintf("%d", opts.Tail)) } else { args = append(args, "all") } if opts.Follow { args = append(args, "-f") } args = append(args, "-o") args = append(args, "short-unix") if opts.Since != "" { args = append(args, "-S") args = append(args, opts.Since) } if opts.Until != "" { args = append(args, "-U") args = append(args, opts.Until) } cmd := commandContext(ctx, journalctl, args...) p, err := cmd.StdoutPipe() if err != nil { return nil, nil, err } if err := cmd.Start(); err != nil { return nil, nil, err } return p, cmd.Wait, nil } // follow synchronously follows the io.Reader, writing each new journal entry to channel. // It stops when the reader is exhausted or the context is cancelled. func follow(ctx context.Context, reader io.Reader, outCh chan api.LogEntry) { scanner := bufio.NewScanner(reader) for scanner.Scan() { select { case outCh <- entry(scanner.Bytes()): case <-ctx.Done(): return } } if err := scanner.Err(); err != nil { outCh <- api.LogEntry{Err: fmt.Errorf("journal logs: %w", err)} } }