forked from Mirrors/bubbletea
feat: add Tracer, a debugging aid
Adds a Tracer interface that is capable of logging all executed tea.Cmd and all processed tea.Msg. RemoteTracer is one standard implementation, which provides access to a live stream of these event logs, through a TCP socket.
This commit is contained in:
parent
2d10416631
commit
d7516fc570
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbletea/tracer"
|
||||||
"github.com/muesli/termenv"
|
"github.com/muesli/termenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -151,3 +152,10 @@ func WithANSICompressor() ProgramOption {
|
||||||
p.startupOptions |= withANSICompressor
|
p.startupOptions |= withANSICompressor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTracer sets a tracer for the program. This is useful for debugging.
|
||||||
|
func WithTracer(t tracer.Tracer) ProgramOption {
|
||||||
|
return func(p *Program) {
|
||||||
|
p.tracer = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
44
tea.go
44
tea.go
|
@ -16,10 +16,13 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"reflect"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/bubbletea/tracer"
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
isatty "github.com/mattn/go-isatty"
|
isatty "github.com/mattn/go-isatty"
|
||||||
"github.com/muesli/cancelreader"
|
"github.com/muesli/cancelreader"
|
||||||
|
@ -122,6 +125,8 @@ type Program struct {
|
||||||
// as this value only comes into play on Windows, hence the ignore comment
|
// as this value only comes into play on Windows, hence the ignore comment
|
||||||
// below.
|
// below.
|
||||||
windowsStdin *os.File //nolint:golint,structcheck,unused
|
windowsStdin *os.File //nolint:golint,structcheck,unused
|
||||||
|
|
||||||
|
tracer tracer.Tracer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quit is a special command that tells the Bubble Tea program to exit.
|
// Quit is a special command that tells the Bubble Tea program to exit.
|
||||||
|
@ -244,8 +249,17 @@ func (p *Program) handleCommands(cmds chan Cmd) chan struct{} {
|
||||||
// possible to cancel them so we'll have to leak the goroutine
|
// possible to cancel them so we'll have to leak the goroutine
|
||||||
// until Cmd returns.
|
// until Cmd returns.
|
||||||
go func() {
|
go func() {
|
||||||
|
tc := tracer.NewCommand()
|
||||||
|
if p.tracer != nil {
|
||||||
|
p.tracer.Command(tc)
|
||||||
|
}
|
||||||
|
|
||||||
msg := cmd() // this can be long.
|
msg := cmd() // this can be long.
|
||||||
p.Send(msg)
|
p.Send(msg)
|
||||||
|
|
||||||
|
if p.tracer != nil {
|
||||||
|
p.traceCommand(tc, msg)
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,6 +268,34 @@ func (p *Program) handleCommands(cmds chan Cmd) chan struct{} {
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Program) traceCommand(tc tracer.Command, msg Msg) {
|
||||||
|
t := "nil"
|
||||||
|
if msg != nil {
|
||||||
|
t = reflect.TypeOf(msg).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.Finished = time.Now()
|
||||||
|
tc.Type = t
|
||||||
|
tc.Msg = fmt.Sprintf("%v", msg)
|
||||||
|
|
||||||
|
p.tracer.Command(tc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Program) traceMessage(model Model, msg Msg) {
|
||||||
|
if p.tracer != nil {
|
||||||
|
t := "nil"
|
||||||
|
if msg != nil {
|
||||||
|
t = reflect.TypeOf(msg).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.tracer.Message(tracer.Message{
|
||||||
|
Model: reflect.TypeOf(model).String(),
|
||||||
|
Type: t,
|
||||||
|
Msg: fmt.Sprintf("%v", msg),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// eventLoop is the central message loop. It receives and handles the default
|
// eventLoop is the central message loop. It receives and handles the default
|
||||||
// Bubble Tea messages, update the model and triggers redraws.
|
// Bubble Tea messages, update the model and triggers redraws.
|
||||||
func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||||
|
@ -266,6 +308,8 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) {
|
||||||
return model, err
|
return model, err
|
||||||
|
|
||||||
case msg := <-p.msgs:
|
case msg := <-p.msgs:
|
||||||
|
p.traceMessage(model, msg)
|
||||||
|
|
||||||
// Handle special internal messages.
|
// Handle special internal messages.
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case quitMsg:
|
case quitMsg:
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
package tracer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tracer interface {
|
||||||
|
Command(c Command)
|
||||||
|
Message(m Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
Timestamp time.Time
|
||||||
|
Message Message
|
||||||
|
Command Command
|
||||||
|
}
|
||||||
|
|
||||||
|
type Message struct {
|
||||||
|
Model string
|
||||||
|
Type string
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
ID int
|
||||||
|
Started time.Time
|
||||||
|
Finished time.Time
|
||||||
|
Type string
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCommand() Command {
|
||||||
|
mtx.Lock()
|
||||||
|
defer mtx.Unlock()
|
||||||
|
id++
|
||||||
|
|
||||||
|
return Command{
|
||||||
|
ID: id,
|
||||||
|
Started: time.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ch = make(chan Event)
|
||||||
|
mtx sync.Mutex
|
||||||
|
id int
|
||||||
|
)
|
||||||
|
|
||||||
|
type RemoteTracer struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemoteTracer() (*RemoteTracer, error) {
|
||||||
|
listen, err := net.Listen("tcp", ":13337")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
conn, err := listen.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go handleTraceConn(conn)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
t := &RemoteTracer{}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RemoteTracer) Command(c Command) {
|
||||||
|
ev := Event{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Command: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- ev
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RemoteTracer) Message(m Message) {
|
||||||
|
ev := Event{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Message: m,
|
||||||
|
}
|
||||||
|
|
||||||
|
ch <- ev
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTraceConn(conn net.Conn) {
|
||||||
|
for ev := range ch {
|
||||||
|
b, _ := json.Marshal(ev)
|
||||||
|
_, _ = conn.Write(b)
|
||||||
|
_, err := conn.Write([]byte("\n"))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close conn
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
Loading…
Reference in New Issue