diff options
| author | adamdottv <[email protected]> | 2025-05-09 13:37:13 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-05-09 13:37:13 -0500 |
| commit | f1007771997bd0401516eda87a7e0ac92f269680 (patch) | |
| tree | d26198d031516eaebcc885870b470925492d8775 /internal/tui/components | |
| parent | f41b7bbd0a0cc731fd7c471b7ee8b26f14a21755 (diff) | |
| download | opencode-f1007771997bd0401516eda87a7e0ac92f269680.tar.gz opencode-f1007771997bd0401516eda87a7e0ac92f269680.zip | |
wip: logging improvements
Diffstat (limited to 'internal/tui/components')
| -rw-r--r-- | internal/tui/components/dialog/filepicker.go | 10 | ||||
| -rw-r--r-- | internal/tui/components/logs/details.go | 42 | ||||
| -rw-r--r-- | internal/tui/components/logs/table.go | 117 |
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{}, } } |
