tconsole: add logs page
This is a basic log console. Future work can be performed to make the
display more compact, allow scrollback functionality and maybe scrolling
to the sides to see longer lines.
Change-Id: I81defe874542acfe89137035d0fc6de9861d3e33
Reviewed-on: https://review.monogon.dev/c/monogon/+/3382
Reviewed-by: Leopold Schabel <leo@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/node/core/main.go b/metropolis/node/core/main.go
index d103693..babee28 100644
--- a/metropolis/node/core/main.go
+++ b/metropolis/node/core/main.go
@@ -214,7 +214,7 @@
// Initialize interactive consoles.
interactiveConsoles := []string{"/dev/tty0"}
for _, c := range interactiveConsoles {
- console, err := tconsole.New(tconsole.TerminalLinux, c, &networkSvc.Status, &rs.LocalRoles, &rs.CuratorConnection)
+ console, err := tconsole.New(tconsole.TerminalLinux, c, lt, &networkSvc.Status, &rs.LocalRoles, &rs.CuratorConnection)
if err != nil {
logger.Info("Failed to initialize interactive console at %s: %v", c, err)
// TODO: fall back to logger
diff --git a/metropolis/node/core/tconsole/BUILD.bazel b/metropolis/node/core/tconsole/BUILD.bazel
index fadc7f3..e16eb41 100644
--- a/metropolis/node/core/tconsole/BUILD.bazel
+++ b/metropolis/node/core/tconsole/BUILD.bazel
@@ -5,6 +5,7 @@
srcs = [
"colors.go",
"draw.go",
+ "page_logs.go",
"page_status.go",
"statusbar.go",
"tconsole.go",
@@ -17,6 +18,7 @@
"//metropolis/proto/common",
"//metropolis/version",
"//osbase/event",
+ "//osbase/logtree",
"//osbase/supervisor",
"//version",
"@com_github_gdamore_tcell_v2//:tcell",
diff --git a/metropolis/node/core/tconsole/page_logs.go b/metropolis/node/core/tconsole/page_logs.go
new file mode 100644
index 0000000..d408d4d
--- /dev/null
+++ b/metropolis/node/core/tconsole/page_logs.go
@@ -0,0 +1,48 @@
+package tconsole
+
+import "github.com/gdamore/tcell/v2"
+
+// pageLogsData encompasses all data to be shown within the logs page.
+type pageLogsData struct {
+ // log lines, simple deque with the newest log lines appended to the end.
+ lines []string
+}
+
+func (p *pageLogsData) appendLine(s string) {
+ p.lines = append(p.lines, s)
+}
+
+// compactData ensures that there's no more lines stored than maxlines by
+// discarding the oldest lines.
+func (p *pageLogsData) compactData(maxlines int) {
+ if extra := len(p.lines) - maxlines; extra > 0 {
+ p.lines = p.lines[extra:]
+ }
+}
+
+// pageLogs renders the logs page to the user given pageLogsData.
+func (c *Console) pageLogs(data *pageLogsData) {
+ c.screen.Clear()
+ sty1 := tcell.StyleDefault.Background(c.color(colorPink)).Foreground(c.color(colorBlack))
+ sty2 := tcell.StyleDefault.Background(c.color(colorBlue)).Foreground(c.color(colorBlack))
+
+ // Draw frame.
+ c.fillRectangle(0, c.width, 0, c.height, sty2)
+ c.fillRectangle(1, c.width-1, 1, c.height-2, sty1)
+
+ // Inner log area size.
+ nlines := (c.height - 2) - 1
+ linelen := (c.width - 1) - 1
+
+ // Compact and draw log lines.
+ data.compactData(nlines)
+ for y := 0; y < nlines; y++ {
+ if y < len(data.lines) {
+ line := data.lines[y]
+ if len(line) > linelen {
+ line = line[:linelen]
+ }
+ c.drawText(1, 1+y, line, sty1)
+ }
+ }
+}
diff --git a/metropolis/node/core/tconsole/standalone/BUILD.bazel b/metropolis/node/core/tconsole/standalone/BUILD.bazel
index cd7a653..bc48d04 100644
--- a/metropolis/node/core/tconsole/standalone/BUILD.bazel
+++ b/metropolis/node/core/tconsole/standalone/BUILD.bazel
@@ -11,6 +11,7 @@
"//metropolis/node/core/tconsole",
"//metropolis/proto/common",
"//osbase/event/memory",
+ "//osbase/logtree",
"//osbase/supervisor",
],
)
diff --git a/metropolis/node/core/tconsole/standalone/main.go b/metropolis/node/core/tconsole/standalone/main.go
index 65b3976..d01ab4f 100644
--- a/metropolis/node/core/tconsole/standalone/main.go
+++ b/metropolis/node/core/tconsole/standalone/main.go
@@ -20,6 +20,7 @@
"source.monogon.dev/metropolis/node/core/tconsole"
cpb "source.monogon.dev/metropolis/proto/common"
"source.monogon.dev/osbase/event/memory"
+ "source.monogon.dev/osbase/logtree"
"source.monogon.dev/osbase/supervisor"
)
@@ -28,7 +29,9 @@
var rolesV memory.Value[*cpb.NodeRoles]
var curV memory.Value[*roleserve.CuratorConnection]
- tc, err := tconsole.New(tconsole.TerminalGeneric, "/proc/self/fd/0", &netV, &rolesV, &curV)
+ lt := logtree.New()
+
+ tc, err := tconsole.New(tconsole.TerminalGeneric, "/proc/self/fd/0", lt, &netV, &rolesV, &curV)
if err != nil {
log.Fatalf("tconsole.New: %v", err)
}
@@ -51,6 +54,14 @@
signal.Ignore(os.Interrupt)
supervisor.New(ctx, func(ctx context.Context) error {
supervisor.Run(ctx, "tconsole", tc.Run)
+ supervisor.Run(ctx, "log-dawdle", func(ctx context.Context) error {
+ for {
+ supervisor.Logger(ctx).Infof("It is currently: %s", time.Now().Format(time.DateTime))
+ if err := delay(ctx, time.Second); err != nil {
+ return err
+ }
+ }
+ })
supervisor.Run(ctx, "net-dawdle", func(ctx context.Context) error {
supervisor.Signal(ctx, supervisor.SignalHealthy)
for {
@@ -84,7 +95,7 @@
supervisor.Signal(ctx, supervisor.SignalHealthy)
<-ctx.Done()
return ctx.Err()
- })
+ }, supervisor.WithExistingLogtree(lt))
<-ctx.Done()
tc.Cleanup()
}
diff --git a/metropolis/node/core/tconsole/tconsole.go b/metropolis/node/core/tconsole/tconsole.go
index 963be34..bbdca82 100644
--- a/metropolis/node/core/tconsole/tconsole.go
+++ b/metropolis/node/core/tconsole/tconsole.go
@@ -4,6 +4,7 @@
"context"
"crypto/sha256"
"encoding/hex"
+ "fmt"
"strings"
"time"
@@ -13,6 +14,7 @@
"source.monogon.dev/metropolis/node/core/roleserve"
cpb "source.monogon.dev/metropolis/proto/common"
"source.monogon.dev/osbase/event"
+ "source.monogon.dev/osbase/logtree"
"source.monogon.dev/osbase/supervisor"
)
@@ -34,6 +36,7 @@
// constructed dynamically in Run.
activePage int
+ reader *logtree.LogReader
network event.Value[*network.Status]
roles event.Value[*cpb.NodeRoles]
curatorConn event.Value[*roleserve.CuratorConnection]
@@ -45,7 +48,12 @@
//
// network, roles, curatorConn point to various Metropolis subsystems that are
// used to populate the console data.
-func New(terminal Terminal, ttyPath string, network event.Value[*network.Status], roles event.Value[*cpb.NodeRoles], curatorConn event.Value[*roleserve.CuratorConnection]) (*Console, error) {
+func New(terminal Terminal, ttyPath string, lt *logtree.LogTree, network event.Value[*network.Status], roles event.Value[*cpb.NodeRoles], curatorConn event.Value[*roleserve.CuratorConnection]) (*Console, error) {
+ reader, err := lt.Read("", logtree.WithChildren(), logtree.WithStream())
+ if err != nil {
+ return nil, fmt.Errorf("lt.Read: %v", err)
+ }
+
tty, err := tcell.NewDevTtyFromDev(ttyPath)
if err != nil {
return nil, err
@@ -80,6 +88,7 @@
palette: pal,
Quit: make(chan struct{}),
activePage: 0,
+ reader: reader,
roles: roles,
curatorConn: curatorConn,
@@ -90,6 +99,7 @@
// the Metropolis console always runs.
func (c *Console) Cleanup() {
c.screen.Fini()
+ c.reader.Close()
}
func (c *Console) processEvent(ev tcell.Event) {
@@ -136,13 +146,15 @@
id: "Waiting...",
fingerprint: "Waiting...",
}
+ pageLogs := pageLogsData{}
// Page references and names.
pages := []func(){
func() { c.pageStatus(&pageStatus) },
+ func() { c.pageLogs(&pageLogs) },
}
pageNames := []string{
- "Status",
+ "Status", "Logs",
}
// Ticker used to maintain redraws at minimum 10Hz, to eg. update the clock in
@@ -190,6 +202,8 @@
sum := sha256.New()
sum.Write(cert.Raw)
pageStatus.fingerprint = hex.EncodeToString(sum.Sum(nil))
+ case le := <-c.reader.Stream:
+ pageLogs.appendLine(le.String())
}
}
}