12 KiB
Compatibility Adapters
Guide to using lixenwraith/log with popular Go networking frameworks through compatibility adapters.
Overview
The compat package provides adapters that allow the lixenwraith/log logger to work seamlessly with:
- gnet v2: High-performance event-driven networking framework
- fasthttp: Fast HTTP implementation
Features
- Full interface compatibility
- Preserves structured logging
- Configurable behavior
- Shared logger instances
- Optional field extraction
gnet Adapter
Basic Usage
import (
"github.com/lixenwraith/log"
"github.com/lixenwraith/log/compat"
"github.com/panjf2000/gnet/v2"
)
// Create logger
logger := log.NewLogger()
cfg := log.DefaultConfig()
cfg.Directory = "/var/log/gnet"
logger.ApplyConfig(cfg)
defer logger.Shutdown()
// Create adapter
adapter := compat.NewGnetAdapter(logger)
// Use with gnet
gnet.Run(eventHandler, "tcp://127.0.0.1:9000",
gnet.WithLogger(adapter),
)
gnet Interface Implementation
The adapter implements all gnet logger methods:
type GnetAdapter struct {
logger *log.Logger
}
// Methods implemented:
// - Debugf(format string, args ...any)
// - Infof(format string, args ...any)
// - Warnf(format string, args ...any)
// - Errorf(format string, args ...any)
// - Fatalf(format string, args ...any)
Custom Fatal Behavior
Override default fatal handling:
adapter := compat.NewGnetAdapter(logger,
compat.WithFatalHandler(func(msg string) {
// Custom cleanup
saveApplicationState()
notifyOperations(msg)
gracefulShutdown()
os.Exit(1)
}),
)
Complete gnet Example
type echoServer struct {
gnet.BuiltinEventEngine
logger gnet.Logger
}
func (es *echoServer) OnBoot(eng gnet.Engine) gnet.Action {
es.logger.Infof("Server started on %s", eng.Addrs)
return gnet.None
}
func (es *echoServer) OnTraffic(c gnet.Conn) gnet.Action {
buf, _ := c.Next(-1)
es.logger.Debugf("Received %d bytes from %s", len(buf), c.RemoteAddr())
c.Write(buf)
return gnet.None
}
func main() {
logger := log.NewLogger()
cfg := log.DefaultConfig()
cfg.Directory = "/var/log/gnet"
cfg.Format = "json"
cfg.BufferSize = 2048
logger.ApplyConfig(cfg)
defer logger.Shutdown()
adapter := compat.NewGnetAdapter(logger)
gnet.Run(
&echoServer{logger: adapter},
"tcp://127.0.0.1:9000",
gnet.WithMulticore(true),
gnet.WithLogger(adapter),
)
}
fasthttp Adapter
Basic Usage
import (
"github.com/lixenwraith/log"
"github.com/lixenwraith/log/compat"
"github.com/valyala/fasthttp"
)
// Create logger
logger := log.NewLogger()
cfg := log.DefaultConfig()
cfg.Directory = "/var/log/fasthttp"
logger.ApplyConfig(cfg)
defer logger.Shutdown()
// Create adapter
adapter := compat.NewFastHTTPAdapter(logger)
// Configure server
server := &fasthttp.Server{
Handler: requestHandler,
Logger: adapter,
}
Level Detection
The adapter automatically detects log levels from message content:
// Default detection rules:
// - Contains "error", "failed", "fatal", "panic" → ERROR
// - Contains "warn", "warning", "deprecated" → WARN
// - Contains "debug", "trace" → DEBUG
// - Otherwise → INFO
Custom Level Detection
adapter := compat.NewFastHTTPAdapter(logger,
compat.WithDefaultLevel(log.LevelInfo),
compat.WithLevelDetector(func(msg string) int64 {
// Custom detection logic
if strings.Contains(msg, "CRITICAL") {
return log.LevelError
}
if strings.Contains(msg, "performance") {
return log.LevelWarn
}
// Return 0 to use the adapter's default log level (log.LevelInfo by default)
return 0
}),
)
Builder Pattern
Using Existing Logger (Recommended)
Share a configured logger across adapters:
// Create and configure your main logger
logger := log.NewLogger()
cfg := log.DefaultConfig()
cfg.Level = log.LevelDebug
logger.ApplyConfig(cfg)
logger.Start()
defer logger.Shutdown()
// Create builder with existing logger
builder := compat.NewBuilder().WithLogger(logger)
// Build adapters
gnetAdapter, _ := builder.BuildGnet()
if err != nil { return err }
fasthttpAdapter, _ := builder.BuildFastHTTP()
if err != nil { return err }
Creating New Logger
Let the builder create a logger with config:
// Option 1: With custom config
cfg := log.DefaultConfig()
cfg.Directory = "/var/log/app"
builder := compat.NewBuilder().WithConfig(cfg)
// Option 2: Default config (created on first build)
builder := compat.NewBuilder()
if err != nil { return err }
// Build adapters
gnetAdapter, _ := builder.BuildGnet()
logger, _ := builder.GetLogger() // Retrieve for direct use
Structured gnet Adapter
Extract fields from printf-style formats:
structuredAdapter, _ := builder.BuildStructuredGnet()
// "client=%s port=%d" → {"client": "...", "port": ...}
Structured Logging
Field Extraction
Structured adapters can extract fields from printf-style formats:
// Regular adapter output:
// "client=192.168.1.1 port=8080"
// Structured adapter output:
// {"client": "192.168.1.1", "port": 8080, "source": "gnet"}
Pattern Detection
The structured adapter recognizes patterns like:
key=%vkey: %vkey = %v
adapter := compat.NewStructuredGnetAdapter(logger)
// These will extract structured fields:
adapter.Infof("client=%s port=%d", "192.168.1.1", 8080)
// → {"client": "192.168.1.1", "port": 8080}
adapter.Errorf("user: %s, error: %s", "john", "auth failed")
// → {"user": "john", "error": "auth failed"}
// These remain as messages:
adapter.Infof("Connected to server")
// → {"msg": "Connected to server"}
Integration Examples
Microservice with Both Frameworks
type Service struct {
gnetAdapter *compat.GnetAdapter
fasthttpAdapter *compat.FastHTTPAdapter
logger *log.Logger
}
func NewService() (*Service, error) {
// Create and configure logger
logger := log.NewLogger()
cfg := log.DefaultConfig()
cfg.Directory = "/var/log/service"
cfg.Format = "json"
cfg.HeartbeatLevel = 2
if err := logger.ApplyConfig(cfg); err != nil {
return nil, err
}
if err := logger.Start(); err != nil {
return nil, err
}
// Create builder with the logger
builder := compat.NewBuilder().WithLogger(logger)
// Build adapters
gnetAdapter, err := builder.BuildGnet()
if err != nil {
logger.Shutdown()
return nil, err
}
fasthttpAdapter, err := builder.BuildFastHTTP()
if err != nil {
logger.Shutdown()
return nil, err
}
return &Service{
gnetAdapter: gnetAdapter,
fasthttpAdapter: fasthttpAdapter,
logger: logger,
}, nil
}
Middleware Integration
// gnet middleware
func loggingMiddleware(adapter *compat.GnetAdapter) gnet.EventHandler {
return func(c gnet.Conn) gnet.Action {
start := time.Now()
addr := c.RemoteAddr()
// Process connection
action := next(c)
adapter.Infof("conn_duration=%v remote=%s action=%v",
time.Since(start), addr, action)
return action
}
}
// fasthttp middleware
func requestLogger(adapter *compat.FastHTTPAdapter) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
start := time.Now()
// Process request
next(ctx)
// Adapter will detect level from status
adapter.Printf("method=%s path=%s status=%d duration=%v",
ctx.Method(), ctx.Path(),
ctx.Response.StatusCode(),
time.Since(start))
}
}
Simple integration example suite
Below simple client and server examples can be used to test the basic functionality of the adapters. They are not included in the package to avoid dependency creep.
gnet server
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"github.com/lixenwraith/log"
"github.com/lixenwraith/log/compat"
"github.com/panjf2000/gnet/v2"
)
type echoServer struct {
gnet.BuiltinEventEngine
adapter *compat.GnetAdapter
}
func (es *echoServer) OnTraffic(c gnet.Conn) gnet.Action {
buf, _ := c.Next(-1)
if len(buf) > 0 {
es.adapter.Infof("Echo %d bytes", len(buf))
c.Write(buf)
}
return gnet.None
}
func main() {
// Minimal logger config
logger, err := log.NewBuilder().
Directory("./logs_gnet").
Format("json").
LevelString("info").
HeartbeatLevel(0).
Build()
if err != nil {
panic(err)
}
if err := logger.Start(); err != nil {
panic(err)
}
adapter, err := compat.NewBuilder().WithLogger(logger).BuildGnet()
if err != nil {
panic(err)
}
handler := &echoServer{adapter: adapter}
fmt.Println("Starting gnet server on :9000")
fmt.Println("Press Ctrl+C to stop")
// Signal handling
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
if err := gnet.Run(handler, "tcp://:9000",
gnet.WithLogger(adapter),
); err != nil {
fmt.Printf("gnet error: %v\n", err)
os.Exit(1)
}
}()
<-sigChan
fmt.Println("\nShutting down...")
logger.Shutdown()
}
fasthttp server
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"github.com/lixenwraith/log"
"github.com/lixenwraith/log/compat"
"github.com/valyala/fasthttp"
)
func main() {
// Minimal logger config
logger, err := log.NewBuilder().
Directory("./logs_fasthttp").
Format("json").
LevelString("info").
HeartbeatLevel(0).
Build()
if err != nil {
panic(err)
}
if err := logger.Start(); err != nil {
panic(err)
}
adapter, err := compat.NewBuilder().WithLogger(logger).BuildFastHTTP()
if err != nil {
panic(err)
}
server := &fasthttp.Server{
Handler: func(ctx *fasthttp.RequestCtx) {
adapter.Printf("Request: %s %s", ctx.Method(), ctx.Path())
ctx.WriteString("OK")
},
Logger: adapter,
Name: "TestServer",
}
fmt.Println("Starting FastHTTP server on :8080")
fmt.Println("Press Ctrl+C to stop")
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
if err := server.ListenAndServe(":8080"); err != nil {
fmt.Printf("FastHTTP error: %v\n", err)
os.Exit(1)
}
}()
<-sigChan
fmt.Println("\nShutting down...")
server.Shutdown()
logger.Shutdown()
}
Fiber server
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/gofiber/fiber/v2"
"github.com/lixenwraith/log"
"github.com/lixenwraith/log/compat"
)
func main() {
// Minimal logger config
logger, err := log.NewBuilder().
Directory("./logs_fiber").
Format("json").
LevelString("info").
HeartbeatLevel(0).
Build()
if err != nil {
panic(err)
}
if err := logger.Start(); err != nil {
panic(err)
}
adapter, err := compat.NewBuilder().WithLogger(logger).BuildFiber()
if err != nil {
panic(err)
}
app := fiber.New(fiber.Config{
DisableStartupMessage: true,
})
app.Use(func(c *fiber.Ctx) error {
adapter.Infow("Request", "method", c.Method(), "path", c.Path())
return c.Next()
})
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("OK")
})
fmt.Println("Starting Fiber server on :3000")
fmt.Println("Press Ctrl+C to stop")
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
if err := app.Listen(":3000"); err != nil {
fmt.Printf("Fiber error: %v\n", err)
os.Exit(1)
}
}()
<-sigChan
fmt.Println("\nShutting down...")
app.ShutdownWithTimeout(2 * time.Second)
logger.Shutdown()
}
Client
Client for all adapter servers.
# Run with:
go run client.go -target=gnet
go run client.go -target=fasthttp
go run client.go -target=fiber
package main
import (
"flag"
"fmt"
"io"
"net"
"net/http"
)
var target = flag.String("target", "fiber", "Target: gnet|fasthttp|fiber")
func main() {
flag.Parse()
switch *target {
case "gnet":
conn, err := net.Dial("tcp", "localhost:9000")
if err != nil {
panic(err)
}
conn.Write([]byte("TEST"))
buf := make([]byte, 4)
conn.Read(buf)
conn.Close()
fmt.Println("gnet: received echo")
case "fasthttp":
resp, err := http.Get("http://localhost:8080/")
if err != nil {
panic(err)
}
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
fmt.Printf("fasthttp: %s\n", body)
case "fiber":
resp, err := http.Get("http://localhost:3000/")
if err != nil {
panic(err)
}
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
fmt.Printf("fiber: %s\n", body)
}
}