Ctrl K
ComunidadGuías…

bldr/util/logfile/hook_test.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package logfile

import (
	"bytes"
	"strings"
	"sync"
	"testing"

	"github.com/aperturerobotics/fastjson"
	"github.com/sirupsen/logrus"
)

// safeBuffer is a thread-safe bytes.Buffer for concurrent test writes.
type safeBuffer struct {
	mu  sync.Mutex
	buf bytes.Buffer
}

// Write implements io.Writer.
func (b *safeBuffer) Write(p []byte) (int, error) {
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.buf.Write(p)
}

// String returns the buffer contents.
func (b *safeBuffer) String() string {
	b.mu.Lock()
	defer b.mu.Unlock()
	return b.buf.String()
}

func TestFileHookTextFormat(t *testing.T) {
	// Create a text file hook with a synchronized output buffer.
	buf := &safeBuffer{}
	hook := NewFileHook(buf, logrus.DebugLevel, "text")

	// Send a text record through the file hook.
	entry := &logrus.Entry{
		Logger:  logrus.StandardLogger(),
		Level:   logrus.InfoLevel,
		Message: "hello world",
		Data:    logrus.Fields{},
	}
	if err := hook.Fire(entry); err != nil {
		t.Fatalf("Fire() error: %v", err)
	}

	// Drain the file hook before inspecting its output.
	hook.Close()

	// Verify the file hook retained the text message.
	out := buf.String()
	if !strings.Contains(out, "hello world") {
		t.Errorf("expected output to contain 'hello world', got %q", out)
	}
}

func TestFileHookJSONFormat(t *testing.T) {
	// Create a JSON file hook with a synchronized output buffer.
	buf := &safeBuffer{}
	hook := NewFileHook(buf, logrus.DebugLevel, "json")

	// Send a record with a structured field through the JSON hook.
	entry := &logrus.Entry{
		Logger:  logrus.StandardLogger(),
		Level:   logrus.InfoLevel,
		Message: "json test",
		Data:    logrus.Fields{"key": "value"},
	}
	if err := hook.Fire(entry); err != nil {
		t.Fatalf("Fire() error: %v", err)
	}

	// Drain the JSON hook before decoding its output.
	hook.Close()

	// Decode the file hook output as a JSON record.
	out := buf.String()
	var p fastjson.Parser
	v, err := p.Parse(strings.TrimSpace(out))
	if err != nil {
		t.Fatalf("output is not valid JSON: %v\noutput: %q", err, out)
	}

	// Verify the JSON record preserves its message and structured field.
	if msg := string(v.GetStringBytes("msg")); msg != "json test" {
		t.Errorf("expected msg 'json test', got %v", msg)
	}
	if key := string(v.GetStringBytes("key")); key != "value" {
		t.Errorf("expected key 'value', got %v", key)
	}
}

func TestFileHookLevelFiltering(t *testing.T) {
	// Create a file hook that subscribes to warning records.
	buf := &safeBuffer{}
	hook := NewFileHook(buf, logrus.WarnLevel, "text")

	// Debug entry should not be written (below WARN threshold).
	entry := &logrus.Entry{
		Logger:  logrus.StandardLogger(),
		Level:   logrus.DebugLevel,
		Message: "debug message",
		Data:    logrus.Fields{},
	}

	// The hook's Levels() method determines which levels are reported.
	// Check that debug is NOT in the levels list.
	levels := hook.Levels()
	for _, lvl := range levels {
		if lvl == logrus.DebugLevel {
			t.Error("DebugLevel should not be in levels for WARN hook")
		}
	}

	// Verify expected levels are present.
	found := make(map[logrus.Level]bool)
	for _, lvl := range levels {
		found[lvl] = true
	}
	for _, expected := range []logrus.Level{logrus.PanicLevel, logrus.FatalLevel, logrus.ErrorLevel, logrus.WarnLevel} {
		if !found[expected] {
			t.Errorf("expected %v in levels", expected)
		}
	}

	// Fire a warn entry -- should be written.
	warnEntry := &logrus.Entry{
		Logger:  logrus.StandardLogger(),
		Level:   logrus.WarnLevel,
		Message: "warn message",
		Data:    logrus.Fields{},
	}
	_ = hook.Fire(warnEntry)

	// Fire a debug entry directly -- it would be written since Fire doesn't
	// filter (logrus does the filtering via Levels()). This verifies Fire works.
	_ = hook.Fire(entry)

	// Drain the warning hook before inspecting its output.
	hook.Close()

	// Verify the warning record reached the file hook output.
	out := buf.String()
	if !strings.Contains(out, "warn message") {
		t.Errorf("expected 'warn message' in output, got %q", out)
	}
}

func TestFileHookCloseDrains(t *testing.T) {
	// Create a buffered file hook for the drain check.
	buf := &safeBuffer{}
	hook := NewFileHook(buf, logrus.DebugLevel, "text")

	// Queue the records that closing the file hook must drain.
	for range 10 {
		entry := &logrus.Entry{
			Logger:  logrus.StandardLogger(),
			Level:   logrus.InfoLevel,
			Message: "drain test",
			Data:    logrus.Fields{},
		}
		_ = hook.Fire(entry)
	}

	// Close the file hook and wait for its writer to finish.
	hook.Close()

	// Verify closing the file hook wrote every queued record.
	out := buf.String()
	count := strings.Count(out, "drain test")
	if count != 10 {
		t.Errorf("expected 10 entries, got %d", count)
	}
}

// TestFileHookDropsAfterClose checks entries fired after Close are not buffered.
func TestFileHookDropsAfterClose(t *testing.T) {
	// Create and close the file hook before delivering more records.
	buf := &safeBuffer{}
	hook := NewFileHook(buf, logrus.DebugLevel, "text")
	hook.Close()

	// Deliver records after the file hook has closed.
	entry := &logrus.Entry{
		Logger:  logrus.StandardLogger(),
		Level:   logrus.InfoLevel,
		Message: "after close",
		Data:    logrus.Fields{},
	}
	for range 10 {
		_ = hook.Fire(entry)
	}

	// Inspect the closed file hook buffer under its broadcast lock.
	var buffered int
	hook.bcast.HoldLock(func(_ func(), _ func() <-chan struct{}) {
		buffered = len(hook.buf)
	})

	// Verify the closed hook neither buffered nor wrote later records.
	if buffered != 0 {
		t.Fatalf("expected no buffered entries after close, got %d", buffered)
	}
	if strings.Contains(buf.String(), "after close") {
		t.Fatal("entry written after close")
	}

	// Confirm closing the file hook again completes.
	hook.Close()
}

func TestFileHookConcurrentFire(t *testing.T) {
	// Create a synchronized output buffer for concurrent hook writes.
	buf := &safeBuffer{}
	hook := NewFileHook(buf, logrus.DebugLevel, "text")

	// Deliver records to the file hook from concurrent writers.
	var wg sync.WaitGroup
	for range 50 {
		wg.Go(func() {
			entry := &logrus.Entry{
				Logger:  logrus.StandardLogger(),
				Level:   logrus.InfoLevel,
				Message: "concurrent",
				Data:    logrus.Fields{},
			}
			_ = hook.Fire(entry)
		})
	}

	// Wait for the writers and drain the file hook.
	wg.Wait()
	hook.Close()

	// Verify every concurrent record reached the file hook output.
	out := buf.String()
	count := strings.Count(out, "concurrent")
	if count != 50 {
		t.Errorf("expected 50 entries, got %d", count)
	}
}