Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Spinner

Display animated spinners during long-running operations:

err := clog.Spinner("Downloading").
  Str("url", fileURL).
  Wait(ctx, func(ctx context.Context) error {
    return download(ctx, fileURL)
  }).
  Msg("Downloaded")

The spinner animates with moon phase emojis (πŸŒ”πŸŒ“πŸŒ’πŸŒ‘πŸŒ˜πŸŒ—πŸŒ–πŸŒ•) while the action runs, then logs the result. This is the DefaultSpinnerStyle, which is used when no custom Style is set.

Spinner demo

Dynamic Status Updates

Use Progress to update the spinner message and fields during execution:

err := clog.Spinner("Processing").
  Progress(ctx, func(ctx context.Context, update *clog.ProgressUpdate) error {
    for i, item := range items {
      update.Msg("Processing").Str("progress", fmt.Sprintf("%d/%d", i+1, len(items))).Send()
      if err := process(ctx, item); err != nil {
        return err
      }
    }
    return nil
  }).
  Msg("Processed all items")

WaitResult Finalisers

MethodSuccess behaviourFailure behaviour
.Msg(s)Logs at INF with messageLogs at ERR with error string
.Err()Logs at INF with spinner messageLogs at ERR with error string as msg
.Send()Logs at configured levelLogs at configured level
.Silent()Returns error, no loggingReturns error, no logging

.Err() is equivalent to calling .Send() with default settings (no OnSuccess/OnError overrides).

All finalisers return the error from the action. You can chain any field method (.Str(), .Int(), .Bool(), .Duration(), etc.) and .Prefix() on a WaitResult before finalising.

Custom Success/Error Behaviour

Use OnSuccessLevel, OnSuccessMessage, OnErrorLevel, and OnErrorMessage to customise how the result is logged, then call .Send():

// Fatal on error instead of the default error level
err := clog.Spinner("Connecting to database").
  Str("host", "db.internal").
  Wait(ctx, connectToDB).
  OnErrorLevel(clog.FatalLevel).
  Send()

When OnErrorMessage is set, the custom message becomes the log message and the original error is included as an error= field. Without it, the error string is used directly as the message with no extra field.

Custom Spinner Style

clog.Spinner("Loading").
  Style(clog.SpinnerDot).
  Wait(ctx, action).
  Msg("Done")

See progress_spinner_presets.go for the full list of available spinner types.

Spinner styles 1 Spinner styles 2 Spinner styles 3 Spinner styles 4 Spinner styles 5 Spinner styles 6 Spinner styles 7 Spinner styles 8 Spinner styles 9

The AnimationBuilder supports the same clickable hyperlink field methods as events:

clog.Spinner("Building").
  Path("dir", "src/").
  Line("config", "config.yaml", 42).
  Column("loc", "main.go", 10, 5).
  URL("docs", "https://example.com").
  Link("help", "https://example.com", "docs").
  Wait(ctx, action).
  Msg("Built")

Elapsed Timer

Add a live elapsed-time field to any animation with .Elapsed(key):

err := clog.Spinner("Processing batch").
  Str("batch", "1/3").
  Elapsed("elapsed").
  Int("workers", 4).
  Wait(ctx, processBatch).
  Msg("Batch processed")
// INF βœ… Batch processed batch=1/3 elapsed=2s workers=4

The elapsed field respects its position relative to other field methods - it appears between batch and workers in the output above because .Elapsed("elapsed") was called between .Str() and .Int().

The display format uses SetElapsedPrecision (default 0 decimal places), rounds to SetElapsedRound (default 1s), hides values below SetElapsedMinimum (default 1s), and can be fully overridden with SetElapsedFormatFunc. Durations >= 1m use composite format (e.g. β€œ1m30s”, β€œ2h15m”).

Per-Event Parts Override

Override the part order for a spinner and its completion message without mutating the logger:

err := clog.Spinner("Indexing files").
  Parts(clog.PartPrefix, clog.PartMessage).
  Wait(ctx, indexFiles).
  Msg("Indexed")
// βœ… Indexed   (no level label or fields)

When set on the AnimationBuilder, the override applies to both the animation rendering and the default completion message. You can further override on the WaitResult if the completion needs different parts:

clog.Spinner("Syncing").
  Parts(clog.PartMessage).          // animation: message only
  Wait(ctx, sync).
  Parts(clog.PartLevel, clog.PartMessage).  // completion: add level back
  Msg("Synced")

Delayed Animation

Use .After(d) to suppress the animation for an initial duration. If the task finishes before the delay, no animation is shown at all - useful for operations that are usually fast but occasionally slow:

err := clog.Spinner("Fetching config").
  After(time.Second).
  Wait(ctx, fetchConfig).
  Msg("Config loaded")

If fetchConfig completes in under 1 second, the user sees nothing until the final β€œConfig loaded” message. If it takes longer, the spinner appears after 1 second.