Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 2 additions & 21 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,30 +105,11 @@ Boy that's a hard word to spell. Anyway, lazygit is translated into several lang

The easiest way to debug lazygit is to have two terminal tabs open at once: one for running lazygit (via `go run main.go -debug` in the project root) and one for viewing lazygit's logs (which can be done via `go run main.go --logs` or just `lazygit --logs`).

From most places in the codebase you have access to a logger e.g. `gui.Log.Warn("blah")`.
From most places in the codebase you have access to a logger e.g. `gui.Log.Warn("blah")` or `self.c.Log.Warn("blah")`.

If you find that the existing logs are too noisy, you can set the log level with e.g. `LOG_LEVEL=warn go run main.go -debug` and then only use `Warn` logs yourself.

If you need to log from code in the vendor directory (e.g. the `gocui` package), you won't have access to the logger, but you can easily add logging support by adding the following:

```go
func newLogger() *logrus.Entry {
// REPLACE THE BELOW PATH WITH YOUR ACTUAL LOG PATH (YOU'LL SEE THIS PRINTED WHEN YOU RUN `lazygit --logs`
logPath := "/Users/jesseduffield/Library/Application Support/jesseduffield/lazygit/development.log"
file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
panic("unable to log to file")
}
logger := logrus.New()
logger.SetLevel(logrus.WarnLevel)
logger.SetOutput(file)
return logger.WithFields(logrus.Fields{})
}

var Log = newLogger()
...
Log.Warn("blah")
```
If you need to log from code in the vendor directory (e.g. the `gocui` package), you won't have access to the logger, but you can easily add logging support by setting the `LAZYGIT_LOG_PATH` environment variable and using `logs.Global.Warn("blah")`. This is a global logger that's only intended for development purposes.

If you keep having to do some setup steps to reproduce an issue, read the Testing section below to see how to create an integration test by recording a lazygit session. It's pretty easy!

Expand Down
14 changes: 14 additions & 0 deletions pkg/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/go-errors/errors"
"github.com/sirupsen/logrus"

"github.com/jesseduffield/generics/slices"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
Expand All @@ -22,6 +23,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/gui"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/logs"
"github.com/jesseduffield/lazygit/pkg/updates"
)

Expand Down Expand Up @@ -76,6 +78,18 @@ func NewCommon(config config.AppConfigurer) (*common.Common, error) {
}, nil
}

func newLogger(cfg config.AppConfigurer) *logrus.Entry {
if cfg.GetDebug() {
logPath, err := config.LogPath()
if err != nil {
log.Fatal(err)
}
return logs.NewDevelopmentLogger(logPath)
} else {
return logs.NewProductionLogger()
}
}

// NewApp bootstrap a new application
func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
app := &App{
Expand Down
9 changes: 7 additions & 2 deletions pkg/app/entry_point.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env"
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
"github.com/jesseduffield/lazygit/pkg/logs"
"github.com/jesseduffield/lazygit/pkg/logs/tail"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
Expand Down Expand Up @@ -106,7 +106,12 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
}

if cliArgs.TailLogs {
logs.TailLogs()
logPath, err := config.LogPath()
if err != nil {
log.Fatal(err.Error())
}

tail.TailLogs(logPath)
os.Exit(0)
}

Expand Down
56 changes: 0 additions & 56 deletions pkg/app/logging.go

This file was deleted.

4 changes: 4 additions & 0 deletions pkg/config/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,5 +301,9 @@ func getDefaultAppState() *AppState {
}

func LogPath() (string, error) {
if os.Getenv("LAZYGIT_LOG_PATH") != "" {
return os.Getenv("LAZYGIT_LOG_PATH"), nil
}

return configFilePath("development.log")
}
66 changes: 48 additions & 18 deletions pkg/logs/logs.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,64 @@
package logs

import (
"fmt"
"io"
"log"
"os"

"github.com/aybabtme/humanlog"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/sirupsen/logrus"
)

