summaryrefslogtreecommitdiffhomepage
path: root/internal/tui/components
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-05-09 13:37:13 -0500
committeradamdottv <[email protected]>2025-05-09 13:37:13 -0500
commitf1007771997bd0401516eda87a7e0ac92f269680 (patch)
treed26198d031516eaebcc885870b470925492d8775 /internal/tui/components
parentf41b7bbd0a0cc731fd7c471b7ee8b26f14a21755 (diff)
downloadopencode-f1007771997bd0401516eda87a7e0ac92f269680.tar.gz
opencode-f1007771997bd0401516eda87a7e0ac92f269680.zip
wip: logging improvements
Diffstat (limited to 'internal/tui/components')
-rw-r--r--internal/tui/components/dialog/filepicker.go10
-rw-r--r--internal/tui/components/logs/details.go42
-rw-r--r--internal/tui/components/logs/table.go117
3 files changed, 115 insertions, 54 deletions
diff --git a/internal/tui/components/dialog/filepicker.go b/internal/tui/components/dialog/filepicker.go
index f747dc9af..fe4223f5d 100644
--- a/internal/tui/components/dialog/filepicker.go
+++ b/internal/tui/components/dialog/filepicker.go
@@ -16,13 +16,13 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/opencode-ai/opencode/internal/app"
"github.com/opencode-ai/opencode/internal/config"
- "github.com/opencode-ai/opencode/internal/logging"
"github.com/opencode-ai/opencode/internal/message"
"github.com/opencode-ai/opencode/internal/status"
"github.com/opencode-ai/opencode/internal/tui/image"
"github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/opencode-ai/opencode/internal/tui/theme"
"github.com/opencode-ai/opencode/internal/tui/util"
+ "log/slog"
)
const (
@@ -376,7 +376,7 @@ func (f *filepickerCmp) IsCWDFocused() bool {
func NewFilepickerCmp(app *app.App) FilepickerCmp {
homepath, err := os.UserHomeDir()
if err != nil {
- logging.Error("error loading user files")
+ slog.Error("error loading user files")
return nil
}
baseDir := DirNode{parent: nil, directory: homepath}
@@ -392,7 +392,7 @@ func NewFilepickerCmp(app *app.App) FilepickerCmp {
func (f *filepickerCmp) getCurrentFileBelowCursor() {
if len(f.dirs) == 0 || f.cursor < 0 || f.cursor >= len(f.dirs) {
- logging.Error(fmt.Sprintf("Invalid cursor position. Dirs length: %d, Cursor: %d", len(f.dirs), f.cursor))
+ slog.Error(fmt.Sprintf("Invalid cursor position. Dirs length: %d, Cursor: %d", len(f.dirs), f.cursor))
f.viewport.SetContent("Preview unavailable")
return
}
@@ -405,7 +405,7 @@ func (f *filepickerCmp) getCurrentFileBelowCursor() {
go func() {
imageString, err := image.ImagePreview(f.viewport.Width-4, fullPath)
if err != nil {
- logging.Error(err.Error())
+ slog.Error(err.Error())
f.viewport.SetContent("Preview unavailable")
return
}
@@ -418,7 +418,7 @@ func (f *filepickerCmp) getCurrentFileBelowCursor() {
}
func readDir(path string, showHidden bool) []os.DirEntry {
- logging.Info(fmt.Sprintf("Reading directory: %s", path))
+ slog.Info(fmt.Sprintf("Reading directory: %s", path))
entriesChan := make(chan []os.DirEntry, 1)
errChan := make(chan error, 1)
diff --git a/internal/tui/components/logs/details.go b/internal/tui/components/logs/details.go
index 7bbfd17dc..da6edff13 100644
--- a/internal/tui/components/logs/details.go
+++ b/internal/tui/components/logs/details.go
@@ -23,17 +23,12 @@ type DetailComponent interface {
type detailCmp struct {
width, height int
- currentLog logging.LogMessage
+ currentLog logging.Log
viewport viewport.Model
focused bool
}
func (i *detailCmp) Init() tea.Cmd {
- messages := logging.List()
- if len(messages) == 0 {
- return nil
- }
- i.currentLog = messages[0]
return nil
}
@@ -42,8 +37,12 @@ func (i *detailCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case selectedLogMsg:
if msg.ID != i.currentLog.ID {
- i.currentLog = logging.LogMessage(msg)
- i.updateContent()
+ i.currentLog = logging.Log(msg)
+ // Defer content update to avoid blocking the UI
+ cmd = tea.Tick(time.Millisecond*1, func(time.Time) tea.Msg {
+ i.updateContent()
+ return nil
+ })
}
case tea.KeyMsg:
// Only process keyboard input when focused
@@ -55,7 +54,7 @@ func (i *detailCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return i, cmd
}
- return i, nil
+ return i, cmd
}
func (i *detailCmp) updateContent() {
@@ -66,9 +65,12 @@ func (i *detailCmp) updateContent() {
timeStyle := lipgloss.NewStyle().Foreground(t.TextMuted())
levelStyle := getLevelStyle(i.currentLog.Level)
+ // Format timestamp
+ timeStr := time.Unix(i.currentLog.Timestamp, 0).Format(time.RFC3339)
+
header := lipgloss.JoinHorizontal(
lipgloss.Center,
- timeStyle.Render(i.currentLog.Time.Format(time.RFC3339)),
+ timeStyle.Render(timeStr),
" ",
levelStyle.Render(i.currentLog.Level),
)
@@ -93,23 +95,33 @@ func (i *detailCmp) updateContent() {
keyStyle := lipgloss.NewStyle().Foreground(t.Primary()).Bold(true)
valueStyle := lipgloss.NewStyle().Foreground(t.Text())
- for _, attr := range i.currentLog.Attributes {
- attrLine := fmt.Sprintf("%s: %s",
- keyStyle.Render(attr.Key),
- valueStyle.Render(attr.Value),
+ for key, value := range i.currentLog.Attributes {
+ attrLine := fmt.Sprintf("%s: %s",
+ keyStyle.Render(key),
+ valueStyle.Render(value),
)
+
content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(attrLine))
content.WriteString("\n")
}
}
+ // Session ID if available
+ if i.currentLog.SessionID != "" {
+ sessionStyle := lipgloss.NewStyle().Bold(true).Foreground(t.Text())
+ content.WriteString("\n")
+ content.WriteString(sessionStyle.Render("Session:"))
+ content.WriteString("\n")
+ content.WriteString(lipgloss.NewStyle().Padding(0, 2).Render(i.currentLog.SessionID))
+ }
+
i.viewport.SetContent(content.String())
}
func getLevelStyle(level string) lipgloss.Style {
style := lipgloss.NewStyle().Bold(true)
t := theme.CurrentTheme()
-
+
switch strings.ToLower(level) {
case "info":
return style.Foreground(t.Info())
diff --git a/internal/tui/components/logs/table.go b/internal/tui/components/logs/table.go
index fe30c6aa0..e7fc4ea70 100644
--- a/internal/tui/components/logs/table.go
+++ b/internal/tui/components/logs/table.go
@@ -1,15 +1,17 @@
package logs
import (
- "slices"
+ "context"
+ "time"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/table"
tea "github.com/charmbracelet/bubbletea"
"github.com/opencode-ai/opencode/internal/logging"
"github.com/opencode-ai/opencode/internal/pubsub"
+ "github.com/opencode-ai/opencode/internal/session"
+ "github.com/opencode-ai/opencode/internal/tui/components/chat"
"github.com/opencode-ai/opencode/internal/tui/layout"
- // "github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/opencode-ai/opencode/internal/tui/theme"
"github.com/opencode-ai/opencode/internal/tui/util"
)
@@ -23,46 +25,97 @@ type TableComponent interface {
type tableCmp struct {
table table.Model
focused bool
+ logs []logging.Log
}
-type selectedLogMsg logging.LogMessage
+type selectedLogMsg logging.Log
+
+// Message for when logs are loaded from the database
+type logsLoadedMsg struct {
+ logs []logging.Log
+}
func (i *tableCmp) Init() tea.Cmd {
- i.setRows()
- return nil
+ return i.fetchLogs()
+}
+
+func (i *tableCmp) fetchLogs() tea.Cmd {
+ return func() tea.Msg {
+ ctx := context.Background()
+ loggingService := logging.GetService()
+ if loggingService == nil {
+ return nil
+ }
+
+ var logs []logging.Log
+ var err error
+ sessionId := session.CurrentSessionID()
+
+ // Limit the number of logs to improve performance
+ const logLimit = 100
+ if sessionId == "" {
+ logs, err = loggingService.ListAll(ctx, logLimit)
+ } else {
+ logs, err = loggingService.ListBySession(ctx, sessionId)
+ // Trim logs if there are too many
+ if err == nil && len(logs) > logLimit {
+ logs = logs[len(logs)-logLimit:]
+ }
+ }
+
+ if err != nil {
+ return nil
+ }
+
+ return logsLoadedMsg{logs: logs}
+ }
}
func (i *tableCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
- switch msg.(type) {
- case pubsub.Event[logging.LogMessage]:
- i.setRows()
+
+ switch msg := msg.(type) {
+ case logsLoadedMsg:
+ i.logs = msg.logs
+ i.updateRows()
+ return i, nil
+
+ case chat.SessionSelectedMsg:
+ return i, i.fetchLogs()
+
+ case pubsub.Event[logging.Log]:
+ // Only handle created events
+ if msg.Type == pubsub.CreatedEvent {
+ // Add the new log to our list
+ i.logs = append([]logging.Log{msg.Payload}, i.logs...)
+ // Keep the list at a reasonable size
+ if len(i.logs) > 100 {
+ i.logs = i.logs[:100]
+ }
+ i.updateRows()
+ }
return i, nil
}
-
+
// Only process keyboard input when focused
if _, ok := msg.(tea.KeyMsg); ok && !i.focused {
return i, nil
}
-
+
t, cmd := i.table.Update(msg)
cmds = append(cmds, cmd)
i.table = t
+
+ // Only send selected log message when selection changes
selectedRow := i.table.SelectedRow()
if selectedRow != nil {
- // Always send the selected log message when a row is selected
- // This fixes the issue where navigation doesn't update the detail pane
- // when returning to the logs page
- var log logging.LogMessage
- for _, row := range logging.List() {
- if row.ID == selectedRow[0] {
- log = row
+ // Use a map for faster lookups by ID
+ for _, log := range i.logs {
+ if log.ID == selectedRow[0] {
+ cmds = append(cmds, util.CmdHandler(selectedLogMsg(log)))
break
}
}
- if log.ID != "" {
- cmds = append(cmds, util.CmdHandler(selectedLogMsg(log)))
- }
}
return i, tea.Batch(cmds...)
}
@@ -105,25 +158,20 @@ func (i *tableCmp) BindingKeys() []key.Binding {
return layout.KeyMapToSlice(i.table.KeyMap)
}
-func (i *tableCmp) setRows() {
- rows := []table.Row{}
+func (i *tableCmp) updateRows() {
+ rows := make([]table.Row, 0, len(i.logs))
- logs := logging.List()
- slices.SortFunc(logs, func(a, b logging.LogMessage) int {
- if a.Time.Before(b.Time) {
- return 1
- }
- if a.Time.After(b.Time) {
- return -1
- }
- return 0
- })
+ // Logs are already sorted by timestamp (newest first) from the database query
+ // Skip the expensive sort operation
+
+ for _, log := range i.logs {
+ // Format timestamp as time
+ timeStr := time.Unix(log.Timestamp, 0).Format("15:04:05")
- for _, log := range logs {
// Include ID as hidden first column for selection
row := table.Row{
log.ID,
- log.Time.Format("15:04:05"),
+ timeStr,
log.Level,
log.Message,
}
@@ -146,6 +194,7 @@ func NewLogsTable() TableComponent {
tableModel.Focus()
return &tableCmp{
table: tableModel,
+ logs: []logging.Log{},
}
}