- config: add optional configPaths parameter to load() for test isolation (tests now use t.TempDir() to avoid interference from D:\app\data) - handler: fix stubAccountRepoForHandler.ListByPlatform to return accounts (was returning nil, nil causing "no available accounts" errors) - logger: fix Sync() blocking on Windows pipes by closing write end first (also add Reset() function to close lumberjack file handles for cleanup)
195 lines
4.3 KiB
Go
195 lines
4.3 KiB
Go
package logger
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestInit_DualOutput(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
logPath := filepath.Join(tmpDir, "logs", "sub2api.log")
|
|
|
|
origStdout := os.Stdout
|
|
origStderr := os.Stderr
|
|
stdoutR, stdoutW, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("create stdout pipe: %v", err)
|
|
}
|
|
stderrR, stderrW, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("create stderr pipe: %v", err)
|
|
}
|
|
os.Stdout = stdoutW
|
|
os.Stderr = stderrW
|
|
t.Cleanup(func() {
|
|
os.Stdout = origStdout
|
|
os.Stderr = origStderr
|
|
_ = stdoutR.Close()
|
|
_ = stderrR.Close()
|
|
_ = stdoutW.Close()
|
|
_ = stderrW.Close()
|
|
Reset()
|
|
})
|
|
|
|
err = Init(InitOptions{
|
|
Level: "debug",
|
|
Format: "json",
|
|
ServiceName: "sub2api",
|
|
Environment: "test",
|
|
Output: OutputOptions{
|
|
ToStdout: true,
|
|
ToFile: true,
|
|
FilePath: logPath,
|
|
},
|
|
Rotation: RotationOptions{
|
|
MaxSizeMB: 10,
|
|
MaxBackups: 2,
|
|
MaxAgeDays: 1,
|
|
},
|
|
Sampling: SamplingOptions{Enabled: false},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Init() error: %v", err)
|
|
}
|
|
|
|
L().Info("dual-output-info")
|
|
L().Warn("dual-output-warn")
|
|
|
|
_ = stdoutW.Close()
|
|
_ = stderrW.Close()
|
|
Sync()
|
|
|
|
stdoutBytes, _ := io.ReadAll(stdoutR)
|
|
stderrBytes, _ := io.ReadAll(stderrR)
|
|
stdoutText := string(stdoutBytes)
|
|
stderrText := string(stderrBytes)
|
|
|
|
if !strings.Contains(stdoutText, "dual-output-info") {
|
|
t.Fatalf("stdout missing info log: %s", stdoutText)
|
|
}
|
|
if !strings.Contains(stderrText, "dual-output-warn") {
|
|
t.Fatalf("stderr missing warn log: %s", stderrText)
|
|
}
|
|
|
|
fileBytes, err := os.ReadFile(logPath)
|
|
if err != nil {
|
|
t.Fatalf("read log file: %v", err)
|
|
}
|
|
fileText := string(fileBytes)
|
|
if !strings.Contains(fileText, "dual-output-info") || !strings.Contains(fileText, "dual-output-warn") {
|
|
t.Fatalf("file missing logs: %s", fileText)
|
|
}
|
|
}
|
|
|
|
func TestInit_FileOutputFailureDowngrade(t *testing.T) {
|
|
origStdout := os.Stdout
|
|
origStderr := os.Stderr
|
|
_, stdoutW, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("create stdout pipe: %v", err)
|
|
}
|
|
stderrR, stderrW, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("create stderr pipe: %v", err)
|
|
}
|
|
os.Stdout = stdoutW
|
|
os.Stderr = stderrW
|
|
t.Cleanup(func() {
|
|
os.Stdout = origStdout
|
|
os.Stderr = origStderr
|
|
_ = stdoutW.Close()
|
|
_ = stderrR.Close()
|
|
_ = stderrW.Close()
|
|
})
|
|
|
|
err = Init(InitOptions{
|
|
Level: "info",
|
|
Format: "json",
|
|
Output: OutputOptions{
|
|
ToStdout: true,
|
|
ToFile: true,
|
|
FilePath: filepath.Join(os.DevNull, "logs", "sub2api.log"),
|
|
},
|
|
Rotation: RotationOptions{
|
|
MaxSizeMB: 10,
|
|
MaxBackups: 1,
|
|
MaxAgeDays: 1,
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Init() should downgrade instead of failing, got: %v", err)
|
|
}
|
|
|
|
_ = stderrW.Close()
|
|
stderrBytes, _ := io.ReadAll(stderrR)
|
|
if !strings.Contains(string(stderrBytes), "日志文件输出初始化失败") {
|
|
t.Fatalf("stderr should contain fallback warning, got: %s", string(stderrBytes))
|
|
}
|
|
}
|
|
|
|
func TestInit_CallerShouldPointToCallsite(t *testing.T) {
|
|
origStdout := os.Stdout
|
|
origStderr := os.Stderr
|
|
stdoutR, stdoutW, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("create stdout pipe: %v", err)
|
|
}
|
|
_, stderrW, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatalf("create stderr pipe: %v", err)
|
|
}
|
|
os.Stdout = stdoutW
|
|
os.Stderr = stderrW
|
|
t.Cleanup(func() {
|
|
os.Stdout = origStdout
|
|
os.Stderr = origStderr
|
|
_ = stdoutR.Close()
|
|
_ = stdoutW.Close()
|
|
_ = stderrW.Close()
|
|
})
|
|
|
|
if err := Init(InitOptions{
|
|
Level: "info",
|
|
Format: "json",
|
|
ServiceName: "sub2api",
|
|
Environment: "test",
|
|
Caller: true,
|
|
Output: OutputOptions{
|
|
ToStdout: true,
|
|
ToFile: false,
|
|
},
|
|
Sampling: SamplingOptions{Enabled: false},
|
|
}); err != nil {
|
|
t.Fatalf("Init() error: %v", err)
|
|
}
|
|
|
|
L().Info("caller-check")
|
|
_ = stdoutW.Close()
|
|
Sync()
|
|
logBytes, _ := io.ReadAll(stdoutR)
|
|
|
|
var line string
|
|
for _, item := range strings.Split(string(logBytes), "\n") {
|
|
if strings.Contains(item, "caller-check") {
|
|
line = item
|
|
break
|
|
}
|
|
}
|
|
if line == "" {
|
|
t.Fatalf("log output missing caller-check: %s", string(logBytes))
|
|
}
|
|
|
|
var payload map[string]any
|
|
if err := json.Unmarshal([]byte(line), &payload); err != nil {
|
|
t.Fatalf("parse log json failed: %v, line=%s", err, line)
|
|
}
|
|
caller, _ := payload["caller"].(string)
|
|
if !strings.Contains(caller, "logger_test.go:") {
|
|
t.Fatalf("caller should point to this test file, got: %s", caller)
|
|
}
|
|
}
|