Skip to content

Style pre-execute errors via logrus formatter#366

Merged
kindermax merged 1 commit into
masterfrom
default-errors-styled
Jun 14, 2026
Merged

Style pre-execute errors via logrus formatter#366
kindermax merged 1 commit into
masterfrom
default-errors-styled

Conversation

@kindermax

@kindermax kindermax commented Jun 14, 2026

Copy link
Copy Markdown
Collaborator

Summary by Sourcery

Style error-level log output using theme-aware lipgloss formatting when running in a TTY, while preserving existing behavior otherwise.

New Features:

  • Add themed, terminal-aware styling for error-level log messages using lipgloss when a color scheme is provided.

Enhancements:

  • Extend the log formatter to support optional error styling based on the active color scheme and terminal width.
  • Wire theme selection into logging initialization so CLI errors respect the configured application theme.

Tests:

  • Update logging tests to account for the new InitLogging signature and to disable styling by passing a nil color scheme.

@sourcery-ai

sourcery-ai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Introduces a terminal-aware logrus formatter that uses lipgloss and a theme-based color scheme to render error-level logs with styled headers and wrapped text, and threads the theme color scheme into logging initialization and CLI startup while keeping non-TTY and tests behavior unchanged.

Sequence diagram for terminal-aware styled error logging

sequenceDiagram
    actor User
    participant CliMain as Main
    participant Logging as InitLogging
    participant FormatterFactory as newFormatter
    participant Formatter

    User->>CliMain: Main
    CliMain->>Logging: InitLogging(os.Stdout, os.Stderr, ColorSchemeByName(theme))
    Logging->>FormatterFactory: newFormatter(errWriter, cs)
    FormatterFactory->>FormatterFactory: term.IsTerminal(file.Fd())
    alt is_terminal and cs_non_nil
        FormatterFactory->>FormatterFactory: lipgloss.HasDarkBackground(os.Stdin, file)
        FormatterFactory->>FormatterFactory: cs(lipgloss.LightDark(isDark))
        FormatterFactory->>Formatter: init errorStyles
    else not_terminal_or_no_scheme
        FormatterFactory-->>Logging: Formatter{errorStyles:nil}
    end
    FormatterFactory-->>Logging: Formatter
    Logging->>Logging: log.SetFormatter(formatter)

    User->>Formatter: Format(errorEntry)
    alt error_level_and_styling_enabled
        Formatter->>Formatter: formatStyledError(entry)
    else other_levels_or_no_styles
        Formatter->>Formatter: standard formatting
    end
Loading

File-Level Changes

Change Details Files
Add terminal-aware, lipgloss-based styling for error-level log entries in the custom logrus formatter.
  • Extend Formatter to hold optional errorStyles for styled error output
  • Add newFormatter constructor that detects TTY errWriter, picks light/dark scheme, clamps terminal width, and builds lipgloss styles from the provided color scheme
  • Update Format to delegate error-level entries to a new styled error renderer when errorStyles is configured
  • Implement formatStyledError to render a bold "ERROR" header and capitalized message body using lipgloss styles
  • Add capitalizeFirst helper to uppercase the first rune of error messages safely
internal/logging/formatter.go
Wire the color scheme into logging initialization and CLI startup so that error styling is enabled only when a scheme is provided and errWriter is a terminal.
  • Change InitLogging signature to accept a fang.ColorSchemeFunc and document behavior
  • Use newFormatter(errWriter, cs) instead of instantiating Formatter directly
  • Pass theme.ColorSchemeByName(appSettings.Theme) from CLI main into InitLogging
internal/logging/log.go
internal/cli/cli.go
Adjust logging tests to work with the new InitLogging signature while keeping existing expectations on stdout/stderr behavior.
  • Update TestLoggingToStd to call InitLogging with a nil color scheme parameter
internal/logging/log_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@kindermax

Copy link
Copy Markdown
Collaborator Author

fixes #361

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey - I've found 2 issues, and left some high level feedback:

  • Consider whether Format should also use the styled error path for FatalLevel and PanicLevel, not just ErrorLevel, so all terminal-facing failures are rendered consistently.
  • In formatStyledError, you unconditionally capitalizeFirst and append a period, which may lead to odd output for messages that already start with an uppercase letter or end with punctuation; consider only adding punctuation when missing and avoiding extra allocation when the message is already correctly formatted.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider whether `Format` should also use the styled error path for `FatalLevel` and `PanicLevel`, not just `ErrorLevel`, so all terminal-facing failures are rendered consistently.
- In `formatStyledError`, you unconditionally `capitalizeFirst` and append a period, which may lead to odd output for messages that already start with an uppercase letter or end with punctuation; consider only adding punctuation when missing and avoiding extra allocation when the message is already correctly formatted.

## Individual Comments

### Comment 1
<location path="internal/logging/formatter.go" line_range="50" />
<code_context>
+	isDark := lipgloss.HasDarkBackground(os.Stdin, file)
+	scheme := cs(lipgloss.LightDark(isDark))
+
+	w, _, err := term.GetSize(file.Fd())
+	if err != nil || w == 0 {
+		w = 160
</code_context>
<issue_to_address>
**issue:** Handle very narrow terminal widths to avoid negative or zero lipgloss width.

For very small widths (e.g. `w == 1`), `Width(w - 2)` may be 0 or negative, which can cause broken or undefined lipgloss rendering. Clamp `w` to a sane minimum (e.g. `w = max(20, min(w, 160))`) or otherwise ensure the value passed to `Width` is always >= 1.
</issue_to_address>

### Comment 2
<location path="internal/logging/formatter.go" line_range="96-101" />
<code_context>
 	return buff.Bytes(), nil
 }

+func (f *Formatter) formatStyledError(entry *log.Entry) []byte {
+	var buf bytes.Buffer
+	buf.WriteString(f.errorStyles.header.String())
+	buf.WriteString("\n")
+	buf.WriteString(f.errorStyles.text.Render(capitalizeFirst(entry.Message) + "."))
+	buf.WriteString("\n\n")
+	return buf.Bytes()
+}
</code_context>
<issue_to_address>
**issue (bug_risk):** Styled error formatting drops all structured fields attached to the entry.

The non-styled formatter includes `entry.Data` via `writeData`, but this styled error formatter omits it and only prints the message. That means any `WithFields` metadata is lost when styling is enabled. Please also render `entry.Data` (e.g., in a secondary style) so structured context is preserved.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

isDark := lipgloss.HasDarkBackground(os.Stdin, file)
scheme := cs(lipgloss.LightDark(isDark))

w, _, err := term.GetSize(file.Fd())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue: Handle very narrow terminal widths to avoid negative or zero lipgloss width.

For very small widths (e.g. w == 1), Width(w - 2) may be 0 or negative, which can cause broken or undefined lipgloss rendering. Clamp w to a sane minimum (e.g. w = max(20, min(w, 160))) or otherwise ensure the value passed to Width is always >= 1.

Comment on lines +96 to +101
func (f *Formatter) formatStyledError(entry *log.Entry) []byte {
var buf bytes.Buffer
buf.WriteString(f.errorStyles.header.String())
buf.WriteString("\n")
buf.WriteString(f.errorStyles.text.Render(capitalizeFirst(entry.Message) + "."))
buf.WriteString("\n\n")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Styled error formatting drops all structured fields attached to the entry.

The non-styled formatter includes entry.Data via writeData, but this styled error formatter omits it and only prints the message. That means any WithFields metadata is lost when styling is enabled. Please also render entry.Data (e.g., in a secondary style) so structured context is preserved.

@kindermax kindermax merged commit 6e08011 into master Jun 14, 2026
5 checks passed
@kindermax kindermax deleted the default-errors-styled branch June 14, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant