Skip to content

Instantly share code, notes, and snippets.

@telemachus
Created August 13, 2023 21:39
Show Gist options
  • Save telemachus/d52423b7aae1c280bef44faa981225c9 to your computer and use it in GitHub Desktop.
Save telemachus/d52423b7aae1c280bef44faa981225c9 to your computer and use it in GitHub Desktop.
package tint_test
import (
"bytes"
"fmt"
"log/slog"
"strings"
"testing"
"testing/slogtest"
"time"
"github.com/lmittmann/tint"
)
// This code is (very lightly) adapted from examples in slog and slogtest.
// Thanks to Jonathan Amsterdam for both.
func TestSlogtest(t *testing.T) {
t.Parallel()
var buf bytes.Buffer
opts := tint.Options{
TimeFormat: time.Kitchen,
NoColor: true,
}
h := tint.NewHandler(&buf, &opts)
results := func() []map[string]any {
ms := []map[string]any{}
for _, line := range bytes.Split(buf.Bytes(), []byte{'\n'}) {
if len(line) == 0 {
continue
}
m, err := parseTint(line)
if err != nil {
t.Fatal(err)
}
ms = append(ms, m)
}
return ms
}
if err := slogtest.TestHandler(h, results); err != nil {
t.Error(err)
}
}
func parseTint(bs []byte) (map[string]any, error) {
top := map[string]any{}
s := string(bytes.TrimSpace(bs))
// First, we need to divide each line into four parts (time, level,
// message, and kv pairs). The time portion of each line is optional.
// (The message portion can contain spaces, but slogtest only emits
// messages without spaces to make parsing easier.)
// Then we need to create proper key-value pairs for time (if present),
// level, and message.
pieces := strings.Split(s, " ")
firstKV := 3
if _, err := time.Parse(time.Kitchen, pieces[0]); err == nil {
top[slog.TimeKey] = strings.TrimSpace(pieces[0])
top[slog.LevelKey] = strings.TrimSpace(pieces[1])
top[slog.MessageKey] = pieces[2]
} else {
top[slog.LevelKey] = strings.TrimSpace(pieces[0])
top[slog.MessageKey] = pieces[1]
firstKV = 2
}
// The rest of the line contains kv pairs that we can (roughly) divide
// by spaces. This is crude since it will split a quoted key or value
// that contains a space. For this test, however, this will work---as
// long as I make sure to set a time format without whitespace.
s = strings.Join(pieces[firstKV:], " ")
for len(s) > 0 {
kv, rest, _ := strings.Cut(s, " ")
k, value, found := strings.Cut(kv, "=")
if !found {
return nil, fmt.Errorf("no '=' in %q", kv)
}
keys := strings.Split(k, ".")
// Populate a tree of maps for a dotted path such as "a.b.c=x".
m := top
for _, key := range keys[:len(keys)-1] {
x, ok := m[key]
var m2 map[string]any
if !ok {
m2 = map[string]any{}
m[key] = m2
} else {
m2, ok = x.(map[string]any)
if !ok {
return nil, fmt.Errorf("value for %q in composite key %q is not map[string]any", key, k)
}
}
m = m2
}
m[keys[len(keys)-1]] = value
s = rest
}
return top, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment