A high-performance, structured logging library for Go, ported from the SeasLog PHP C extension.
@author Chitao.Gao [neeke@php.net]
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
- 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
- Convenient, standardized log records for Go applications
- Efficient mass log analysis (via structured output and syslog forwarding)
- Configurable, multi-channel log warning and forwarding
go get github.com/SeasX/SeasLog4Gopackage 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")
}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
}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 underbasePath/logger/.
WithLevel(level)sets the log level threshold. Only messages at or below this level are recorded. Default isLevelAll(8), which records everything.
WithBuffer(size)enables memory buffering. Log entries are accumulated in memory and flushed when the count reachessize. Ifsizeis 0, entries are buffered without a count-based flush (flushed onClose()orFlushBuffer()).
WithDistingType()enables per-level log file separation. When enabled, files are named like20240115.INFO.log,20240115.ERROR.log, etc.
WithDistingByHour()enables hourly log rotation. Files are named like2024011515.loginstead of20240115.log.
WithDistingFolder(enabled)controls whether loggers are separated by directories (true, default) or by filename prefix with underscore (false). Whenfalse, files are named likebasePath/default_20240115.loginstead ofbasePath/default/20240115.log.
WithTrimWrap()strips all\rand\ncharacters 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 is0.
WithLoggerAuto()enables automatic logger name derivation from the caller's filename (without.goextension). When enabled, each log call's logger is determined by which source file called it — e.g. a call fromuser_service.gowrites tobasePath/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).Loginbecomesmyapp/UserService/Login.
WithAppender(appender)selects the output backend:AppenderFile(1),AppenderTCP(2), orAppenderUDP(3).
WithRemoteHost(host, port)configures the TCP/UDP destination for network appenders.
SeasLog4Go provides a powerful template engine, ported from the SeasLog C TemplateFormatter.c. Users can customize the log format using pre-defined variables.
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"),
)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%Bplaceholders 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.
SeasLog4Go defines 8 log levels, aligned with RFC 5424:
"EMERGENCY"— System is unusable.
"ALERT"— Action must be taken immediately. Relevant personnel should be notified for emergency repair.
"CRITICAL"— Critical conditions. Program component is unavailable and requires immediate repair.
"ERROR"— Runtime errors that do not require immediate action but should be monitored.
"WARNING"— Exceptional occurrences that are not errors. Potentially aberrant information requiring attention.
"NOTICE"— Normal but significant events. More important than INFO during execution.
"INFO"— Interesting events. Emphasizes the running process of the application.
"DEBUG"— Detailed debug information. Fine-grained information events.
"ALL"— All levels. Used as the default level threshold.
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 = 0records only EMERGENCY.
Level = 1records EMERGENCY, ALERT.
Level = 3records EMERGENCY, ALERT, CRITICAL, ERROR.
Level = 7records EMERGENCY through DEBUG.
Level = 8(default) records all levels.
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() stringbasePath1 := 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 viaWithBasePath()orSetBasePath()).
SetBasePath()changes the base path and clears the logger cache, forcing re-resolution of all logger paths on next use.
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 amoduleparameter) will write to this logger's path.
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 |
// 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 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")
}When LoggerAuto is enabled and no explicit module parameter is passed to Log():
- The caller's file and function name are captured via
runtime.Caller()at the API entry point. - The logger name is derived from the captured info:
- By File:
filepath.Base(file)with.gosuffix removed → e.g.user_service - By Function: function name with receiver syntax stripped, dots replaced by slashes → e.g.
myapp/UserService/Login
- By File:
- The derived logger name is passed to
LoggerManager.ProcessLogger()for path resolution and caching.
When
LoggerAutois disabled (default), the behavior falls back toGetLastLogger()— the logger set viaSetLogger().The
moduleparameter inLog()always takes priority over auto-logger. If a non-emptymoduleis passed, auto-logger is bypassed for that call.
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() // 1Default
recall_depthis0, meaning the immediate caller of the SeasLog4Go API method (e.g.Info(),Error(),Log()) is used.Each increment of
recall_depthskips one additional stack frame. For a wrapper that callsInfo()directly, userecall_depth=1. For a wrapper that calls another wrapper that callsInfo(), userecall_depth=2.
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
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
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
PRIvalue is calculated asfacility * 8 + severity, wherefacilityisLOG_USER(8) by default andseverityis the RFC 5424 numeric level (0-7).
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.
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 asmap[path][]logLine.
GetBufferCount()— Returns the total number of buffered entries across all paths.
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:0ClientIP:127.0.0.1RequestURI:/RequestMethod:CLI
SeasLog4Go preserves all key performance optimizations from the C implementation:
-
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 redundanttime.Format()calls on every log entry. -
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).
-
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.
-
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(). -
Template Pre-compilation — Static template placeholders (
%Hhostname,%PPID,%Bbase path) are resolved at initialization time. Per-log-call formatting only substitutes dynamic values (%T,%t,%L,%M,%Q,%F,%C, etc.). -
Concurrent Safety — All shared state is protected by
sync.Mutexorsync.RWMutex. The RWMutex on the logger cache allows concurrent reads (common case) while serializing writes.
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 |
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)
- 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.inientries. - No Error Hooks: Go has
panic/recoverand structured error handling instead of PHP's error/exception hooks. - No Performance Profiler: Go has
runtime/pproffor 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 likegrep,ELK, orLokifor log analysis. - Go Time Format: Uses Go's
timepackage 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.
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%+
Apache License 2.0
Neeke.Gao [neeke@php.net]