// TailLogs lets us run `lazygit --logs` to print the logs produced by other lazygit processes.
// This makes for easier debugging.
func TailLogs() {
logFilePath, err := config.LogPath()
if err != nil {
log.Fatal(err)
// It's important that this package does not depend on any other package because we
// may want to import it from anywhere, and we don't want to create a circular dependency
// (because Go refuses to compile circular dependencies).

// Global is a global logger that can be used anywhere in the app, for
// _development purposes only_. I want to avoid global variables when possible,
// so if you want to log something that's printed when the -debug flag is set,
// you'll need to ensure the struct you're working with has a logger field (
// and most of them do).
// Global is only available if the LAZYGIT_LOG_PATH environment variable is set.
var Global *logrus.Entry

func init() {
logPath := os.Getenv("LAZYGIT_LOG_PATH")
if logPath != "" {
Global = NewDevelopmentLogger(logPath)
}
}

fmt.Printf("Tailing log file %s\n\n", logFilePath)
func NewProductionLogger() *logrus.Entry {
logger := logrus.New()
logger.Out = io.Discard
logger.SetLevel(logrus.ErrorLevel)
return formatted(logger)
}

opts := humanlog.DefaultOptions
opts.Truncates = false
func NewDevelopmentLogger(logPath string) *logrus.Entry {
logger := logrus.New()
logger.SetLevel(getLogLevel())

_, err = os.Stat(logFilePath)
file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
if err != nil {
if os.IsNotExist(err) {
log.Fatal("Log file does not exist. Run `lazygit --debug` first to create the log file")
}
log.Fatal(err)
log.Fatalf("Unable to log to log file: %v", err)
}
logger.SetOutput(file)
return formatted(logger)
}

TailLogsForPlatform(logFilePath, opts)
func formatted(log *logrus.Logger) *logrus.Entry {
// highly recommended: tail -f development.log | humanlog
// https://github.com/aybabtme/humanlog
log.Formatter = &logrus.JSONFormatter{}

return log.WithFields(logrus.Fields{})
}

func getLogLevel() logrus.Level {
strLevel := os.Getenv("LOG_LEVEL")
level, err := logrus.ParseLevel(strLevel)
if err != nil {
return logrus.DebugLevel
}
return level
}
4 changes: 2 additions & 2 deletions pkg/logs/logs_default.go → pkg/logs/tail/logs_default.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//go:build !windows
// +build !windows

package logs
package tail

import (
"log"
Expand All @@ -11,7 +11,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/secureexec"
)

func TailLogsForPlatform(logFilePath string, opts *humanlog.HandlerOptions) {
func tailLogsForPlatform(logFilePath string, opts *humanlog.HandlerOptions) {
cmd := secureexec.Command("tail", "-f", logFilePath)

stdout, _ := cmd.StdoutPipe()
Expand Down
12 changes: 6 additions & 6 deletions pkg/logs/logs_windows.go → pkg/logs/tail/logs_windows.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//go:build windows
// +build windows

package logs
package tail

import (
"bufio"
Expand All @@ -13,7 +13,7 @@ import (
"github.com/aybabtme/humanlog"
)

func TailLogsForPlatform(logFilePath string, opts *humanlog.HandlerOptions) {
func tailLogsForPlatform(logFilePath string, opts *humanlog.HandlerOptions) {
var lastModified int64 = 0
var lastOffset int64 = 0
for {
Expand All @@ -22,7 +22,7 @@ func TailLogsForPlatform(logFilePath string, opts *humanlog.HandlerOptions) {
log.Fatal(err)
}
if stat.ModTime().Unix() > lastModified {
err = TailFrom(lastOffset, logFilePath, opts)
err = tailFrom(lastOffset, logFilePath, opts)
if err != nil {
log.Fatal(err)
}
Expand All @@ -32,7 +32,7 @@ func TailLogsForPlatform(logFilePath string, opts *humanlog.HandlerOptions) {
}
}

func OpenAndSeek(filepath string, offset int64) (*os.File, error) {
func openAndSeek(filepath string, offset int64) (*os.File, error) {
file, err := os.Open(filepath)
if err != nil {
return nil, err
Expand All @@ -46,8 +46,8 @@ func OpenAndSeek(filepath string, offset int64) (*os.File, error) {
return file, nil
}

func TailFrom(lastOffset int64, logFilePath string, opts *humanlog.HandlerOptions) error {
file, err := OpenAndSeek(logFilePath, lastOffset)
func tailFrom(lastOffset int64, logFilePath string, opts *humanlog.HandlerOptions) error {
file, err := openAndSeek(logFilePath, lastOffset)
if err != nil {
return err
}
Expand Down
28 changes: 28 additions & 0 deletions pkg/logs/tail/tail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package tail

import (
"fmt"
"log"
"os"

"github.com/aybabtme/humanlog"
)

// TailLogs lets us run `lazygit --logs` to print the logs produced by other lazygit processes.
// This makes for easier debugging.
func TailLogs(logFilePath string) {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved this into its own package to ensure we have as few dependencies as possible in the logs package

fmt.Printf("Tailing log file %s\n\n", logFilePath)

opts := humanlog.DefaultOptions
opts.Truncates = false

_, err := os.Stat(logFilePath)
if err != nil {
if os.IsNotExist(err) {
log.Fatal("Log file does not exist. Run `lazygit --debug` first to create the log file")
}
log.Fatal(err)
}

tailLogsForPlatform(logFilePath, opts)
}