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

zslogfx

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2026 License: MIT Imports: 14 Imported by: 0

README

zslogfx

CI Go Reference

zslogfx configures a zap-backed standard log/slog logger and owns it for the lifetime of an Uber Fx application. Services use a plain *slog.Logger; zap is only the fast JSON backend, through Uber's official go.uber.org/zap/exp/zapslog handler. Writes are unbuffered by default; buffering is explicit.

Install

go get github.com/uchaloop/zslogfx

Quick start

Load a config, install the logger, and use slog anywhere - including inside fx.Invoke - without passing a logger through function arguments.

# config.toml
[log]
level = "info"
package main

import (
	"log/slog"

	"github.com/uchaloop/confmaker/confx"
	"github.com/uchaloop/zslogfx"
	"go.uber.org/fx"
)

func main() {
    fx.New(
        confx.LoadModule("config.toml"),
        confx.ProvideDefault[zslogfx.Config]("log"),
        zslogfx.Module(),

        fx.Invoke(
            func() {
                // slog.Default is already the zap-backed JSON logger.
                slog.Info("service started", "port", 8080)
            },
        ),
    ).Run()
}

Without a config file, supply Config directly instead of the two confx lines:

fx.Supply(zslogfx.Config{Level: "info"}),
zslogfx.Module(),

Configuration

Config carries passive koanf (file) and env (environment) tags; zslogfx reads no source itself - the application chooses one (confmaker/confx above, any other loader, or plain Go). With the [log] section, the environment variables are LOG_LEVEL, LOG_CALLER_ENABLED, LOG_BUFFER_ENABLED, LOG_BUFFER_SIZE, and LOG_BUFFER_FLUSH_INTERVAL; env overrides file through confmaker/confx.

Module installs the logger as slog.Default and also provides *slog.Logger (and the *Logger that owns it) for code that prefers explicit injection. The logger is built during fx.New - fx.WithLogger and an eager invoke both depend on it - so slog.Default is set before any component runs: the logging sequence is deterministic. Pass Module() at the fx.New root.

Dynamic fields

To stamp a value resolved from the container (a build version, environment name, ...) onto every record, provide an Option constructor through AsOptions - it can depend on anything in the Fx graph:

// application-defined; not part of zslogfx
type BuildInfo struct{ Version string }

fx.New(
    fx.Supply(BuildInfo{Version: "1.2.3"}),
    confx.LoadModule("config.toml"),
    confx.ProvideDefault[zslogfx.Config]("log"),
    zslogfx.Module(),
    zslogfx.AsOptions(func(b BuildInfo) zslogfx.Option {
        return zslogfx.WithFields(slog.String("service.version", b.Version))
    }),
)

For static fields no constructor is needed - supply the option directly: fx.Supply(zslogfx.WithFields(slog.String("service.name", "orders"))). Fields are plain slog.Attr, so application code never imports zap.

Format

One JSON format, ECS-aligned for OpenSearch: @timestamp, log.level, log.logger, message, error.stack_trace, a scalar caller (kept out of the ECS log.origin object so index mappings never conflict), and durations as numeric milliseconds so they aggregate. The format is fixed - there is no encoder override. Because logging goes through slog, a value implementing slog.LogValuer (such as secret.Secret) is masked automatically.

Buffering

Buffering is off by default. Enable it in config or with an option:

zslogfx.WithBuffer(4*1024*1024, time.Second)

At shutdown the module flushes: with buffering it calls BufferedWriteSyncer.Stop (stops the background goroutine and drains pending records); without buffering it calls the core's Sync.

Buffering trades durability for throughput. Records are flushed only on a graceful shutdown (Fx OnStop) or a graph-build error. A crash, os.Exit, panic, OOM-kill, or SIGKILL after the grace period loses up to Size (or one FlushInterval) of records - exactly the logs just before the failure. Leave buffering off unless log volume makes direct stdout writes a bottleneck (e.g. a busy Kubernetes pod), and make sure the app terminates through Fx within terminationGracePeriodSeconds.

Acknowledgements

zslogfx stands on uber-go/zap (and its zap/exp/zapslog bridge) and uber-go/fx. Thanks to their authors and maintainers.

License

MIT.

Documentation

Overview

Package zslogfx builds a standard log/slog logger backed by zap and wires its ownership into an Uber Fx application.

Services use a plain *slog.Logger; zap is only the fast JSON backend, through Uber's official go.uber.org/zap/exp/zapslog handler. The output is one fixed JSON format, ECS-aligned for OpenSearch. Module installs the logger as slog.Default so any code can log without receiving it as an argument, and also provides it for explicit injection. Writes are unbuffered by default; buffering is opt-in.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AsOptions

func AsOptions(ctors ...any) fx.Option

AsOptions provides Option constructors to Module through an Fx value group. Constructors may depend on any other values in the Fx graph.

func Module

func Module() fx.Option

Module installs one application logger as slog.Default, provides *Logger and *slog.Logger, routes Fx events through it, and closes it on application shutdown.

The logger is constructed before any application component: fx.WithLogger and the eager fx.Invoke below both depend on it, so slog.Default is installed during fx.New - before OnStart hooks and application code run. This makes the logging sequence deterministic: no component logs before the logger is ready.

Module must be passed at the fx.New root because fx.WithLogger is scoped to the scope it is declared in.

Types

type BufferConfig

type BufferConfig struct {
	Enabled       bool          `koanf:"enabled" env:"BUFFER_ENABLED"`
	Size          int           `koanf:"size" env:"BUFFER_SIZE"`
	FlushInterval time.Duration `koanf:"flush_interval" env:"BUFFER_FLUSH_INTERVAL"`
}

BufferConfig controls optional write buffering. Buffering is disabled by default. Size and FlushInterval use zap defaults when left at zero.

type CallerConfig

type CallerConfig struct {
	Enabled *bool `koanf:"enabled" env:"CALLER_ENABLED"`
}

CallerConfig controls caller annotations. Unless Enabled is explicitly set, caller annotations are enabled at debug level and disabled otherwise.

type Config

type Config struct {
	Level  string       `koanf:"level" env:"LEVEL"`
	Caller CallerConfig `koanf:"caller"`
	Buffer BufferConfig `koanf:"buffer"`
}

Config is the serializable logger configuration.

The tags are deliberately passive: zslogfx does not read files or the environment. Applications may fill Config with confmaker/confx, another configuration library, or plain Go code. With confmaker, file values are decoded through the koanf tags and environment values override them through the env tags.

func (Config) Validate

func (c Config) Validate() error

Validate checks values that can be validated independently of runtime Options. It is called automatically by confmaker/confx.

type Logger

type Logger struct {
	Slog *slog.Logger
	// contains filtered or unexported fields
}

Logger owns the slog view of the logging core and the resources behind it. zap is only the backend; the logger a service uses is a plain *slog.Logger.

Close is safe to call more than once. Fx applications normally do not need to call it directly; Module registers it with the application lifecycle.

func Make

func Make(cfg Config, opts ...Option) (*Logger, error)

Make builds a logger without installing it as slog.Default.

func (*Logger) Close

func (l *Logger) Close() error

Close flushes pending records and stops buffer resources, when enabled.

type Option

type Option func(*settings)

Option customizes logger construction after Config has been applied.

func WithBuffer

func WithBuffer(size int, flushInterval time.Duration) Option

WithBuffer enables the built-in buffer. Zero values select the defaults: 4 MiB and a one-second flush interval.

func WithCaller

func WithCaller(enabled bool) Option

WithCaller overrides Config.Caller.Enabled.

func WithFields

func WithFields(attrs ...slog.Attr) Option

WithFields adds slog attributes to every record.

func WithLevel

func WithLevel(level string) Option

WithLevel overrides Config.Level.

func WithStacktraceLevel

func WithStacktraceLevel(level slog.Level) Option

WithStacktraceLevel adds a stack trace to records at or above level.

func WithWriteSyncer

func WithWriteSyncer(ws zapcore.WriteSyncer) Option

WithWriteSyncer replaces stdout as the destination. A nil syncer is ignored (stdout stays the default). It does not enable the built-in buffer; wrap the destination yourself when custom buffering is required.

func WithoutBuffer

func WithoutBuffer() Option

WithoutBuffer disables buffering even when Config enables it.

Jump to

Keyboard shortcuts

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