Skip to content

Commit

Permalink
support global debug/warning/error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
vito committed Jul 14, 2023
1 parent 7918760 commit 2e5d1c4
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 16 deletions.
12 changes: 9 additions & 3 deletions progress.proto
Original file line number Diff line number Diff line change
Expand Up @@ -155,16 +155,22 @@ message Message {
}

// MessageLevel indicates the severity of a message.
//
// Note that there isn't an INFO level as Messages aren't meant to be used for
// general-purpose logging or updates; those should go through the regular
// vertex status update flow instead. Nevertheless, room has been left for more
// levels in the future, and the enum values are aligned with Go's log/slog
// package.
enum MessageLevel {
// DEBUG indicates that the message should only be shown if debugging is
// enabled.
DEBUG = 0;
DEBUG = -4;
// WARNING indicates that the message should be shown to the user at all
// times, but that execution of the program can continue as normal.
WARNING = 1;
WARNING = 4;
// ERROR indicates that the message should be shown to the user, and that
// executation of the program cannot continue.
ERROR = 2;
ERROR = 8;
}

// ProgressService is a service that allows clients to stream updates to a
Expand Down
57 changes: 57 additions & 0 deletions recorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,63 @@ func (recorder *Recorder) Record(status *StatusUpdate) error {
return recorder.w.WriteStatus(clone)
}

// MessageOpt is an option for creating a Message.
type MessageOpt func(*Message)

// WithMessageLevel sets the message level.
func WithMessageLevel(level MessageLevel) MessageOpt {
return func(m *Message) {
m.Level = level
}
}

// WithMessageCode sets the message code.
func WithMessageCode(code string) MessageOpt {
return func(m *Message) {
m.Code = &code
}
}

// WithMessageLabels sets the message labels.
func WithMessageLabels(labels ...*Label) MessageOpt {
return func(m *Message) {
m.Labels = append(m.Labels, labels...)
}
}

// Debug sends a progress update with a debug-level message.
func (recorder *Recorder) Debug(msg string, opts ...MessageOpt) {
opts = append(opts, WithMessageLevel(MessageLevel_DEBUG))
recorder.message(msg, opts...)
}

// Warn sends a progress update with a warning-level message.
func (recorder *Recorder) Warn(msg string, opts ...MessageOpt) {
opts = append(opts, WithMessageLevel(MessageLevel_WARNING))
recorder.message(msg, opts...)
}

// Warn sends a progress update with an error-level message.
func (recorder *Recorder) Error(msg string, opts ...MessageOpt) {
opts = append(opts, WithMessageLevel(MessageLevel_ERROR))
recorder.message(msg, opts...)
}

// message sends a progress update with a message.
func (recorder *Recorder) message(msg string, opts ...MessageOpt) {
message := &Message{
Message: msg,
}

for _, o := range opts {
o(message)
}

recorder.Record(&StatusUpdate{
Messages: []*Message{message},
})
}

// GroupOpt is an option for creating a Group.
type GroupOpt func(*Group)

Expand Down
83 changes: 70 additions & 13 deletions tape.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ type Tape struct {
showAllOutput bool // show output even for completed vertexes
focus bool // only show 'focused' vertex output, condensing the rest

debug *ui.Vterm
// output from messages and internal debugging
globalLogs *ui.Vterm

// minimum message level to display to the user
messageLevel MessageLevel

l sync.Mutex
}
Expand Down Expand Up @@ -97,7 +101,9 @@ func NewTape() *Tape {
// sane default before window size is known
termHeight: 10,

debug: ui.NewVterm(),
globalLogs: ui.NewVterm(),

messageLevel: MessageLevel_WARNING,
}
}

Expand Down Expand Up @@ -170,9 +176,39 @@ func (tape *Tape) WriteStatus(status *StatusUpdate) error {
}
}

for _, msg := range status.Messages {
tape.log(msg)
}

return nil
}

func (tape *Tape) log(msg *Message) {
if msg.Level < tape.messageLevel {
return
}

var prefix termenv.Style
switch msg.Level {
case MessageLevel_DEBUG:
prefix = termenv.String("DEBUG:").Foreground(termenv.ANSIBlue).Bold()
case MessageLevel_WARNING:
prefix = termenv.String("WARNING:").Foreground(termenv.ANSIYellow).Bold()
case MessageLevel_ERROR:
prefix = termenv.String("ERROR:").Foreground(termenv.ANSIRed).Bold()
}

out := msg.Message
for _, l := range msg.Labels {
out += " "
out += termenv.String(fmt.Sprintf("%s=%q", l.Name, l.Value)).
Foreground(termenv.ANSIBrightBlack).
String()
}

fmt.Fprintln(tape.globalLogs, prefix, out)
}

func (tape *Tape) Vertices() []*Vertex {
tape.l.Lock()
defer tape.l.Unlock()
Expand Down Expand Up @@ -299,6 +335,13 @@ func (tape *Tape) Focus(focused bool) {
tape.focus = focused
}

// MessageLevel sets the minimum level for messages to display.
func (tape *Tape) MessageLevel(level MessageLevel) {
tape.l.Lock()
defer tape.l.Unlock()
tape.messageLevel = level
}

// SetWindowSize sets the size of the terminal UI, which influences the
// dimensions for vertex logs, progress bars, etc.
func (tape *Tape) SetWindowSize(w, h int) {
Expand All @@ -309,7 +352,7 @@ func (tape *Tape) SetWindowSize(w, h int) {
for _, l := range tape.logs {
l.SetWidth(w)
}
tape.debug.SetWidth(w)
tape.globalLogs.SetWidth(w)
tape.l.Unlock()
}

Expand Down Expand Up @@ -370,7 +413,7 @@ func (tape *Tape) Render(w io.Writer, u *UI) error {
groups = groups.Reap(groupsW, u, order[i:])

for _, g := range b.Groups(vtx) {
groups = groups.AddGroup(groupsW, u, b, g, tape.debug)
groups = groups.AddGroup(groupsW, u, b, g, tape.log)
}

if tape.filteredOut(vtx) {
Expand All @@ -382,7 +425,7 @@ func (tape *Tape) Render(w io.Writer, u *UI) error {
symbol, _, _ = u.Spinner.ViewFrame(pulse)
}

groups.VertexPrefix(groupsW, u, vtx, symbol, tape.debug)
groups.VertexPrefix(groupsW, u, vtx, symbol, tape.log)
if err := u.RenderVertex(w, vtx); err != nil {
return err
}
Expand Down Expand Up @@ -444,8 +487,8 @@ func (tape *Tape) Render(w io.Writer, u *UI) error {

groups.Reap(groupsW, u, nil)

tape.debug.SetHeight(10)
fmt.Fprint(w, tape.debug.View())
tape.globalLogs.SetHeight(10)
fmt.Fprint(w, tape.globalLogs.View())

return nil
}
Expand Down Expand Up @@ -707,7 +750,7 @@ func (vg vertexGroup) WitnessedAll() bool {
}

// AddVertex adds a group to the set of groups. It also renders the new groups.
func (groups progressGroups) AddGroup(w io.Writer, u *UI, b *bouncer, group *Group, debug io.Writer) progressGroups {
func (groups progressGroups) AddGroup(w io.Writer, u *UI, b *bouncer, group *Group, log func(*Message)) progressGroups {
pg := progGroup{group, b}

if len(groups) == 0 && group.Name == RootGroup {
Expand All @@ -731,12 +774,19 @@ func (groups progressGroups) AddGroup(w io.Writer, u *UI, b *bouncer, group *Gro
if parentIdx == -1 && group.Parent != nil {
parent, found := b.Parent(group)
if !found {
fmt.Fprintln(debug, "group", group.Id, "has unknown parent:", group.GetParent())
log(&Message{
Level: MessageLevel_DEBUG,
Message: "group has unknown parent",
Labels: []*Label{
{Name: "group", Value: group.Id},
{Name: "parent", Value: group.GetParent()},
},
})
return groups
}

return groups.AddGroup(w, u, b, parent, debug).
AddGroup(w, u, b, group, debug)
return groups.AddGroup(w, u, b, parent, log).
AddGroup(w, u, b, group, log)
}

var added bool
Expand Down Expand Up @@ -894,7 +944,7 @@ func (groups progressGroups) AddVertex(w io.Writer, u *UI, allGroups map[string]
}

// VertexPrefix prints the prefix for a vertex.
func (groups progressGroups) VertexPrefix(w io.Writer, u *UI, vtx *Vertex, selfSymbol string, debug io.Writer) {
func (groups progressGroups) VertexPrefix(w io.Writer, u *UI, vtx *Vertex, selfSymbol string, log func(*Message)) {
var firstParentIdx = -1
var lastParentIdx = -1
var vtxIdx = -1
Expand All @@ -916,7 +966,14 @@ func (groups progressGroups) VertexPrefix(w io.Writer, u *UI, vtx *Vertex, selfS
}

if vtxIdx == -1 {
fmt.Fprintln(debug, "vertex has no containing group?", vtx)
log(&Message{
Level: MessageLevel_DEBUG,
Message: "vertex has no containing group",
Labels: []*Label{
{Name: "vertex", Value: vtx.Id},
{Name: "name", Value: vtx.Name},
},
})
}

for i, g := range groups {
Expand Down
35 changes: 35 additions & 0 deletions tape_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,41 @@ func TestAutoResize(t *testing.T) {
})
}

func TestMessages(t *testing.T) {
t.Run("debug messages are not shown by default", func(t *testing.T) {
tape := progrock.NewTape()
recorder := progrock.NewRecorder(tape)
recorder.Vertex("a", "some vertex").Done(nil)
recorder.Debug("hello")
testGolden(t, tape)
})

t.Run("debug messages are not shown if level is set", func(t *testing.T) {
tape := progrock.NewTape()
tape.MessageLevel(progrock.MessageLevel_DEBUG)
recorder := progrock.NewRecorder(tape)
recorder.Vertex("a", "some vertex").Done(nil)
recorder.Debug("hello")
testGolden(t, tape)
})

t.Run("warnings are shown by default", func(t *testing.T) {
tape := progrock.NewTape()
recorder := progrock.NewRecorder(tape)
recorder.Vertex("a", "some vertex").Done(nil)
recorder.Warn("uh oh")
testGolden(t, tape)
})

t.Run("errors are shown by default", func(t *testing.T) {
tape := progrock.NewTape()
recorder := progrock.NewRecorder(tape)
recorder.Vertex("a", "some vertex").Done(nil)
recorder.Error("oh no")
testGolden(t, tape)
})
}

func testGolden(t *testing.T, tape *progrock.Tape) {
buf := new(bytes.Buffer)
tape.SetWindowSize(80, 24)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
█ [0.00s] some vertex
┻
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
█ [0.00s] some vertex
┻
DEBUG: hello 
3 changes: 3 additions & 0 deletions testdata/TestMessages/errors_are_shown_by_default.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
█ [0.00s] some vertex
┻
ERROR: oh no 
3 changes: 3 additions & 0 deletions testdata/TestMessages/warnings_are_shown_by_default.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
█ [0.00s] some vertex
┻
WARNING: uh oh 

0 comments on commit 2e5d1c4

Please sign in to comment.