Skip to content

Commit

Permalink
Merge branch 'main' into chore/upgrade-golangci-lint
Browse files Browse the repository at this point in the history
* main:
  Add export of service logs to `dev run` command (#148)
  • Loading branch information
steezeburger committed Aug 14, 2024
2 parents 4288afb + 5e1be10 commit d0660ca
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 1 deletion.
1 change: 1 addition & 0 deletions modules/cli/cmd/devrunner/config/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const (
duskNum = "9"
dawnNum = "0"
BinariesDirName = "bin"
LogsDirName = "logs"
DataDirName = "data"
DefaultBaseConfigName = "base-config.toml"
DefaultCometbftGenesisFilename = "genesis.json"
Expand Down
4 changes: 4 additions & 0 deletions modules/cli/cmd/devrunner/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ func runInitialization(c *cobra.Command, _ []string) {
log.Info("Creating new instance in:", instanceDir)
cmd.CreateDirOrPanic(instanceDir)

// create a directory for all log files
logsDir := filepath.Join(instanceDir, config.LogsDirName)
cmd.CreateDirOrPanic(logsDir)

// create the local bin directory for downloaded binaries
localBinPath := filepath.Join(instanceDir, config.BinariesDirName)
log.Info("Binary files for locally running a sequencer placed in: ", localBinPath)
Expand Down
30 changes: 30 additions & 0 deletions modules/cli/cmd/devrunner/purge.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,41 @@ func purgeAllCmdHandler(c *cobra.Command, _ []string) {
log.Infof("Successfully deleted instance '%s'", instance)
}

// purgeLogsCmd represents the 'purge logs' command
var purgeLogsCmd = &cobra.Command{
Use: "logs",
Short: "Delete all logs for a given instance. Re-initializing is NOT required after using this command.",
Run: purgeLogsCmdHandler,
}

func purgeLogsCmdHandler(c *cobra.Command, _ []string) {
flagHandler := cmd.CreateCliFlagHandler(c, cmd.EnvPrefix)

instance := flagHandler.GetValue("instance")
config.IsInstanceNameValidOrPanic(instance)

homeDir := cmd.GetUserHomeDirOrPanic()
logDir := filepath.Join(homeDir, ".astria", instance, config.LogsDirName)

log.Infof("Deleting logs for instance '%s'", instance)

err := os.RemoveAll(logDir)
if err != nil {
fmt.Println("Error removing file:", err)
return
}
cmd.CreateDirOrPanic(logDir)

log.Infof("Successfully deleted logs for instance '%s'", instance)

}

func init() {
// top level command
devCmd.AddCommand(purgeCmd)

// subcommands
purgeCmd.AddCommand(purgeBinariesCmd)
purgeCmd.AddCommand(purgeAllCmd)
purgeCmd.AddCommand(purgeLogsCmd)
}
16 changes: 16 additions & 0 deletions modules/cli/cmd/devrunner/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func init() {
flagHandler.BindStringFlag("cometbft-path", "", "Provide an override path to a specific cometbft binary.")
flagHandler.BindStringFlag("composer-path", "", "Provide an override path to a specific composer binary.")
flagHandler.BindStringFlag("sequencer-path", "", "Provide an override path to a specific sequencer binary.")
flagHandler.BindBoolFlag("export-logs", false, "Export logs to files.")
}

func runCmdHandler(c *cobra.Command, _ []string) {
Expand All @@ -49,6 +50,11 @@ func runCmdHandler(c *cobra.Command, _ []string) {
instance := flagHandler.GetValue("instance")
config.IsInstanceNameValidOrPanic(instance)

exportLogs := flagHandler.GetValue("export-logs") == "true"
logsDir := filepath.Join(astriaDir, instance, config.LogsDirName)
currentTime := time.Now()
appStartTime := currentTime.Format("20060102-150405") // YYYYMMDD-HHMMSS

cmd.CreateUILog(filepath.Join(astriaDir, instance))

network := flagHandler.GetValue("network")
Expand Down Expand Up @@ -106,6 +112,8 @@ func runCmdHandler(c *cobra.Command, _ []string) {
Env: environment,
Args: nil,
ReadyCheck: &seqReadinessCheck,
LogPath: filepath.Join(logsDir, appStartTime+"-astria-sequencer.log"),
ExportLogs: exportLogs,
}
seqRunner = processrunner.NewProcessRunner(ctx, seqOpts)
case "composer":
Expand All @@ -116,6 +124,8 @@ func runCmdHandler(c *cobra.Command, _ []string) {
Env: environment,
Args: nil,
ReadyCheck: nil,
LogPath: filepath.Join(logsDir, appStartTime+"-astria-composer.log"),
ExportLogs: exportLogs,
}
compRunner = processrunner.NewProcessRunner(ctx, composerOpts)
case "conductor":
Expand All @@ -126,6 +136,8 @@ func runCmdHandler(c *cobra.Command, _ []string) {
Env: environment,
Args: nil,
ReadyCheck: nil,
LogPath: filepath.Join(logsDir, appStartTime+"-astria-conductor.log"),
ExportLogs: exportLogs,
}
condRunner = processrunner.NewProcessRunner(ctx, conductorOpts)
case "cometbft":
Expand All @@ -146,6 +158,8 @@ func runCmdHandler(c *cobra.Command, _ []string) {
Env: environment,
Args: []string{"node", "--home", cometDataPath, "--log_level", serviceLogLevel},
ReadyCheck: &cometReadinessCheck,
LogPath: filepath.Join(logsDir, appStartTime+"-cometbft.log"),
ExportLogs: exportLogs,
}
cometRunner = processrunner.NewProcessRunner(ctx, cometOpts)
default:
Expand All @@ -155,6 +169,8 @@ func runCmdHandler(c *cobra.Command, _ []string) {
Env: environment,
Args: nil, // TODO: implement generic args?
ReadyCheck: nil,
LogPath: filepath.Join(logsDir, appStartTime+"-"+service.Name+".log"),
ExportLogs: exportLogs,
}
genericRunner := processrunner.NewProcessRunner(ctx, genericOpts)
genericRunners = append(genericRunners, genericRunner)
Expand Down
70 changes: 70 additions & 0 deletions modules/cli/internal/processrunner/logHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package processrunner

import (
"os"
"regexp"

log "github.com/sirupsen/logrus"
)

// LogHandler is a struct that manages the writing of process logs to a file.
type LogHandler struct {
logPath string
exportLogs bool
fileDescriptor *os.File
}

// NewLogHandler creates a new LogHandler to be used by a ProcessRunner to write
// the process logs to a file.
func NewLogHandler(logPath string, exportLogs bool) *LogHandler {
var fileDescriptor *os.File

// conditionally create the log file
if exportLogs {
// Open the log file
logFile, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("error opening file: %v", err)
}
log.Info("New log file created successfully:", logPath)
fileDescriptor = logFile
} else {
fileDescriptor = nil
}

return &LogHandler{
logPath: logPath,
exportLogs: exportLogs,
fileDescriptor: fileDescriptor,
}
}

// Writeable reports if the data sent to the LogHandler.Write() function will be
// written to the log file. If Writeable() returns false, the data will not be
// written to a log file. If Writeable() returns true, the data will be written
// to the log file when the Write() function is called.
func (lh *LogHandler) Writeable() bool {
return lh.exportLogs
}

// Write writes the data to the log file managed by the LogHandler.
func (lh *LogHandler) Write(data string) error {
// Remove ANSI escape codes from data
ansiRegex := regexp.MustCompile(`\x1b\[[0-9;]*[mGKH]`)
cleanData := ansiRegex.ReplaceAllString(data, "")

_, err := lh.fileDescriptor.Write([]byte(cleanData))
if err != nil {
log.Fatalf("error writing to logfile %s: %v", lh.logPath, err)
return err
}
return nil
}

// Close closes the log file within the LogHandler.
func (lh *LogHandler) Close() error {
if lh.fileDescriptor != nil {
return lh.fileDescriptor.Close()
}
return nil
}
31 changes: 30 additions & 1 deletion modules/cli/internal/processrunner/processrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ type ProcessRunner interface {
GetOutputAndClearBuf() string
GetInfo() string
GetEnvironment() []string
CanWriteToLog() bool
WriteToLog(data string) error
}

// ProcessRunner is a struct that represents a process to be run.
Expand All @@ -41,6 +43,7 @@ type processRunner struct {
outputBuf *safebuffer.SafeBuffer

readyChecker *ReadyChecker
logHandler *LogHandler
}

type NewProcessRunnerOpts struct {
Expand All @@ -49,6 +52,8 @@ type NewProcessRunnerOpts struct {
Env []string
Args []string
ReadyCheck *ReadyChecker
LogPath string
ExportLogs bool
}

// NewProcessRunner creates a new ProcessRunner.
Expand All @@ -58,6 +63,7 @@ func NewProcessRunner(ctx context.Context, opts NewProcessRunnerOpts) ProcessRun
// using exec.CommandContext to allow for cancellation from caller
cmd := exec.CommandContext(ctx, opts.BinPath, opts.Args...)
cmd.Env = opts.Env
logHandler := NewLogHandler(opts.LogPath, opts.ExportLogs)
return &processRunner{
ctx: ctx,
cmd: cmd,
Expand All @@ -67,6 +73,7 @@ func NewProcessRunner(ctx context.Context, opts NewProcessRunnerOpts) ProcessRun
opts: opts,
env: opts.Env,
readyChecker: opts.ReadyCheck,
logHandler: logHandler,
}
}

Expand Down Expand Up @@ -175,7 +182,7 @@ func (pr *processRunner) Start(ctx context.Context, depStarted <-chan bool) erro
} else {
exitStatusMessage := fmt.Sprintf("%s process exited cleanly", pr.title)
outputStatusMessage := fmt.Sprintf("[black:white][astria-go] %s[-:-]", exitStatusMessage)
log.Infof(exitStatusMessage)
log.Info(exitStatusMessage)
_, err := pr.outputBuf.WriteString(outputStatusMessage)
if err != nil {
return
Expand All @@ -188,6 +195,9 @@ func (pr *processRunner) Start(ctx context.Context, depStarted <-chan bool) erro

// Stop stops the process.
func (pr *processRunner) Stop() {
if err := pr.logHandler.Close(); err != nil {
log.WithError(err).Errorf("Error closing log file for process %s", pr.title)
}
// send SIGINT to the process
if err := pr.cmd.Process.Signal(syscall.SIGINT); err != nil {
log.WithError(err).Errorf("Error sending SIGINT for process %s", pr.title)
Expand Down Expand Up @@ -224,3 +234,22 @@ func (pr *processRunner) GetInfo() string {
func (pr *processRunner) GetEnvironment() []string {
return pr.env
}

// CanWriteToLog returns whether the service terminal outputs can be written to
// a log file. If CanWriteToLog() returns false, the data will not be written to
// a log file. If CanWriteToLog() returns true, a log file exists and the data
// can be written to the log file when the WriteToLog() function is called.
func (pr *processRunner) CanWriteToLog() bool {
return pr.logHandler.Writeable()
}

// WriteToLog writes the data to the log file managed by the LogHandler within
// the ProcessRunner.
func (pr *processRunner) WriteToLog(data string) error {
err := pr.logHandler.Write(data)
if err != nil {
return err
}

return nil
}
9 changes: 9 additions & 0 deletions modules/cli/internal/testutils/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,12 @@ func (m *MockProcessRunner) GetInfo() string {
args := m.Called()
return args.String(0)
}

func (m *MockProcessRunner) CanWriteToLog() bool {
return false
}

func (m *MockProcessRunner) WriteToLog(data string) error {
args := m.Called(data)
return args.Error(0)
}
8 changes: 8 additions & 0 deletions modules/cli/internal/ui/processpane.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ func (pp *ProcessPane) StartScan() {

// new, unprocessed data.
pp.tApp.QueueUpdateDraw(func() {
// write output data to logs if possible
if pp.pr.CanWriteToLog() {
err := pp.pr.WriteToLog(currentOutput)
if err != nil {
log.WithError(err).Error("Error writing to log")
}
}
// write output data to ui element
_, err := pp.ansiWriter.Write([]byte(currentOutput))
if err != nil {
log.WithError(err).Error("Error writing to textView")
Expand Down

0 comments on commit d0660ca

Please sign in to comment.