unlog

package module
v0.0.10 Latest Latest
Warning

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

Go to latest
Published: Aug 15, 2025 License: MIT Imports: 20 Imported by: 2

README

Unlog - Unstructured Logging for Go log/slog

This package provides support for removing the structure from structured go logs. It uses Go templates to expand structured logs into text, and applies colorization using github.com/liamg/tml and line wrapping using github.com/manifoldco/ansiwrap.

It supports both short and long unstructuring templates present as comments in the code, and the tool cmd/unlog can be used to extract those templates, do linting across a source based, and manually unstructure input logs.

Installing the tool

go install bitbucket.org/fufoo/unlog/cmd/unlog@latest

Collecting a message catalog

For example, to collect messages across an entire repo

unlog collect -l -c messages.unl $(find . -name '*.go')

Using unstructurable errors

import (
    "bitbucket.org/fufoo/unlog/errors"
)

func DoIt(x int) error {
    if x < 0 {
        // FOO-1234 the value {{.x}} is negative, which
        // is not a supported configuration.
        //
        // Calling DoIt({{.x}}) is not supported; this functionality
        // only applies to non-negative values
        return errors.New("it doesn't work",
            slog.String("msgid", "FOO-1234"),
            slog.Int("x", x))
    }
}

Using a message catalog

From the command line

Using the command line tool:

unlog format -c messages.unl somelogs.json
Programatically

To programmatically make use of a message catalog, the most convenient approach is to use the new "embed" feature of Go to embed the message catalog into the executable, and then load it into an in-memory message catalog something like this:

import (
    _ "embed"
    "os"

    "bitbucket.org/fufoo/unlog"
)

//go:embed messages.unl
var ourCatalog []byte

func explain(err error) {
    cat := unlog.New()
    cat.Load(ourCatalog)
    wr := unlog.NewWriter(os.Stdout, unlog.WithCatalog(cat))
    wr.Explain(err)
}
Working with git

If you are committing your message catalog to the source repository using git, then add a .gitattributes file to mark it as binary:

*.unl binary

Usage with bazel

Since this is a bazel repo as well as a golang module, when consuming this functionality in bazel, instead of using gazelle (TBH I don't know how to use gazelle to load this module; if you figure it out, let me know; it seems that since this is also a golang module, either approach should work but I get an error about not being able to resolve @rules_go when using gazelle 🤷), add this to your MODULE.bazel:

bazel_dep( name = "fufoo_unlog", version = "0.0.1" )

git_override(
    module_name = "fufoo_unlog",
    remote = "[email protected]:fufoo/unlog.git",
    commit = "c7278db5c7b403f630d2873b7eb7f3d0c6f55f75",
)

then you can express a dependency in a BUILD file like so:

    deps = [
        "@fufoo_unlog//:unlog",
    ],

For embedded message catalogs, don't forget to include the embedsrcs in your build, e.g.:

load("@rules_go//go:def.bzl", "go_binary")

go_binary(
    name = "app",
    srcs = ["app.go"],
    visibility = ["//visibility:public"],
    deps = [
        "//:unlog",
    ],
    embedsrcs = ["messages.unl"],
)

Usage outside of bazel

For non-bazel users - i.e., basically everybody - we include the generated protobuf files.

Local development

To test local changes on another repo, first compile a local binary using bazel. From the root of this project, run:

bazel build //cmd/unlog

Then symlink this for ease of use:

mkdir bin
ln -s <your-path-to-project>/bazel-bin/cmd/unlog/unlog_/unlog bin/unlog

From here you can call the binary on other repos. From the root of this project:

./bin/unlog collect ../reka-streams/pkg/

Documentation

Index

Constants

Variables

This section is empty.

Functions

func FlattenAttrs

func FlattenAttrs(list []slog.Attr) map[string]any

FlattenAttrs takes a list of slog Attrs and turns it into a JSON-able map. Time and duration values are included as-is, because they are JSON marshallable.

func FlattenValue

func FlattenValue(v slog.Value) any

func Less added in v0.0.8

func Less(a, b string) bool

Less compares two message ids of the form XXX-NNNN, using lexicographic comparison for the first part and numeric comparison for the second part. This allows message ids with different numeric lengths to compare sensibly so that "FOO-905" < "FOO-1000"

Types

type Catalog

type Catalog struct {
	// contains filtered or unexported fields
}

A Catalog is a collection of message entries indexed by message id

func New

func New() *Catalog

New creates a new, empty catalog

func (*Catalog) Each added in v0.0.8

func (c *Catalog) Each(fn func(*Entry) error) error

Each iterates over the messages in the catalog, invoking the given function for each message. If the function returns an error, iteration stops and the error is returned. The messages are traversed in order of ID as determined by Less.

func (*Catalog) Get

func (c *Catalog) Get(msgid string) *Entry

Get returns the catalog entry with the given msgid, or nil if no there is no such entry.

func (*Catalog) Load

func (c *Catalog) Load(src []byte) error

Load loads messages from a catalog file. It merges new messages from the file into the catalog. If there are any duplicate message ids, and error is returned.

type Entry

type Entry struct {
	// contains filtered or unexported fields
}

An Entry represents the occurrence of a message in a program

func (*Entry) ID added in v0.0.8

func (e *Entry) ID() string

ID returns the message id

func (*Entry) Message added in v0.0.8

func (e *Entry) Message() *pb.Message

Message returns the full *pb.Message object

func (*Entry) Source added in v0.0.8

func (e *Entry) Source() (string, int)

Source returns the file and line number where the message was declared in the source code

type Flag

type Flag uint
const (
	FlagIncludeMsgID Flag = 1 << iota
	FlagIncludeTime
	FlagIncludeLevel
	FlagIncludeSource
	FlagIncludeSourceFirst
	FlagIncludeExtras
	FlagWrap
	FlagIndent
	FlagHangIndent
	FlagLong // use the long message form
)

type Handler added in v0.0.9

type Handler struct {
	// contains filtered or unexported fields
}

func NewUnlogHandler added in v0.0.9

func NewUnlogHandler(dst io.Writer, enable slog.Level, opts ...WriterOption) *Handler

func (*Handler) Enabled added in v0.0.9

func (h *Handler) Enabled(ctx context.Context, level slog.Level) bool

func (*Handler) Handle added in v0.0.9

func (h *Handler) Handle(ctx context.Context, rec slog.Record) error

func (*Handler) WithAttrs added in v0.0.9

func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler

func (*Handler) WithGroup added in v0.0.9

func (h *Handler) WithGroup(name string) slog.Handler

type Record

type Record struct {
	Catalog *Catalog
	Level   string
	Time    time.Time
	Msg     string
	MsgID   string
	Source  *Source
	Data    map[string]any
}

type Source

type Source struct {
	Function string
	File     string
	Line     int
}

func (*Source) String

func (s *Source) String() string

type Writer

type Writer struct {
	Output  io.Writer
	Width   int
	Catalog *Catalog
	Flags   Flag
}

func NewWriter

func NewWriter(dest io.Writer, opts ...WriterOption) *Writer

func (*Writer) Explain

func (w *Writer) Explain(err error)

Explain produces a longer explanation for an error produced by the unlog/errors package, or just the plain error message otherwise.

func (*Writer) Format

func (w *Writer) Format(rec *Record)

func (*Writer) RenderLong

func (w *Writer) RenderLong(e *Entry, data map[string]any) (int, error)

func (*Writer) RenderShort

func (w *Writer) RenderShort(e *Entry, data map[string]any) (int, error)

type WriterOption

type WriterOption func(*Writer)

func WithCatalog

func WithCatalog(cat *Catalog) WriterOption

func WithWidth

func WithWidth(width int) WriterOption

func WithoutSource

func WithoutSource() WriterOption

func WithoutTime

func WithoutTime() WriterOption

Directories

Path Synopsis
cmd
unlog command

Jump to

Keyboard shortcuts

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