pretty

package module
v0.0.0-...-8a597d4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 11, 2026 License: MIT Imports: 12 Imported by: 5

README

go-pretty

Pretty printing for complex Go types

Overview

go-pretty provides compact, single-line pretty printing of Go values suitable for logging and debugging. It handles circular references, implements smart truncation, and offers customizable formatting options.

Features

  • Single-line output: All values formatted on one line for easy log parsing
  • Circular reference detection: Automatically detects and handles circular data structures
  • Smart string handling: Byte slices containing valid UTF-8 are printed as strings
  • Truncation support: Configurable limits for strings, errors, and slices
  • Custom formatting: Implement Printable interface for custom types
  • Null handling: Support for nullable types via Nullable interface
  • Sorted maps: Map keys are automatically sorted for consistent output
  • Context awareness: Special handling for context.Context types

Installation

go get github.com/domonda/go-pretty

Usage

Basic Printing
import "github.com/domonda/go-pretty"

// Print to stdout
pretty.Println(myStruct)
pretty.Print(myValue)

// Print to string
s := pretty.Sprint(myValue)

// Print to io.Writer
pretty.Fprint(writer, myValue)
pretty.Fprintln(writer, myValue)
Indented Output

All print functions accept optional indent arguments:

  • No arguments: prints on a single line without indentation
  • One argument: uses indent[0] as indent string for nested structures
  • Two+ arguments: uses indent[0] as indent string and indent[1:] concatenated as line prefix
// Single line output (no indentation)
pretty.Println(myStruct)

// Multi-line with 2-space indentation
pretty.Println(myStruct, "  ")

// Multi-line with 2-space indentation and line prefix ">>> "
pretty.Println(myStruct, "  ", ">>> ")

// Multi-line with line prefix concatenated from multiple strings
pretty.Println(myStruct, "  ", "[", "LOG", "] ")  // Line prefix: "[LOG] "
Custom Printer Configuration
printer := pretty.Printer{
    MaxStringLength: 200,  // Truncate strings longer than 200 chars
    MaxErrorLength:  2000, // Truncate errors longer than 2000 chars
    MaxSliceLength:  20,   // Truncate slices with more than 20 elements
}

printer.Println(myValue)
s := printer.Sprint(myValue)
JSON Output
// Print as indented JSON
pretty.PrintAsJSON(myStruct)

// Custom indent
pretty.PrintAsJSON(myStruct, "    ")
Custom Type Formatting

Implement the Printable interface for custom formatting:

type MyType struct {
    field string
}

func (m MyType) PrettyPrint(w io.Writer) {
    fmt.Fprintf(w, "MyType{%s}", m.field)
}
Nullable Types

Implement the Nullable interface to print "null" for zero values:

type MyNullable struct {
    value *string
}

func (m MyNullable) IsNull() bool {
    return m.value == nil
}
Advanced: Custom Formatting with AsPrintable

The Printer.AsPrintable field allows you to customize how values are printed based on their reflect.Value. This is useful when you want to:

  • Add custom formatting for types you don't control
  • Adapt types that implement different interfaces (e.g., fmt.Stringer, custom serializers)
  • Change formatting based on runtime conditions
  • Wrap values with additional context
Example: Adapting Other Interfaces

You can use AsPrintable to enable pretty printing for types that implement other interfaces:

import (
    "fmt"
    "io"
    "reflect"
    "github.com/domonda/go-pretty"
)

// Assume you have types implementing fmt.Stringer or custom interfaces
type CustomStringer struct {
    Name string
}

func (c CustomStringer) String() string {
    return fmt.Sprintf("Custom<%s>", c.Name)
}

// Wrapper that adapts any interface to pretty.Printable
type printableAdapter struct {
    format func(io.Writer)
}

func (p printableAdapter) PrettyPrint(w io.Writer) {
    p.format(w)
}

// Create a printer that handles fmt.Stringer types
printer := &pretty.Printer{
    AsPrintable: func(v reflect.Value) (pretty.Printable, bool) {
        stringer, ok := v.Interface().(fmt.Stringer)
        if !ok && v.CanAddr() {
            stringer, ok = v.Addr().Interface().(fmt.Stringer)
        }
        if ok {
            return printableAdapter{
                format: func(w io.Writer) {
                    fmt.Fprint(w, stringer.String())
                },
            }, true
        }
        return pretty.AsPrintable(v) // Use default
    },
}

printer.Println(CustomStringer{Name: "test"})
// Output: Custom<test>
Example: Runtime Conditional Formatting
// Mask sensitive data based on type or field tags
printer := &pretty.Printer{
    AsPrintable: func(v reflect.Value) (pretty.Printable, bool) {
        // Customize based on type name
        if v.Kind() == reflect.String && v.String() == "a sensitive string" {
            return printableAdapter{
                format: func(w io.Writer) {
                    fmt.Fprint(w, "`***REDACTED***`")
                },
            }, true
        }
        return pretty.AsPrintable(v) // Use default
    },
}

printer.Println("a sensitive string")
// Output: `***REDACTED***`

Note: If Printer.AsPrintable is not set, the package-level AsPrintable function is used, which checks if the value implements the Printable interface.

Integration with go-errs

The go-errs package uses a configurable Printer variable (of type *pretty.Printer) for formatting function parameters in error call stacks. You can customize this printer to mask secrets, adapt types, or change formatting without implementing the Printable interface on your types.

Use cases:

  • Hide sensitive data (secrets, passwords, tokens) in error messages and stack traces
  • Customize call stack formatting in error output
  • Mask PII (Personally Identifiable Information) in logs
  • Adapt types that implement other interfaces globally
import (
    "fmt"
    "io"
    "reflect"
    "strings"
    "github.com/domonda/go-errs"
    "github.com/domonda/go-pretty"
)

// Wrapper that adapts any interface to pretty.Printable
type printableAdapter struct {
    format func(io.Writer)
}

func (p printableAdapter) PrettyPrint(w io.Writer) {
    p.format(w)
}

func init() {
    // Configure the Printer used by go-errs for error call stacks
    errs.Printer.AsPrintable = func(v reflect.Value) (pretty.Printable, bool) {
        // Mask sensitive strings
        if v.Kind() == reflect.String {
            str := v.String()
            // Check for common secret patterns
            if strings.Contains(str, "password") ||
               strings.Contains(str, "token") ||
               strings.Contains(str, "secret") {
                return printableAdapter{
                    format: func(w io.Writer) {
                        fmt.Fprint(w, "`***REDACTED***`")
                    },
                }, true
            }
        }

        // Hide sensitive struct fields
        if v.Kind() == reflect.Struct {
            t := v.Type()
            for i := 0; i < t.NumField(); i++ {
                field := t.Field(i)
                // Check for "secret" tag
                if field.Tag.Get("secret") == "true" {
                    // Return custom formatter that masks this field
                    // (implementation would format all fields except sensitive ones)
                }
            }
        }

        return pretty.AsPrintable(v) // Use default
    }
}

// Now all error stack traces from go-errs will automatically mask secrets
// without needing to implement Printable on your types

This approach allows you to:

  1. Centrally control how all values are formatted in error call stacks
  2. Protect sensitive data in logs, error traces, and debug output
  3. Customize error formatting without modifying go-errs code
  4. Apply formatting rules to types you don't control

Note: The errs.Printer variable is a *pretty.Printer that can be fully configured with custom settings like MaxStringLength, MaxErrorLength, MaxSliceLength, and AsPrintable.

Output Examples

// Strings are backtick-quoted
pretty.Sprint("hello")  // `hello`

// Structs show field names
type Person struct { Name string; Age int }
pretty.Sprint(Person{"Alice", 30})  // Person{Name:`Alice`;Age:30}

// Slices and arrays
pretty.Sprint([]int{1, 2, 3})  // [1,2,3]

// Maps with sorted keys
pretty.Sprint(map[string]int{"b": 2, "a": 1})  // map[string]int{`a`:1;`b`:2}

// Circular references
type Node struct { Next *Node }
n := &Node{}
n.Next = n
pretty.Sprint(n)  // Node{Next:CIRCULAR_REF}

// Byte slices as strings
pretty.Sprint([]byte("hello"))  // `hello`

// Time and Duration
pretty.Sprint(time.Now())  // Time(`2024-01-15T10:30:00Z`)
pretty.Sprint(5*time.Second)  // Duration(`5s`)

// Nil values
pretty.Sprint((*int)(nil))  // nil
pretty.Sprint(error(nil))  // nil

// Context
ctx := context.Background()
pretty.Sprint(ctx)  // Context{}

Configuration

The default printer used by package-level functions:

var DefaultPrinter = Printer{
    MaxStringLength: 200,
    MaxErrorLength:  2000,
    MaxSliceLength:  20,
}

Set to 0 or negative values to disable truncation.

License

See LICENSE file

Documentation

Overview

Package pretty offers print functions that format values of any Go type in a compact single line string suitable for logging and debugging. Strings are escaped to be single line with fmt.Sprintf("%#q", s). %#q is used instead of %q to minimize the number of double quotes that would have to be escaped in JSON logs.

MaxStringLength, MaxErrorLength, MaxSliceLength can be set to values greater zero to prevent excessive log sizes. An ellipsis rune is used as last element to represent the truncated elements.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// DefaultPrinter is used by the package level print functions
	DefaultPrinter = Printer{
		MaxStringLength: 200,
		MaxErrorLength:  2000,
		MaxSliceLength:  20,
	}

	// CircularRef is a replacement token (default "CIRCULAR_REF")
	// that will be printed instad of a circular data reference.
	CircularRef = "CIRCULAR_REF"
)

Functions

func Fprint

func Fprint(w io.Writer, value any, indent ...string)

Fprint pretty prints a value to a io.Writer. The optional indent parameter controls indentation:

  • No arguments: prints on a single line without indentation
  • One argument: uses indent[0] as indent string for nested structures
  • Two+ arguments: uses indent[0] as indent string and indent[1:] concatenated as line prefix

func Fprintln

func Fprintln(w io.Writer, value any, indent ...string)

Fprintln pretty prints a value to a io.Writer followed by a newline. The optional indent parameter controls indentation:

  • No arguments: prints on a single line without indentation
  • One argument: uses indent[0] as indent string for nested structures
  • Two+ arguments: uses indent[0] as indent string and indent[1:] concatenated as line prefix

func Indent

func Indent(source []byte, indent string, linePrefix ...string) []byte

Indent pretty printed source using the passed indent string and an optional linePrefix used for every line in case of a multiple line result. Multiple linePrefix values are concatenated into a single string.

func Print

func Print(value any, indent ...string)

Print pretty prints a value to os.Stdout. The optional indent parameter controls indentation:

  • No arguments: prints on a single line without indentation
  • One argument: uses indent[0] as indent string for nested structures
  • Two+ arguments: uses indent[0] as indent string and indent[1:] concatenated as line prefix

func PrintAsJSON

func PrintAsJSON(input any, indent ...string)

PrintAsJSON marshalles input as indented JSON and calles fmt.Println with the result. If indent arguments are given, they are joined into a string and used as JSON line indent. If no indet argument is given, two spaces will be used to indent JSON lines. A byte slice as input will be marshalled as json.RawMessage.

func Println

func Println(value any, indent ...string)

Println pretty prints a value to os.Stdout followed by a newline. The optional indent parameter controls indentation:

  • No arguments: prints on a single line without indentation
  • One argument: uses indent[0] as indent string for nested structures
  • Two+ arguments: uses indent[0] as indent string and indent[1:] concatenated as line prefix
Example
type Parent struct {
	Map map[int]string
}

type Struct struct {
	Parent
	Int        int
	unexported bool
	Str        string
	Sub        struct {
		Map map[string]string
	}
}

value := &Struct{
	Sub: struct{ Map map[string]string }{
		Map: map[string]string{
			"key": "value",
			// Note that the resulting `Multi\nLine` is not a valid Go string.
			// Double quotes are avoided for better readability of
			// pretty printed strings in JSON.
			"Multi\nLine": "true",
		},
	},
}

Println(value)
Println(value, "  ")
Println(value, "  ", "    ")
Output:

Struct{Parent{Map:nil};Int:0;Str:``;Sub:{Map:{`Multi\nLine`:`true`;`key`:`value`}}}
Struct{
  Parent{
    Map: nil
  }
  Int: 0
  Str: ``
  Sub: {
    Map: {
      `Multi\nLine`: `true`
      `key`: `value`
    }
  }
}
    Struct{
      Parent{
        Map: nil
      }
      Int: 0
      Str: ``
      Sub: {
        Map: {
          `Multi\nLine`: `true`
          `key`: `value`
        }
      }
    }

func Sprint

func Sprint(value any, indent ...string) string

Sprint pretty prints a value to a string. The optional indent parameter controls indentation:

  • No arguments: prints on a single line without indentation
  • One argument: uses indent[0] as indent string for nested structures
  • Two+ arguments: uses indent[0] as indent string and indent[1:] concatenated as line prefix

Types

type Nullable

type Nullable interface {
	// IsNull returns true if the implementing value is considered null.
	IsNull() bool
}

Nullable can be implemented to print "null" instead of the representation of the underlying type's value.

type Printable

type Printable interface {
	// PrettyPrint the implementation's data
	PrettyPrint(io.Writer)
}

Printable can be implemented to customize the pretty printing of a type.

func AsPrintable

func AsPrintable(v reflect.Value) (p Printable, ok bool)

AsPrintable returns a Printable and true if the reflect.Value implements the Printable interface.

type Printer

type Printer struct {
	// MaxStringLength is the maximum length for escaped strings.
	// Longer strings will be truncated with an ellipsis rune at the end.
	// A value <= 0 will disable truncating.
	MaxStringLength int

	// MaxErrorLength is the maximum length for escaped errors.
	// Longer errors will be truncated with an ellipsis rune at the end.
	// A value <= 0 will disable truncating.
	MaxErrorLength int

	// MaxSliceLength is the maximum length for slices.
	// Longer slices will be truncated with an ellipsis rune as last element.
	// A value <= 0 will disable truncating.
	MaxSliceLength int

	// AsPrintable can be used to customize the printing of a
	// value. If the function returns true, the Printable will be used
	// to print the value. If the function returns false, the default
	// pretty printing format will be used.
	// If not set, the package level AsPrintable function will be used instead.
	AsPrintable func(v reflect.Value) (p Printable, ok bool)
}

Printer holds a pretty-print configuration

func (*Printer) Fprint

func (p *Printer) Fprint(w io.Writer, value any, indent ...string)

Fprint pretty prints a value to a io.Writer. The optional indent parameter controls indentation:

  • No arguments: prints on a single line without indentation
  • One argument: uses indent[0] as indent string for nested structures
  • Two+ arguments: uses indent[0] as indent string and indent[1:] concatenated as line prefix

func (*Printer) Fprintln

func (p *Printer) Fprintln(w io.Writer, value any, indent ...string)

Fprintln pretty prints a value to a io.Writer followed by a newline. The optional indent parameter controls indentation:

  • No arguments: prints on a single line without indentation
  • One argument: uses indent[0] as indent string for nested structures
  • Two+ arguments: uses indent[0] as indent string and indent[1:] concatenated as line prefix

func (*Printer) Print

func (p *Printer) Print(value any, indent ...string)

Print pretty prints a value to os.Stdout. The optional indent parameter controls indentation:

  • No arguments: prints on a single line without indentation
  • One argument: uses indent[0] as indent string for nested structures
  • Two+ arguments: uses indent[0] as indent string and indent[1:] concatenated as line prefix

func (*Printer) Println

func (p *Printer) Println(value any, indent ...string)

Println pretty prints a value to os.Stdout followed by a newline. The optional indent parameter controls indentation:

  • No arguments: prints on a single line without indentation
  • One argument: uses indent[0] as indent string for nested structures
  • Two+ arguments: uses indent[0] as indent string and indent[1:] concatenated as line prefix

func (*Printer) Sprint

func (p *Printer) Sprint(value any, indent ...string) string

Sprint pretty prints a value to a string. The optional indent parameter controls indentation:

  • No arguments: prints on a single line without indentation
  • One argument: uses indent[0] as indent string for nested structures
  • Two+ arguments: uses indent[0] as indent string and indent[1:] concatenated as line prefix

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL