Files
log/doc/adapters.md

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

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=%v
  • key: %v
  • key = %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)
	}
}