183 lines
4.1 KiB
Go
183 lines
4.1 KiB
Go
package journslog
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"testing/slogtest"
|
|
"time"
|
|
"uuid"
|
|
|
|
"src.lwithers.me.uk/go/journal"
|
|
"src.lwithers.me.uk/go/journal/testsink"
|
|
)
|
|
|
|
// TestHandler emits a log message with various attributes / groups and ensures
|
|
// the result is logged successfully.
|
|
func TestHandler(t *testing.T) {
|
|
sockpath := filepath.Join(t.TempDir(), "socket")
|
|
sink, err := testsink.New(sockpath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer sink.Stop()
|
|
|
|
conn, err := journal.Connect(sockpath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
jh, err := NewHandler(WithConn(conn))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
httpGroup := slog.Group("http", "path", "/foo", "status", http.StatusOK)
|
|
logger := slog.New(jh)
|
|
logger = logger.With("global_attr_key", "global_attr_val", httpGroup).WithGroup("app")
|
|
logger.Info("hello, journal", "slowkey", slowValue("slowval"), slog.Group("g1", "attr1", "val1", "attr2", 123))
|
|
|
|
msg, err := sink.Message(0)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
msgText, attrs, err := msg.Decode()
|
|
if err != nil {
|
|
t.Error(err)
|
|
}
|
|
if msgText != "hello, journal" {
|
|
t.Errorf("unexpected message text %q", msgText)
|
|
}
|
|
|
|
expAttrs := []testsink.DecodedAttr{
|
|
testsink.DecodedAttr{Key: "PRIORITY", Val: "6"},
|
|
testsink.DecodedAttr{Key: "GLOBAL_ATTR_KEY", Val: "global_attr_val"},
|
|
testsink.DecodedAttr{Key: "HTTP_PATH", Val: "/foo"},
|
|
testsink.DecodedAttr{Key: "HTTP_STATUS", Val: "200"},
|
|
testsink.DecodedAttr{Key: "APP_SLOWKEY", Val: "slowval"},
|
|
testsink.DecodedAttr{Key: "APP_G1_ATTR1", Val: "val1"},
|
|
testsink.DecodedAttr{Key: "APP_G1_ATTR2", Val: "123"},
|
|
}
|
|
|
|
for i := range expAttrs {
|
|
key := expAttrs[i].Key
|
|
val, ok := testsink.GetAttr(attrs, key)
|
|
switch {
|
|
case !ok:
|
|
t.Errorf("missing %s attribute", key)
|
|
case val != expAttrs[i].Val:
|
|
t.Errorf("unexpected %s value", key)
|
|
}
|
|
}
|
|
|
|
if t.Failed() {
|
|
for i := range attrs {
|
|
t.Errorf("attr %q=%q", attrs[i].Key, attrs[i].Val)
|
|
}
|
|
}
|
|
}
|
|
|
|
type slowValue string
|
|
|
|
func (s slowValue) LogValue() slog.Value {
|
|
return slog.StringValue(string(s))
|
|
}
|
|
|
|
// TestSlog integrates with [testing/slogtest].
|
|
func TestSlog(t *testing.T) {
|
|
tmpdir := t.TempDir()
|
|
|
|
attrsFromTime := func(t time.Time) []slog.Attr {
|
|
return []slog.Attr{
|
|
{
|
|
Key: "TEST_TIME",
|
|
Value: slog.StringValue(t.Format(time.RFC3339Nano)),
|
|
},
|
|
}
|
|
}
|
|
|
|
attrsFromLevel := func(l slog.Level) []slog.Attr {
|
|
return []slog.Attr{
|
|
{
|
|
Key: "TEST_LEVEL",
|
|
Value: slog.StringValue(l.String()),
|
|
},
|
|
}
|
|
}
|
|
|
|
// XXX: this relies on [slogtest.Run] executing its test cases
|
|
// completely sequentially
|
|
var curSink *testsink.Sink
|
|
|
|
newHandler := func(t *testing.T) slog.Handler {
|
|
sockpath := filepath.Join(tmpdir, uuid.New().String())
|
|
sink, err := testsink.New(sockpath)
|
|
if err != nil {
|
|
t.Fatalf("failed to create test sink: %v", err)
|
|
}
|
|
|
|
conn, err := journal.Connect(sockpath)
|
|
if err != nil {
|
|
sink.Stop()
|
|
t.Fatalf("failed to connect to test sink socket: %v", err)
|
|
}
|
|
|
|
h, err := NewHandler(WithConn(conn), WithAttrsFromTime(attrsFromTime), WithAttrsFromLevel(attrsFromLevel), WithHandlerOptions(slog.HandlerOptions{
|
|
Level: slog.LevelInfo,
|
|
}))
|
|
if err != nil {
|
|
sink.Stop()
|
|
t.Fatalf("failed to create handler: %v", err)
|
|
}
|
|
|
|
curSink = sink
|
|
return h
|
|
}
|
|
|
|
result := func(t *testing.T) map[string]any {
|
|
defer curSink.Stop()
|
|
|
|
m, err := curSink.Message(0)
|
|
if err != nil {
|
|
t.Fatalf("failed to fetch message from test sink: %v", err)
|
|
}
|
|
|
|
msg, attrs, err := m.Decode()
|
|
if err != nil {
|
|
t.Errorf("failed to decode message from test sink: %v", err)
|
|
}
|
|
|
|
out := make(map[string]any)
|
|
out[slog.MessageKey] = msg
|
|
for i := range attrs {
|
|
k, v := attrs[i].Key, attrs[i].Val
|
|
switch k {
|
|
case "TEST_TIME":
|
|
out[slog.TimeKey] = v
|
|
case "TEST_LEVEL":
|
|
out[slog.LevelKey] = v
|
|
default:
|
|
parts := strings.Split(k, "_")
|
|
Ng := len(parts) - 1 // number of groups
|
|
g := out
|
|
for i := range Ng {
|
|
next, _ := g[parts[i]].(map[string]any)
|
|
if next == nil {
|
|
next = make(map[string]any)
|
|
g[parts[i]] = next
|
|
}
|
|
g = next
|
|
}
|
|
g[strings.ToLower(parts[Ng])] = v
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
slogtest.Run(t, newHandler, result)
|
|
}
|