Detailed changes
@@ -35,6 +35,7 @@ var logsCmd = &cobra.Command{
return fmt.Errorf("failed to tail log file: %v", err)
}
+ log.SetLevel(log.DebugLevel)
// Print the text of each received line
for line := range t.Lines {
var data map[string]any
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
+ "log/slog"
"os"
"sync"
"time"
@@ -14,7 +15,7 @@ import (
"github.com/charmbracelet/crush/internal/db"
"github.com/charmbracelet/crush/internal/format"
"github.com/charmbracelet/crush/internal/llm/agent"
- "github.com/charmbracelet/crush/internal/logging"
+ "github.com/charmbracelet/crush/internal/log"
"github.com/charmbracelet/crush/internal/pubsub"
"github.com/charmbracelet/crush/internal/tui"
"github.com/charmbracelet/crush/internal/version"
@@ -36,7 +37,7 @@ to assist developers in writing, debugging, and understanding code directly from
# Run with debug logging
crush -d
- # Run with debug logging in a specific directory
+ # Run with debug slog.in a specific directory
crush -d -c /path/to/project
# Print version
@@ -92,7 +93,7 @@ to assist developers in writing, debugging, and understanding code directly from
app, err := app.New(ctx, conn)
if err != nil {
- logging.Error("Failed to create app: %v", err)
+ slog.Error("Failed to create app: %v", err)
return err
}
// Defer shutdown here so it runs for both interactive and non-interactive modes
@@ -103,7 +104,7 @@ to assist developers in writing, debugging, and understanding code directly from
prompt, err = maybePrependStdin(prompt)
if err != nil {
- logging.Error("Failed to read stdin: %v", err)
+ slog.Error("Failed to read stdin: %v", err)
return err
}
@@ -132,18 +133,18 @@ to assist developers in writing, debugging, and understanding code directly from
// Set up message handling for the TUI
go func() {
defer tuiWg.Done()
- defer logging.RecoverPanic("TUI-message-handler", func() {
+ defer log.RecoverPanic("TUI-message-handler", func() {
attemptTUIRecovery(program)
})
for {
select {
case <-tuiCtx.Done():
- logging.Info("TUI message handler shutting down")
+ slog.Info("TUI message handler shutting down")
return
case msg, ok := <-ch:
if !ok {
- logging.Info("TUI message channel closed")
+ slog.Info("TUI message channel closed")
return
}
program.Send(msg)
@@ -165,7 +166,7 @@ to assist developers in writing, debugging, and understanding code directly from
// Wait for TUI message handler to finish
tuiWg.Wait()
- logging.Info("All goroutines cleaned up")
+ slog.Info("All goroutines cleaned up")
}
// Run the TUI
@@ -173,18 +174,18 @@ to assist developers in writing, debugging, and understanding code directly from
cleanup()
if err != nil {
- logging.Error("TUI error: %v", err)
+ slog.Error("TUI error: %v", err)
return fmt.Errorf("TUI error: %v", err)
}
- logging.Info("TUI exited with result: %v", result)
+ slog.Info("TUI exited with result: %v", result)
return nil
},
}
// attemptTUIRecovery tries to recover the TUI after a panic
func attemptTUIRecovery(program *tea.Program) {
- logging.Info("Attempting to recover TUI after panic")
+ slog.Info("Attempting to recover TUI after panic")
// We could try to restart the TUI or gracefully exit
// For now, we'll just quit the program to avoid further issues
@@ -193,7 +194,7 @@ func attemptTUIRecovery(program *tea.Program) {
func initMCPTools(ctx context.Context, app *app.App) {
go func() {
- defer logging.RecoverPanic("MCP-goroutine", nil)
+ defer log.RecoverPanic("MCP-goroutine", nil)
// Create a context with timeout for the initial MCP tools fetch
ctxWithTimeout, cancel := context.WithTimeout(ctx, 30*time.Second)
@@ -201,7 +202,7 @@ func initMCPTools(ctx context.Context, app *app.App) {
// Set this up once with proper error handling
agent.GetMcpTools(ctxWithTimeout, app.Permissions)
- logging.Info("MCP message handling goroutine exiting")
+ slog.Info("MCP message handling goroutine exiting")
}()
}
@@ -215,7 +216,7 @@ func setupSubscriber[T any](
wg.Add(1)
go func() {
defer wg.Done()
- defer logging.RecoverPanic(fmt.Sprintf("subscription-%s", name), nil)
+ defer log.RecoverPanic(fmt.Sprintf("subscription-%s", name), nil)
subCh := subscriber(ctx)
@@ -223,7 +224,7 @@ func setupSubscriber[T any](
select {
case event, ok := <-subCh:
if !ok {
- logging.Info("subscription channel closed", "name", name)
+ slog.Info("subscription channel closed", "name", name)
return
}
@@ -232,13 +233,13 @@ func setupSubscriber[T any](
select {
case outputCh <- msg:
case <-time.After(2 * time.Second):
- logging.Warn("message dropped due to slow consumer", "name", name)
+ slog.Warn("message dropped due to slow consumer", "name", name)
case <-ctx.Done():
- logging.Info("subscription cancelled", "name", name)
+ slog.Info("subscription cancelled", "name", name)
return
}
case <-ctx.Done():
- logging.Info("subscription cancelled", "name", name)
+ slog.Info("subscription cancelled", "name", name)
return
}
}
@@ -251,7 +252,6 @@ func setupSubscriptions(app *app.App, parentCtx context.Context) (chan tea.Msg,
wg := sync.WaitGroup{}
ctx, cancel := context.WithCancel(parentCtx) // Inherit from parent context
- setupSubscriber(ctx, &wg, "logging", logging.Subscribe, ch)
setupSubscriber(ctx, &wg, "sessions", app.Sessions.Subscribe, ch)
setupSubscriber(ctx, &wg, "messages", app.Messages.Subscribe, ch)
setupSubscriber(ctx, &wg, "permissions", app.Permissions.Subscribe, ch)
@@ -259,22 +259,22 @@ func setupSubscriptions(app *app.App, parentCtx context.Context) (chan tea.Msg,
setupSubscriber(ctx, &wg, "history", app.History.Subscribe, ch)
cleanupFunc := func() {
- logging.Info("Cancelling all subscriptions")
+ slog.Info("Cancelling all subscriptions")
cancel() // Signal all goroutines to stop
waitCh := make(chan struct{})
go func() {
- defer logging.RecoverPanic("subscription-cleanup", nil)
+ defer log.RecoverPanic("subscription-cleanup", nil)
wg.Wait()
close(waitCh)
}()
select {
case <-waitCh:
- logging.Info("All subscription goroutines completed successfully")
+ slog.Info("All subscription goroutines completed successfully")
close(ch) // Only close after all writers are confirmed done
case <-time.After(5 * time.Second):
- logging.Warn("Timed out waiting for some subscription goroutines to complete")
+ slog.Warn("Timed out waiting for some subscription goroutines to complete")
close(ch)
}
}
@@ -14,7 +14,7 @@ import (
"github.com/charmbracelet/crush/internal/format"
"github.com/charmbracelet/crush/internal/history"
"github.com/charmbracelet/crush/internal/llm/agent"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/message"
"github.com/charmbracelet/crush/internal/permission"
@@ -73,7 +73,7 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) {
app.LSPClients,
)
if err != nil {
- logging.Error("Failed to create coder agent", err)
+ slog.Error("Failed to create coder agent", err)
return nil, err
}
@@ -82,7 +82,7 @@ func New(ctx context.Context, conn *sql.DB) (*App, error) {
// RunNonInteractive handles the execution flow when a prompt is provided via CLI flag.
func (a *App) RunNonInteractive(ctx context.Context, prompt string, outputFormat string, quiet bool) error {
- logging.Info("Running in non-interactive mode")
+ slog.Info("Running in non-interactive mode")
// Start spinner if not in quiet mode
var spinner *format.Spinner
@@ -107,7 +107,7 @@ func (a *App) RunNonInteractive(ctx context.Context, prompt string, outputFormat
if err != nil {
return fmt.Errorf("failed to create session for non-interactive mode: %w", err)
}
- logging.Info("Created session for non-interactive run", "session_id", sess.ID)
+ slog.Info("Created session for non-interactive run", "session_id", sess.ID)
// Automatically approve all permission requests for this non-interactive session
a.Permissions.AutoApproveSession(sess.ID)
@@ -120,7 +120,7 @@ func (a *App) RunNonInteractive(ctx context.Context, prompt string, outputFormat
result := <-done
if result.Error != nil {
if errors.Is(result.Error, context.Canceled) || errors.Is(result.Error, agent.ErrRequestCancelled) {
- logging.Info("Agent processing cancelled", "session_id", sess.ID)
+ slog.Info("Agent processing cancelled", "session_id", sess.ID)
return nil
}
return fmt.Errorf("agent processing failed: %w", result.Error)
@@ -139,7 +139,7 @@ func (a *App) RunNonInteractive(ctx context.Context, prompt string, outputFormat
fmt.Println(format.FormatOutput(content, outputFormat))
- logging.Info("Non-interactive run completed", "session_id", sess.ID)
+ slog.Info("Non-interactive run completed", "session_id", sess.ID)
return nil
}
@@ -163,7 +163,7 @@ func (app *App) Shutdown() {
for name, client := range clients {
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
if err := client.Shutdown(shutdownCtx); err != nil {
- logging.Error("Failed to shutdown LSP client", "name", name, "error", err)
+ slog.Error("Failed to shutdown LSP client", "name", name, "error", err)
}
cancel()
}
@@ -2,10 +2,11 @@ package app
import (
"context"
+ "log/slog"
"time"
"github.com/charmbracelet/crush/internal/config"
- "github.com/charmbracelet/crush/internal/logging"
+ "github.com/charmbracelet/crush/internal/log"
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/lsp/watcher"
)
@@ -18,18 +19,18 @@ func (app *App) initLSPClients(ctx context.Context) {
// Start each client initialization in its own goroutine
go app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
}
- logging.Info("LSP clients initialization started in background")
+ slog.Info("LSP clients initialization started in background")
}
// createAndStartLSPClient creates a new LSP client, initializes it, and starts its workspace watcher
func (app *App) createAndStartLSPClient(ctx context.Context, name string, command string, args ...string) {
// Create a specific context for initialization with a timeout
- logging.Info("Creating LSP client", "name", name, "command", command, "args", args)
+ slog.Info("Creating LSP client", "name", name, "command", command, "args", args)
// Create the LSP client
lspClient, err := lsp.NewClient(ctx, command, args...)
if err != nil {
- logging.Error("Failed to create LSP client for", name, err)
+ slog.Error("Failed to create LSP client for", name, err)
return
}
@@ -40,7 +41,7 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, comman
// Initialize with the initialization context
_, err = lspClient.InitializeLSPClient(initCtx, config.Get().WorkingDir())
if err != nil {
- logging.Error("Initialize failed", "name", name, "error", err)
+ slog.Error("Initialize failed", "name", name, "error", err)
// Clean up the client to prevent resource leaks
lspClient.Close()
return
@@ -48,15 +49,15 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, comman
// Wait for the server to be ready
if err := lspClient.WaitForServerReady(initCtx); err != nil {
- logging.Error("Server failed to become ready", "name", name, "error", err)
+ slog.Error("Server failed to become ready", "name", name, "error", err)
// We'll continue anyway, as some functionality might still work
lspClient.SetServerState(lsp.StateError)
} else {
- logging.Info("LSP server is ready", "name", name)
+ slog.Info("LSP server is ready", "name", name)
lspClient.SetServerState(lsp.StateReady)
}
- logging.Info("LSP client initialized", "name", name)
+ slog.Info("LSP client initialized", "name", name)
// Create a child context that can be canceled when the app is shutting down
watchCtx, cancelFunc := context.WithCancel(ctx)
@@ -86,13 +87,13 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, comman
// runWorkspaceWatcher executes the workspace watcher for an LSP client
func (app *App) runWorkspaceWatcher(ctx context.Context, name string, workspaceWatcher *watcher.WorkspaceWatcher) {
defer app.watcherWG.Done()
- defer logging.RecoverPanic("LSP-"+name, func() {
+ defer log.RecoverPanic("LSP-"+name, func() {
// Try to restart the client
app.restartLSPClient(ctx, name)
})
workspaceWatcher.WatchWorkspace(ctx, config.Get().WorkingDir())
- logging.Info("Workspace watcher stopped", "client", name)
+ slog.Info("Workspace watcher stopped", "client", name)
}
// restartLSPClient attempts to restart a crashed or failed LSP client
@@ -101,7 +102,7 @@ func (app *App) restartLSPClient(ctx context.Context, name string) {
cfg := config.Get()
clientConfig, exists := cfg.LSP[name]
if !exists {
- logging.Error("Cannot restart client, configuration not found", "client", name)
+ slog.Error("Cannot restart client, configuration not found", "client", name)
return
}
@@ -122,5 +123,5 @@ func (app *App) restartLSPClient(ctx context.Context, name string) {
// Create a new client using the shared function
app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
- logging.Info("Successfully restarted LSP client", "client", name)
+ slog.Info("Successfully restarted LSP client", "client", name)
}
@@ -8,7 +8,7 @@ import (
"sync"
"sync/atomic"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
)
const (
@@ -32,7 +32,7 @@ func Init(workingDir string, debug bool) (*Config, error) {
cwd = workingDir
cfg, err := Load(cwd, debug)
if err != nil {
- logging.Error("Failed to load config", "error", err)
+ slog.Error("Failed to load config", "error", err)
}
instance.Store(cfg)
})
@@ -42,6 +42,11 @@ func Load(workingDir string, debug bool) (*Config, error) {
filepath.Join(workingDir, fmt.Sprintf(".%s.json", appName)),
}
cfg, err := loadFromConfigPaths(configPaths)
+ if err != nil {
+ return nil, fmt.Errorf("failed to load config from paths %v: %w", configPaths, err)
+ }
+
+ cfg.setDefaults(workingDir)
if debug {
cfg.Options.Debug = true
@@ -57,8 +62,6 @@ func Load(workingDir string, debug bool) (*Config, error) {
return nil, fmt.Errorf("failed to load config: %w", err)
}
- cfg.setDefaults(workingDir)
-
// Load known providers, this loads the config from fur
providers, err := LoadProviders(client.New())
if err != nil || len(providers) == 0 {
@@ -11,7 +11,7 @@ import (
_ "github.com/ncruces/go-sqlite3/embed"
"github.com/charmbracelet/crush/internal/config"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
"github.com/pressly/goose/v3"
)
@@ -48,21 +48,21 @@ func Connect(ctx context.Context) (*sql.DB, error) {
for _, pragma := range pragmas {
if _, err = db.ExecContext(ctx, pragma); err != nil {
- logging.Error("Failed to set pragma", pragma, err)
+ slog.Error("Failed to set pragma", pragma, err)
} else {
- logging.Debug("Set pragma", "pragma", pragma)
+ slog.Debug("Set pragma", "pragma", pragma)
}
}
goose.SetBaseFS(FS)
if err := goose.SetDialect("sqlite3"); err != nil {
- logging.Error("Failed to set dialect", "error", err)
+ slog.Error("Failed to set dialect", "error", err)
return nil, fmt.Errorf("failed to set dialect: %w", err)
}
if err := goose.Up(db, "migrations"); err != nil {
- logging.Error("Failed to apply migrations", "error", err)
+ slog.Error("Failed to apply migrations", "error", err)
return nil, fmt.Errorf("failed to apply migrations: %w", err)
}
return db, nil
@@ -11,7 +11,7 @@ import (
"github.com/bmatcuk/doublestar/v4"
"github.com/charlievieth/fastwalk"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
ignore "github.com/sabhiram/go-gitignore"
)
@@ -24,11 +24,11 @@ func init() {
var err error
rgPath, err = exec.LookPath("rg")
if err != nil {
- logging.Warn("Ripgrep (rg) not found in $PATH. Some features might be limited or slower.")
+ slog.Warn("Ripgrep (rg) not found in $PATH. Some features might be limited or slower.")
}
fzfPath, err = exec.LookPath("fzf")
if err != nil {
- logging.Warn("FZF not found in $PATH. Some features might be limited or slower.")
+ slog.Warn("FZF not found in $PATH. Some features might be limited or slower.")
}
}
@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
+ "log/slog"
"slices"
"strings"
"sync"
@@ -15,7 +16,7 @@ import (
"github.com/charmbracelet/crush/internal/llm/prompt"
"github.com/charmbracelet/crush/internal/llm/provider"
"github.com/charmbracelet/crush/internal/llm/tools"
- "github.com/charmbracelet/crush/internal/logging"
+ "github.com/charmbracelet/crush/internal/log"
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/message"
"github.com/charmbracelet/crush/internal/permission"
@@ -223,7 +224,7 @@ func (a *agent) Cancel(sessionID string) {
// Cancel regular requests
if cancelFunc, exists := a.activeRequests.LoadAndDelete(sessionID); exists {
if cancel, ok := cancelFunc.(context.CancelFunc); ok {
- logging.InfoPersist(fmt.Sprintf("Request cancellation initiated for session: %s", sessionID))
+ slog.Info(fmt.Sprintf("Request cancellation initiated for session: %s", sessionID))
cancel()
}
}
@@ -231,7 +232,7 @@ func (a *agent) Cancel(sessionID string) {
// Also check for summarize requests
if cancelFunc, exists := a.activeRequests.LoadAndDelete(sessionID + "-summarize"); exists {
if cancel, ok := cancelFunc.(context.CancelFunc); ok {
- logging.InfoPersist(fmt.Sprintf("Summarize cancellation initiated for session: %s", sessionID))
+ slog.Info(fmt.Sprintf("Summarize cancellation initiated for session: %s", sessionID))
cancel()
}
}
@@ -325,8 +326,8 @@ func (a *agent) Run(ctx context.Context, sessionID string, content string, attac
a.activeRequests.Store(sessionID, cancel)
go func() {
- logging.Debug("Request started", "sessionID", sessionID)
- defer logging.RecoverPanic("agent.Run", func() {
+ slog.Debug("Request started", "sessionID", sessionID)
+ defer log.RecoverPanic("agent.Run", func() {
events <- a.err(fmt.Errorf("panic while running the agent"))
})
var attachmentParts []message.ContentPart
@@ -335,9 +336,9 @@ func (a *agent) Run(ctx context.Context, sessionID string, content string, attac
}
result := a.processGeneration(genCtx, sessionID, content, attachmentParts)
if result.Error != nil && !errors.Is(result.Error, ErrRequestCancelled) && !errors.Is(result.Error, context.Canceled) {
- logging.ErrorPersist(result.Error.Error())
+ slog.Error(result.Error.Error())
}
- logging.Debug("Request completed", "sessionID", sessionID)
+ slog.Debug("Request completed", "sessionID", sessionID)
a.activeRequests.Delete(sessionID)
cancel()
a.Publish(pubsub.CreatedEvent, result)
@@ -356,12 +357,12 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
}
if len(msgs) == 0 {
go func() {
- defer logging.RecoverPanic("agent.Run", func() {
- logging.ErrorPersist("panic while generating title")
+ defer log.RecoverPanic("agent.Run", func() {
+ slog.Error("panic while generating title")
})
titleErr := a.generateTitle(context.Background(), sessionID, content)
if titleErr != nil && !errors.Is(titleErr, context.Canceled) && !errors.Is(titleErr, context.DeadlineExceeded) {
- logging.ErrorPersist(fmt.Sprintf("failed to generate title: %v", titleErr))
+ slog.Error(fmt.Sprintf("failed to generate title: %v", titleErr))
}
}()
}
@@ -408,11 +409,7 @@ func (a *agent) processGeneration(ctx context.Context, sessionID, content string
return a.err(fmt.Errorf("failed to process events: %w", err))
}
if cfg.Options.Debug {
- seqId := (len(msgHistory) + 1) / 2
- toolResultFilepath := logging.WriteToolResultsJson(sessionID, seqId, toolResults)
- logging.Info("Result", "message", agentMessage.FinishReason(), "toolResults", "{}", "filepath", toolResultFilepath)
- } else {
- logging.Info("Result", "message", agentMessage.FinishReason(), "toolResults", toolResults)
+ slog.Info("Result", "message", agentMessage.FinishReason(), "toolResults", toolResults)
}
if (agentMessage.FinishReason() == message.FinishReasonToolUse) && toolResults != nil {
// We are not done, we need to respond with the tool response
@@ -571,22 +568,22 @@ func (a *agent) processEvent(ctx context.Context, sessionID string, assistantMsg
assistantMsg.AppendContent(event.Content)
return a.messages.Update(ctx, *assistantMsg)
case provider.EventToolUseStart:
- logging.Info("Tool call started", "toolCall", event.ToolCall)
+ slog.Info("Tool call started", "toolCall", event.ToolCall)
assistantMsg.AddToolCall(*event.ToolCall)
return a.messages.Update(ctx, *assistantMsg)
case provider.EventToolUseDelta:
assistantMsg.AppendToolCallInput(event.ToolCall.ID, event.ToolCall.Input)
return a.messages.Update(ctx, *assistantMsg)
case provider.EventToolUseStop:
- logging.Info("Finished tool call", "toolCall", event.ToolCall)
+ slog.Info("Finished tool call", "toolCall", event.ToolCall)
assistantMsg.FinishToolCall(event.ToolCall.ID)
return a.messages.Update(ctx, *assistantMsg)
case provider.EventError:
if errors.Is(event.Error, context.Canceled) {
- logging.InfoPersist(fmt.Sprintf("Event processing canceled for session: %s", sessionID))
+ slog.Info(fmt.Sprintf("Event processing canceled for session: %s", sessionID))
return context.Canceled
}
- logging.ErrorPersist(event.Error.Error())
+ slog.Error(event.Error.Error())
return event.Error
case provider.EventComplete:
assistantMsg.SetToolCalls(event.Response.ToolCalls)
@@ -7,7 +7,7 @@ import (
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/llm/tools"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
"github.com/charmbracelet/crush/internal/permission"
"github.com/charmbracelet/crush/internal/version"
@@ -164,13 +164,13 @@ func getTools(ctx context.Context, name string, m config.MCPConfig, permissions
_, err := c.Initialize(ctx, initRequest)
if err != nil {
- logging.Error("error initializing mcp client", "error", err)
+ slog.Error("error initializing mcp client", "error", err)
return stdioTools
}
toolsRequest := mcp.ListToolsRequest{}
tools, err := c.ListTools(ctx, toolsRequest)
if err != nil {
- logging.Error("error listing tools", "error", err)
+ slog.Error("error listing tools", "error", err)
return stdioTools
}
for _, t := range tools.Tools {
@@ -193,7 +193,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
m.Args...,
)
if err != nil {
- logging.Error("error creating mcp client", "error", err)
+ slog.Error("error creating mcp client", "error", err)
continue
}
@@ -204,7 +204,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
transport.WithHTTPHeaders(m.Headers),
)
if err != nil {
- logging.Error("error creating mcp client", "error", err)
+ slog.Error("error creating mcp client", "error", err)
continue
}
mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
@@ -214,7 +214,7 @@ func GetMcpTools(ctx context.Context, permissions permission.Service) []tools.Ba
client.WithHeaders(m.Headers),
)
if err != nil {
- logging.Error("error creating mcp client", "error", err)
+ slog.Error("error creating mcp client", "error", err)
continue
}
mcpTools = append(mcpTools, getTools(ctx, name, m, permissions, c)...)
@@ -11,7 +11,7 @@ import (
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/fur/provider"
"github.com/charmbracelet/crush/internal/llm/tools"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
)
func CoderPrompt(p string, contextFiles ...string) string {
@@ -29,7 +29,7 @@ func CoderPrompt(p string, contextFiles ...string) string {
basePrompt = fmt.Sprintf("%s\n\n%s\n%s", basePrompt, envInfo, lspInformation())
contextContent := getContextFromPaths(contextFiles)
- logging.Debug("Context content", "Context", contextContent)
+ slog.Debug("Context content", "Context", contextContent)
if contextContent != "" {
return fmt.Sprintf("%s\n\n# Project-Specific Context\n Make sure to follow the instructions in the context below\n%s", basePrompt, contextContent)
}
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
+ "log/slog"
"regexp"
"strconv"
"time"
@@ -16,7 +17,6 @@ import (
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/fur/provider"
"github.com/charmbracelet/crush/internal/llm/tools"
- "github.com/charmbracelet/crush/internal/logging"
"github.com/charmbracelet/crush/internal/message"
)
@@ -92,7 +92,7 @@ func (a *anthropicClient) convertMessages(messages []message.Message) (anthropic
}
if len(blocks) == 0 {
- logging.Warn("There is a message without content, investigate, this should not happen")
+ slog.Warn("There is a message without content, investigate, this should not happen")
continue
}
anthropicMessages = append(anthropicMessages, anthropic.NewAssistantMessage(blocks...))
@@ -207,7 +207,7 @@ func (a *anthropicClient) send(ctx context.Context, messages []message.Message,
preparedMessages := a.preparedMessages(a.convertMessages(messages), a.convertTools(tools))
if cfg.Options.Debug {
jsonData, _ := json.Marshal(preparedMessages)
- logging.Debug("Prepared messages", "messages", string(jsonData))
+ slog.Debug("Prepared messages", "messages", string(jsonData))
}
anthropicResponse, err := a.client.Messages.New(
@@ -216,13 +216,13 @@ func (a *anthropicClient) send(ctx context.Context, messages []message.Message,
)
// If there is an error we are going to see if we can retry the call
if err != nil {
- logging.Error("Error in Anthropic API call", "error", err)
+ slog.Error("Error in Anthropic API call", "error", err)
retry, after, retryErr := a.shouldRetry(attempts, err)
if retryErr != nil {
return nil, retryErr
}
if retry {
- logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
select {
case <-ctx.Done():
return nil, ctx.Err()
@@ -259,7 +259,7 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
preparedMessages := a.preparedMessages(a.convertMessages(messages), a.convertTools(tools))
if cfg.Options.Debug {
jsonData, _ := json.Marshal(preparedMessages)
- logging.Debug("Prepared messages", "messages", string(jsonData))
+ slog.Debug("Prepared messages", "messages", string(jsonData))
}
anthropicStream := a.client.Messages.NewStreaming(
@@ -273,7 +273,7 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
event := anthropicStream.Current()
err := accumulatedMessage.Accumulate(event)
if err != nil {
- logging.Warn("Error accumulating message", "error", err)
+ slog.Warn("Error accumulating message", "error", err)
continue
}
@@ -364,7 +364,7 @@ func (a *anthropicClient) stream(ctx context.Context, messages []message.Message
return
}
if retry {
- logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
select {
case <-ctx.Done():
// context cancelled
@@ -411,7 +411,7 @@ func (a *anthropicClient) shouldRetry(attempts int, err error) (bool, int64, err
if apiErr.StatusCode == 400 {
if adjusted, ok := a.handleContextLimitError(apiErr); ok {
a.adjustedMaxTokens = adjusted
- logging.Debug("Adjusted max_tokens due to context limit", "new_max_tokens", adjusted)
+ slog.Debug("Adjusted max_tokens due to context limit", "new_max_tokens", adjusted)
return true, 0, nil
}
}
@@ -6,13 +6,13 @@ import (
"errors"
"fmt"
"io"
+ "log/slog"
"strings"
"time"
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/fur/provider"
"github.com/charmbracelet/crush/internal/llm/tools"
- "github.com/charmbracelet/crush/internal/logging"
"github.com/charmbracelet/crush/internal/message"
"github.com/google/uuid"
"google.golang.org/genai"
@@ -28,7 +28,7 @@ type GeminiClient ProviderClient
func newGeminiClient(opts providerClientOptions) GeminiClient {
client, err := createGeminiClient(opts)
if err != nil {
- logging.Error("Failed to create Gemini client", "error", err)
+ slog.Error("Failed to create Gemini client", "error", err)
return nil
}
@@ -168,7 +168,7 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
cfg := config.Get()
if cfg.Options.Debug {
jsonData, _ := json.Marshal(geminiMessages)
- logging.Debug("Prepared messages", "messages", string(jsonData))
+ slog.Debug("Prepared messages", "messages", string(jsonData))
}
modelConfig := cfg.Models[config.SelectedModelTypeLarge]
@@ -210,7 +210,7 @@ func (g *geminiClient) send(ctx context.Context, messages []message.Message, too
return nil, retryErr
}
if retry {
- logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
select {
case <-ctx.Done():
return nil, ctx.Err()
@@ -266,7 +266,7 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
cfg := config.Get()
if cfg.Options.Debug {
jsonData, _ := json.Marshal(geminiMessages)
- logging.Debug("Prepared messages", "messages", string(jsonData))
+ slog.Debug("Prepared messages", "messages", string(jsonData))
}
modelConfig := cfg.Models[config.SelectedModelTypeLarge]
@@ -323,7 +323,7 @@ func (g *geminiClient) stream(ctx context.Context, messages []message.Message, t
return
}
if retry {
- logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
select {
case <-ctx.Done():
if ctx.Err() != nil {
@@ -6,12 +6,12 @@ import (
"errors"
"fmt"
"io"
+ "log/slog"
"time"
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/fur/provider"
"github.com/charmbracelet/crush/internal/llm/tools"
- "github.com/charmbracelet/crush/internal/logging"
"github.com/charmbracelet/crush/internal/message"
"github.com/openai/openai-go"
"github.com/openai/openai-go/option"
@@ -194,7 +194,7 @@ func (o *openaiClient) send(ctx context.Context, messages []message.Message, too
cfg := config.Get()
if cfg.Options.Debug {
jsonData, _ := json.Marshal(params)
- logging.Debug("Prepared messages", "messages", string(jsonData))
+ slog.Debug("Prepared messages", "messages", string(jsonData))
}
attempts := 0
for {
@@ -210,7 +210,7 @@ func (o *openaiClient) send(ctx context.Context, messages []message.Message, too
return nil, retryErr
}
if retry {
- logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
select {
case <-ctx.Done():
return nil, ctx.Err()
@@ -251,7 +251,7 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
cfg := config.Get()
if cfg.Options.Debug {
jsonData, _ := json.Marshal(params)
- logging.Debug("Prepared messages", "messages", string(jsonData))
+ slog.Debug("Prepared messages", "messages", string(jsonData))
}
attempts := 0
@@ -288,7 +288,7 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
if err == nil || errors.Is(err, io.EOF) {
if cfg.Options.Debug {
jsonData, _ := json.Marshal(acc.ChatCompletion)
- logging.Debug("Response", "messages", string(jsonData))
+ slog.Debug("Response", "messages", string(jsonData))
}
resultFinishReason := acc.ChatCompletion.Choices[0].FinishReason
if resultFinishReason == "" {
@@ -326,7 +326,7 @@ func (o *openaiClient) stream(ctx context.Context, messages []message.Message, t
return
}
if retry {
- logging.WarnPersist(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries), logging.PersistTimeArg, time.Millisecond*time.Duration(after+100))
+ slog.Warn(fmt.Sprintf("Retrying due to rate limit... attempt %d of %d", attempts, maxRetries))
select {
case <-ctx.Done():
// context cancelled
@@ -3,7 +3,7 @@ package provider
import (
"context"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
"google.golang.org/genai"
)
@@ -18,7 +18,7 @@ func newVertexAIClient(opts providerClientOptions) VertexAIClient {
Backend: genai.BackendVertexAI,
})
if err != nil {
- logging.Error("Failed to create VertexAI client", "error", err)
+ slog.Error("Failed to create VertexAI client", "error", err)
return nil
}
@@ -12,7 +12,7 @@ import (
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/diff"
"github.com/charmbracelet/crush/internal/history"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/permission"
)
@@ -246,7 +246,7 @@ func (e *editTool) createNewFile(ctx context.Context, filePath, content string)
_, err = e.files.CreateVersion(ctx, sessionID, filePath, content)
if err != nil {
// Log error but don't fail the operation
- logging.Debug("Error creating file history version", "error", err)
+ slog.Debug("Error creating file history version", "error", err)
}
recordFileWrite(filePath)
@@ -361,13 +361,13 @@ func (e *editTool) deleteContent(ctx context.Context, filePath, oldString string
// User Manually changed the content store an intermediate version
_, err = e.files.CreateVersion(ctx, sessionID, filePath, oldContent)
if err != nil {
- logging.Debug("Error creating file history version", "error", err)
+ slog.Debug("Error creating file history version", "error", err)
}
}
// Store the new version
_, err = e.files.CreateVersion(ctx, sessionID, filePath, "")
if err != nil {
- logging.Debug("Error creating file history version", "error", err)
+ slog.Debug("Error creating file history version", "error", err)
}
recordFileWrite(filePath)
@@ -483,13 +483,13 @@ func (e *editTool) replaceContent(ctx context.Context, filePath, oldString, newS
// User Manually changed the content store an intermediate version
_, err = e.files.CreateVersion(ctx, sessionID, filePath, oldContent)
if err != nil {
- logging.Debug("Error creating file history version", "error", err)
+ slog.Debug("Error creating file history version", "error", err)
}
}
// Store the new version
_, err = e.files.CreateVersion(ctx, sessionID, filePath, newContent)
if err != nil {
- logging.Debug("Error creating file history version", "error", err)
+ slog.Debug("Error creating file history version", "error", err)
}
recordFileWrite(filePath)
@@ -12,7 +12,7 @@ import (
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/fsext"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
)
const (
@@ -143,7 +143,7 @@ func globFiles(pattern, searchPath string, limit int) ([]string, bool, error) {
if err == nil {
return matches, len(matches) >= limit && limit > 0, nil
}
- logging.Warn(fmt.Sprintf("Ripgrep execution failed: %v. Falling back to doublestar.", err))
+ slog.Warn(fmt.Sprintf("Ripgrep execution failed: %v. Falling back to doublestar.", err))
}
return fsext.GlobWithDoubleStar(pattern, searchPath, limit)
@@ -12,7 +12,7 @@ import (
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/diff"
"github.com/charmbracelet/crush/internal/history"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/permission"
)
@@ -211,13 +211,13 @@ func (w *writeTool) Run(ctx context.Context, call ToolCall) (ToolResponse, error
// User Manually changed the content store an intermediate version
_, err = w.files.CreateVersion(ctx, sessionID, filePath, oldContent)
if err != nil {
- logging.Debug("Error creating file history version", "error", err)
+ slog.Debug("Error creating file history version", "error", err)
}
}
// Store the new version
_, err = w.files.CreateVersion(ctx, sessionID, filePath, params.Content)
if err != nil {
- logging.Debug("Error creating file history version", "error", err)
+ slog.Debug("Error creating file history version", "error", err)
}
recordFileWrite(filePath)
@@ -1,8 +1,12 @@
package log
import (
+ "fmt"
"log/slog"
+ "os"
+ "runtime/debug"
"sync"
+ "time"
"gopkg.in/natefinch/lumberjack.v2"
)
@@ -32,3 +36,26 @@ func Init(logFile string, debug bool) {
slog.SetDefault(slog.New(logger))
})
}
+
+func RecoverPanic(name string, cleanup func()) {
+ if r := recover(); r != nil {
+ // Create a timestamped panic log file
+ timestamp := time.Now().Format("20060102-150405")
+ filename := fmt.Sprintf("crush-panic-%s-%s.log", name, timestamp)
+
+ file, err := os.Create(filename)
+ if err == nil {
+ defer file.Close()
+
+ // Write panic information and stack trace
+ fmt.Fprintf(file, "Panic in %s: %v\n\n", name, r)
+ fmt.Fprintf(file, "Time: %s\n\n", time.Now().Format(time.RFC3339))
+ fmt.Fprintf(file, "Stack Trace:\n%s\n", debug.Stack())
+
+ // Execute cleanup function if provided
+ if cleanup != nil {
+ cleanup()
+ }
+ }
+ }
+}
@@ -1,209 +0,0 @@
-package logging
-
-import (
- "fmt"
- "log/slog"
- "os"
-
- // "path/filepath"
- "encoding/json"
- "runtime"
- "runtime/debug"
- "sync"
- "time"
-)
-
-func getCaller() string {
- var caller string
- if _, file, line, ok := runtime.Caller(2); ok {
- // caller = fmt.Sprintf("%s:%d", filepath.Base(file), line)
- caller = fmt.Sprintf("%s:%d", file, line)
- } else {
- caller = "unknown"
- }
- return caller
-}
-
-func Info(msg string, args ...any) {
- source := getCaller()
- slog.Info(msg, append([]any{"source", source}, args...)...)
-}
-
-func Debug(msg string, args ...any) {
- // slog.Debug(msg, args...)
- source := getCaller()
- slog.Debug(msg, append([]any{"source", source}, args...)...)
-}
-
-func Warn(msg string, args ...any) {
- slog.Warn(msg, args...)
-}
-
-func Error(msg string, args ...any) {
- slog.Error(msg, args...)
-}
-
-func InfoPersist(msg string, args ...any) {
- args = append(args, persistKeyArg, true)
- slog.Info(msg, args...)
-}
-
-func DebugPersist(msg string, args ...any) {
- args = append(args, persistKeyArg, true)
- slog.Debug(msg, args...)
-}
-
-func WarnPersist(msg string, args ...any) {
- args = append(args, persistKeyArg, true)
- slog.Warn(msg, args...)
-}
-
-func ErrorPersist(msg string, args ...any) {
- args = append(args, persistKeyArg, true)
- slog.Error(msg, args...)
-}
-
-// RecoverPanic is a common function to handle panics gracefully.
-// It logs the error, creates a panic log file with stack trace,
-// and executes an optional cleanup function before returning.
-func RecoverPanic(name string, cleanup func()) {
- if r := recover(); r != nil {
- // Log the panic
- ErrorPersist(fmt.Sprintf("Panic in %s: %v", name, r))
-
- // Create a timestamped panic log file
- timestamp := time.Now().Format("20060102-150405")
- filename := fmt.Sprintf("crush-panic-%s-%s.log", name, timestamp)
-
- file, err := os.Create(filename)
- if err != nil {
- ErrorPersist(fmt.Sprintf("Failed to create panic log: %v", err))
- } else {
- defer file.Close()
-
- // Write panic information and stack trace
- fmt.Fprintf(file, "Panic in %s: %v\n\n", name, r)
- fmt.Fprintf(file, "Time: %s\n\n", time.Now().Format(time.RFC3339))
- fmt.Fprintf(file, "Stack Trace:\n%s\n", debug.Stack())
-
- InfoPersist(fmt.Sprintf("Panic details written to %s", filename))
- }
-
- // Execute cleanup function if provided
- if cleanup != nil {
- cleanup()
- }
- }
-}
-
-// Message Logging for Debug
-var MessageDir string
-
-func GetSessionPrefix(sessionId string) string {
- return sessionId[:8]
-}
-
-var sessionLogMutex sync.Mutex
-
-func AppendToSessionLogFile(sessionId string, filename string, content string) string {
- if MessageDir == "" || sessionId == "" {
- return ""
- }
- sessionPrefix := GetSessionPrefix(sessionId)
-
- sessionLogMutex.Lock()
- defer sessionLogMutex.Unlock()
-
- sessionPath := fmt.Sprintf("%s/%s", MessageDir, sessionPrefix)
- if _, err := os.Stat(sessionPath); os.IsNotExist(err) {
- if err := os.MkdirAll(sessionPath, 0o766); err != nil {
- Error("Failed to create session directory", "dirpath", sessionPath, "error", err)
- return ""
- }
- }
-
- filePath := fmt.Sprintf("%s/%s", sessionPath, filename)
-
- f, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
- if err != nil {
- Error("Failed to open session log file", "filepath", filePath, "error", err)
- return ""
- }
- defer f.Close()
-
- // Append chunk to file
- _, err = f.WriteString(content)
- if err != nil {
- Error("Failed to write chunk to session log file", "filepath", filePath, "error", err)
- return ""
- }
- return filePath
-}
-
-func WriteRequestMessageJson(sessionId string, requestSeqId int, message any) string {
- if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
- return ""
- }
- msgJson, err := json.Marshal(message)
- if err != nil {
- Error("Failed to marshal message", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
- return ""
- }
- return WriteRequestMessage(sessionId, requestSeqId, string(msgJson))
-}
-
-func WriteRequestMessage(sessionId string, requestSeqId int, message string) string {
- if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
- return ""
- }
- filename := fmt.Sprintf("%d_request.json", requestSeqId)
-
- return AppendToSessionLogFile(sessionId, filename, message)
-}
-
-func AppendToStreamSessionLogJson(sessionId string, requestSeqId int, jsonableChunk any) string {
- if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
- return ""
- }
- chunkJson, err := json.Marshal(jsonableChunk)
- if err != nil {
- Error("Failed to marshal message", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
- return ""
- }
- return AppendToStreamSessionLog(sessionId, requestSeqId, string(chunkJson))
-}
-
-func AppendToStreamSessionLog(sessionId string, requestSeqId int, chunk string) string {
- if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
- return ""
- }
- filename := fmt.Sprintf("%d_response_stream.log", requestSeqId)
- return AppendToSessionLogFile(sessionId, filename, chunk)
-}
-
-func WriteChatResponseJson(sessionId string, requestSeqId int, response any) string {
- if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
- return ""
- }
- responseJson, err := json.Marshal(response)
- if err != nil {
- Error("Failed to marshal response", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
- return ""
- }
- filename := fmt.Sprintf("%d_response.json", requestSeqId)
-
- return AppendToSessionLogFile(sessionId, filename, string(responseJson))
-}
-
-func WriteToolResultsJson(sessionId string, requestSeqId int, toolResults any) string {
- if MessageDir == "" || sessionId == "" || requestSeqId <= 0 {
- return ""
- }
- toolResultsJson, err := json.Marshal(toolResults)
- if err != nil {
- Error("Failed to marshal tool results", "session_id", sessionId, "request_seq_id", requestSeqId, "error", err)
- return ""
- }
- filename := fmt.Sprintf("%d_tool_results.json", requestSeqId)
- return AppendToSessionLogFile(sessionId, filename, string(toolResultsJson))
-}
@@ -1,21 +0,0 @@
-package logging
-
-import (
- "time"
-)
-
-// LogMessage is the event payload for a log message
-type LogMessage struct {
- ID string
- Time time.Time
- Level string
- Persist bool // used when we want to show the mesage in the status bar
- PersistTime time.Duration // used when we want to show the mesage in the status bar
- Message string `json:"msg"`
- Attributes []Attr
-}
-
-type Attr struct {
- Key string
- Value string
-}
@@ -1,102 +0,0 @@
-package logging
-
-import (
- "bytes"
- "context"
- "fmt"
- "strings"
- "sync"
- "time"
-
- "github.com/charmbracelet/crush/internal/pubsub"
- "github.com/go-logfmt/logfmt"
-)
-
-const (
- persistKeyArg = "$_persist"
- PersistTimeArg = "$_persist_time"
-)
-
-type LogData struct {
- messages []LogMessage
- *pubsub.Broker[LogMessage]
- lock sync.Mutex
-}
-
-func (l *LogData) Add(msg LogMessage) {
- l.lock.Lock()
- defer l.lock.Unlock()
- l.messages = append(l.messages, msg)
- l.Publish(pubsub.CreatedEvent, msg)
-}
-
-func (l *LogData) List() []LogMessage {
- l.lock.Lock()
- defer l.lock.Unlock()
- return l.messages
-}
-
-var defaultLogData = &LogData{
- messages: make([]LogMessage, 0),
- Broker: pubsub.NewBroker[LogMessage](),
-}
-
-type writer struct{}
-
-func (w *writer) Write(p []byte) (int, error) {
- d := logfmt.NewDecoder(bytes.NewReader(p))
-
- for d.ScanRecord() {
- msg := LogMessage{
- ID: fmt.Sprintf("%d", time.Now().UnixNano()),
- Time: time.Now(),
- }
- for d.ScanKeyval() {
- switch string(d.Key()) {
- case "time":
- parsed, err := time.Parse(time.RFC3339, string(d.Value()))
- if err != nil {
- return 0, fmt.Errorf("parsing time: %w", err)
- }
- msg.Time = parsed
- case "level":
- msg.Level = strings.ToLower(string(d.Value()))
- case "msg":
- msg.Message = string(d.Value())
- default:
- if string(d.Key()) == persistKeyArg {
- msg.Persist = true
- } else if string(d.Key()) == PersistTimeArg {
- parsed, err := time.ParseDuration(string(d.Value()))
- if err != nil {
- continue
- }
- msg.PersistTime = parsed
- } else {
- msg.Attributes = append(msg.Attributes, Attr{
- Key: string(d.Key()),
- Value: string(d.Value()),
- })
- }
- }
- }
- defaultLogData.Add(msg)
- }
- if d.Err() != nil {
- return 0, d.Err()
- }
- return len(p), nil
-}
-
-func NewWriter() *writer {
- w := &writer{}
- return w
-}
-
-func Subscribe(ctx context.Context) <-chan pubsub.Event[LogMessage] {
- return defaultLogData.Subscribe(ctx)
-}
-
-func List() []LogMessage {
- return defaultLogData.List()
-}
@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "log/slog"
"os"
"os/exec"
"path/filepath"
@@ -15,7 +16,7 @@ import (
"time"
"github.com/charmbracelet/crush/internal/config"
- "github.com/charmbracelet/crush/internal/logging"
+ "github.com/charmbracelet/crush/internal/log"
"github.com/charmbracelet/crush/internal/lsp/protocol"
)
@@ -96,17 +97,17 @@ func NewClient(ctx context.Context, command string, args ...string) (*Client, er
go func() {
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
- logging.Error("LSP Server", "err", scanner.Text())
+ slog.Error("LSP Server", "err", scanner.Text())
}
if err := scanner.Err(); err != nil {
- logging.Error("Error reading", "err", err)
+ slog.Error("Error reading", "err", err)
}
}()
// Start message handling loop
go func() {
- defer logging.RecoverPanic("LSP-message-handler", func() {
- logging.ErrorPersist("LSP message handler crashed, LSP functionality may be impaired")
+ defer log.RecoverPanic("LSP-message-handler", func() {
+ slog.Error("LSP message handler crashed, LSP functionality may be impaired")
})
client.handleMessages()
}()
@@ -300,7 +301,7 @@ func (c *Client) WaitForServerReady(ctx context.Context) error {
defer ticker.Stop()
if cfg.Options.DebugLSP {
- logging.Debug("Waiting for LSP server to be ready...")
+ slog.Debug("Waiting for LSP server to be ready...")
}
// Determine server type for specialized initialization
@@ -309,7 +310,7 @@ func (c *Client) WaitForServerReady(ctx context.Context) error {
// For TypeScript-like servers, we need to open some key files first
if serverType == ServerTypeTypeScript {
if cfg.Options.DebugLSP {
- logging.Debug("TypeScript-like server detected, opening key configuration files")
+ slog.Debug("TypeScript-like server detected, opening key configuration files")
}
c.openKeyConfigFiles(ctx)
}
@@ -326,15 +327,15 @@ func (c *Client) WaitForServerReady(ctx context.Context) error {
// Server responded successfully
c.SetServerState(StateReady)
if cfg.Options.DebugLSP {
- logging.Debug("LSP server is ready")
+ slog.Debug("LSP server is ready")
}
return nil
} else {
- logging.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
+ slog.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
}
if cfg.Options.DebugLSP {
- logging.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
+ slog.Debug("LSP server not ready yet", "error", err, "serverType", serverType)
}
}
}
@@ -409,9 +410,9 @@ func (c *Client) openKeyConfigFiles(ctx context.Context) {
if _, err := os.Stat(file); err == nil {
// File exists, try to open it
if err := c.OpenFile(ctx, file); err != nil {
- logging.Debug("Failed to open key config file", "file", file, "error", err)
+ slog.Debug("Failed to open key config file", "file", file, "error", err)
} else {
- logging.Debug("Opened key config file for initialization", "file", file)
+ slog.Debug("Opened key config file for initialization", "file", file)
}
}
}
@@ -487,7 +488,7 @@ func (c *Client) pingTypeScriptServer(ctx context.Context) error {
return nil
})
if err != nil {
- logging.Debug("Error walking directory for TypeScript files", "error", err)
+ slog.Debug("Error walking directory for TypeScript files", "error", err)
}
// Final fallback - just try a generic capability
@@ -527,7 +528,7 @@ func (c *Client) openTypeScriptFiles(ctx context.Context, workDir string) {
if err := c.OpenFile(ctx, path); err == nil {
filesOpened++
if cfg.Options.DebugLSP {
- logging.Debug("Opened TypeScript file for initialization", "file", path)
+ slog.Debug("Opened TypeScript file for initialization", "file", path)
}
}
}
@@ -536,11 +537,11 @@ func (c *Client) openTypeScriptFiles(ctx context.Context, workDir string) {
})
if err != nil && cfg.Options.DebugLSP {
- logging.Debug("Error walking directory for TypeScript files", "error", err)
+ slog.Debug("Error walking directory for TypeScript files", "error", err)
}
if cfg.Options.DebugLSP {
- logging.Debug("Opened TypeScript files for initialization", "count", filesOpened)
+ slog.Debug("Opened TypeScript files for initialization", "count", filesOpened)
}
}
@@ -681,7 +682,7 @@ func (c *Client) CloseFile(ctx context.Context, filepath string) error {
}
if cfg.Options.DebugLSP {
- logging.Debug("Closing file", "file", filepath)
+ slog.Debug("Closing file", "file", filepath)
}
if err := c.Notify(ctx, "textDocument/didClose", params); err != nil {
return err
@@ -720,12 +721,12 @@ func (c *Client) CloseAllFiles(ctx context.Context) {
for _, filePath := range filesToClose {
err := c.CloseFile(ctx, filePath)
if err != nil && cfg.Options.DebugLSP {
- logging.Warn("Error closing file", "file", filePath, "error", err)
+ slog.Warn("Error closing file", "file", filePath, "error", err)
}
}
if cfg.Options.DebugLSP {
- logging.Debug("Closed all files", "files", filesToClose)
+ slog.Debug("Closed all files", "files", filesToClose)
}
}
@@ -4,7 +4,7 @@ import (
"encoding/json"
"github.com/charmbracelet/crush/internal/config"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
"github.com/charmbracelet/crush/internal/lsp/protocol"
"github.com/charmbracelet/crush/internal/lsp/util"
)
@@ -18,7 +18,7 @@ func HandleWorkspaceConfiguration(params json.RawMessage) (any, error) {
func HandleRegisterCapability(params json.RawMessage) (any, error) {
var registerParams protocol.RegistrationParams
if err := json.Unmarshal(params, ®isterParams); err != nil {
- logging.Error("Error unmarshaling registration params", "error", err)
+ slog.Error("Error unmarshaling registration params", "error", err)
return nil, err
}
@@ -28,13 +28,13 @@ func HandleRegisterCapability(params json.RawMessage) (any, error) {
// Parse the registration options
optionsJSON, err := json.Marshal(reg.RegisterOptions)
if err != nil {
- logging.Error("Error marshaling registration options", "error", err)
+ slog.Error("Error marshaling registration options", "error", err)
continue
}
var options protocol.DidChangeWatchedFilesRegistrationOptions
if err := json.Unmarshal(optionsJSON, &options); err != nil {
- logging.Error("Error unmarshaling registration options", "error", err)
+ slog.Error("Error unmarshaling registration options", "error", err)
continue
}
@@ -54,7 +54,7 @@ func HandleApplyEdit(params json.RawMessage) (any, error) {
err := util.ApplyWorkspaceEdit(edit.Edit)
if err != nil {
- logging.Error("Error applying workspace edit", "error", err)
+ slog.Error("Error applying workspace edit", "error", err)
return protocol.ApplyWorkspaceEditResult{Applied: false, FailureReason: err.Error()}, nil
}
@@ -89,7 +89,7 @@ func HandleServerMessage(params json.RawMessage) {
}
if err := json.Unmarshal(params, &msg); err == nil {
if cfg.Options.DebugLSP {
- logging.Debug("Server message", "type", msg.Type, "message", msg.Message)
+ slog.Debug("Server message", "type", msg.Type, "message", msg.Message)
}
}
}
@@ -97,7 +97,7 @@ func HandleServerMessage(params json.RawMessage) {
func HandleDiagnostics(client *Client, params json.RawMessage) {
var diagParams protocol.PublishDiagnosticsParams
if err := json.Unmarshal(params, &diagParams); err != nil {
- logging.Error("Error unmarshaling diagnostics params", "error", err)
+ slog.Error("Error unmarshaling diagnostics params", "error", err)
return
}
@@ -55,7 +55,7 @@ type ApplyWorkspaceEditResult struct {
// Indicates whether the edit was applied or not.
Applied bool `json:"applied"`
// An optional textual description for why the edit was not applied.
- // This may be used by the server for diagnostic logging or to provide
+ // This may be used by the server for diagnostic slog.or to provide
// a suitable error for a request that triggered the edit.
FailureReason string `json:"failureReason,omitempty"`
// Depending on the client's failure handling strategy `failedChange` might
@@ -9,7 +9,7 @@ import (
"strings"
"github.com/charmbracelet/crush/internal/config"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
)
// Write writes an LSP message to the given writer
@@ -21,7 +21,7 @@ func WriteMessage(w io.Writer, msg *Message) error {
cfg := config.Get()
if cfg.Options.DebugLSP {
- logging.Debug("Sending message to server", "method", msg.Method, "id", msg.ID)
+ slog.Debug("Sending message to server", "method", msg.Method, "id", msg.ID)
}
_, err = fmt.Fprintf(w, "Content-Length: %d\r\n\r\n", len(data))
@@ -50,7 +50,7 @@ func ReadMessage(r *bufio.Reader) (*Message, error) {
line = strings.TrimSpace(line)
if cfg.Options.DebugLSP {
- logging.Debug("Received header", "line", line)
+ slog.Debug("Received header", "line", line)
}
if line == "" {
@@ -66,7 +66,7 @@ func ReadMessage(r *bufio.Reader) (*Message, error) {
}
if cfg.Options.DebugLSP {
- logging.Debug("Content-Length", "length", contentLength)
+ slog.Debug("Content-Length", "length", contentLength)
}
// Read content
@@ -77,7 +77,7 @@ func ReadMessage(r *bufio.Reader) (*Message, error) {
}
if cfg.Options.DebugLSP {
- logging.Debug("Received content", "content", string(content))
+ slog.Debug("Received content", "content", string(content))
}
// Parse message
@@ -96,7 +96,7 @@ func (c *Client) handleMessages() {
msg, err := ReadMessage(c.stdout)
if err != nil {
if cfg.Options.DebugLSP {
- logging.Error("Error reading message", "error", err)
+ slog.Error("Error reading message", "error", err)
}
return
}
@@ -104,7 +104,7 @@ func (c *Client) handleMessages() {
// Handle server->client request (has both Method and ID)
if msg.Method != "" && msg.ID != 0 {
if cfg.Options.DebugLSP {
- logging.Debug("Received request from server", "method", msg.Method, "id", msg.ID)
+ slog.Debug("Received request from server", "method", msg.Method, "id", msg.ID)
}
response := &Message{
@@ -144,7 +144,7 @@ func (c *Client) handleMessages() {
// Send response back to server
if err := WriteMessage(c.stdin, response); err != nil {
- logging.Error("Error sending response to server", "error", err)
+ slog.Error("Error sending response to server", "error", err)
}
continue
@@ -158,11 +158,11 @@ func (c *Client) handleMessages() {
if ok {
if cfg.Options.DebugLSP {
- logging.Debug("Handling notification", "method", msg.Method)
+ slog.Debug("Handling notification", "method", msg.Method)
}
go handler(msg.Params)
} else if cfg.Options.DebugLSP {
- logging.Debug("No handler for notification", "method", msg.Method)
+ slog.Debug("No handler for notification", "method", msg.Method)
}
continue
}
@@ -175,12 +175,12 @@ func (c *Client) handleMessages() {
if ok {
if cfg.Options.DebugLSP {
- logging.Debug("Received response for request", "id", msg.ID)
+ slog.Debug("Received response for request", "id", msg.ID)
}
ch <- msg
close(ch)
} else if cfg.Options.DebugLSP {
- logging.Debug("No handler for response", "id", msg.ID)
+ slog.Debug("No handler for response", "id", msg.ID)
}
}
}
@@ -192,7 +192,7 @@ func (c *Client) Call(ctx context.Context, method string, params any, result any
id := c.nextID.Add(1)
if cfg.Options.DebugLSP {
- logging.Debug("Making call", "method", method, "id", id)
+ slog.Debug("Making call", "method", method, "id", id)
}
msg, err := NewRequest(id, method, params)
@@ -218,14 +218,14 @@ func (c *Client) Call(ctx context.Context, method string, params any, result any
}
if cfg.Options.DebugLSP {
- logging.Debug("Request sent", "method", method, "id", id)
+ slog.Debug("Request sent", "method", method, "id", id)
}
// Wait for response
resp := <-ch
if cfg.Options.DebugLSP {
- logging.Debug("Received response", "id", id)
+ slog.Debug("Received response", "id", id)
}
if resp.Error != nil {
@@ -251,7 +251,7 @@ func (c *Client) Call(ctx context.Context, method string, params any, result any
func (c *Client) Notify(ctx context.Context, method string, params any) error {
cfg := config.Get()
if cfg.Options.DebugLSP {
- logging.Debug("Sending notification", "method", method)
+ slog.Debug("Sending notification", "method", method)
}
msg, err := NewNotification(method, params)
@@ -11,7 +11,7 @@ import (
"github.com/bmatcuk/doublestar/v4"
"github.com/charmbracelet/crush/internal/config"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/lsp/protocol"
"github.com/fsnotify/fsnotify"
@@ -45,7 +45,7 @@ func NewWorkspaceWatcher(client *lsp.Client) *WorkspaceWatcher {
func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watchers []protocol.FileSystemWatcher) {
cfg := config.Get()
- logging.Debug("Adding file watcher registrations")
+ slog.Debug("Adding file watcher registrations")
w.registrationMu.Lock()
defer w.registrationMu.Unlock()
@@ -54,33 +54,33 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
// Print detailed registration information for debugging
if cfg.Options.DebugLSP {
- logging.Debug("Adding file watcher registrations",
+ slog.Debug("Adding file watcher registrations",
"id", id,
"watchers", len(watchers),
"total", len(w.registrations),
)
for i, watcher := range watchers {
- logging.Debug("Registration", "index", i+1)
+ slog.Debug("Registration", "index", i+1)
// Log the GlobPattern
switch v := watcher.GlobPattern.Value.(type) {
case string:
- logging.Debug("GlobPattern", "pattern", v)
+ slog.Debug("GlobPattern", "pattern", v)
case protocol.RelativePattern:
- logging.Debug("GlobPattern", "pattern", v.Pattern)
+ slog.Debug("GlobPattern", "pattern", v.Pattern)
// Log BaseURI details
switch u := v.BaseURI.Value.(type) {
case string:
- logging.Debug("BaseURI", "baseURI", u)
+ slog.Debug("BaseURI", "baseURI", u)
case protocol.DocumentUri:
- logging.Debug("BaseURI", "baseURI", u)
+ slog.Debug("BaseURI", "baseURI", u)
default:
- logging.Debug("BaseURI", "baseURI", u)
+ slog.Debug("BaseURI", "baseURI", u)
}
default:
- logging.Debug("GlobPattern", "unknown type", fmt.Sprintf("%T", v))
+ slog.Debug("GlobPattern", "unknown type", fmt.Sprintf("%T", v))
}
// Log WatchKind
@@ -89,13 +89,13 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
watchKind = *watcher.Kind
}
- logging.Debug("WatchKind", "kind", watchKind)
+ slog.Debug("WatchKind", "kind", watchKind)
}
}
// Determine server type for specialized handling
serverName := getServerNameFromContext(ctx)
- logging.Debug("Server type detected", "serverName", serverName)
+ slog.Debug("Server type detected", "serverName", serverName)
// Check if this server has sent file watchers
hasFileWatchers := len(watchers) > 0
@@ -123,7 +123,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
filesOpened += highPriorityFilesOpened
if cfg.Options.DebugLSP {
- logging.Debug("Opened high-priority files",
+ slog.Debug("Opened high-priority files",
"count", highPriorityFilesOpened,
"serverName", serverName)
}
@@ -131,7 +131,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
// If we've already opened enough high-priority files, we might not need more
if filesOpened >= maxFilesToOpen {
if cfg.Options.DebugLSP {
- logging.Debug("Reached file limit with high-priority files",
+ slog.Debug("Reached file limit with high-priority files",
"filesOpened", filesOpened,
"maxFiles", maxFilesToOpen)
}
@@ -149,7 +149,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
if d.IsDir() {
if path != w.workspacePath && shouldExcludeDir(path) {
if cfg.Options.DebugLSP {
- logging.Debug("Skipping excluded directory", "path", path)
+ slog.Debug("Skipping excluded directory", "path", path)
}
return filepath.SkipDir
}
@@ -177,7 +177,7 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
elapsedTime := time.Since(startTime)
if cfg.Options.DebugLSP {
- logging.Debug("Limited workspace scan complete",
+ slog.Debug("Limited workspace scan complete",
"filesOpened", filesOpened,
"maxFiles", maxFilesToOpen,
"elapsedTime", elapsedTime.Seconds(),
@@ -186,11 +186,11 @@ func (w *WorkspaceWatcher) AddRegistrations(ctx context.Context, id string, watc
}
if err != nil && cfg.Options.DebugLSP {
- logging.Debug("Error scanning workspace for files to open", "error", err)
+ slog.Debug("Error scanning workspace for files to open", "error", err)
}
}()
} else if cfg.Options.DebugLSP {
- logging.Debug("Using on-demand file loading for server", "server", serverName)
+ slog.Debug("Using on-demand file loading for server", "server", serverName)
}
}
@@ -266,7 +266,7 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
matches, err := doublestar.Glob(os.DirFS(w.workspacePath), pattern)
if err != nil {
if cfg.Options.DebugLSP {
- logging.Debug("Error finding high-priority files", "pattern", pattern, "error", err)
+ slog.Debug("Error finding high-priority files", "pattern", pattern, "error", err)
}
continue
}
@@ -300,12 +300,12 @@ func (w *WorkspaceWatcher) openHighPriorityFiles(ctx context.Context, serverName
fullPath := filesToOpen[j]
if err := w.client.OpenFile(ctx, fullPath); err != nil {
if cfg.Options.DebugLSP {
- logging.Debug("Error opening high-priority file", "path", fullPath, "error", err)
+ slog.Debug("Error opening high-priority file", "path", fullPath, "error", err)
}
} else {
filesOpened++
if cfg.Options.DebugLSP {
- logging.Debug("Opened high-priority file", "path", fullPath)
+ slog.Debug("Opened high-priority file", "path", fullPath)
}
}
}
@@ -334,7 +334,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
}
serverName := getServerNameFromContext(ctx)
- logging.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", serverName)
+ slog.Debug("Starting workspace watcher", "workspacePath", workspacePath, "serverName", serverName)
// Register handler for file watcher registrations from the server
lsp.RegisterFileWatchHandler(func(id string, watchers []protocol.FileSystemWatcher) {
@@ -343,7 +343,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
watcher, err := fsnotify.NewWatcher()
if err != nil {
- logging.Error("Error creating watcher", "error", err)
+ slog.Error("Error creating watcher", "error", err)
}
defer watcher.Close()
@@ -357,7 +357,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
if d.IsDir() && path != workspacePath {
if shouldExcludeDir(path) {
if cfg.Options.DebugLSP {
- logging.Debug("Skipping excluded directory", "path", path)
+ slog.Debug("Skipping excluded directory", "path", path)
}
return filepath.SkipDir
}
@@ -367,14 +367,14 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
if d.IsDir() {
err = watcher.Add(path)
if err != nil {
- logging.Error("Error watching path", "path", path, "error", err)
+ slog.Error("Error watching path", "path", path, "error", err)
}
}
return nil
})
if err != nil {
- logging.Error("Error walking workspace", "error", err)
+ slog.Error("Error walking workspace", "error", err)
}
// Event loop
@@ -396,7 +396,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
// Skip excluded directories
if !shouldExcludeDir(event.Name) {
if err := watcher.Add(event.Name); err != nil {
- logging.Error("Error adding directory to watcher", "path", event.Name, "error", err)
+ slog.Error("Error adding directory to watcher", "path", event.Name, "error", err)
}
}
} else {
@@ -411,7 +411,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
// Debug logging
if cfg.Options.DebugLSP {
matched, kind := w.isPathWatched(event.Name)
- logging.Debug("File event",
+ slog.Debug("File event",
"path", event.Name,
"operation", event.Op.String(),
"watched", matched,
@@ -431,7 +431,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
// Just send the notification if needed
info, err := os.Stat(event.Name)
if err != nil {
- logging.Error("Error getting file info", "path", event.Name, "error", err)
+ slog.Error("Error getting file info", "path", event.Name, "error", err)
return
}
if !info.IsDir() && watchKind&protocol.WatchCreate != 0 {
@@ -459,7 +459,7 @@ func (w *WorkspaceWatcher) WatchWorkspace(ctx context.Context, workspacePath str
if !ok {
return
}
- logging.Error("Error watching file", "error", err)
+ slog.Error("Error watching file", "error", err)
}
}
}
@@ -584,7 +584,7 @@ func matchesSimpleGlob(pattern, path string) bool {
// Fall back to simple matching for simpler patterns
matched, err := filepath.Match(pattern, path)
if err != nil {
- logging.Error("Error matching pattern", "pattern", pattern, "path", path, "error", err)
+ slog.Error("Error matching pattern", "pattern", pattern, "path", path, "error", err)
return false
}
@@ -595,7 +595,7 @@ func matchesSimpleGlob(pattern, path string) bool {
func (w *WorkspaceWatcher) matchesPattern(path string, pattern protocol.GlobPattern) bool {
patternInfo, err := pattern.AsPattern()
if err != nil {
- logging.Error("Error parsing pattern", "pattern", pattern, "error", err)
+ slog.Error("Error parsing pattern", "pattern", pattern, "error", err)
return false
}
@@ -620,7 +620,7 @@ func (w *WorkspaceWatcher) matchesPattern(path string, pattern protocol.GlobPatt
// Make path relative to basePath for matching
relPath, err := filepath.Rel(basePath, path)
if err != nil {
- logging.Error("Error getting relative path", "path", path, "basePath", basePath, "error", err)
+ slog.Error("Error getting relative path", "path", path, "basePath", basePath, "error", err)
return false
}
relPath = filepath.ToSlash(relPath)
@@ -663,14 +663,14 @@ func (w *WorkspaceWatcher) handleFileEvent(ctx context.Context, uri string, chan
} else if changeType == protocol.FileChangeType(protocol.Changed) && w.client.IsFileOpen(filePath) {
err := w.client.NotifyChange(ctx, filePath)
if err != nil {
- logging.Error("Error notifying change", "error", err)
+ slog.Error("Error notifying change", "error", err)
}
return
}
// Notify LSP server about the file event using didChangeWatchedFiles
if err := w.notifyFileEvent(ctx, uri, changeType); err != nil {
- logging.Error("Error notifying LSP server about file event", "error", err)
+ slog.Error("Error notifying LSP server about file event", "error", err)
}
}
@@ -678,7 +678,7 @@ func (w *WorkspaceWatcher) handleFileEvent(ctx context.Context, uri string, chan
func (w *WorkspaceWatcher) notifyFileEvent(ctx context.Context, uri string, changeType protocol.FileChangeType) error {
cfg := config.Get()
if cfg.Options.DebugLSP {
- logging.Debug("Notifying file event",
+ slog.Debug("Notifying file event",
"uri", uri,
"changeType", changeType,
)
@@ -853,7 +853,7 @@ func shouldExcludeFile(filePath string) bool {
// Skip large files
if info.Size() > maxFileSize {
if cfg.Options.DebugLSP {
- logging.Debug("Skipping large file",
+ slog.Debug("Skipping large file",
"path", filePath,
"size", info.Size(),
"maxSize", maxFileSize,
@@ -891,10 +891,10 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
// This helps with project initialization for certain language servers
if isHighPriorityFile(path, serverName) {
if cfg.Options.DebugLSP {
- logging.Debug("Opening high-priority file", "path", path, "serverName", serverName)
+ slog.Debug("Opening high-priority file", "path", path, "serverName", serverName)
}
if err := w.client.OpenFile(ctx, path); err != nil && cfg.Options.DebugLSP {
- logging.Error("Error opening high-priority file", "path", path, "error", err)
+ slog.Error("Error opening high-priority file", "path", path, "error", err)
}
return
}
@@ -906,7 +906,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
// Check file size - for preloading we're more conservative
if info.Size() > (1 * 1024 * 1024) { // 1MB limit for preloaded files
if cfg.Options.DebugLSP {
- logging.Debug("Skipping large file for preloading", "path", path, "size", info.Size())
+ slog.Debug("Skipping large file for preloading", "path", path, "size", info.Size())
}
return
}
@@ -938,7 +938,7 @@ func (w *WorkspaceWatcher) openMatchingFile(ctx context.Context, path string) {
if shouldOpen {
// Don't need to check if it's already open - the client.OpenFile handles that
if err := w.client.OpenFile(ctx, path); err != nil && cfg.Options.DebugLSP {
- logging.Error("Error opening file", "path", path, "error", err)
+ slog.Error("Error opening file", "path", path, "error", err)
}
}
}
@@ -1,9 +1,8 @@
package shell
import (
+ "log/slog"
"sync"
-
- "github.com/charmbracelet/crush/internal/logging"
)
// PersistentShell is a singleton shell instance that maintains state across the application
@@ -30,9 +29,9 @@ func GetPersistentShell(cwd string) *PersistentShell {
return shellInstance
}
-// loggingAdapter adapts the internal logging package to the Logger interface
+// slog.dapter adapts the internal slog.package to the Logger interface
type loggingAdapter struct{}
func (l *loggingAdapter) InfoPersist(msg string, keysAndValues ...interface{}) {
- logging.InfoPersist(msg, keysAndValues...)
+ slog.Info(msg, keysAndValues...)
}
@@ -14,7 +14,6 @@ import (
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/crush/internal/app"
"github.com/charmbracelet/crush/internal/fsext"
- "github.com/charmbracelet/crush/internal/logging"
"github.com/charmbracelet/crush/internal/message"
"github.com/charmbracelet/crush/internal/session"
"github.com/charmbracelet/crush/internal/tui/components/chat"
@@ -153,8 +152,7 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
case filepicker.FilePickedMsg:
if len(m.attachments) >= maxAttachments {
- logging.ErrorPersist(fmt.Sprintf("cannot add more than %d images", maxAttachments))
- return m, cmd
+ return m, util.ReportError(fmt.Errorf("cannot add more than %d images", maxAttachments))
}
m.attachments = append(m.attachments, msg.Attachment)
return m, nil
@@ -13,7 +13,7 @@ import (
"github.com/charmbracelet/crush/internal/diff"
"github.com/charmbracelet/crush/internal/fsext"
"github.com/charmbracelet/crush/internal/history"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/lsp/protocol"
"github.com/charmbracelet/crush/internal/pubsub"
@@ -94,7 +94,7 @@ func (m *sidebarCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case chat.SessionClearedMsg:
m.session = session.Session{}
case pubsub.Event[history.File]:
- logging.Info("sidebar", "Received file history event", "file", msg.Payload.Path, "session", msg.Payload.SessionID)
+ slog.Info("sidebar", "Received file history event", "file", msg.Payload.Path, "session", msg.Payload.SessionID)
return m, m.handleFileHistoryEvent(msg)
case pubsub.Event[session.Session]:
if msg.Type == pubsub.UpdatedEvent {
@@ -3,7 +3,7 @@ package layout
import (
"github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
- "github.com/charmbracelet/crush/internal/logging"
+ "log/slog"
"github.com/charmbracelet/crush/internal/tui/styles"
"github.com/charmbracelet/crush/internal/tui/util"
"github.com/charmbracelet/lipgloss/v2"
@@ -154,7 +154,7 @@ func (s *splitPaneLayout) View() tea.View {
func (s *splitPaneLayout) SetSize(width, height int) tea.Cmd {
s.width = width
s.height = height
- logging.Info("Setting split pane size", "width", width, "height", height)
+ slog.Info("Setting split pane size", "width", width, "height", height)
var topHeight, bottomHeight int
var cmds []tea.Cmd
@@ -6,8 +6,6 @@ import (
"github.com/charmbracelet/bubbles/v2/help"
tea "github.com/charmbracelet/bubbletea/v2"
- "github.com/charmbracelet/crush/internal/logging"
- "github.com/charmbracelet/crush/internal/pubsub"
"github.com/charmbracelet/crush/internal/session"
"github.com/charmbracelet/crush/internal/tui/styles"
"github.com/charmbracelet/crush/internal/tui/util"
@@ -59,37 +57,6 @@ func (m *statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case util.ClearStatusMsg:
m.info = util.InfoMsg{}
- // Handle persistent logs
- case pubsub.Event[logging.LogMessage]:
- if msg.Payload.Persist {
- switch msg.Payload.Level {
- case "error":
- m.info = util.InfoMsg{
- Type: util.InfoTypeError,
- Msg: msg.Payload.Message,
- TTL: msg.Payload.PersistTime,
- }
- case "info":
- m.info = util.InfoMsg{
- Type: util.InfoTypeInfo,
- Msg: msg.Payload.Message,
- TTL: msg.Payload.PersistTime,
- }
- case "warn":
- m.info = util.InfoMsg{
- Type: util.InfoTypeWarn,
- Msg: msg.Payload.Message,
- TTL: msg.Payload.PersistTime,
- }
- default:
- m.info = util.InfoMsg{
- Type: util.InfoTypeInfo,
- Msg: msg.Payload.Message,
- TTL: msg.Payload.PersistTime,
- }
- }
- return m, m.clearMessageCmd(m.info.TTL)
- }
}
return m, nil
}
@@ -11,7 +11,6 @@ import (
"github.com/charmbracelet/bubbles/v2/help"
"github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
- "github.com/charmbracelet/crush/internal/logging"
"github.com/charmbracelet/crush/internal/message"
"github.com/charmbracelet/crush/internal/tui/components/core"
"github.com/charmbracelet/crush/internal/tui/components/dialogs"
@@ -119,18 +118,15 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func() tea.Msg {
isFileLarge, err := ValidateFileSize(path, maxAttachmentSize)
if err != nil {
- logging.ErrorPersist("unable to read the image")
- return nil
+ return util.ReportError(fmt.Errorf("unable to read the image: %w", err))
}
if isFileLarge {
- logging.ErrorPersist("file too large, max 5MB")
- return nil
+ return util.ReportError(fmt.Errorf("file too large, max 5MB"))
}
content, err := os.ReadFile(path)
if err != nil {
- logging.ErrorPersist("Unable read selected file")
- return nil
+ return util.ReportError(fmt.Errorf("unable to read the image: %w", err))
}
mimeBufferSize := min(512, len(content))
@@ -1,176 +0,0 @@
-package logs
-
-import (
- "fmt"
- "strings"
- "time"
-
- "github.com/charmbracelet/bubbles/v2/viewport"
- tea "github.com/charmbracelet/bubbletea/v2"
- "github.com/charmbracelet/crush/internal/logging"
- "github.com/charmbracelet/crush/internal/tui/components/core/layout"
- "github.com/charmbracelet/crush/internal/tui/styles"
- "github.com/charmbracelet/crush/internal/tui/util"
- "github.com/charmbracelet/lipgloss/v2"
-)
-
-type DetailComponent interface {
- util.Model
- layout.Sizeable
-}
-
-type detailCmp struct {
- width, height int
- currentLog logging.LogMessage
- viewport viewport.Model
-}
-
-func (i *detailCmp) Init() tea.Cmd {
- messages := logging.List()
- if len(messages) == 0 {
- return nil
- }
- i.currentLog = messages[0]
- return nil
-}
-
-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()
- }
- }
-
- return i, nil
-}
-
-func (i *detailCmp) updateContent() {
- var content strings.Builder
- t := styles.CurrentTheme()
-
- if i.currentLog.ID == "" {
- content.WriteString(t.S().Muted.Render("No log selected"))
- i.viewport.SetContent(content.String())
- return
- }
-
- // Level badge with background color
- levelStyle := getLevelStyle(i.currentLog.Level)
- levelBadge := levelStyle.Padding(0, 1).Render(strings.ToUpper(i.currentLog.Level))
-
- // Timestamp with relative time
- timeStr := i.currentLog.Time.Format("2006-01-05 15:04:05 UTC")
- relativeTime := getRelativeTime(i.currentLog.Time)
- timeStyle := t.S().Muted
-
- // Header line
- header := lipgloss.JoinHorizontal(
- lipgloss.Left,
- timeStr,
- " ",
- timeStyle.Render(relativeTime),
- )
-
- content.WriteString(levelBadge)
- content.WriteString("\n\n")
- content.WriteString(header)
- content.WriteString("\n\n")
-
- // Message section
- messageHeaderStyle := t.S().Base.Foreground(t.Blue).Bold(true)
- content.WriteString(messageHeaderStyle.Render("Message"))
- content.WriteString("\n")
- content.WriteString(i.currentLog.Message)
- content.WriteString("\n\n")
-
- // Attributes section
- if len(i.currentLog.Attributes) > 0 {
- attrHeaderStyle := t.S().Base.Foreground(t.Blue).Bold(true)
- content.WriteString(attrHeaderStyle.Render("Attributes"))
- content.WriteString("\n")
-
- for _, attr := range i.currentLog.Attributes {
- keyStyle := t.S().Base.Foreground(t.Accent)
- valueStyle := t.S().Text
- attrLine := fmt.Sprintf("%s: %s",
- keyStyle.Render(attr.Key),
- valueStyle.Render(attr.Value),
- )
- content.WriteString(attrLine)
- content.WriteString("\n")
- }
- }
-
- i.viewport.SetContent(content.String())
-}
-
-func getLevelStyle(level string) lipgloss.Style {
- t := styles.CurrentTheme()
- style := t.S().Base.Bold(true)
-
- switch strings.ToLower(level) {
- case "info":
- return style.Foreground(t.White).Background(t.Info)
- case "warn", "warning":
- return style.Foreground(t.White).Background(t.Warning)
- case "error", "err":
- return style.Foreground(t.White).Background(t.Error)
- case "debug":
- return style.Foreground(t.White).Background(t.Success)
- case "fatal":
- return style.Foreground(t.White).Background(t.Error)
- default:
- return style.Foreground(t.FgBase)
- }
-}
-
-func getRelativeTime(logTime time.Time) string {
- now := time.Now()
- diff := now.Sub(logTime)
-
- if diff < time.Minute {
- return fmt.Sprintf("%ds ago", int(diff.Seconds()))
- } else if diff < time.Hour {
- return fmt.Sprintf("%dm ago", int(diff.Minutes()))
- } else if diff < 24*time.Hour {
- return fmt.Sprintf("%dh ago", int(diff.Hours()))
- } else if diff < 30*24*time.Hour {
- return fmt.Sprintf("%dd ago", int(diff.Hours()/24))
- } else if diff < 365*24*time.Hour {
- return fmt.Sprintf("%dmo ago", int(diff.Hours()/(24*30)))
- } else {
- return fmt.Sprintf("%dy ago", int(diff.Hours()/(24*365)))
- }
-}
-
-func (i *detailCmp) View() tea.View {
- t := styles.CurrentTheme()
- style := t.S().Base.
- BorderStyle(lipgloss.RoundedBorder()).
- BorderForeground(t.BorderFocus).
- Width(i.width - 2). // Adjust width for border
- Height(i.height - 2). // Adjust height for border
- Padding(1)
- return tea.NewView(style.Render(i.viewport.View()))
-}
-
-func (i *detailCmp) GetSize() (int, int) {
- return i.width, i.height
-}
-
-func (i *detailCmp) SetSize(width int, height int) tea.Cmd {
- i.width = width
- i.height = height
- i.viewport.SetWidth(i.width - 4)
- i.viewport.SetHeight(i.height - 4)
- i.updateContent()
- return nil
-}
-
-func NewLogsDetails() DetailComponent {
- return &detailCmp{
- viewport: viewport.New(),
- }
-}
@@ -1,197 +0,0 @@
-package logs
-
-import (
- "fmt"
- "slices"
- "strings"
-
- "github.com/charmbracelet/bubbles/v2/table"
- tea "github.com/charmbracelet/bubbletea/v2"
- "github.com/charmbracelet/crush/internal/logging"
- "github.com/charmbracelet/crush/internal/pubsub"
- "github.com/charmbracelet/crush/internal/tui/components/core/layout"
- "github.com/charmbracelet/crush/internal/tui/styles"
- "github.com/charmbracelet/crush/internal/tui/util"
- "github.com/charmbracelet/lipgloss/v2"
-)
-
-type TableComponent interface {
- util.Model
- layout.Sizeable
-}
-
-type tableCmp struct {
- table table.Model
- logs []logging.LogMessage
-}
-
-type selectedLogMsg logging.LogMessage
-
-func (i *tableCmp) Init() tea.Cmd {
- i.logs = logging.List()
- i.setRows()
- return nil
-}
-
-func (i *tableCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- var cmds []tea.Cmd
- switch msg := msg.(type) {
- case pubsub.Event[logging.LogMessage]:
- return i, func() tea.Msg {
- if msg.Type == pubsub.CreatedEvent {
- rows := i.table.Rows()
- for _, row := range rows {
- if row[1] == msg.Payload.ID {
- return nil // If the log already exists, do not add it again
- }
- }
- i.logs = append(i.logs, msg.Payload)
- i.table.SetRows(
- append(
- []table.Row{
- logToRow(msg.Payload),
- },
- i.table.Rows()...,
- ),
- )
- }
- return selectedLogMsg(msg.Payload)
- }
- }
- t, cmd := i.table.Update(msg)
- cmds = append(cmds, cmd)
- i.table = t
-
- cmds = append(cmds, func() tea.Msg {
- for _, log := range logging.List() {
- if log.ID == i.table.SelectedRow()[1] {
- // If the selected row matches the log ID, return the selected log message
- return selectedLogMsg(log)
- }
- }
- return nil
- })
- return i, tea.Batch(cmds...)
-}
-
-func (i *tableCmp) View() tea.View {
- t := styles.CurrentTheme()
- defaultStyles := table.DefaultStyles()
-
- // Header styling
- defaultStyles.Header = defaultStyles.Header.
- Foreground(t.Primary).
- Bold(true).
- BorderStyle(lipgloss.NormalBorder()).
- BorderBottom(true).
- BorderForeground(t.Border)
-
- // Selected row styling
- defaultStyles.Selected = defaultStyles.Selected.
- Foreground(t.FgSelected).
- Background(t.Primary).
- Bold(false)
-
- // Cell styling
- defaultStyles.Cell = defaultStyles.Cell.
- Foreground(t.FgBase)
-
- i.table.SetStyles(defaultStyles)
- return tea.NewView(i.table.View())
-}
-
-func (i *tableCmp) GetSize() (int, int) {
- return i.table.Width(), i.table.Height()
-}
-
-func (i *tableCmp) SetSize(width int, height int) tea.Cmd {
- i.table.SetWidth(width)
- i.table.SetHeight(height)
-
- columnWidth := (width - 10) / 4
- i.table.SetColumns([]table.Column{
- {
- Title: "Level",
- Width: 10,
- },
- {
- Title: "ID",
- Width: columnWidth,
- },
- {
- Title: "Time",
- Width: columnWidth,
- },
- {
- Title: "Message",
- Width: columnWidth,
- },
- {
- Title: "Attributes",
- Width: columnWidth,
- },
- })
- return nil
-}
-
-func (i *tableCmp) setRows() {
- rows := []table.Row{}
-
- slices.SortFunc(i.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
- })
-
- for _, log := range i.logs {
- rows = append(rows, logToRow(log))
- }
- i.table.SetRows(rows)
-}
-
-func logToRow(log logging.LogMessage) table.Row {
- // Format attributes as JSON string
- var attrStr string
- if len(log.Attributes) > 0 {
- var parts []string
- for _, attr := range log.Attributes {
- parts = append(parts, fmt.Sprintf(`{"Key":"%s","Value":"%s"}`, attr.Key, attr.Value))
- }
- attrStr = "[" + strings.Join(parts, ",") + "]"
- }
-
- // Format time with relative time
- timeStr := log.Time.Format("2006-01-05 15:04:05 UTC")
- relativeTime := getRelativeTime(log.Time)
- fullTimeStr := timeStr + " " + relativeTime
-
- return table.Row{
- strings.ToUpper(log.Level),
- log.ID,
- fullTimeStr,
- log.Message,
- attrStr,
- }
-}
-
-func NewLogsTable() TableComponent {
- columns := []table.Column{
- {Title: "Level"},
- {Title: "ID"},
- {Title: "Time"},
- {Title: "Message"},
- {Title: "Attributes"},
- }
-
- tableModel := table.New(
- table.WithColumns(columns),
- )
- tableModel.Focus()
- return &tableCmp{
- table: tableModel,
- }
-}
@@ -5,7 +5,6 @@ import (
)
type KeyMap struct {
- Logs key.Binding
Quit key.Binding
Help key.Binding
Commands key.Binding
@@ -16,10 +15,6 @@ type KeyMap struct {
func DefaultKeyMap() KeyMap {
return KeyMap{
- Logs: key.NewBinding(
- key.WithKeys("ctrl+l"),
- key.WithHelp("ctrl+l", "logs"),
- ),
Quit: key.NewBinding(
key.WithKeys("ctrl+c"),
key.WithHelp("ctrl+c", "quit"),
@@ -47,7 +42,6 @@ func (k KeyMap) FullHelp() [][]key.Binding {
k.Sessions,
k.Quit,
k.Help,
- k.Logs,
}
slice = k.prependEscAndTab(slice)
slice = append(slice, k.pageBindings...)
@@ -1,43 +0,0 @@
-package logs
-
-import (
- "github.com/charmbracelet/bubbles/v2/key"
-)
-
-type KeyMap struct {
- Back key.Binding
-}
-
-func DefaultKeyMap() KeyMap {
- return KeyMap{
- Back: key.NewBinding(
- key.WithKeys("esc", "backspace"),
- key.WithHelp("esc/backspace", "back to chat"),
- ),
- }
-}
-
-// KeyBindings implements layout.KeyMapProvider
-func (k KeyMap) KeyBindings() []key.Binding {
- return []key.Binding{
- k.Back,
- }
-}
-
-// FullHelp implements help.KeyMap.
-func (k KeyMap) FullHelp() [][]key.Binding {
- m := [][]key.Binding{}
- slice := k.KeyBindings()
- for i := 0; i < len(slice); i += 4 {
- end := min(i+4, len(slice))
- m = append(m, slice[i:end])
- }
- return m
-}
-
-// ShortHelp implements help.KeyMap.
-func (k KeyMap) ShortHelp() []key.Binding {
- return []key.Binding{
- k.Back,
- }
-}
@@ -1,100 +0,0 @@
-package logs
-
-import (
- "github.com/charmbracelet/bubbles/v2/key"
- tea "github.com/charmbracelet/bubbletea/v2"
- "github.com/charmbracelet/crush/internal/tui/components/core"
- "github.com/charmbracelet/crush/internal/tui/components/core/layout"
- logsComponents "github.com/charmbracelet/crush/internal/tui/components/logs"
- "github.com/charmbracelet/crush/internal/tui/page"
- "github.com/charmbracelet/crush/internal/tui/page/chat"
- "github.com/charmbracelet/crush/internal/tui/styles"
- "github.com/charmbracelet/crush/internal/tui/util"
- "github.com/charmbracelet/lipgloss/v2"
-)
-
-var LogsPage page.PageID = "logs"
-
-type LogPage interface {
- util.Model
- layout.Sizeable
-}
-
-type logsPage struct {
- width, height int
- table logsComponents.TableComponent
- details logsComponents.DetailComponent
- keyMap KeyMap
-}
-
-func (p *logsPage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- var cmds []tea.Cmd
- switch msg := msg.(type) {
- case tea.WindowSizeMsg:
- p.width = msg.Width
- p.height = msg.Height
- return p, p.SetSize(msg.Width, msg.Height)
- case tea.KeyMsg:
- switch {
- case key.Matches(msg, p.keyMap.Back):
- return p, util.CmdHandler(page.PageChangeMsg{ID: chat.ChatPageID})
- }
- }
-
- table, cmd := p.table.Update(msg)
- cmds = append(cmds, cmd)
- p.table = table.(logsComponents.TableComponent)
- details, cmd := p.details.Update(msg)
- cmds = append(cmds, cmd)
- p.details = details.(logsComponents.DetailComponent)
-
- return p, tea.Batch(cmds...)
-}
-
-func (p *logsPage) View() tea.View {
- baseStyle := styles.CurrentTheme().S().Base
- style := baseStyle.Width(p.width).Height(p.height).Padding(1)
- title := core.Title("Logs", p.width-2)
-
- return tea.NewView(
- style.Render(
- lipgloss.JoinVertical(lipgloss.Top,
- title,
- p.details.View().String(),
- p.table.View().String(),
- ),
- ),
- )
-}
-
-// GetSize implements LogPage.
-func (p *logsPage) GetSize() (int, int) {
- return p.width, p.height
-}
-
-// SetSize implements LogPage.
-func (p *logsPage) SetSize(width int, height int) tea.Cmd {
- p.width = width
- p.height = height
- availableHeight := height - 2 // Padding for top and bottom
- availableHeight -= 1 // title height
- return tea.Batch(
- p.table.SetSize(width-2, availableHeight/2),
- p.details.SetSize(width-2, availableHeight/2),
- )
-}
-
-func (p *logsPage) Init() tea.Cmd {
- return tea.Batch(
- p.table.Init(),
- p.details.Init(),
- )
-}
-
-func NewLogsPage() LogPage {
- return &logsPage{
- details: logsComponents.NewLogsDetails(),
- table: logsComponents.NewLogsTable(),
- keyMap: DefaultKeyMap(),
- }
-}
@@ -9,7 +9,6 @@ import (
"github.com/charmbracelet/crush/internal/app"
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/llm/agent"
- "github.com/charmbracelet/crush/internal/logging"
"github.com/charmbracelet/crush/internal/permission"
"github.com/charmbracelet/crush/internal/pubsub"
cmpChat "github.com/charmbracelet/crush/internal/tui/components/chat"
@@ -27,7 +26,6 @@ import (
"github.com/charmbracelet/crush/internal/tui/components/dialogs/sessions"
"github.com/charmbracelet/crush/internal/tui/page"
"github.com/charmbracelet/crush/internal/tui/page/chat"
- "github.com/charmbracelet/crush/internal/tui/page/logs"
"github.com/charmbracelet/crush/internal/tui/styles"
"github.com/charmbracelet/crush/internal/tui/util"
"github.com/charmbracelet/lipgloss/v2"
@@ -135,20 +133,6 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.selectedSessionID = msg.ID
case cmpChat.SessionClearedMsg:
a.selectedSessionID = ""
- // Logs
- case pubsub.Event[logging.LogMessage]:
- // Send to the status component
- s, statusCmd := a.status.Update(msg)
- a.status = s.(status.StatusCmp)
- cmds = append(cmds, statusCmd)
-
- // If the current page is logs, update the logs view
- if a.currentPage == logs.LogsPage {
- updated, pageCmd := a.pages[a.currentPage].Update(msg)
- a.pages[a.currentPage] = updated.(util.Model)
- cmds = append(cmds, pageCmd)
- }
- return a, tea.Batch(cmds...)
// Commands
case commands.SwitchSessionsMsg:
return a, func() tea.Msg {
@@ -176,7 +160,6 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Update the agent with the new model/provider configuration
if err := a.app.UpdateAgentModel(); err != nil {
- logging.ErrorPersist(fmt.Sprintf("Failed to update agent model: %v", err))
return a, util.ReportError(fmt.Errorf("model changed to %s but failed to update agent: %v", msg.Model.Model, err))
}
@@ -348,10 +331,6 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
},
)
return tea.Sequence(cmds...)
- // Page navigation
- case key.Matches(msg, a.keyMap.Logs):
- return a.moveToPage(logs.LogsPage)
-
default:
if a.dialog.HasDialogs() {
u, dialogCmd := a.dialog.Update(msg)
@@ -453,7 +432,6 @@ func New(app *app.App) tea.Model {
pages: map[page.PageID]util.Model{
chat.ChatPageID: chatPage,
- logs.LogsPage: logs.NewLogsPage(),
},
dialog: dialogs.NewDialogCmp(),
@@ -1,6 +1,7 @@
package main
import (
+ "log/slog"
"net/http"
"os"
@@ -9,19 +10,19 @@ import (
_ "github.com/joho/godotenv/autoload" // automatically load .env files
"github.com/charmbracelet/crush/cmd"
- "github.com/charmbracelet/crush/internal/logging"
+ "github.com/charmbracelet/crush/internal/log"
)
func main() {
- defer logging.RecoverPanic("main", func() {
- logging.ErrorPersist("Application terminated due to unhandled panic")
+ defer log.RecoverPanic("main", func() {
+ slog.Error("Application terminated due to unhandled panic")
})
if os.Getenv("CRUSH_PROFILE") != "" {
go func() {
- logging.Info("Serving pprof at localhost:6060")
+ slog.Info("Serving pprof at localhost:6060")
if httpErr := http.ListenAndServe("localhost:6060", nil); httpErr != nil {
- logging.Error("Failed to pprof listen: %v", httpErr)
+ slog.Error("Failed to pprof listen: %v", httpErr)
}
}()
}