slogs

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: MIT Imports: 17 Imported by: 0

README

slogs

中文文档

A lightweight, zero-external-dependency unified logging package built on Go's standard log/slog (Go 1.21+), providing structured logging, file rotation, multi-target output, and named logger management.

Features

  • Zero external dependencies — Built on log/slog (Go 1.21+ stdlib); file rotation is vendored internally (adapted from lumberjack, MIT License)
  • Dual-target output — Console (stdout) + file, dispatched via a built-in fan-out multiHandler
  • File rotation — Built-in Rotator with size/count/age-based rolling + gzip compression
  • Structured attributesWith() attaches key-value context across all output targets
  • Named loggersManager manages multiple isolated named logger instances
  • Global default — Lazy initialization + package-level convenience functions, ready out of the box
  • Thread-safe — All exported types and functions are safe for concurrent use

Installation

go get github.com/winezer0/slogs

Quick Start

Package-level Functions (Zero Config)
import "github.com/winezer0/slogs"

slogs.Info("server started", "port", 8080)
slogs.Errorf("connection failed: %v", err)
Explicit Initialization
cfg := slogs.NewConfig("debug", "logs/app.log", "json")
if err := slogs.Init(cfg); err != nil {
    log.Fatal(err)
}
slogs.Debug("initialized with file output")
Standalone Logger Instance
cfg := slogs.LogConfig{
    ConsoleLevel: "info",  // console level (empty defaults to "info")
    // FileLevel defaults to "debug" when empty → files capture debug logs
    LogFilePath:  "logs/audit.log",
    MaxSize:      50,
    MaxBackups:   5,
    MaxAge:       14,
    Compress:     true,
}
logger, err := slogs.NewLogger(cfg)
if err != nil {
    log.Fatal(err)
}
defer logger.Close()

// Structured attributes
reqLogger := logger.With("request_id", "abc-123")
reqLogger.Info("processing request", "method", "GET", "path", "/api/scan")

// Context-aware methods forward ctx to the underlying slog.Handler.
reqLogger.InfoContext(ctx, "request completed", "status", 200)
reqLogger.LogAttrs(ctx, slog.LevelDebug, "request details", slog.String("path", "/api/scan"))
Named Loggers (Multi-module Isolation)
// Create
scanLogger, _ := slogs.CreateLogger("scanengine", slogs.NewConfig("info", "logs/scan.log", "json"))
auditLogger, _ := slogs.CreateLogger("auditflow", slogs.NewConfig("debug", "logs/audit.log", "json"))

// Retrieve
logger, ok := slogs.GetLogger("scanengine")

// Close all on shutdown
slogs.CloseAll()
Access Underlying slog.Logger
logger := slogs.Default()
slogLogger := logger.Slog() // *slog.Logger, can be passed to frameworks like eino

The instance logger also exposes DebugContext, InfoContext, WarnContext, ErrorContext, Log, and LogAttrs with the same calling conventions as the standard library slog.Logger. Use Slog() when a business package explicitly depends on the standard logger interface.

Configuration

logging:
  console_level: info   # console: debug | info | warn | error (empty = info)
  file_level: ""        # file: debug | info | warn | error (empty = debug)
  console_format: ""    # console: "" | text | json | off | mask string like "TLCM" (empty = mask "LCM")
  log_file_format: ""   # file: "" | text | json | off | mask string like "CM" (empty = json)
  log_file_path: ""     # log file path; empty = console only
  max_size: 100         # max megabytes per file before rotation
  max_backups: 3        # max number of old files to retain
  max_age: 30           # max days to retain old files
  compress: true        # gzip compress rotated files
Field Type Default Description
ConsoleLevel string "info" Minimum log level for console: debug, info, warn, error
FileLevel string "debug" Minimum log level for file: debug, info, warn, error
ConsoleFormat string "" Console format: text, json, off, or a mask string (e.g. "TLCM"); empty = mask "LCM"
LogFileFormat string "" File format: text, json, off, or a mask string (e.g. "CM"); empty = json
LogFilePath string "" Log file path; empty disables file output
MaxSize int 100 Max MB per file before rotation
MaxBackups int 3 Max old files to retain
MaxAge int 30 Max days to retain old files
Compress bool true Gzip compress rotated files

Console and file levels are independent. For example, ConsoleLevel: "info" with an empty FileLevel (defaults to "debug") keeps the console quiet while files capture debug logs.

"off" semantics: a format field set to "off" disables its target output (console or file). A file target is disabled even when LogFilePath is non-empty. If all targets are disabled, the logger silently discards every record.

NewConfig Convenience Mapping

NewConfig(level, filePath, format) keeps the legacy 3-argument signature and stores the format argument verbatim in ConsoleFormat:

format argument Result
"text", "json", "off" Console text / json / disabled
a mask string, e.g. "TLCM", "CM" Console mask format
"" (empty) Console defaults to mask "LCM"

The file output defaults to LogFileFormat = "" → json when a file path is set.

Log Levels

Level Value Purpose
DEBUG -4 Development debug info
INFO 0 Normal operational status
WARN 4 Recoverable anomalies / degradation
ERROR 8 Errors requiring attention

Output Format

Console text mode (stdout) — actual output for each level:

time=2026-08-01T03:12:20.090+08:00 level=DEBUG source=logger.go:69 msg="debug message" db=users slow=true
time=2026-08-01T03:12:20.116+08:00 level=INFO source=logger.go:72 msg="server started" port=8080
time=2026-08-01T03:12:20.116+08:00 level=WARN source=logger.go:75 msg="disk low" free_gb=1.5
time=2026-08-01T03:12:20.116+08:00 level=ERROR source=logger.go:78 msg="connection failed" err=timeout

Each line: time (ISO8601 with milliseconds), level, source (basename:line, shortened by ReplaceAttr), msg, then any attributes as key=value.

Console json mode (stdout):

{"time":"2026-07-27T18:00:00.000+08:00","level":"INFO","source":{"function":"main.main","file":"main.go","line":42},"msg":"server started","port":8080}

File output (format follows LogFileFormat):

{"time":"2026-07-27T18:00:00.000+08:00","level":"INFO","source":{"function":"main.main","file":"main.go","line":42},"msg":"server started","port":8080}
Mask Format

Mask format renders only the selected fields in a compact single line, controlled by a mask string: T=time, L=level, C=caller, M=message (any combination, e.g. "TLCM", "CM", "M"). Set the mask directly on ConsoleFormat/LogFileFormat — any value that is not text/json/off/empty is treated as a mask string.

cfg := slogs.LogConfig{
    ConsoleLevel:  "info",
    ConsoleFormat: "TLC",   // console: time + level + caller only
    LogFileFormat: "json",  // file: normal json, independent of console
    LogFilePath:   "logs/app.log",
}

Console with ConsoleFormat: "TLCM" (stdout):

2026-07-27T18:00:00+08:00 INFO main.go:42 server started

Console with ConsoleFormat: "M" (stdout):

server started

File Structure

File Responsibility
doc.go Package-level documentation
config.go Config struct, defaults, factory functions
logger.go Logger wrapper, multiHandler, level parsing, file handler
default.go Global default logger + package-level functions
manager.go Named logger registry (create/get/close-all)
mask_handler.go Mask format handler (T/L/C/M field selection)
rotator.go Built-in log file rotator (adapted from lumberjack)
chown.go No-op chown for non-Linux platforms
chown_linux.go Preserve file ownership on Linux

Dependencies

  • log/slog — Go standard library
  • No third-party dependencies (file rotation is vendored in rotator.go, adapted from lumberjack MIT License)

Testing

go test ./... -v -cover

API Documentation

Full API documentation is available at pkg.go.dev/github.com/winezer0/slogs.

License

MIT

Documentation

Overview

Package slogs provides a lightweight, zero-external-dependency unified logging solution built on Go's standard log/slog package (Go 1.21+).

Features

  • Dual-target output: console (stdout) and file (JSON), dispatched via a built-in fan-out multiHandler.
  • File rotation: built-in Rotator (adapted from lumberjack, MIT License) supports size-based rolling, backup retention, age-based cleanup, and gzip compression.
  • Structured attributes: Logger.With attaches key-value context that propagates to all output targets.
  • Named loggers: package-level CreateLogger/GetLogger manage multiple isolated logger instances via a singleton Manager.
  • Global default: lazy-initialized default logger with package-level convenience functions (Info, Debug, Error, etc.).

Quick Start

Zero-configuration usage with the global default logger:

slogs.Info("server started", "port", 8080)
slogs.Errorf("connection failed: %v", err)

Explicit initialization with file output:

cfg := slogs.NewConfig("debug", "logs/app.log", "json")
if err := slogs.Init(cfg); err != nil {
    log.Fatal(err)
}

Standalone logger instance:

logger, err := slogs.NewLogger(slogs.LogConfig{
    ConsoleLevel: "info",
    LogFilePath:  "logs/audit.log",
    MaxSize:      50,
})
if err != nil {
    log.Fatal(err)
}
defer logger.Close()
logger.Info("audit event", "user", "admin")

Named loggers for multi-module isolation:

scanLog, _ := slogs.CreateLogger("scan", slogs.NewConfig("info", "logs/scan.log", "json"))
auditLog, _ := slogs.CreateLogger("audit", slogs.NewConfig("debug", "logs/audit.log", "json"))
defer slogs.CloseAll()

Configuration

The LogConfig struct controls logging behavior:

  • ConsoleLevel: minimum log level for console output ("debug", "info", "warn", "error"; empty defaults to "info").
  • FileLevel: minimum log level for file output ("debug", "info", "warn", "error"; empty defaults to "debug").
  • ConsoleFormat: console output format: "text" or "json"; empty = mask "LCM"; a mask string (e.g. "TLCM") enables mask format; "off" disables console.
  • LogFileFormat: file output format (same options; empty = json).
  • LogFilePath: log file path; empty disables file output.
  • MaxSize: max megabytes per file before rotation (default 100).
  • MaxBackups: max old files to retain (default 3).
  • MaxAge: max days to retain old files (default 30).
  • Compress: gzip-compress rotated files (default true).

Console and file levels are independent: set ConsoleLevel to "info" for a quiet console while FileLevel defaults to "debug" so files capture debug logs.

Thread Safety

All exported types and functions are safe for concurrent use. The Manager uses a read-write mutex; Logger and Rotator use internal synchronization.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func CloseAll

func CloseAll() error

CloseAll closes all managed loggers, closes the default logger, and resets the registry.

func Debug

func Debug(msg string, args ...any)

Debug logs at debug level using the default logger.

func DebugContext added in v0.1.1

func DebugContext(ctx context.Context, msg string, args ...any)

DebugContext logs at debug level using the default logger and context.

func Debugf

func Debugf(template string, args ...any)

Debugf logs a formatted message at debug level using the default logger.

func Error

func Error(msg string, args ...any)

Error logs at error level using the default logger.

func ErrorContext added in v0.1.1

func ErrorContext(ctx context.Context, msg string, args ...any)

ErrorContext logs at error level using the default logger and context.

func Errorf

func Errorf(template string, args ...any)

Errorf logs a formatted message at error level using the default logger.

func Info

func Info(msg string, args ...any)

Info logs at info level using the default logger.

func InfoContext added in v0.1.1

func InfoContext(ctx context.Context, msg string, args ...any)

InfoContext logs at info level using the default logger and context.

func Infof

func Infof(template string, args ...any)

Infof logs a formatted message at info level using the default logger.

func Init

func Init(config LogConfig) error

Init initializes the global default logger with the given configuration. Once the default logger has been initialized (either via Init or lazy init), subsequent calls are no-ops and return nil.

Example

ExampleInit demonstrates initializing the global default logger with file output.

package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/winezer0/slogs"
)

func main() {
	dir, _ := os.MkdirTemp("", "slogs-example")
	defer os.RemoveAll(dir)

	cfg := slogs.NewConfig("debug", filepath.Join(dir, "app.log"), "json")
	if err := slogs.Init(cfg); err != nil {
		fmt.Println("init error:", err)
		return
	}
	slogs.Debug("initialized with file output")
	// Output is written to stdout and the log file.
}

func LogAttrs added in v0.1.1

func LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr)

LogAttrs logs pre-built attributes at the supplied level using the default logger.

func LogContext added in v0.1.1

func LogContext(ctx context.Context, level slog.Level, msg string, args ...any)

LogContext logs at the supplied level using the default logger and context.

func RemoveLogger added in v0.0.3

func RemoveLogger(name string) error

RemoveLogger removes and closes a named logger from the registry.

func SetDefault

func SetDefault(logger *Logger)

SetDefault replaces the global default logger (useful for testing or late configuration). The previous default logger is closed to prevent resource leaks.

Example

ExampleSetDefault demonstrates replacing the global default logger.

package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/winezer0/slogs"
)

func main() {
	dir, _ := os.MkdirTemp("", "slogs-example")
	defer os.RemoveAll(dir)

	cfg := slogs.NewConfig("warn", filepath.Join(dir, "warn.log"), "json")
	logger, err := slogs.NewLogger(cfg)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer logger.Close()

	slogs.SetDefault(logger)
	// Now package-level functions use the new logger.
	slogs.Warn("disk usage high", "percent", 92)
}

func Warn

func Warn(msg string, args ...any)

Warn logs at warn level using the default logger.

func WarnContext added in v0.1.1

func WarnContext(ctx context.Context, msg string, args ...any)

WarnContext logs at warn level using the default logger and context.

func Warnf

func Warnf(template string, args ...any)

Warnf logs a formatted message at warn level using the default logger.

Types

type LogConfig added in v0.1.0

type LogConfig struct {
	// ConsoleLevel is the minimum log level for console output: debug, info, warn, error.
	ConsoleLevel string `yaml:"console_level"`

	// FileLevel is the minimum log level for file output: debug, info, warn, error.
	// Empty string defaults to "debug".
	FileLevel string `yaml:"file_level"`

	// ConsoleFormat is the console (stdout) output format:
	//   ""        - default mask "LCM"
	//   "text"    - slog text (key=value)
	//   "json"    - slog json
	//   "off"     - disable console output
	//   otherwise - mask format (T=time, L=level, C=caller, M=message), e.g. "TLCM", "CM"
	ConsoleFormat string `yaml:"console_format"`

	// LogFileFormat is the file output format (same options as ConsoleFormat;
	// empty defaults to "json").
	LogFileFormat string `yaml:"log_file_format"`
	// LogFilePath is the log file path; empty means no file output.
	LogFilePath string `yaml:"log_file_path"`
	// MaxSize is the maximum size in megabytes of a single log file before rotation.
	MaxSize int `yaml:"max_size"`
	// MaxBackups is the maximum number of old log files to retain.
	MaxBackups int `yaml:"max_backups"`
	// MaxAge is the maximum number of days to retain old log files.
	MaxAge int `yaml:"max_age"`
	// Compress determines whether rotated files are compressed.
	Compress bool `yaml:"compress"`
}

LogConfig holds logging configuration.

func DefaultConfig

func DefaultConfig() LogConfig

DefaultConfig 创建日志配置实例,提供全部默认值

Example

ExampleDefaultConfig demonstrates obtaining the default configuration.

package main

import (
	"fmt"

	"github.com/winezer0/slogs"
)

func main() {
	cfg := slogs.DefaultConfig()
	fmt.Println(cfg.ConsoleLevel)
	fmt.Println(cfg.FileLevel)
	fmt.Println(cfg.MaxSize)
}
Output:
info
debug
100

func NewConfig

func NewConfig(level, filePath, format string) LogConfig

NewConfig creates a LogConfig with the given console level, file path, and console format, applying defaults for rotation parameters. FileLevel is left empty (defaults to "debug" at runtime).

The format argument is stored verbatim in ConsoleFormat:

  • "text", "json", "off" → console text / json / disabled
  • a mask string (e.g. "TLCM", "CM") → console mask format
  • empty → console defaults to mask "LCM"
Example

ExampleNewConfig demonstrates creating a configuration with custom parameters.

package main

import (
	"fmt"

	"github.com/winezer0/slogs"
)

func main() {
	cfg := slogs.NewConfig("error", "/var/log/myapp.log", "json")
	fmt.Println(cfg.ConsoleLevel)
	fmt.Println(cfg.FileLevel)
	fmt.Println(cfg.LogFilePath)
	fmt.Println(cfg.Compress)
}
Output:
error
debug
/var/log/myapp.log
true

type Logger

type Logger struct {
	// contains filtered or unexported fields
}

Logger wraps slog.Logger with configuration and lifecycle management.

func CreateLogger

func CreateLogger(name string, config LogConfig) (*Logger, error)

CreateLogger creates and registers a named logger.

Example

ExampleCreateLogger demonstrates creating and retrieving named loggers.

package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/winezer0/slogs"
)

func main() {
	dir, _ := os.MkdirTemp("", "slogs-example")
	defer os.RemoveAll(dir)
	defer slogs.CloseAll()

	_, err := slogs.CreateLogger("scan", slogs.NewConfig("info", filepath.Join(dir, "scan.log"), "off"))
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	logger, ok := slogs.GetLogger("scan")
	fmt.Println("retrieved:", ok)
	logger.Info("scan completed", "targets", 42)
}
Output:
retrieved: true

func Default

func Default() *Logger

Default returns the global default logger, initializing it if necessary.

Example

ExampleDefault demonstrates using the global default logger with zero configuration.

package main

import (
	"github.com/winezer0/slogs"
)

func main() {
	logger := slogs.Default()
	logger.Info("using default logger", "version", "1.0.0")
}

func GetLogger

func GetLogger(name string) (*Logger, bool)

GetLogger retrieves a previously created named logger.

func NewLogger

func NewLogger(config LogConfig) (*Logger, error)

NewLogger creates a Logger from the given configuration. Console output goes to stdout; file output (if configured) uses rotation. A format value of "off" disables the corresponding target. If every target is disabled, the logger silently discards all records.

Console level comes from ConsoleLevel (empty defaults to "info"); file level comes from FileLevel (empty defaults to "debug").

Example

ExampleNewLogger demonstrates creating a standalone logger instance.

package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/winezer0/slogs"
)

func main() {
	dir, _ := os.MkdirTemp("", "slogs-example")
	defer os.RemoveAll(dir)

	cfg := slogs.LogConfig{
		ConsoleLevel: "info",
		LogFilePath:  filepath.Join(dir, "audit.log"),
		MaxSize:      50,
		MaxBackups:   5,
		MaxAge:       14,
		Compress:     true,
	}
	logger, err := slogs.NewLogger(cfg)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	defer logger.Close()

	logger.Info("audit event", "user", "admin", "action", "login")
}

func (*Logger) Close

func (l *Logger) Close() error

Close flushes and releases file resources.

func (*Logger) Debug

func (l *Logger) Debug(msg string, args ...any)

Debug logs at debug level.

func (*Logger) DebugContext added in v0.1.1

func (l *Logger) DebugContext(ctx context.Context, msg string, args ...any)

DebugContext logs at debug level with the supplied context.

func (*Logger) Debugf

func (l *Logger) Debugf(template string, args ...any)

Debugf logs a formatted message at debug level.

func (*Logger) Enabled added in v0.1.1

func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool

Enabled reports whether any configured output accepts the level.

func (*Logger) Error

func (l *Logger) Error(msg string, args ...any)

Error logs at error level.

func (*Logger) ErrorContext added in v0.1.1

func (l *Logger) ErrorContext(ctx context.Context, msg string, args ...any)

ErrorContext logs at error level with the supplied context.

func (*Logger) Errorf

func (l *Logger) Errorf(template string, args ...any)

Errorf logs a formatted message at error level.

func (*Logger) Info

func (l *Logger) Info(msg string, args ...any)

Info logs at info level.

func (*Logger) InfoContext added in v0.1.1

func (l *Logger) InfoContext(ctx context.Context, msg string, args ...any)

InfoContext logs at info level with the supplied context.

func (*Logger) Infof

func (l *Logger) Infof(template string, args ...any)

Infof logs a formatted message at info level.

func (*Logger) Log added in v0.1.1

func (l *Logger) Log(ctx context.Context, level slog.Level, msg string, args ...any)

Log records a message at the supplied level.

func (*Logger) LogAttrs added in v0.1.1

func (l *Logger) LogAttrs(ctx context.Context, level slog.Level, msg string, attrs ...slog.Attr)

LogAttrs records a message with pre-built attributes at the supplied level.

func (*Logger) Slog

func (l *Logger) Slog() *slog.Logger

Slog returns the underlying *slog.Logger for direct use.

func (*Logger) Warn

func (l *Logger) Warn(msg string, args ...any)

Warn logs at warn level.

func (*Logger) WarnContext added in v0.1.1

func (l *Logger) WarnContext(ctx context.Context, msg string, args ...any)

WarnContext logs at warn level with the supplied context.

func (*Logger) Warnf

func (l *Logger) Warnf(template string, args ...any)

Warnf logs a formatted message at warn level.

func (*Logger) With

func (l *Logger) With(args ...any) *Logger

With returns a Logger with the given attributes attached.

Example

ExampleLogger_With demonstrates attaching structured attributes to a logger.

package main

import (
	"github.com/winezer0/slogs"
)

func main() {
	logger := slogs.Default()
	reqLogger := logger.With("request_id", "abc-123", "service", "gateway")
	reqLogger.Info("processing request", "method", "GET", "path", "/api/users")
}

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager manages named logger instances.

func (*Manager) CloseAll

func (m *Manager) CloseAll() error

CloseAll closes all managed loggers, closes the default logger, and clears the registry.

func (*Manager) Create

func (m *Manager) Create(name string, config LogConfig) (*Logger, error)

Create creates and registers a named logger.

func (*Manager) Get

func (m *Manager) Get(name string) (*Logger, bool)

Get retrieves a named logger.

func (*Manager) Remove added in v0.0.3

func (m *Manager) Remove(name string) error

Remove removes and closes a named logger from the registry. Returns an error if the logger does not exist.

type Rotator

type Rotator struct {
	// Filename is the file to write logs to. Backup log files will be retained
	// in the same directory.
	Filename string

	// MaxSize is the maximum size in megabytes of the log file before rotation.
	// Defaults to 100 megabytes.
	MaxSize int

	// MaxAge is the maximum number of days to retain old log files.
	MaxAge int

	// MaxBackups is the maximum number of old log files to retain.
	MaxBackups int

	// LocalTime determines if the time used for formatting the timestamps in
	// backup files is the computer's local time. Default is UTC.
	LocalTime bool

	// Compress determines if rotated log files should be compressed using gzip.
	Compress bool
	// contains filtered or unexported fields
}

Rotator is an io.WriteCloser that writes to a rotating log file.

Rotator opens or creates the logfile on first Write. If the file exists and is less than MaxSize megabytes, it will open and append to that file. If the file exists and its size is >= MaxSize megabytes, the file is renamed by putting the current time in a timestamp in the name immediately before the file's extension. A new log file is then created using original filename.

Backups use the form `name-timestamp.ext` where timestamp is formatted with `2006-01-02T15-04-05.000`.

func (*Rotator) Close

func (r *Rotator) Close() error

Close implements io.Closer, and closes the current logfile.

func (*Rotator) Rotate

func (r *Rotator) Rotate() error

Rotate causes Rotator to close the existing log file and immediately create a new one. After rotating, this initiates compression and removal of old log files according to the configuration.

func (*Rotator) Write

func (r *Rotator) Write(p []byte) (n int, err error)

Write implements io.Writer. If a write would cause the log file to exceed MaxSize, the file is closed, renamed with a timestamp, and a new file created.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL