Files
log/doc/adapters.md
T
lixen 2417e51da1
Test / test (push) Canceled after 0s
Test / fuzz (./compat, FuzzParseFormat) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatJSON) (push) Canceled after 0s
Test / fuzz (./formatter, FuzzFormatTextPolicy) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSanitize) (push) Canceled after 0s
Test / fuzz (./sanitizer, FuzzSerializerJSON) (push) Canceled after 0s
v0.1.11 harden logging lifecycle, sanitization, and formatter edge cases
2026-09-26 14:25:14 -04:00

9.1 KiB

Compatibility adapters

The compat package provides logging adapters for gnet v2, fasthttp, and Fiber v2. Framework dependencies are not imported by this module; applications supply them.

Shared logger setup

Configure and start the logger before passing an adapter to a framework:

logger, err := log.NewBuilder().
    Directory("/var/log/service").
    EnableFile(true).
    Format("json").
    LevelString("info").
    Build()
if err != nil { return err }
if err := logger.Start(); err != nil {
    _ = logger.Shutdown()
    return err
}
defer logger.Shutdown(5 * time.Second)

builder := compat.NewBuilder().WithLogger(logger)
gnetAdapter, err := builder.BuildGnet()
if err != nil { return err }
fastHTTPAdapter, err := builder.BuildFastHTTP()
if err != nil { return err }
fiberAdapter, err := builder.BuildFiber()
if err != nil { return err }

Alternatively, compat.NewBuilder().WithConfig(cfg) creates an initialized logger on the first build. Retrieve it with GetLogger() and call Start() explicitly. WithLogger takes precedence over WithConfig. Builders and option application are single-goroutine operations; completed adapters may be shared with a shared Logger.

gnet

GnetAdapter implements Debugf, Infof, Warnf, Errorf, and Fatalf. Pass it through gnet.WithLogger(adapter):

adapter := compat.NewGnetAdapter(logger)
err := gnet.Run(eventHandler, "tcp://127.0.0.1:9000", gnet.WithLogger(adapter))

Ordinary messages include msg and source (gnet) as positional arguments. With JSON output, these appear in the fields array. Use the logger's LogStructured or LogContext with FlagKV directly if a keyed JSON object is required.

Structured field extraction

NewStructuredGnetAdapter extracts simple key=%v or key: %v expressions with unindexed, single-argument verbs:

adapter := compat.NewStructuredGnetAdapter(logger)
adapter.Infof("client=%s port=%d", "192.168.1.1", 8080)
// JSON fields: ["client", "192.168.1.1", "port", 8080, "source", "gnet"]

Text outside the expressions is retained in msg. Formats mixing unrelated conversions, argument indexes, width/precision, escaped percent signs, or a different argument count fall back to a complete fmt.Sprintf message. Extraction never guesses which argument belongs to a key.

fasthttp

FastHTTPAdapter implements the Printf method accepted by fasthttp.Server.Logger:

adapter := compat.NewFastHTTPAdapter(logger)
server := &fasthttp.Server{
    Handler: requestHandler,
    Logger: adapter,
}

The default detector performs a case-insensitive substring check:

Message contains Level
error, failed, fatal, or panic Error
warn or deprecated Warn
debug or trace Debug
Otherwise Configured default (Info initially)

This heuristic does not interpret HTTP status numbers. Disable it with WithLevelDetector(nil), or provide a custom function. A detector result of zero selects the configured default; nonzero custom levels are preserved.

adapter := compat.NewFastHTTPAdapter(logger,
    compat.WithDefaultLevel(log.LevelWarn),
    compat.WithLevelDetector(nil),
)

Fiber

FiberAdapter provides plain, printf-style (Infof), and key/value (Infow) logging methods, and implements io.Writer through Write([]byte). Trace methods currently map to Debug with a level, trace marker. Use it directly in Fiber middleware:

adapter := compat.NewFiberAdapter(logger)
app.Use(func(c *fiber.Ctx) error {
    err := c.Next()
    adapter.Infow("request", "method", c.Method(), "path", c.Path(),
        "status", c.Response().StatusCode())
    return err
})

Fatal and panic behavior

Fatal methods enqueue an Error record, attempt a flush for 100 ms, and invoke the configured handler. Defaults call os.Exit(1) for fatal and panic(msg) for Fiber panic methods. These effects still occur when the log level suppresses the record. A timed-out or failed flush cannot guarantee that the final message was written.

Override behavior with WithFatalHandler, WithFiberFatalHandler, or WithFiberPanicHandler. A nil handler disables that action. Shut down the application/framework before the shared logger so shutdown messages can still be recorded.

Simple integration example suite

These 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").
		EnableFile(true).
		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").
		EnableFile(true).
		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").
		EnableFile(true).
		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)
	}
}