v0.1.0 Release
This commit is contained in:
@ -7,22 +7,22 @@ import (
|
||||
"github.com/lixenwraith/log"
|
||||
)
|
||||
|
||||
// Builder provides a flexible way to create configured logger adapters for gnet and fasthttp.
|
||||
// It can use an existing *log.Logger instance or create a new one from a *log.Config.
|
||||
// Builder provides a flexible way to create configured logger adapters for gnet and fasthttp
|
||||
// It can use an existing *log.Logger instance or create a new one from a *log.Config
|
||||
type Builder struct {
|
||||
logger *log.Logger
|
||||
logCfg *log.Config
|
||||
err error
|
||||
}
|
||||
|
||||
// NewBuilder creates a new adapter builder.
|
||||
// NewBuilder creates a new adapter builder
|
||||
func NewBuilder() *Builder {
|
||||
return &Builder{}
|
||||
}
|
||||
|
||||
// WithLogger specifies an existing logger to use for the adapters. This is the recommended
|
||||
// approach for applications that already have a central logger instance.
|
||||
// If this is set, any configuration passed via WithConfig is ignored.
|
||||
// WithLogger specifies an existing logger to use for the adapters
|
||||
// Recommended for applications that already have a central logger instance
|
||||
// If this is set WithConfig is ignored
|
||||
func (b *Builder) WithLogger(l *log.Logger) *Builder {
|
||||
if l == nil {
|
||||
b.err = fmt.Errorf("log/compat: provided logger cannot be nil")
|
||||
@ -32,46 +32,45 @@ func (b *Builder) WithLogger(l *log.Logger) *Builder {
|
||||
return b
|
||||
}
|
||||
|
||||
// WithConfig provides a configuration for a new logger instance.
|
||||
// This is used only if an existing logger is NOT provided via WithLogger.
|
||||
// If neither WithLogger nor WithConfig is used, a default logger will be created.
|
||||
// WithConfig provides a configuration for a new logger instance
|
||||
// This is used only if an existing logger is NOT provided via WithLogger
|
||||
// If neither WithLogger nor WithConfig is used, a default logger will be created
|
||||
func (b *Builder) WithConfig(cfg *log.Config) *Builder {
|
||||
b.logCfg = cfg
|
||||
return b
|
||||
}
|
||||
|
||||
// getLogger resolves the logger to be used, creating one if necessary.
|
||||
// It's called internally by the build methods.
|
||||
// getLogger resolves the logger to be used, creating one if necessary
|
||||
func (b *Builder) getLogger() (*log.Logger, error) {
|
||||
if b.err != nil {
|
||||
return nil, b.err
|
||||
}
|
||||
|
||||
// An existing logger was provided, so we use it.
|
||||
// An existing logger was provided, so we use it
|
||||
if b.logger != nil {
|
||||
return b.logger, nil
|
||||
}
|
||||
|
||||
// Create a new logger instance.
|
||||
// Create a new logger instance
|
||||
l := log.NewLogger()
|
||||
cfg := b.logCfg
|
||||
if cfg == nil {
|
||||
// If no config was provided, use the default.
|
||||
// If no config was provided, use the default
|
||||
cfg = log.DefaultConfig()
|
||||
}
|
||||
|
||||
// Apply the configuration.
|
||||
// Apply the configuration
|
||||
if err := l.ApplyConfig(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Cache the newly created logger for subsequent builds with this builder.
|
||||
// Cache the newly created logger for subsequent builds with this builder
|
||||
b.logger = l
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// BuildGnet creates a gnet adapter.
|
||||
// It can be used for servers that require a standard gnet logger.
|
||||
// BuildGnet creates a gnet adapter
|
||||
// It can be used for servers that require a standard gnet logger
|
||||
func (b *Builder) BuildGnet(opts ...GnetOption) (*GnetAdapter, error) {
|
||||
l, err := b.getLogger()
|
||||
if err != nil {
|
||||
@ -81,7 +80,7 @@ func (b *Builder) BuildGnet(opts ...GnetOption) (*GnetAdapter, error) {
|
||||
}
|
||||
|
||||
// BuildStructuredGnet creates a gnet adapter that attempts to extract structured
|
||||
// fields from log messages for richer, queryable logs.
|
||||
// fields from log messages for richer, queryable logs
|
||||
func (b *Builder) BuildStructuredGnet(opts ...GnetOption) (*StructuredGnetAdapter, error) {
|
||||
l, err := b.getLogger()
|
||||
if err != nil {
|
||||
@ -90,7 +89,7 @@ func (b *Builder) BuildStructuredGnet(opts ...GnetOption) (*StructuredGnetAdapte
|
||||
return NewStructuredGnetAdapter(l, opts...), nil
|
||||
}
|
||||
|
||||
// BuildFastHTTP creates a fasthttp adapter.
|
||||
// BuildFastHTTP creates a fasthttp adapter
|
||||
func (b *Builder) BuildFastHTTP(opts ...FastHTTPOption) (*FastHTTPAdapter, error) {
|
||||
l, err := b.getLogger()
|
||||
if err != nil {
|
||||
@ -99,8 +98,8 @@ func (b *Builder) BuildFastHTTP(opts ...FastHTTPOption) (*FastHTTPAdapter, error
|
||||
return NewFastHTTPAdapter(l, opts...), nil
|
||||
}
|
||||
|
||||
// GetLogger returns the underlying *log.Logger instance.
|
||||
// If a logger has not been provided or created yet, it will be initialized.
|
||||
// GetLogger returns the underlying *log.Logger instance
|
||||
// If a logger has not been provided or created yet, it will be initialized
|
||||
func (b *Builder) GetLogger() (*log.Logger, error) {
|
||||
return b.getLogger()
|
||||
}
|
||||
@ -108,9 +107,9 @@ func (b *Builder) GetLogger() (*log.Logger, error) {
|
||||
// --- Example Usage ---
|
||||
//
|
||||
// The following demonstrates how to integrate lixenwraith/log with gnet and fasthttp
|
||||
// using a single, shared logger instance.
|
||||
// using a single, shared logger instance
|
||||
//
|
||||
// // 1. Create and configure your application's main logger.
|
||||
// // 1. Create and configure application's main logger
|
||||
// appLogger := log.NewLogger()
|
||||
// logCfg := log.DefaultConfig()
|
||||
// logCfg.Level = log.LevelDebug
|
||||
@ -118,25 +117,25 @@ func (b *Builder) GetLogger() (*log.Logger, error) {
|
||||
// panic(fmt.Sprintf("failed to configure logger: %v", err))
|
||||
// }
|
||||
//
|
||||
// // 2. Create a builder and provide the existing logger.
|
||||
// // 2. Create a builder and provide the existing logger
|
||||
// builder := compat.NewBuilder().WithLogger(appLogger)
|
||||
//
|
||||
// // 3. Build the required adapters.
|
||||
// // 3. Build the required adapters
|
||||
// gnetLogger, err := builder.BuildGnet()
|
||||
// if err != nil { /* handle error */ }
|
||||
//
|
||||
// fasthttpLogger, err := builder.BuildFastHTTP()
|
||||
// if err != nil { /* handle error */ }
|
||||
//
|
||||
// // 4. Configure your servers with the adapters.
|
||||
// // 4. Configure your servers with the adapters
|
||||
//
|
||||
// // For gnet:
|
||||
// var events gnet.EventHandler // your-event-handler
|
||||
// // The adapter is passed directly into the gnet options.
|
||||
// // The adapter is passed directly into the gnet options
|
||||
// go gnet.Run(events, "tcp://:9000", gnet.WithLogger(gnetLogger))
|
||||
//
|
||||
// // For fasthttp:
|
||||
// // The adapter is assigned directly to the server's Logger field.
|
||||
// // The adapter is assigned directly to the server's Logger field
|
||||
// server := &fasthttp.Server{
|
||||
// Handler: func(ctx *fasthttp.RequestCtx) {
|
||||
// ctx.WriteString("Hello, world!")
|
||||
|
||||
@ -6,7 +6,6 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -15,7 +14,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// createTestCompatBuilder creates a standard setup for compatibility adapter tests.
|
||||
// createTestCompatBuilder creates a standard setup for compatibility adapter tests
|
||||
func createTestCompatBuilder(t *testing.T) (*Builder, *log.Logger, string) {
|
||||
t.Helper()
|
||||
tmpDir := t.TempDir()
|
||||
@ -26,7 +25,7 @@ func createTestCompatBuilder(t *testing.T) (*Builder, *log.Logger, string) {
|
||||
Build()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Start the logger before using it.
|
||||
// Start the logger before using it
|
||||
err = appLogger.Start()
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -34,12 +33,12 @@ func createTestCompatBuilder(t *testing.T) (*Builder, *log.Logger, string) {
|
||||
return builder, appLogger, tmpDir
|
||||
}
|
||||
|
||||
// readLogFile reads a log file, retrying briefly to await async writes.
|
||||
// readLogFile reads a log file, retrying briefly to await async writes
|
||||
func readLogFile(t *testing.T, dir string, expectedLines int) []string {
|
||||
t.Helper()
|
||||
var err error
|
||||
|
||||
// Retry for a short period to handle logging delays.
|
||||
// Retry for a short period to handle logging delays
|
||||
for i := 0; i < 20; i++ {
|
||||
var files []os.DirEntry
|
||||
files, err = os.ReadDir(dir)
|
||||
@ -65,6 +64,7 @@ func readLogFile(t *testing.T, dir string, expectedLines int) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestCompatBuilder verifies the compatibility builder can be initialized correctly
|
||||
func TestCompatBuilder(t *testing.T) {
|
||||
t.Run("with existing logger", func(t *testing.T) {
|
||||
builder, logger, _ := createTestCompatBuilder(t)
|
||||
@ -86,12 +86,13 @@ func TestCompatBuilder(t *testing.T) {
|
||||
assert.NotNil(t, fasthttpAdapter)
|
||||
|
||||
logger1, _ := builder.GetLogger()
|
||||
// The builder now creates AND starts the logger internally if needed.
|
||||
// We need to defer shutdown to clean up resources.
|
||||
// The builder now creates AND starts the logger internally if needed
|
||||
// We need to defer shutdown to clean up resources
|
||||
defer logger1.Shutdown()
|
||||
})
|
||||
}
|
||||
|
||||
// TestGnetAdapter tests the gnet adapter's logging output and format
|
||||
func TestGnetAdapter(t *testing.T) {
|
||||
builder, logger, tmpDir := createTestCompatBuilder(t)
|
||||
defer logger.Shutdown()
|
||||
@ -111,10 +112,9 @@ func TestGnetAdapter(t *testing.T) {
|
||||
err = logger.Flush(time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The "Logger started" message is also logged, so we expect 6 lines.
|
||||
lines := readLogFile(t, tmpDir, 6)
|
||||
lines := readLogFile(t, tmpDir, 5)
|
||||
|
||||
// Define expected log data. The order in the "fields" array is fixed by the adapter call.
|
||||
// Define expected log data. The order in the "fields" array is fixed by the adapter call
|
||||
expected := []struct{ level, msg string }{
|
||||
{"DEBUG", "gnet debug id=1"},
|
||||
{"INFO", "gnet info id=2"},
|
||||
@ -126,22 +126,20 @@ func TestGnetAdapter(t *testing.T) {
|
||||
// Filter out the "Logger started" line
|
||||
var logLines []string
|
||||
for _, line := range lines {
|
||||
if !strings.Contains(line, "Logger started") {
|
||||
logLines = append(logLines, line)
|
||||
}
|
||||
logLines = append(logLines, line)
|
||||
}
|
||||
require.Len(t, logLines, 5, "Should have 5 gnet log lines after filtering")
|
||||
|
||||
for i, line := range logLines {
|
||||
var entry map[string]interface{}
|
||||
var entry map[string]any
|
||||
err := json.Unmarshal([]byte(line), &entry)
|
||||
require.NoError(t, err, "Failed to parse log line: %s", line)
|
||||
|
||||
assert.Equal(t, expected[i].level, entry["level"])
|
||||
|
||||
// The logger puts all arguments into a "fields" array.
|
||||
// The logger puts all arguments into a "fields" array
|
||||
// The adapter's calls look like: logger.Info("msg", msg, "source", "gnet")
|
||||
fields := entry["fields"].([]interface{})
|
||||
fields := entry["fields"].([]any)
|
||||
assert.Equal(t, "msg", fields[0])
|
||||
assert.Equal(t, expected[i].msg, fields[1])
|
||||
assert.Equal(t, "source", fields[2])
|
||||
@ -150,6 +148,7 @@ func TestGnetAdapter(t *testing.T) {
|
||||
assert.True(t, fatalCalled, "Custom fatal handler should have been called")
|
||||
}
|
||||
|
||||
// TestStructuredGnetAdapter tests the gnet adapter with structured field extraction
|
||||
func TestStructuredGnetAdapter(t *testing.T) {
|
||||
builder, logger, tmpDir := createTestCompatBuilder(t)
|
||||
defer logger.Shutdown()
|
||||
@ -162,25 +161,19 @@ func TestStructuredGnetAdapter(t *testing.T) {
|
||||
err = logger.Flush(time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The "Logger started" message is also logged, so we expect 2 lines.
|
||||
lines := readLogFile(t, tmpDir, 2)
|
||||
lines := readLogFile(t, tmpDir, 1)
|
||||
|
||||
// Find our specific log line
|
||||
var logLine string
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "request served") {
|
||||
logLine = line
|
||||
break
|
||||
}
|
||||
}
|
||||
require.Len(t, lines, 1, "Should be exactly one log line")
|
||||
logLine := lines[0]
|
||||
require.NotEmpty(t, logLine, "Did not find the structured gnet log line")
|
||||
|
||||
var entry map[string]interface{}
|
||||
var entry map[string]any
|
||||
err = json.Unmarshal([]byte(logLine), &entry)
|
||||
require.NoError(t, err)
|
||||
|
||||
// The structured adapter parses keys and values, so we check them directly.
|
||||
fields := entry["fields"].([]interface{})
|
||||
// The structured adapter parses keys and values, so we check them directly
|
||||
fields := entry["fields"].([]any)
|
||||
assert.Equal(t, "INFO", entry["level"])
|
||||
assert.Equal(t, "msg", fields[0])
|
||||
assert.Equal(t, "request served", fields[1])
|
||||
@ -192,6 +185,7 @@ func TestStructuredGnetAdapter(t *testing.T) {
|
||||
assert.Equal(t, "gnet", fields[7])
|
||||
}
|
||||
|
||||
// TestFastHTTPAdapter tests the fasthttp adapter's logging output and level detection
|
||||
func TestFastHTTPAdapter(t *testing.T) {
|
||||
builder, logger, tmpDir := createTestCompatBuilder(t)
|
||||
defer logger.Shutdown()
|
||||
@ -212,26 +206,19 @@ func TestFastHTTPAdapter(t *testing.T) {
|
||||
err = logger.Flush(time.Second)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Expect 4 test messages + 1 "Logger started" message
|
||||
lines := readLogFile(t, tmpDir, 5)
|
||||
// Expect 4 test messages
|
||||
lines := readLogFile(t, tmpDir, 4)
|
||||
expectedLevels := []string{"INFO", "DEBUG", "WARN", "ERROR"}
|
||||
|
||||
// Filter out the "Logger started" line
|
||||
var logLines []string
|
||||
for _, line := range lines {
|
||||
if !strings.Contains(line, "Logger started") {
|
||||
logLines = append(logLines, line)
|
||||
}
|
||||
}
|
||||
require.Len(t, logLines, 4, "Should have 4 fasthttp log lines after filtering")
|
||||
require.Len(t, lines, 4, "Should have 4 fasthttp log lines")
|
||||
|
||||
for i, line := range logLines {
|
||||
var entry map[string]interface{}
|
||||
for i, line := range lines {
|
||||
var entry map[string]any
|
||||
err := json.Unmarshal([]byte(line), &entry)
|
||||
require.NoError(t, err, "Failed to parse log line: %s", line)
|
||||
|
||||
assert.Equal(t, expectedLevels[i], entry["level"])
|
||||
fields := entry["fields"].([]interface{})
|
||||
fields := entry["fields"].([]any)
|
||||
assert.Equal(t, "msg", fields[0])
|
||||
assert.Equal(t, testMessages[i], fields[1])
|
||||
assert.Equal(t, "source", fields[2])
|
||||
|
||||
@ -8,7 +8,7 @@ import (
|
||||
"github.com/lixenwraith/log"
|
||||
)
|
||||
|
||||
// FastHTTPAdapter wraps lixenwraith/log.Logger to implement fasthttp's Logger interface
|
||||
// FastHTTPAdapter wraps lixenwraith/log.Logger to implement fasthttp Logger interface
|
||||
type FastHTTPAdapter struct {
|
||||
logger *log.Logger
|
||||
defaultLevel int64
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
"github.com/lixenwraith/log"
|
||||
)
|
||||
|
||||
// GnetAdapter wraps lixenwraith/log.Logger to implement gnet's logging.Logger interface
|
||||
// GnetAdapter wraps lixenwraith/log.Logger to implement gnet logging.Logger interface
|
||||
type GnetAdapter struct {
|
||||
logger *log.Logger
|
||||
fatalHandler func(msg string) // Customizable fatal behavior
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
// parseFormat attempts to extract structured fields from printf-style format strings
|
||||
// This is useful for preserving structured logging semantics
|
||||
// Useful for preserving structured logging semantics
|
||||
func parseFormat(format string, args []any) []any {
|
||||
// Pattern to detect common structured patterns like "key=%v" or "key: %v"
|
||||
keyValuePattern := regexp.MustCompile(`(\w+)\s*[:=]\s*%[vsdqxXeEfFgGpbcU]`)
|
||||
|
||||
Reference in New Issue
Block a user