# Architecture of lazygit's Logging and Debugging System: A Deep Dive

> Explore lazygit's dual-layer logging architecture. Understand its structured JSON diagnostics and human readable UI command logs, all managed by logrus.

- Repository: [Jesse Duffield/lazygit](https://github.com/jesseduffield/lazygit)
- Tags: architecture
- Published: 2026-03-02

---

**lazygit employs a dual-layer logging architecture built on logrus that separates structured JSON file diagnostics (via `NewDevelopmentLogger`) from human-readable UI command logs, with both systems accessible through a shared `*logrus.Entry` stored in the `Common` struct.**

The lazygit terminal UI client implements a sophisticated yet lightweight logging and debugging architecture designed to support both end-user troubleshooting and developer diagnostics. Built on top of the **logrus** structured logging library, the system orchestrates file-based debug logs, environment-driven configuration, and real-time log tailing through a centralized dependency injection pattern. Understanding this architecture is essential for contributors extending the codebase or debugging complex Git operations.

## High-Level Architecture Overview

The lazygit logging system operates through distinct architectural layers that handle CLI parsing, logger instantiation, and output distribution.

### Entry Point and Configuration

The logging lifecycle begins in [`pkg/app/entry_point.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/app/entry_point.go) (lines 95-98), where the application parses the `--debug` (`-d`) flag and checks for the legacy `DEBUG=TRUE` environment variable. When activated, this triggers the instantiation of a development logger rather than the production null logger.

### Logger Factory and Common Struct

The [`pkg/logs/logs.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/logs/logs.go) file serves as the central factory for logger instances. The `NewDevelopmentLogger` function creates a JSON-writing file logger, while `NewProductionLogger` returns a discarded logger for normal operation. The resulting `*logrus.Entry` is stored in `common.Common.Log` and injected throughout the application via the `newLogger()` function in [`pkg/app/app.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/app/app.go) (lines 82-91).

### UI Command Log vs File Logger

lazygit maintains two independent logging mechanisms. The **structured file logger** captures diagnostic JSON output for debugging purposes, while the **UI command log** displays human-readable action summaries in the Extras panel. The UI implementation in [`pkg/gui/command_log_panel.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/command_log_panel.go) uses `LogAction` and `LogCommand` for display-only output that never persists to disk.

## Core Components and Implementation Details

### Flag and Environment Variable Handling

In [`pkg/app/entry_point.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/app/entry_point.go), the application evaluates debug mode through both modern CLI flags and backward-compatible environment variables:

```go
flaggy.Bool(&debug, "d", "debug", "Run in debug mode with logging...")
if os.Getenv("DEBUG") == "TRUE" {
    debug = true
}

```

### Log File Resolution

The [`pkg/config/app_config.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/config/app_config.go) file (lines 713-718) defines the `LogPath()` function, which determines the log destination through environment variable or default path resolution:

```go
func LogPath() (string, error) {
    if os.Getenv("LAZYGIT_LOG_PATH") != "" {
        return os.Getenv("LAZYGIT_LOG_PATH"), nil
    }
    return stateFilePath("development.log")
}

```

### Development vs Production Loggers

The [`pkg/logs/logs.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/logs/logs.go) file contains the core logging logic. The `NewDevelopmentLogger` function configures logrus with a JSON formatter and file output:

```go
func NewDevelopmentLogger(logPath string) *logrus.Entry {
    logger := logrus.New()
    logger.SetLevel(getLogLevel())
    file, err := os.OpenFile(logPath,
        os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666)
    if err != nil { log.Fatalf("Unable to log to log file: %v", err) }
    logger.SetOutput(file)
    return formatted(logger) // Attaches JSONFormatter
}

```

The `getLogLevel()` function (lines 57-64) reads the `LOG_LEVEL` environment variable, defaulting to `DebugLevel` when parsing fails or the variable is unset.

### Wiring the Logger into the Application

In [`pkg/app/app.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/app/app.go) (lines 82-91), the `newLogger()` function instantiates the appropriate logger based on the debug configuration:

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

```

### Real-time Log Viewing

The [`pkg/logs/tail/tail.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/logs/tail/tail.go) file implements the `lazygit --logs` functionality. The `TailLogs` function streams the JSON log file and pretty-prints it using the **humanlog** library:

```go
func TailLogs(logFilePath string) {
    fmt.Printf("Tailing log file %s\n\n", logFilePath)
    tailLogsForPlatform(logFilePath, humanlog.DefaultOptions)
}

```

## Practical Usage Examples

### Enable Debug Logging with Custom Path

Run lazygit with debug mode and specify a custom log file location:

```bash
LAZYGIT_LOG_PATH=/tmp/lg-debug.log LOG_LEVEL=debug lazygit --debug

```

This writes structured JSON logs to `/tmp/lg-debug.log` with debug-level verbosity.

### View Logs in Real-Time

In a separate terminal, tail the log file with human-readable formatting:

```bash
lazygit --logs

```

Or view the raw JSON directly:

```bash
tail -f ~/.config/jesseduffield/lazygit/development.log

```

### Add Logging to Custom Components

When extending lazygit, access the logger through the `Common` struct:

```go
package myfeature

import "github.com/jesseduffield/lazygit/pkg/common"

func DoSomething(c *common.Common) {
    c.Log.Infof("myfeature.DoSomething started")
    // ... logic ...
    c.Log.Errorf("myfeature.DoSomething failed: %v", err)
}

```

## Summary

- **Debug activation** occurs via the `--debug` flag or `DEBUG=TRUE` environment variable, triggering `NewDevelopmentLogger` in [`pkg/logs/logs.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/logs/logs.go).
- **Log file location** defaults to `development.log` in the config directory, overrideable via `LAZYGIT_LOG_PATH` as resolved by `LogPath()` in [`pkg/config/app_config.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/config/app_config.go).
- **Log level control** uses the `LOG_LEVEL` environment variable, parsed by `getLogLevel()` to configure logrus thresholds.
- **Shared access** is provided through `common.Common.Log`, a `*logrus.Entry` injected into all subsystems via [`pkg/app/app.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/app/app.go) (lines 82-91).
- **Dual logging system** separates structured JSON diagnostics (file) from human-readable action summaries (UI Extras panel via [`pkg/gui/command_log_panel.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/command_log_panel.go)).
- **Log viewing** via `lazygit --logs` uses `TailLogs` in [`pkg/logs/tail/tail.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/logs/tail/tail.go) with humanlog formatting for readable output.

## Frequently Asked Questions

### How do I enable debug logging in lazygit?

Enable debug logging by running `lazygit --debug` or setting the environment variable `DEBUG=TRUE` before launching. This instantiates the development logger via `NewDevelopmentLogger` in [`pkg/logs/logs.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/logs/logs.go) and begins writing structured JSON logs to the configured log file path.

### Where are lazygit log files stored?

By default, lazygit stores log files in the user configuration directory at `~/.config/jesseduffield/lazygit/development.log`. Override this location by setting the `LAZYGIT_LOG_PATH` environment variable, which is checked by the `LogPath()` function in [`pkg/config/app_config.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/config/app_config.go) (lines 713-718).

### What is the difference between the file logger and the UI command log?

The **file logger** captures detailed, structured JSON output for debugging purposes and writes to a file via logrus in [`pkg/logs/logs.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/logs/logs.go). The **UI command log** displays simplified, human-readable action strings in the Extras panel using `LogAction` and `LogCommand` in [`pkg/gui/command_log_panel.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/gui/command_log_panel.go), and does not persist to disk or use the JSON formatter.

### How can I change the log level in lazygit?

Set the `LOG_LEVEL` environment variable to `debug`, `info`, `warn`, or `error` before starting lazygit. The `getLogLevel()` function in [`pkg/logs/logs.go`](https://github.com/jesseduffield/lazygit/blob/main/pkg/logs/logs.go) (lines 57-64) parses this variable and configures the logrus logger accordingly, defaulting to `DebugLevel` if the variable is unset or contains an invalid value.