srs/internal/logger/log_test.go
Winlin d8696434cb
Proxy: Refine logger and environment APIs. v7.0.146 (#4670)
This PR refines the next-generation proxy internals and workspace
documentation:

  - Reworks internal/logger to expose clearer slog-style APIs:
      - Replaces Vf/Df/Wf/Ef with Info/Debug/Warn/Error.
      - Adds structured key/value log arguments.
      - Adds version to every log record.
      - Uses standard slog level labels (DEBUG, INFO, WARN, ERROR).
      - Keeps compatibility for existing printf-style messages.
  - Renames proxy configuration abstractions:
      - Environment → ProxyEnvironment.
      - NewEnvironment → NewProxyEnvironment.
- Regenerates/renames the counterfeiter fake to FakeProxyEnvironment.
- Updates all proxy bootstrap, load balancer, protocol, signal, debug,
and utility call sites for the new logger and
    environment APIs.
  - Consolidates proxy codebase navigation:
      - Deletes docs/proxy/proxy-files.md.
- Moves the useful file/module map details into
.openclaw/memory/srs-codebase-map.md.
- Replaces agent instruction symlinks with explicit workspace
instruction files for Claude, Codex, and Kiro.
  - Updates OpenClaw tool notes with Codex commit-prefix guidance.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 07:18:45 -04:00

166 lines
4.2 KiB
Go

// Copyright (c) 2026 Winlin
//
// SPDX-License-Identifier: MIT
package logger
import (
"bytes"
"context"
"encoding/json"
"io"
"log/slog"
"os"
"testing"
"time"
)
func decodeLine(t *testing.T, line []byte) map[string]any {
t.Helper()
var m map[string]any
if err := json.Unmarshal(bytes.TrimSpace(line), &m); err != nil {
t.Fatalf("decode %q: %v", line, err)
}
return m
}
func bufLoggerPlus(w io.Writer, level slog.Level) *loggerPlus {
return newLoggerPlus(func(l *loggerPlus) {
l.logger = newJSONLogger(w)
l.level = level
})
}
func TestLog_EmitsAllFields(t *testing.T) {
var buf bytes.Buffer
lp := bufLoggerPlus(&buf, slog.LevelDebug)
ctx := withContextID(context.Background(), "abc1234")
lp.Log(ctx, "hello %s %d", "world", 42)
m := decodeLine(t, buf.Bytes())
if m["level"] != "DEBUG" {
t.Errorf("level = %v, want DEBUG", m["level"])
}
if m["msg"] != "hello world 42" {
t.Errorf("msg = %v, want %q", m["msg"], "hello world 42")
}
if m["cid"] != "abc1234" {
t.Errorf("cid = %v, want abc1234", m["cid"])
}
pid, ok := m["pid"].(float64)
if !ok || int(pid) != os.Getpid() {
t.Errorf("pid = %v, want %d", m["pid"], os.Getpid())
}
ts, ok := m["time"].(string)
if !ok {
t.Fatalf("time = %v, want string", m["time"])
}
if _, err := time.Parse(time.RFC3339Nano, ts); err != nil {
t.Errorf("time %q not RFC3339Nano: %v", ts, err)
}
}
func TestLog_OmitsCIDWhenAbsent(t *testing.T) {
var buf bytes.Buffer
bufLoggerPlus(&buf, slog.LevelWarn).Log(context.Background(), "no cid here")
m := decodeLine(t, buf.Bytes())
if v, present := m["cid"]; present {
t.Errorf("cid should be absent, got %v", v)
}
if m["level"] != "WARN" {
t.Errorf("level = %v, want WARN", m["level"])
}
}
func TestLog_EmitsStructuredArgs(t *testing.T) {
var buf bytes.Buffer
bufLoggerPlus(&buf, slog.LevelInfo).Log(context.Background(), "hello", "stream", "live/livestream", "retry", 2)
m := decodeLine(t, buf.Bytes())
if m["msg"] != "hello" {
t.Errorf("msg = %v, want hello", m["msg"])
}
if m["stream"] != "live/livestream" {
t.Errorf("stream = %v, want live/livestream", m["stream"])
}
if retry, ok := m["retry"].(float64); !ok || retry != 2 {
t.Errorf("retry = %v, want 2", m["retry"])
}
}
func TestLog_AllLevelsMapToLabel(t *testing.T) {
cases := []struct {
level slog.Level
label string
}{
{slog.LevelInfo, "INFO"},
{slog.LevelDebug, "DEBUG"},
{slog.LevelWarn, "WARN"},
{slog.LevelError, "ERROR"},
}
for _, tc := range cases {
var buf bytes.Buffer
bufLoggerPlus(&buf, tc.level).Log(context.Background(), "hi")
m := decodeLine(t, buf.Bytes())
if m["level"] != tc.label {
t.Errorf("level(%v) rendered as %v, want %q", tc.level, m["level"], tc.label)
}
}
}
func TestNewJSONLogger_GroupedAttrsPassThrough(t *testing.T) {
var buf bytes.Buffer
lg := newJSONLogger(&buf)
lg.LogAttrs(context.Background(), slog.LevelDebug, "grouped",
slog.Group("meta", slog.String("inner", "v")))
m := decodeLine(t, buf.Bytes())
meta, ok := m["meta"].(map[string]any)
if !ok {
t.Fatalf("meta not an object: %v", m["meta"])
}
if meta["inner"] != "v" {
t.Errorf("meta.inner = %v, want v", meta["inner"])
}
}
func TestPackageWrappers_RouteToRightLogger(t *testing.T) {
origI, origD, origW, origE := infoLogger, debugLogger, warnLogger, errorLogger
t.Cleanup(func() {
infoLogger, debugLogger, warnLogger, errorLogger = origI, origD, origW, origE
})
iBuf, dBuf, wBuf, eBuf := &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}
infoLogger = bufLoggerPlus(iBuf, slog.LevelInfo)
debugLogger = bufLoggerPlus(dBuf, slog.LevelDebug)
warnLogger = bufLoggerPlus(wBuf, slog.LevelWarn)
errorLogger = bufLoggerPlus(eBuf, slog.LevelError)
ctx := context.Background()
Info(ctx, "v=%d", 1)
Debug(ctx, "d=%d", 2)
Warn(ctx, "w=%d", 3)
Error(ctx, "e=%d", 4)
checks := []struct {
name string
buf *bytes.Buffer
label string
msg string
}{
{"Info", iBuf, "INFO", "v=1"},
{"Debug", dBuf, "DEBUG", "d=2"},
{"Warn", wBuf, "WARN", "w=3"},
{"Error", eBuf, "ERROR", "e=4"},
}
for _, c := range checks {
m := decodeLine(t, c.buf.Bytes())
if m["level"] != c.label {
t.Errorf("%s level = %v, want %v", c.name, m["level"], c.label)
}
if m["msg"] != c.msg {
t.Errorf("%s msg = %v, want %v", c.name, m["msg"], c.msg)
}
}
}