Skip to content

About

SeasLog for Golang

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

SeasLog4Go

A high-performance, structured logging library for Go, ported from the SeasLog PHP C extension.

@author Chitao.Gao [neeke@php.net]

中文文档



Synopsis

Why use SeasLog4Go

Logs are the operating record of systems, software, and applications. Through log analysis, users can understand the operational status of systems and applications. If your application logs are rich enough, you can also analyze user behavior, preferences, regional distribution, and more. If an application's logs are also divided into multiple levels, you can easily analyze the application's health status, quickly identify problems, and remedy losses.

Go's standard library log package is simple and convenient, but it lacks log levels, structured formatting, output routing, and buffering. Third-party libraries like logrus, zap, and zerolog are powerful, but most do not provide the modular logger path management, template engine, and multi-appender dispatch that SeasLog offers.

SeasLog4Go was created to meet the following requirements:

  • Modular and leveled — Logs are organized by logger (module) and RFC 5424 severity level
  • Simple configuration — Sensible defaults, zero-config to start, functional options for customization
  • Clear log format — Customizable template engine with 16 pre-defined variables
  • High performance — Time caching, hash-based logger caching, stream pooling, memory buffering
  • Multi-channel output — File, TCP (syslog), and UDP (syslog) appenders
  • Thread-safe — Full concurrency support with sync.Mutex / sync.RWMutex

What is provided at present

  • Record logs in Go projects with a standard, structured format
  • Configurable default log directory and module (logger)
  • Specified log directory and on-the-fly logger switching
  • Efficient log buffer with convenient buffer debugging
  • Follow RFC 5424 syslog protocol for TCP/UDP output
  • Support RequestId to distinguish individual requests
  • Support for log template customization with 16 pre-defined variables
  • Automatic daily and hourly log file rotation
  • Optional per-level log file separation (disting_type)
  • Automatic logger name derivation from caller's file or function name (logger_auto)
  • Configurable caller stack skip depth (recall_depth) for wrapper functions
  • Thread-safe concurrent logging with mutex protection
  • Stream pooling for file handles and network connections
  • Hash-based logger path resolution caching (FNV-1a)
  • Per-second timestamp formatting cache and per-minute date cache
  • Context placeholder replacement in log messages
  • Manual stream release and buffer flush control

What is the target

  • Convenient, standardized log records for Go applications
  • Efficient mass log analysis (via structured output and syslog forwarding)
  • Configurable, multi-channel log warning and forwarding

Install

go get github.com/SeasX/SeasLog4Go

Quick Start

package main

import "github.com/SeasX/SeasLog4Go"

func main() {
    // Create with functional options
    log := seaslog.NewSeasLog(
        seaslog.WithBasePath("/var/log/myapp"),
        seaslog.WithLogger("app"),
    )
    defer log.Close()

    // Basic logging
    log.Info("Server started")
    log.Error("Database connection failed")

    // With context placeholder replacement
    log.Info("User {name} logged in from {ip}", map[string]string{
        "name": "Alice",
        "ip":   "192.168.1.1",
    })

    // Switch loggers on the fly
    log.SetLogger("auth")
    log.Warning("Failed login attempt")

    // Log at all levels
    log.Debug("Debugging connection pool")
    log.Notice("Cache miss rate above threshold")
    log.Critical("Disk space below 5%")
    log.Alert("Primary database unreachable")
    log.Emergency("System-wide failure detected")
}

Configuration

Configuration options

SeasLog4Go's configuration is managed through the Config struct, which mirrors the SeasLog C extension's INI entries. All options have sensible defaults via DefaultConfig().

