Files
go-omni/logx/timeZone_test.go

134 lines
4.0 KiB
Go

package logx
import (
"bytes"
"context"
"encoding/json"
"io"
"log/slog"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
)
func TestLoggerTimeZoneIsolation(t *testing.T) {
originalLocal := time.Local
stamp := time.Date(2026, 7, 1, 0, 0, 0, 123456000, time.UTC)
for _, format := range []string{"json", "text"} {
t.Run(format, func(t *testing.T) {
var a, b bytes.Buffer
sh, err := New(Config{TimeZone: "Asia/Shanghai", Format: format, Writer: &a})
if err != nil {
t.Fatal(err)
}
defer sh.Close()
utc, err := New(Config{TimeZone: "UTC", Format: format, Writer: &b})
if err != nil {
t.Fatal(err)
}
defer utc.Close()
r := slog.NewRecord(stamp, slog.LevelInfo, "timestamp", 0)
r.AddAttrs(slog.Time("eventTime", stamp), slog.String("password", "secret"))
if err := sh.With("service", "first").WithGroup("data").logger.Handler().Handle(context.Background(), r); err != nil {
t.Fatal(err)
}
if err := utc.logger.Handler().Handle(context.Background(), r); err != nil {
t.Fatal(err)
}
if !strings.Contains(a.String(), "08:00:00") || !strings.Contains(a.String(), "+08:00") || !strings.Contains(b.String(), "00:00:00") || strings.Contains(b.String(), "+08:00") {
t.Fatalf("a=%s b=%s", a.String(), b.String())
}
if strings.Contains(a.String(), "secret") || strings.Contains(b.String(), "secret") {
t.Fatal("redaction lost")
}
if format == "json" {
var out map[string]any
if err := json.Unmarshal(a.Bytes(), &out); err != nil {
t.Fatal(err)
}
if out["time"] != "2026-07-01T08:00:00.123456+08:00" || out["data"].(map[string]any)["eventTime"] != "2026-07-01T00:00:00.123456Z" {
t.Fatal(out)
}
}
})
}
if time.Local != originalLocal {
t.Fatal("global time.Local changed")
}
}
func TestLoggerTimeZoneValidationAndDST(t *testing.T) {
path := filepath.Join(t.TempDir(), "not-created", "app.log")
if _, err := New(Config{TimeZone: "Secret/Not_A_Zone", File: path}); err == nil || strings.Contains(err.Error(), "Secret") {
t.Fatal(err)
}
if _, err := os.Stat(filepath.Dir(path)); !os.IsNotExist(err) {
t.Fatal("invalid zone opened writer")
}
var out bytes.Buffer
l, err := New(Config{TimeZone: "America/New_York", Writer: &out})
if err != nil {
t.Fatal(err)
}
defer l.Close()
for _, month := range []time.Month{time.January, time.July} {
r := slog.NewRecord(time.Date(2026, month, 1, 12, 0, 0, 0, time.UTC), slog.LevelInfo, "dst", 0)
if err := l.logger.Handler().Handle(context.Background(), r); err != nil {
t.Fatal(err)
}
}
if !strings.Contains(out.String(), "-05:00") || !strings.Contains(out.String(), "-04:00") {
t.Fatal(out.String())
}
// Explicit empty location preserves an incoming record's original zone.
out.Reset()
plain, _ := New(Config{Writer: &out})
defer plain.Close()
r := slog.NewRecord(time.Date(2026, 1, 1, 0, 0, 0, 0, time.FixedZone("original", 7200)), slog.LevelInfo, "unchanged", 0)
plain.logger.Handler().Handle(context.Background(), r)
if !strings.Contains(out.String(), "+02:00") {
t.Fatal(out.String())
}
}
func TestLoggerTimeZoneConcurrentChildren(t *testing.T) {
var out bytes.Buffer
l, err := New(Config{TimeZone: "Asia/Shanghai", Writer: &out})
if err != nil {
t.Fatal(err)
}
defer l.Close()
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
child := l.With("worker", i).WithGroup("event")
for n := 0; n < 10; n++ {
child.Standard(slog.LevelInfo).Print("message")
}
}(i)
}
wg.Wait()
if strings.Count(out.String(), "+08:00") != 200 {
t.Fatal("timestamps missing")
}
}
func BenchmarkLoggerTimeZone(b *testing.B) {
for _, zone := range []string{"", "UTC", "Asia/Shanghai"} {
b.Run(zone, func(b *testing.B) {
l, err := New(Config{TimeZone: zone, Writer: io.Discard})
if err != nil {
b.Fatal(err)
}
defer l.Close()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
l.InfoContext(context.Background(), "benchmark", "count", 1)
}
})
}
}