Help us shape the future of Go! Take the 2026 Go Developer Survey

glogs

package module
v0.0.0-...-244342c Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 16 Imported by: 0

README

English | 简体中文

glogs

Structured logging for Go services with named channels, rolling files, ANSI colors and request context fields.

Status: development snapshot (untagged) — usable core; API may still change before v1.0. No tags are published until a stable release; use commit pseudo-versions. License: MIT.

Features

  • Named channels: access, default, depend, app, error, event (configurable; each channel can have its own file and level).
  • Rolling files: size + date based rotation, max age and total size cap, file naming <channel>.YYYY-MM-DD.<index>.log.
  • ANSI colors on console (auto/on/off, NO_COLOR/FORCE_COLOR aware).
  • Structured text or JSON output.
  • Context fields: request-scoped attributes via glogs.WithFields.
  • Stat helper: `key=value ...` lines.
  • HTTP access middleware (glogs/access): method, URI, status, latency, client IP, trace id, optional body capture.

Bridging other loggers

Third-party packages rarely log through slog. glogs/bridge turns their output into channel records so one process keeps one format, one file set and one redaction policy:

// logrus / zerolog / zap, or anything that writes to an io.Writer
logrus.SetOutput(bridge.NewWriter(logger, bridge.Options{Source: "logrus", SniffLevel: true}))

// the standard library: one call covers every package that calls log.Printf
restore := bridge.Stdlib(logger, bridge.Options{Channel: glogs.ChannelDepend, Source: "stdlib"})
defer restore()

A bridge sees lines, so it cannot recover structured fields; prefer a native adapter when a library offers one (slog, zapcore.Core, a logrus Hook). See bridge/README.md for the rules, including the one that matters most: message text is not redacted, only attributes are.

Failure visibility

slog discards the error a handler returns, so a full disk used to lose lines with no signal. A channel now reports it two ways:

logger, _ := glogs.New(glogs.Config{
    // ...
    OnError: func(channel glogs.Channel, err error) { alert(channel, err) },
})

stats, _ := logger.FileStats(glogs.ChannelApp)
// stats.Errors, stats.LastErr, stats.Bytes

FileStats(file).Errors > 0 with a small Bytes is the signature of a broken disk rather than a quiet service.

Redaction

Nothing masks values unless you ask. A logged credential lives in the file forever, so production should set a redactor:

logger, err := glogs.New(glogs.Config{
    AppName:  "xdmp",
    Redactor: glogs.NewMaskRedactor("", "deviceSecret"), // "" = default mask
})

It matches attribute keys case-insensitively against a deny list (password, *token*, authorization, cookie, idCard, … plus extra keys) and masks JSON bodies field by field at any depth. Groups are walked too, so slog.Group("auth", "accessToken", …) is covered.

The access middleware applies the same policy to captured bodies:

access.Middleware(logger,
    access.WithBodyLimit(1024),
    access.WithBodyRedactor(func(body string) string {
        return redactor.RedactBody([]byte(body), false)
    }),
    access.WithLoggableContentTypes("application/json", "text/"),
)

Bodies are only captured for allow-listed content types; an undeclared or binary type is skipped rather than guessed at.

Async writes

By default a log line is written synchronously: a slow disk delays the request that logged it. Set Async on a channel to move the write off the caller's goroutine:

Channels: map[glogs.Channel]glogs.ChannelConfig{
    glogs.ChannelApp: {Enabled: true, Async: true, AsyncBuffer: 4096, Path: "logs/app.log"},
}

The queue is a hard bound. When it is full the line is dropped and counted — never queued without limit, never blocking the request. That trade-off is visible:

stats, err := logger.AsyncStats(glogs.ChannelApp)
// stats.Dropped, stats.Errors, stats.LastErr, stats.Pending

Close drains the queue (bounded by a 5s timeout) before closing the files, so shutdown does not silently discard accepted lines.

Install

go get github.com/maxid/glogs

Quick start

logger, err := glogs.New(glogs.DefaultConfig("my-service"))
if err != nil {
    panic(err)
}
defer logger.Close()

logger.Root().Info("console output")
logger.Channel(glogs.ChannelApp).Info("app event", "deviceKey", "dev-1")
logger.Channel(glogs.ChannelError).Error("connect failed", "err", err)

ctx := glogs.WithFields(context.Background(), slog.String("trace_id", "abc"))
logger.Channel(glogs.ChannelDefault).InfoContext(ctx, "with context fields")

Default file layout (relative to the working directory):

logs/<appName>/common/access/access.log
logs/<appName>/common/default/default.log
logs/<appName>/common/depend/depend.log
logs/<appName>/common/app/app.log
logs/<appName>/common/error/error.log
logs/<appName>/common/event/event.log

Defaults per channel: 50 MiB per file, 7 days history, 500 MiB total cap. access is also mirrored to the console.

Channels

Channel Purpose
access HTTP/request access logs
default general application logs
depend dependency/RPC logs
app business/application events
error errors (also usable for alerting pipelines)
event lifecycle/audit-style events

Access middleware

handler := glogsaccess.Middleware(logger,
    glogsaccess.WithSkip(func(r *http.Request) bool { return r.URL.Path == "/healthz" }),
    glogsaccess.WithBodyLimit(4096),
)(mux)

Config

cfg := glogs.DefaultConfig("my-service")
cfg.Root = glogs.ConsoleConfig{Enabled: true, Format: glogs.FormatText, Color: glogs.ColorAuto}
cfg.Channels[glogs.ChannelAccess] = glogs.ChannelConfig{
    Enabled: true, Level: slog.LevelInfo,
    Path: "logs/my-service/common/access/access.log",
    Console: true, MaxSize: 50 << 20, MaxAge: 7, TotalCap: 500 << 20,
}

Limits / roadmap

  • Async buffered appender, gzip compression, YAML config loader and OpenTelemetry auto-injection are planned (see ROADMAP.md).
  • Database-backed audit logs are out of scope for a process logger.

License

MIT. See LICENSE.

Documentation

Overview

Package glogs is a small structured logging facade for Go services.

It provides named channels, per-channel rolling files, ANSI colored console output, structured text/JSON output and request context fields.

Index

Constants

View Source
const DefaultMask = "***"

DefaultMask replaces a redacted value.

View Source
const Version = "0.0.0-dev"

Version is the semantic version of this library.

Variables

DefaultChannels is the default channel set.

View Source
var DefaultSensitiveKeys = []string{
	"password", "passwd", "pwd", "secret", "token", "accesstoken", "refreshtoken",
	"verifytoken", "authorization", "cookie", "set-cookie", "credential",
	"privatekey", "apikey", "api-key", "sessionid", "session", "salt",
	"idcard", "idnumber", "bankcard", "cvv",

	"密码", "令牌", "密钥",
}

DefaultSensitiveKeys is the deny list applied by key name, case-insensitively. It is intentionally coarse: a false positive costs a masked debug field, a false negative writes a credential to disk.

View Source
var ErrWriterClosed = errors.New("glogs: writer is closed")

ErrWriterClosed means a write arrived after Close.

Functions

func WithFields

func WithFields(ctx context.Context, attrs ...slog.Attr) context.Context

WithFields returns a context carrying extra structured fields that handlers append to every record. This is the Go replacement for LogContext/MDC.

Types

type AsyncStats

type AsyncStats struct {
	Written int64
	Dropped int64
	Errors  int64
	Pending int
	LastErr string
}

AsyncStats reports what an async writer did. Dropping is lossy by design: a log line must never block a request goroutine, and the queue is bounded so memory cannot grow without limit. The counter is how that trade-off stays visible.

type Channel

type Channel string

Channel is a named log channel.

const (
	ChannelAccess  Channel = "access"
	ChannelDefault Channel = "default"
	ChannelDepend  Channel = "depend"
	ChannelApp     Channel = "app"
	ChannelError   Channel = "error"
	ChannelEvent   Channel = "event"
)

type ChannelConfig

type ChannelConfig struct {
	Enabled  bool
	Level    slog.Level
	Path     string
	Console  bool
	MaxSize  int64 // bytes per file, default 50 MiB
	MaxAge   int   // days, default 7
	TotalCap int64 // bytes, default 500 MiB

	// Async moves the file write off the caller's goroutine. The queue is
	// bounded by AsyncBuffer; when it is full the line is dropped and counted
	// rather than blocking the request. Off by default so an existing deployment
	// keeps its current durability.
	Async bool
	// AsyncBuffer is the queue depth when Async is set. Default 1024.
	AsyncBuffer int
}

ChannelConfig configures one channel's file output and rotation.

type ColorMode

type ColorMode int

ColorMode controls ANSI colors on the console.

const (
	ColorAuto ColorMode = iota
	ColorOn
	ColorOff
)

type Config

type Config struct {
	AppName  string
	Dir      string // base directory, default "./logs"
	Root     ConsoleConfig
	Level    slog.Level
	Channels map[Channel]ChannelConfig

	// Redactor masks sensitive attributes and bodies. Nil disables redaction,
	// which is only appropriate for trusted local debugging: a logged credential
	// lives in the file forever.
	Redactor Redactor

	// OnError is told about every file write, rotation and cleanup failure.
	// slog discards handler errors, so this is the only way a broken disk becomes
	// visible instead of silently dropping log lines.
	OnError func(channel Channel, err error)
}

Config is the top-level logging configuration.

func DefaultConfig

func DefaultConfig(appName string) Config

DefaultConfig returns a configuration matching the platform defaults.

type ConsoleConfig

type ConsoleConfig struct {
	Enabled bool
	Format  Format
	Color   ColorMode
}

ConsoleConfig configures console output.

type FileStats

type FileStats struct {
	Errors  int64
	LastErr string
	Bytes   int64
}

FileStats reports the failures a rolling writer has seen. A non-zero Errors with an empty log file is the signature of a broken disk, not of a quiet service.

type Format

type Format int

Format is the output format.

const (
	FormatText Format = iota
	FormatJSON
)

type Logger

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

Logger holds the root console logger and the named channel loggers.

func New

func New(cfg Config) (*Logger, error)

New builds a Logger from cfg.

func (*Logger) AsyncStats

func (l *Logger) AsyncStats(channel Channel) (AsyncStats, error)

AsyncStats reports drop and error counters for one channel. It returns an error when that channel does not use an async writer.

func (*Logger) Channel

func (l *Logger) Channel(name Channel) *slog.Logger

Channel returns the logger for a named channel. Unknown channels fall back to the root logger.

func (*Logger) Close

func (l *Logger) Close() error

Close flushes and closes all file writers. Close drains async writers, then closes the files. The drain is bounded: a disk that stopped accepting writes must not hang shutdown, and unsent lines are reported through AsyncStats instead of disappearing silently.

func (*Logger) FileStats

func (l *Logger) FileStats(channel Channel) (FileStats, error)

FileStats reports the write and rotation failures of one channel's file. It returns an error when that channel writes no file.

This is the answer to "slog ignores handler errors": a full disk shows up here (and through Config.OnError) instead of disappearing.

func (*Logger) Root

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

Root returns the root logger (console).

func (*Logger) Stat

func (l *Logger) Stat() *Stat

Stat returns a new Stat builder.

type MaskRedactor

type MaskRedactor struct {
	// Keys extends DefaultSensitiveKeys.
	Keys []string
	// Mask is the replacement text; DefaultMask when empty.
	Mask string
	// MaxBodyBytes bounds how much of a body is walked or returned.
	MaxBodyBytes int
}

MaskRedactor is the default Redactor: substring match on key names plus a JSON-field walker for bodies.

func NewMaskRedactor

func NewMaskRedactor(mask string, extraKeys ...string) *MaskRedactor

NewMaskRedactor builds a redactor with the default deny list plus extra keys.

func (*MaskRedactor) RedactBody

func (r *MaskRedactor) RedactBody(body []byte, maskAll bool) string

RedactBody implements Redactor.

A body that cannot be parsed as JSON is masked entirely when maskAll is set (a non-JSON body might be anything, so keeping it would defeat the purpose), and returned untouched otherwise. The size bound keeps a large upload from being copied into the log path.

func (*MaskRedactor) RedactValue

func (r *MaskRedactor) RedactValue(key string, value any) any

RedactValue implements Redactor.

func (*MaskRedactor) Sensitive

func (r *MaskRedactor) Sensitive(key string) bool

Sensitive implements Redactor.

type Redactor

type Redactor interface {
	// RedactValue masks a value whose key matched (or returns it unchanged).
	RedactValue(key string, value any) any
	// Sensitive reports whether an attribute key is sensitive.
	Sensitive(key string) bool
	// RedactBody masks the sensitive fields of a JSON request/response body and
	// returns the rewritten body. Bodies that are not JSON are returned as is
	// unless maskAll is set.
	RedactBody(body []byte, maskAll bool) string
}

Redactor decides what must not reach a log file, and what a masked value looks like. The default implementation covers attribute keys and JSON bodies; a deployment can replace it.

type Stat

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

Stat builds a `key=value ...` line.

func (*Stat) Add

func (s *Stat) Add(key string, value any) *Stat

Add appends a key/value pair.

func (*Stat) Log

func (s *Stat) Log(logger *slog.Logger)

Log writes the stat line to the logger.

Directories

Path Synopsis
Package access provides HTTP access logging middleware backed by glogs.
Package access provides HTTP access logging middleware backed by glogs.
Package bridge captures the output of loggers that are not slog-based.
Package bridge captures the output of loggers that are not slog-based.
examples
basic command

Jump to

Keyboard shortcuts

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