type Config struct {
    DefaultBasePath          string        // Default log root directory. Default: "/var/log/www"
    DefaultLogger            string        // Default logger (module) name. Default: "default"
    DefaultFilePrefix        string        // Log file name prefix, e.g. "app-". Default: ""
    DefaultFileDatetimeSep   string        // Date separator in file names. Default: ""
    DefaultDatetimeFormat    string        // Go time layout for %T placeholder. Default: "2006-01-02 15:04:05"
    DefaultTemplate          string        // Log template. Default: "%T | %L | %P | %Q | %t | %M"

    DistingFolder            bool          // Use directories to separate loggers. Default: true
    DistingType              bool          // Separate files per log level. Default: false
    DistingByHour            bool          // Hourly log file rotation. Default: false

    UseBuffer                bool          // Enable memory buffering. Default: false
    BufferSize               int           // Buffer flush threshold (entry count). Default: 0
    BufferDisabledInCLI      bool          // Disable buffer in CLI mode. Default: false

    Level                    int           // Log level threshold (0-8). Default: 8 (LevelAll)
    RecallDepth              int           // Stack skip depth for caller info (%F, %C). Default: 0

    LoggerAuto               bool          // Enable automatic logger name from caller. Default: false
    LoggerAutoBy             int           // LoggerAutoByFile=0 (default), LoggerAutoByFunc=1

    Appender                  int           // Output target: 1=File, 2=TCP, 3=UDP. Default: 1
    AppenderRetry             int           // Write retry count on failure. Default: 0
    RemoteHost                string        // TCP/UDP remote host. Default: "127.0.0.1"
    RemotePort                int           // TCP/UDP remote port. Default: 514
    RemoteTimeout             time.Duration // TCP/UDP dial timeout. Default: 1s

    TrimWrap                  bool          // Strip \r and \n from log messages. Default: false
    ThrowException            bool          // Whether to throw SeasLog errors. Default: true
    IgnoreWarning             bool          // Whether to suppress SeasLog warnings. Default: true
}

Functional options

For convenience, SeasLog4Go provides functional options for the most common configuration:

log := seaslog.NewSeasLog(
    seaslog.WithBasePath("/var/log/myapp"),
    seaslog.WithLogger("default"),
    seaslog.WithLevel(seaslog.LevelDebug),
    seaslog.WithTemplate("%T | %L | %P | %Q | %t | %M"),
    seaslog.WithBuffer(1000),        // Enable buffering with 1000-entry threshold
    seaslog.WithDistingType(),       // Separate files per log level
    seaslog.WithDistingByHour(),     // Hourly log file rotation
    seaslog.WithDistingFolder(true), // Use directories to separate loggers (default)
    seaslog.WithTrimWrap(),          // Strip newlines from messages
    seaslog.WithRecallDepth(1),      // Skip 1 extra stack frame for %F/%C
    seaslog.WithLoggerAuto(),        // Auto-derive logger from caller's filename
    // seaslog.WithLoggerAutoByFunc(), // Auto-derive logger from caller's function name
    seaslog.WithAppender(seaslog.AppenderFile),
    // seaslog.WithAppender(seaslog.AppenderTCP),
    // seaslog.WithRemoteHost("127.0.0.1", 514),
)

WithBasePath(path) sets the root directory for all log files. All logger paths are resolved relative to this directory.

WithLogger(name) sets the default logger (module) name. Log files will be written under basePath/logger/.

WithLevel(level) sets the log level threshold. Only messages at or below this level are recorded. Default is LevelAll (8), which records everything.

WithBuffer(size) enables memory buffering. Log entries are accumulated in memory and flushed when the count reaches size. If size is 0, entries are buffered without a count-based flush (flushed on Close() or FlushBuffer()).

WithDistingType() enables per-level log file separation. When enabled, files are named like 20240115.INFO.log, 20240115.ERROR.log, etc.

WithDistingByHour() enables hourly log rotation. Files are named like 2024011515.log instead of 20240115.log.

WithDistingFolder(enabled) controls whether loggers are separated by directories (true, default) or by filename prefix with underscore (false). When false, files are named like basePath/default_20240115.log instead of basePath/default/20240115.log.

WithTrimWrap() strips all \r and \n characters from log messages, replacing them with spaces. This prevents log injection attacks.

WithRecallDepth(depth) sets the number of additional stack frames to skip when capturing caller information for %F (filename:line) and %C (function name) template placeholders. Useful when your logging calls are wrapped in helper functions. Default is 0.

WithLoggerAuto() enables automatic logger name derivation from the caller's filename (without .go extension). When enabled, each log call's logger is determined by which source file called it — e.g. a call from user_service.go writes to basePath/user_service/.

WithLoggerAutoByFunc() enables automatic logger name derivation from the caller's function name. Dots in the function name are replaced with slashes to create a hierarchical path — e.g. myapp.(*UserService).Login becomes myapp/UserService/Login.

WithAppender(appender) selects the output backend: AppenderFile (1), AppenderTCP (2), or AppenderUDP (3).

WithRemoteHost(host, port) configures the TCP/UDP destination for network appenders.

Custom log template

SeasLog4Go provides a powerful template engine, ported from the SeasLog C TemplateFormatter.c. Users can customize the log format using pre-defined variables.

Log template overview

The default log template is: %T | %L | %P | %Q | %t | %M

This means the default log format is: {dateTime} | {level} | {pid} | {uniqid} | {timeStamp} | {logInfo}

You can customize the template, for example: [%T]:%L %P %Q %t %M

Then the log format becomes: [{dateTime}]:{level} {pid} {uniqid} {timeStamp} {logInfo}

To set a custom template:

log.SetTemplate("[%T]:%L %P %Q %t %M")

Or via functional option:

log := seaslog.NewSeasLog(
    seaslog.WithTemplate("[%T]:%L %P %Q %t %M"),
)

Default variable table

SeasLog4Go provides the following pre-defined variables that can be used directly in the log template. They are replaced with corresponding values when the log is generated.

Placeholder Description Example
%T DateTime — formatted according to DefaultDatetimeFormat. 2024-01-15 19:30:05
%t Timestamp — Unix timestamp with millisecond precision. 1705323005.862
%L Level — the log level string. INFO
%M Message — the log message content. If empty, outputs (null). hello world
%P ProcessId — the OS process ID. 12345
%Q RequestId — unique request identifier. Auto-generated if not set via SetRequestID(). a1b2c3d4e5f6
%H HostName — the machine hostname. web-server-01
%B BasePath — the configured log base path. /var/log/myapp
%D Domain:Port — domain and port from request variables. CLI mode: 0.0.0.0:0. example.com:8080
%R Request URI — the request URI. CLI mode: /. /api/users
%m Request Method — the HTTP method. CLI mode: CLI. GET
%I Client IP — the client IP address. CLI mode: 127.0.0.1. 192.168.1.100
%F FileName:LineNo — the source file and line number of the caller. main.go:42
%U MemoryUsage — current memory allocation in bytes (via runtime.ReadMemStats). 1048576
%u PeakMemoryUsage — peak memory allocation in bytes. 2097152
%C Class::Action — the caller function name. main.processRequest

Default template output example:

2024-01-15 19:30:05 | INFO | 12345 | a1b2c3d4e5f6 | 1705323005.862 | Server started

The %H, %P, and %B placeholders are resolved at initialization time (template pre-compilation) and do not incur per-log-call overhead. The remaining placeholders are resolved on each log call.

Usage

Constants and functions

Constant list

SeasLog4Go defines 8 log levels, aligned with RFC 5424:

LevelEmergency
  • "EMERGENCY" — System is unusable.
LevelAlert
  • "ALERT" — Action must be taken immediately. Relevant personnel should be notified for emergency repair.
LevelCritical
  • "CRITICAL" — Critical conditions. Program component is unavailable and requires immediate repair.
LevelError
  • "ERROR" — Runtime errors that do not require immediate action but should be monitored.
LevelWarning
  • "WARNING" — Exceptional occurrences that are not errors. Potentially aberrant information requiring attention.
LevelNotice
  • "NOTICE" — Normal but significant events. More important than INFO during execution.
LevelInfo
  • "INFO" — Interesting events. Emphasizes the running process of the application.
LevelDebug
  • "DEBUG" — Detailed debug information. Fine-grained information events.
LevelAll
  • "ALL" — All levels. Used as the default level threshold.
Auto-logger constants
const (
    LoggerAutoByFile = 0 // Derive logger from caller's filename (without .go)
    LoggerAutoByFunc = 1 // Derive logger from caller's function name
)
var (
    LevelEmergency = 0
    LevelAlert     = 1
    LevelCritical  = 2
    LevelError     = 3
    LevelWarning   = 4
    LevelNotice    = 5
    LevelInfo      = 6
    LevelDebug     = 7
    LevelAll       = 8
)

Level = 0 records only EMERGENCY.

Level = 1 records EMERGENCY, ALERT.

Level = 3 records EMERGENCY, ALERT, CRITICAL, ERROR.

Level = 7 records EMERGENCY through DEBUG.

Level = 8 (default) records all levels.

Function list

SeasLog4Go provides a comprehensive API set for getting and setting the root directory, logger, and for fast logging:

// --- Creation ---

// Create a new SeasLog instance with functional options
func NewSeasLog(opts ...Option) *SeasLog

// Create a new SeasLog instance with an explicit Config
func NewSeasLogWithConfig(config *Config) *SeasLog

// --- Configuration ---

func (sl *SeasLog) SetBasePath(path string)
func (sl *SeasLog) GetBasePath() string

func (sl *SeasLog) SetLogger(logger string) error
func (sl *SeasLog) GetLastLogger() string

func (sl *SeasLog) SetLevel(level int)
func (sl *SeasLog) GetLevel() int

func (sl *SeasLog) SetAppender(appender int)     // AppenderFile, AppenderTCP, AppenderUDP
func (sl *SeasLog) SetRemoteHost(host string, port int)

func (sl *SeasLog) SetTemplate(template string)
func (sl *SeasLog) GetTemplate() string
func (sl *SeasLog) SetDatetimeFormat(format string)  // Go time layout
func (sl *SeasLog) GetDatetimeFormat() string

func (sl *SeasLog) SetRequestID(id string)
func (sl *SeasLog) GetRequestID() string

func (sl *SeasLog) SetFilePrefix(prefix string)
func (sl *SeasLog) SetRequestVariable(rv *RequestVariable)
func (sl *SeasLog) GetRequestVariable() *RequestVariable

func (sl *SeasLog) SetDistingType(enabled bool)
func (sl *SeasLog) SetDistingByHour(enabled bool)
func (sl *SeasLog) SetDistingFolder(enabled bool)
func (sl *SeasLog) SetTrimWrap(enabled bool)

func (sl *SeasLog) SetRecallDepth(depth int)
func (sl *SeasLog) GetRecallDepth() int

func (sl *SeasLog) SetLoggerAuto(enabled bool, by int)  // by: LoggerAutoByFile or LoggerAutoByFunc
func (sl *SeasLog) GetLoggerAuto() bool

// --- Logging ---

func (sl *SeasLog) Log(level int, message string, context map[string]string, module string) error
func (sl *SeasLog) Emergency(message string, context ...map[string]string) error
func (sl *SeasLog) Alert(message string, context ...map[string]string) error
func (sl *SeasLog) Critical(message string, context ...map[string]string) error
func (sl *SeasLog) Error(message string, context ...map[string]string) error
func (sl *SeasLog) Warning(message string, context ...map[string]string) error
func (sl *SeasLog) Notice(message string, context ...map[string]string) error
func (sl *SeasLog) Info(message string, context ...map[string]string) error
func (sl *SeasLog) Debug(message string, context ...map[string]string) error

// --- Buffer ---

func (sl *SeasLog) SetBuffer(enabled bool, size int)
func (sl *SeasLog) FlushBuffer() error
func (sl *SeasLog) GetBuffer() map[string][]string
func (sl *SeasLog) GetBufferCount() int
func (sl *SeasLog) GetBufferEnabled() bool

// --- Stream ---

func (sl *SeasLog) CloseLoggerStream(mod int, logger string) bool
func (sl *SeasLog) Close() error

// --- Utility ---

func GetVersion() string
func GetAuthor() string

Get and set base path

basePath1 := log.GetBasePath()

log.SetBasePath("/log/base_test")
basePath2 := log.GetBasePath()

fmt.Println(basePath1) // "/var/log/www"
fmt.Println(basePath2) // "/log/base_test"

GetBasePath() returns the configured base path (set via WithBasePath() or SetBasePath()).

SetBasePath() changes the base path and clears the logger cache, forcing re-resolution of all logger paths on next use.

Get and set logger

lastLogger1 := log.GetLastLogger()

log.SetLogger("testModule/app1")
lastLogger2 := log.GetLastLogger()

fmt.Println(lastLogger1) // "default"
fmt.Println(lastLogger2) // "testModule/app1"

GetLastLogger() returns the currently active logger name.

SetLogger() switches the active logger. All subsequent log calls (without a module parameter) will write to this logger's path.

Auto logger

SeasLog4Go can automatically derive the logger name from the caller's source location, eliminating the need for manual SetLogger() calls. This mirrors the seaslog.default_logger = "" + auto-detection behavior in the C extension.

Two modes are available:

Mode Constant Logger Name Source Example
By File LoggerAutoByFile (0) Caller's filename without .go extension user_service.go → user_service
By Function LoggerAutoByFunc (1) Caller's full function name, dots→slashes myapp.(*UserService).Login → myapp/UserService/Login

Enable via functional options

// Derive logger from caller's filename
log := seaslog.NewSeasLog(
    seaslog.WithBasePath("/var/log/myapp"),
    seaslog.WithLoggerAuto(),           // LoggerAutoByFile (default)
)

// Or derive from caller's function name
log := seaslog.NewSeasLog(
    seaslog.WithBasePath("/var/log/myapp"),
    seaslog.WithLoggerAutoByFunc(),     // LoggerAutoByFunc
)

Enable at runtime

// Enable auto-logger by filename
log.SetLoggerAuto(true, seaslog.LoggerAutoByFile)

// Enable auto-logger by function name
log.SetLoggerAuto(true, seaslog.LoggerAutoByFunc)

// Disable
log.SetLoggerAuto(false, 0)

// Check if enabled
if log.GetLoggerAuto() {
    fmt.Println("Auto-logger is active")
}

How it works

When LoggerAuto is enabled and no explicit module parameter is passed to Log():

  1. The caller's file and function name are captured via runtime.Caller() at the API entry point.
  2. The logger name is derived from the captured info:
    • By File: filepath.Base(file) with .go suffix removed → e.g. user_service
    • By Function: function name with receiver syntax stripped, dots replaced by slashes → e.g. myapp/UserService/Login
  3. The derived logger name is passed to LoggerManager.ProcessLogger() for path resolution and caching.

When LoggerAuto is disabled (default), the behavior falls back to GetLastLogger() — the logger set via SetLogger().

The module parameter in Log() always takes priority over auto-logger. If a non-empty module is passed, auto-logger is bypassed for that call.

Recall depth

The recall_depth setting controls how many additional stack frames to skip when capturing caller information for the %F (FileName:LineNo) and %C (Class::Action) template placeholders.

This is useful when you wrap SeasLog4Go calls in your own helper functions:

// Without recall_depth: %F shows helper.go instead of the actual caller
func myLogHelper(msg string) {
    log.Info(msg)  // %F = "helper.go:5", but you want "main.go:20"
}

// With recall_depth=1: %F skips the helper and shows the real caller
log := seaslog.NewSeasLog(
    seaslog.WithRecallDepth(1),
)
// Now %F = "main.go:20" (the caller of myLogHelper)
// Set at runtime
log.SetRecallDepth(1)

// Get current value
depth := log.GetRecallDepth()  // 1

Default recall_depth is 0, meaning the immediate caller of the SeasLog4Go API method (e.g. Info(), Error(), Log()) is used.

Each increment of recall_depth skips one additional stack frame. For a wrapper that calls Info() directly, use recall_depth=1. For a wrapper that calls another wrapper that calls Info(), use recall_depth=2.

Fast write log

Once basePath and logger are set, the log directory is determined:

Log directory = basePath / logger / {fileName}.log

The log file name begins with YearMonthDay. For example, if today is January 15, 2024, the file name will be 20240115.

By default (DistingType = false), all levels write to the same file:

basePath/logger/20240115.log

If DistingType = true, files are separated by level:

basePath/logger/20240115.INFO.log
basePath/logger/20240115.WARNING.log
basePath/logger/20240115.ERROR.log

If DistingByHour = true, files are rotated hourly:

basePath/logger/2024011515.log

If DistingFolder = false, loggers use underscores instead of directories:

basePath/default_20240115.log

Context replacement

Log messages support {placeholder} context replacement. The context parameter is a map[string]string where keys are placeholder names:

// Simple logging
log.Info("Server started")
log.Error("Database connection failed")

// With context placeholder replacement
log.Info("User {name} logged in from {ip}", map[string]string{
    "name": "Alice",
    "ip":   "192.168.1.1",
})
// Output: ... | INFO | ... | User Alice logged in from 192.168.1.1

log.Warning("your {website} was down, please {action} it ASAP!", map[string]string{
    "website": "github.com",
    "action":  "reboot",
})
// Output: ... | WARNING | ... | your github.com was down, please reboot it ASAP!

// Using Log() with explicit level and module
log.Log(seaslog.LevelError, "this is a error test by ::log", nil, "")

// Using Log() with a temporary module (does not change GetLastLogger())
log.Log(seaslog.LevelError, "payment failed", map[string]string{
    "orderId": "ORD-12345",
}, "payment")

The log format is affected by the configured template. With the default template %T | %L | %P | %Q | %t | %M, the output looks like:

2024-01-15 19:30:05 | INFO  | 12345 | a1b2c3d4e5f6 | 1705323005.862 | Server started
2024-01-15 19:30:05 | ERROR | 12345 | a1b2c3d4e5f6 | 1705323005.862 | Database connection failed
2024-01-15 19:30:05 | INFO  | 12345 | a1b2c3d4e5f6 | 1705323005.862 | User Alice logged in from 192.168.1.1

Data format when sent by TCP or UDP

When the appender is set to AppenderTCP (2) or AppenderUDP (3), SeasLog4Go sends logs to remote_host:remote_port following the RFC 5424 syslog protocol.

The message format is:

<PRI>1 {timestamp_RFC3339} {hostname} {domain:port} {pid} {logger} {log_content}

The {log_content} is the formatted log entry produced by the template engine.

Example output:

<14>1 2024-01-15T19:30:05+08:00 web-01 0.0.0.0:0 12345 myapp 2024-01-15 19:30:05 | INFO | 12345 | a1b2c3d4e5f6 | 1705323005.862 | Server started
<11>1 2024-01-15T19:30:05+08:00 web-01 0.0.0.0:0 12345 myapp 2024-01-15 19:30:05 | ERROR | 12345 | a1b2c3d4e5f6 | 1705323005.862 | Database connection failed

The PRI value is calculated as facility * 8 + severity, where facility is LOG_USER (8) by default and severity is the RFC 5424 numeric level (0-7).

Manually release stream flow from logger

SeasLog4Go caches stream handles (file descriptors and network connections) opened by log loggers to save the overhead of creating streams. Handles are released automatically when Close() is called. You can also manually release them:

// Close all logger stream handles
log.CloseLoggerStream(seaslog.CloseStreamModAll, "")

// Close a specific logger stream handle
log.CloseLoggerStream(seaslog.CloseStreamModAssign, "auth")

CloseStreamModAll = 1 — Close all cached streams.

CloseStreamModAssign = 2 — Close streams whose path contains the specified logger name.

Buffer usage

When buffering is enabled, log entries are accumulated in memory, grouped by destination path. The buffer is automatically flushed when the entry count reaches the configured buffer_size.

// Enable buffering with 100-entry threshold
log.SetBuffer(true, 100)

// Write logs (they will be buffered in memory)
log.Info("This is buffered")
log.Error("This is also buffered")

// Check buffer status
fmt.Println(log.GetBufferEnabled()) // true
fmt.Println(log.GetBufferCount())   // 2

// Manually flush all buffered entries to disk
log.FlushBuffer()

// Disable buffering (auto-flushes remaining entries)
log.SetBuffer(false, 0)

SetBuffer(true, 100) — When the buffer reaches 100 entries, it is automatically flushed to the output streams.

FlushBuffer() — Immediately writes all buffered entries to their respective streams.

GetBuffer() — Returns a copy of the current buffer contents as map[path][]logLine.

GetBufferCount() — Returns the total number of buffered entries across all paths.

Request variables

SeasLog4Go supports request-related template variables (%D, %R, %m, %I). In a web server context, these should be set from the HTTP request:

func handler(w http.ResponseWriter, r *http.Request) {
    // Set request variables from the HTTP request
    log.SetRequestVariable(&seaslog.RequestVariable{
        DomainPort:    r.Host,
        ClientIP:      getClientIP(r),
        RequestURI:    r.URL.Path,
        RequestMethod: r.Method,
    })

    log.Info("Request received: {path}", map[string]string{
        "path": r.URL.Path,
    })
}

For CLI applications, the defaults are:

  • DomainPort: 0.0.0.0:0
  • ClientIP: 127.0.0.1
  • RequestURI: /
  • RequestMethod: CLI

Performance Optimizations

SeasLog4Go preserves all key performance optimizations from the C implementation:

  1. Time Caching — Formatted timestamp strings (%T) are computed at most once per second. Date strings for file naming are computed at most once per minute. This eliminates redundant time.Format() calls on every log entry.

  2. Logger Hash Caching — Logger path resolution results are cached by FNV-1a hash. When the same logger name is used repeatedly, path resolution and directory creation are performed only once. The hash lookup is O(1).

  3. Stream Pooling — File handles and network connections are pooled and reused across log calls. Dead connections are detected via zero-length reads and reconnected automatically. This avoids the overhead of opening/closing files or dialing network connections on every write.

  4. Memory Buffering — Log entries are accumulated in memory, grouped by destination path. Batch writes reduce I/O system calls. Auto-flush occurs when the buffer reaches the configured threshold, and on Close().

  5. Template Pre-compilation — Static template placeholders (%H hostname, %P PID, %B base path) are resolved at initialization time. Per-log-call formatting only substitutes dynamic values (%T, %t, %L, %M, %Q, %F, %C, etc.).

  6. Concurrent Safety — All shared state is protected by sync.Mutex or sync.RWMutex. The RWMutex on the logger cache allows concurrent reads (common case) while serializing writes.

Architecture

The codebase mirrors the SeasLog C extension's modular design:

Module C Source Go File Responsibility
Level Common.c level.go Log level constants and string/int conversion
Config seaslog.c INI config.go Configuration structure and defaults
Common Common.c common.go String replacement, trim_wrap, uniqid, caller info
Datetime Datetime.c datetime.go Per-second and per-minute time caching, RFC 3339
Template TemplateFormatter.c template.go Template engine with 16 placeholders
Logger Logger.c logger.go Logger management with FNV-1a hash caching
Stream StreamWrapper.c stream.go File handle and network connection pooling
Buffer Buffer.c buffer.go Memory buffering and batch writes
Appender Appender.c appender.go Output dispatch (File/TCP/UDP), syslog formatting
Request Request.c request.go Request variables (domain, IP, URI, method)
Core seaslog.c seaslog.go Core API, logging pipeline, configuration methods

Logging pipeline

API Call (Info/Debug/Error/...)
    |
    v
Level Check (level <= config.Level)
    |
    v
Caller Capture (runtime.Caller with recall_depth)
    |
    v
Logger Resolution
    +-- module param (highest priority)
    +-- LoggerAuto: derive from caller's file or function name
    +-- GetLastLogger() (default fallback)
    |
    v
Message Processing (trim_wrap / context replacement)
    |
    v
Appender Dispatch
    +-- File:  Build file path -> Template format -> Buffer/Direct write
    +-- TCP:   Build syslog message -> Buffer/Direct write
    +-- UDP:   Build syslog message -> Buffer/Direct write
    |
    v
Buffer Decision
    +-- Enabled:  Group by path, flush on threshold
    +-- Disabled: Get stream from pool, write directly
    |
    v
Stream Pool (reuse cached handle)
    |
    v
Write (with retry on failure)

Differences from PHP SeasLog

  • Concurrency: Full thread-safety with mutexes. PHP SeasLog is single-threaded per request; SeasLog4Go supports concurrent goroutines.
  • No INI: Configuration via Go struct and functional options instead of php.ini entries.
  • No Error Hooks: Go has panic/recover and structured error handling instead of PHP's error/exception hooks.
  • No Performance Profiler: Go has runtime/pprof for profiling; the C extension's built-in performance tracing is not ported.
  • No Analyzer: Shell-pipe-based log analysis (analyzerCount/analyzerDetail) is not included. Use tools like grep, ELK, or Loki for log analysis.
  • Go Time Format: Uses Go's time package layout (e.g. "2006-01-02 15:04:05") instead of PHP date format (e.g. "Y-m-d H:i:s").
  • Functional Options: Uses Go idiomatic functional options pattern instead of static class methods.

Testing

SeasLog4Go includes comprehensive unit tests for all modules:

# Run all tests
go test -v ./...

# Run with coverage report
go test -cover ./...

# Run benchmarks
go test -bench=. ./...

Test files and their coverage:

Test File Module Coverage Focus
level_test.go Level constants Level string/int conversion, edge cases
common_test.go Common utilities strReplace, strtrArray, trimWrap, uniqid, caller info
datetime_test.go Datetime cache Per-second/per-minute caching, format conversion
template_test.go Template engine All 16 placeholders, pre-compilation, level template
logger_test.go Logger manager Hash caching, path resolution, directory creation
stream_test.go Stream manager File/TCP/UDP pooling, validation, close
buffer_test.go Buffer manager Set/flush, threshold, clear, direct write
appender_test.go Appender File path construction, syslog formatting
seaslog_test.go Core API All log levels, config methods, buffer, stream close

Test coverage: 80%+

License

Apache License 2.0

Author

Neeke.Gao [neeke@php.net]

About

SeasLog for Golang

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages