❯ go-clikit
v0.10.0 pkg.go.dev ↗

packages › progress & output › progress/eventlog

eventlog

import "github.com/GSI-HPC/go-clikit/progress/eventlog"

Read back the event log that a program or its child wrote, as it grows, into runs or into a sink.

since v0.7.0 imports: progress decisions 33 · 45
The place of progress/eventlog in go-clikit: the packages it imports, and the packages that import it.termtextprogressdisplaycliprogressprogresstestmcpprogresseventlogfanoutfanouttestclioutdoctorfsutilcobratreecobratreetestlocalwebsessionsselocalwebtestlaunchassetspagehtmx

eventlog reads the event log that progress.Log writes. A view of what a program did reads it, and so does a server that runs a command as a child and sends on the child’s progress.

progress.jsonlgrows, rotatesTail.PollReducerForwarderruns, for a viewevents, to a Sink
A Tail polls the log and gives each whole line to a Feed.
NameWhat it does
eventlog.Decodeone line back into what was written, every text sanitised
eventlog.NewTail, eventlog.Tailreads a log as it grows, and follows it when it is rotated
eventlog.NewReducer, eventlog.Reducerfolds the lines into runs, each the tree of its spans
eventlog.NewForwarder, eventlog.Forwardergives the events of one trace to a sink, once each
eventlog.ErrVersiona line of another version of the format
dir, err := os.MkdirTemp("", "view")
if err != nil {
	panic(err)
}
defer func() { _ = os.RemoveAll(dir) }()
path := filepath.Join(dir, "activity.jsonl")

// A command of the program, which appends to its log.
f, err := os.Create(path)
if err != nil {
	panic(err)
}
log := progress.NewLog(f, progress.LogOptions{RunID: "0123456789abcdef", Version: "v1.2.3"})
bus := progress.NewBus(progress.BusOptions{Sinks: []progress.Sink{log}, Program: "prog"})
ctx, cmd := progress.Start(progress.WithBus(context.Background(), bus), progress.KindCommand, "status")
fanout.Map(ctx, []string{"exe1", "exe2"}, fanout.MapOptions[string]{Step: "probe the nodes"},
	func(_ context.Context, node string) (struct{}, error) {
		if node == "exe2" {
			return struct{}{}, errors.New("no route to host")
		}
		return struct{}{}, nil
	})
cmd.End(nil)
bus.Close()
if err := log.Close(); err != nil {
	panic(err)
}
_ = f.Close()

// The view.
red := eventlog.NewReducer(eventlog.ReducerOptions{})
tail := eventlog.NewTail(path, red)
if _, err := tail.Poll(); err != nil {
	panic(err)
}
fmt.Println(red.Changed())
for _, run := range red.Runs() {
	fmt.Println(run.Program, run.Version, run.Root.Name, run.Root.Status)
	for _, step := range run.Root.Children {
		fmt.Println(" ", step.Kind, step.Name, step.Status, step.Err)
		for _, target := range step.Children {
			fmt.Println("   ", target.Kind, target.Node, target.Status)
		}
	}
}
[0123456789abcdef]
prog v1.2.3 status ok
  step probe the nodes failed 1 of 2 failed: exe2
    target exe1 ok
    target exe2 failed
ExampleReducer in progress/eventlog/example_test.go. CI runs it and checks its output.
NOTE

A reader of version 1 passes over the keys and the values that it does not know. So a log that a later release writes, with a key added, still reads. A line of another version is refused with ErrVersion.