175 lines
4.6 KiB
Go
175 lines
4.6 KiB
Go
package starlog
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func waitObserverCount(t *testing.T, observer *Observer, want int, timeout time.Duration) {
|
|
t.Helper()
|
|
deadline := time.Now().Add(timeout)
|
|
for time.Now().Before(deadline) {
|
|
if observer != nil && observer.Count() >= want {
|
|
return
|
|
}
|
|
time.Sleep(5 * time.Millisecond)
|
|
}
|
|
got := 0
|
|
if observer != nil {
|
|
got = observer.Count()
|
|
}
|
|
t.Fatalf("observer count timeout, want >= %d got %d", want, got)
|
|
}
|
|
|
|
func waitObserverCondition(t *testing.T, timeout time.Duration, cond func() bool, reason string) {
|
|
t.Helper()
|
|
deadline := time.Now().Add(timeout)
|
|
for time.Now().Before(deadline) {
|
|
if cond() {
|
|
return
|
|
}
|
|
time.Sleep(5 * time.Millisecond)
|
|
}
|
|
t.Fatalf("observer condition timeout: %s", reason)
|
|
}
|
|
|
|
func TestObserverCollectsEntries(t *testing.T) {
|
|
defer StopStacks()
|
|
var buf bytes.Buffer
|
|
logger := newStructuredTestLogger(&buf)
|
|
observer := NewObserver()
|
|
logger.AppendEntryHandler(observer)
|
|
|
|
logger.WithField("user_id", 42).Info("login ok")
|
|
waitObserverCount(t, observer, 1, 300*time.Millisecond)
|
|
|
|
entries := observer.Entries()
|
|
if len(entries) == 0 {
|
|
t.Fatalf("observer should collect entries")
|
|
}
|
|
last := entries[len(entries)-1]
|
|
if last.Message != "login ok" {
|
|
t.Fatalf("unexpected observed message: %q", last.Message)
|
|
}
|
|
if got, ok := last.Fields["user_id"]; !ok || got != 42 {
|
|
t.Fatalf("unexpected observed fields: %+v", last.Fields)
|
|
}
|
|
}
|
|
|
|
func TestObserverLimitAndDropped(t *testing.T) {
|
|
defer StopStacks()
|
|
observer := NewObserverWithLimit(2)
|
|
logger := newStructuredTestLogger(&bytes.Buffer{})
|
|
logger.AppendEntryHandler(observer)
|
|
|
|
logger.Info("one")
|
|
logger.Info("two")
|
|
logger.Info("three")
|
|
waitObserverCondition(t, 400*time.Millisecond, func() bool {
|
|
if observer.Dropped() == 0 {
|
|
return false
|
|
}
|
|
entries := observer.Entries()
|
|
if len(entries) != 2 {
|
|
return false
|
|
}
|
|
return entries[0].Message == "two" && entries[1].Message == "three"
|
|
}, "observer limit should keep newest two entries")
|
|
|
|
entries := observer.Entries()
|
|
if len(entries) != 2 {
|
|
t.Fatalf("observer should keep only limited entries, got %d", len(entries))
|
|
}
|
|
if entries[0].Message != "two" || entries[1].Message != "three" {
|
|
t.Fatalf("observer should keep newest entries, got %q %q", entries[0].Message, entries[1].Message)
|
|
}
|
|
if observer.Dropped() == 0 {
|
|
t.Fatalf("observer dropped count should increase when over limit")
|
|
}
|
|
}
|
|
|
|
func TestObserverTakeAllAndReset(t *testing.T) {
|
|
defer StopStacks()
|
|
observer := NewObserver()
|
|
logger := newStructuredTestLogger(&bytes.Buffer{})
|
|
logger.AppendEntryHandler(observer)
|
|
|
|
logger.Info("a")
|
|
logger.Info("b")
|
|
waitObserverCount(t, observer, 2, 300*time.Millisecond)
|
|
|
|
all := observer.TakeAll()
|
|
if len(all) != 2 {
|
|
t.Fatalf("take all should return all collected entries, got %d", len(all))
|
|
}
|
|
if observer.Count() != 0 {
|
|
t.Fatalf("take all should clear observer entries")
|
|
}
|
|
|
|
logger.Info("c")
|
|
waitObserverCount(t, observer, 1, 300*time.Millisecond)
|
|
observer.Reset()
|
|
if observer.Count() != 0 || observer.Dropped() != 0 {
|
|
t.Fatalf("reset should clear observer state")
|
|
}
|
|
}
|
|
|
|
func TestTestHookAttachAndRestore(t *testing.T) {
|
|
defer StopStacks()
|
|
var buf bytes.Buffer
|
|
logger := newStructuredTestLogger(&buf)
|
|
var previousCount uint64
|
|
logger.SetEntryHandler(HandlerFunc(func(_ context.Context, _ *Entry) error {
|
|
atomic.AddUint64(&previousCount, 1)
|
|
return nil
|
|
}))
|
|
|
|
hook := NewTestHook(logger)
|
|
if hook == nil {
|
|
t.Fatalf("new test hook should not be nil")
|
|
}
|
|
|
|
logger.Info("hooked")
|
|
waitObserverCount(t, hook.Observer(), 1, 300*time.Millisecond)
|
|
if atomic.LoadUint64(&previousCount) == 0 {
|
|
t.Fatalf("test hook should keep previous handler in chain")
|
|
}
|
|
last, ok := hook.Last()
|
|
if !ok || last.Message != "hooked" {
|
|
t.Fatalf("unexpected hook last entry: ok=%v msg=%q", ok, last.Message)
|
|
}
|
|
|
|
if !hook.Close() {
|
|
t.Fatalf("hook close should restore previous handler when unchanged")
|
|
}
|
|
|
|
before := hook.Count()
|
|
logger.Info("after-close")
|
|
time.Sleep(30 * time.Millisecond)
|
|
if hook.Count() != before {
|
|
t.Fatalf("closed hook should stop collecting new entries")
|
|
}
|
|
if atomic.LoadUint64(&previousCount) < 2 {
|
|
t.Fatalf("previous handler should continue working after hook close")
|
|
}
|
|
|
|
if !strings.Contains(buf.String(), "after-close") {
|
|
t.Fatalf("logger output should still contain logs after hook close")
|
|
}
|
|
}
|
|
|
|
func TestTestHookCloseWhenHandlerReplaced(t *testing.T) {
|
|
defer StopStacks()
|
|
logger := newStructuredTestLogger(&bytes.Buffer{})
|
|
hook := NewTestHook(logger)
|
|
logger.SetEntryHandler(nil)
|
|
|
|
if hook.Close() {
|
|
t.Fatalf("hook close should fail when handler replaced externally")
|
|
}
|
|
}
|