From ed36f3c03e00c754dc5751e434e305c3929eb16e Mon Sep 17 00:00:00 2001 From: Toma Puljak Date: Thu, 25 Apr 2024 09:23:48 +0000 Subject: [PATCH] feat: agent logs User can now get agent logs inside the project with the agent logs command Signed-off-by: Toma Puljak --- internal/util/log_reader.go | 4 +- pkg/agent/agent.go | 2 + pkg/agent/config/config.go | 21 ++++++++-- pkg/agent/git/service.go | 7 +++- pkg/agent/log.go | 39 ++++++++++++++++++ pkg/agent/types.go | 2 + pkg/api/controllers/log/websocket.go | 6 +-- pkg/cmd/agent/agent.go | 20 +++++++++ pkg/cmd/agent/logs.go | 61 ++++++++++++++++++++++++++++ pkg/workspace/workspace.go | 2 + 10 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 pkg/agent/log.go create mode 100644 pkg/cmd/agent/logs.go diff --git a/internal/util/log_reader.go b/internal/util/log_reader.go index eb2b2a5328..9ceb5c22a5 100644 --- a/internal/util/log_reader.go +++ b/internal/util/log_reader.go @@ -11,8 +11,8 @@ import ( "time" ) -func ReadLog(ctx context.Context, logReader *io.Reader, follow bool, c chan []byte, errChan chan error) { - reader := bufio.NewReader(*logReader) +func ReadLog(ctx context.Context, logReader io.Reader, follow bool, c chan []byte, errChan chan error) { + reader := bufio.NewReader(logReader) for { select { diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 311582581e..21660507dc 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -19,6 +19,8 @@ import ( ) func (a *Agent) Start() error { + a.initLogs() + log.Info("Starting Daytona Agent") a.startTime = time.Now() diff --git a/pkg/agent/config/config.go b/pkg/agent/config/config.go index 34fe93641d..4a317d1654 100644 --- a/pkg/agent/config/config.go +++ b/pkg/agent/config/config.go @@ -6,6 +6,7 @@ package config import ( "errors" "os" + "strings" "github.com/go-playground/validator/v10" "github.com/kelseyhightower/envconfig" @@ -20,9 +21,10 @@ type DaytonaServerConfig struct { } type Config struct { - ProjectDir string `envconfig:"DAYTONA_WS_DIR"` - ProjectName string `envconfig:"DAYTONA_WS_PROJECT_NAME"` - WorkspaceId string `envconfig:"DAYTONA_WS_ID" validate:"required"` + ProjectDir string `envconfig:"DAYTONA_WS_DIR"` + ProjectName string `envconfig:"DAYTONA_WS_PROJECT_NAME"` + WorkspaceId string `envconfig:"DAYTONA_WS_ID" validate:"required"` + LogFilePath *string `envconfig:"DAYTONA_AGENT_LOG_FILE_PATH"` Server DaytonaServerConfig Mode Mode } @@ -69,5 +71,18 @@ func GetConfig(mode Mode) (*Config, error) { } } + config.LogFilePath = GetLogFilePath() + return config, nil } + +func GetLogFilePath() *string { + logFilePath, ok := os.LookupEnv("DAYTONA_AGENT_LOG_FILE_PATH") + if !ok { + return nil + } + + logFilePath = strings.Replace(logFilePath, "$HOME", os.Getenv("HOME"), 1) + + return &logFilePath +} diff --git a/pkg/agent/git/service.go b/pkg/agent/git/service.go index 24805713d9..838259b0d0 100644 --- a/pkg/agent/git/service.go +++ b/pkg/agent/git/service.go @@ -5,6 +5,7 @@ package git import ( "bytes" + "io" "os" "github.com/daytonaio/daytona/pkg/serverapiclient" @@ -17,16 +18,20 @@ import ( type Service struct { ProjectDir string GitConfigFileName string + LogWriter io.Writer } func (s *Service) CloneRepository(project *serverapiclient.Project, authToken *string) error { cloneOptions := &git.CloneOptions{ URL: *project.Repository.Url, - Progress: os.Stdout, SingleBranch: true, InsecureSkipTLS: true, } + if s.LogWriter != nil { + cloneOptions.Progress = s.LogWriter + } + if authToken != nil { cloneOptions.Auth = &http.BasicAuth{ Username: "daytona", diff --git a/pkg/agent/log.go b/pkg/agent/log.go new file mode 100644 index 0000000000..98e7413678 --- /dev/null +++ b/pkg/agent/log.go @@ -0,0 +1,39 @@ +package agent + +import ( + "io" + + log "github.com/sirupsen/logrus" +) + +type logFormatter struct { + textFormatter *log.TextFormatter + agentLogWriter io.Writer +} + +func (f *logFormatter) Format(entry *log.Entry) ([]byte, error) { + formatted, err := f.textFormatter.Format(entry) + if err != nil { + return nil, err + } + + if f.agentLogWriter != nil { + _, err = f.agentLogWriter.Write(formatted) + if err != nil { + return nil, err + } + } + + return formatted, nil +} + +func (s *Agent) initLogs() { + logFormatter := &logFormatter{ + textFormatter: &log.TextFormatter{ + ForceColors: true, + }, + agentLogWriter: s.LogWriter, + } + + log.SetFormatter(logFormatter) +} diff --git a/pkg/agent/types.go b/pkg/agent/types.go index e0b8b233ff..aa76cf048f 100644 --- a/pkg/agent/types.go +++ b/pkg/agent/types.go @@ -4,6 +4,7 @@ package agent import ( + "io" "time" "github.com/daytonaio/daytona/pkg/agent/config" @@ -29,5 +30,6 @@ type Agent struct { Git GitService Ssh SshServer Tailscale TailscaleServer + LogWriter io.Writer startTime time.Time } diff --git a/pkg/api/controllers/log/websocket.go b/pkg/api/controllers/log/websocket.go index 3f4da60b6b..a05ff577f0 100644 --- a/pkg/api/controllers/log/websocket.go +++ b/pkg/api/controllers/log/websocket.go @@ -31,7 +31,7 @@ func writeToWs(ws *websocket.Conn, c chan []byte, errChan chan error) { } } -func readLog(ginCtx *gin.Context, logReader *io.Reader) { +func readLog(ginCtx *gin.Context, logReader io.Reader) { followQuery := ginCtx.Query("follow") follow := followQuery == "true" @@ -92,7 +92,7 @@ func ReadServerLog(ginCtx *gin.Context) { return } - readLog(ginCtx, &reader) + readLog(ginCtx, reader) } func ReadWorkspaceLog(ginCtx *gin.Context) { @@ -106,5 +106,5 @@ func ReadWorkspaceLog(ginCtx *gin.Context) { return } - readLog(ginCtx, &wsLogReader) + readLog(ginCtx, wsLogReader) } diff --git a/pkg/cmd/agent/agent.go b/pkg/cmd/agent/agent.go index f2024ef66c..7e858ad61a 100644 --- a/pkg/cmd/agent/agent.go +++ b/pkg/cmd/agent/agent.go @@ -6,6 +6,7 @@ package agent import ( + "io" "os" "path" @@ -26,6 +27,10 @@ var AgentCmd = &cobra.Command{ Short: "Start the agent process", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { + if log.GetLevel() < log.InfoLevel { + log.SetLevel(log.InfoLevel) + } + agentMode := config.ModeProject if hostModeFlag { @@ -37,9 +42,22 @@ var AgentCmd = &cobra.Command{ log.Fatal(err) } + gitLogWriter := io.MultiWriter(os.Stdout) + var agentLogWriter io.Writer + if config.LogFilePath != nil { + logFile, err := os.OpenFile(*config.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Fatal(err) + } + defer logFile.Close() + gitLogWriter = io.MultiWriter(os.Stdout, logFile) + agentLogWriter = logFile + } + git := &git.Service{ ProjectDir: config.ProjectDir, GitConfigFileName: path.Join(os.Getenv("HOME"), ".gitconfig"), + LogWriter: gitLogWriter, } sshServer := &ssh.Server{ @@ -62,6 +80,7 @@ var AgentCmd = &cobra.Command{ Git: git, Ssh: sshServer, Tailscale: tailscaleServer, + LogWriter: agentLogWriter, } err = agent.Start() @@ -73,4 +92,5 @@ var AgentCmd = &cobra.Command{ func init() { AgentCmd.Flags().BoolVar(&hostModeFlag, "host", false, "Run the agent in host mode") + AgentCmd.AddCommand(logsCmd) } diff --git a/pkg/cmd/agent/logs.go b/pkg/cmd/agent/logs.go new file mode 100644 index 0000000000..a98f7da634 --- /dev/null +++ b/pkg/cmd/agent/logs.go @@ -0,0 +1,61 @@ +// Copyright 2024 Daytona Platforms Inc. +// SPDX-License-Identifier: Apache-2.0 + +package agent + +import ( + "context" + "fmt" + "io" + "os" + + "github.com/daytonaio/daytona/internal/util" + "github.com/daytonaio/daytona/pkg/agent/config" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var followFlag bool + +var logsCmd = &cobra.Command{ + Use: "logs", + Short: "Output Daytona Agent logs", + Run: func(cmd *cobra.Command, args []string) { + logFilePath := config.GetLogFilePath() + + if logFilePath == nil { + log.Fatal("Log file path not set") + } + + file, err := os.Open(*logFilePath) + if err != nil { + log.Fatal(err) + } + defer file.Close() + + msgChan := make(chan []byte) + errChan := make(chan error) + + go util.ReadLog(context.Background(), file, followFlag, msgChan, errChan) + + for { + select { + case <-context.Background().Done(): + return + case err := <-errChan: + if err != nil { + if err != io.EOF { + log.Fatal(err) + } + return + } + case msg := <-msgChan: + fmt.Println(string(msg)) + } + } + }, +} + +func init() { + logsCmd.Flags().BoolVarP(&followFlag, "follow", "f", false, "Follow logs") +} diff --git a/pkg/workspace/workspace.go b/pkg/workspace/workspace.go index 7367c05867..12a1086705 100644 --- a/pkg/workspace/workspace.go +++ b/pkg/workspace/workspace.go @@ -80,6 +80,8 @@ func GetProjectEnvVars(project *Project, apiUrl, serverUrl string) map[string]st "DAYTONA_SERVER_VERSION": internal.Version, "DAYTONA_SERVER_URL": serverUrl, "DAYTONA_SERVER_API_URL": apiUrl, + // $HOME will be replaced at runtime + "DAYTONA_AGENT_LOG_FILE_PATH": "$HOME/.daytona-agent.log", } return envVars