Skip to content

altafino/logger

Repository files navigation

logger

Go Reference test Go Report Card

A small, opinionated logging library built on the standard library's structured logging package log/slog.

It adds what slog doesn't give you out of the box:

  • Extra levels for servicesHTTP for request logs and CRITICAL for pager-worthy failures, plus an ONLY level for temporary debugging.
  • Pretty, colored terminal output for development, powered by lmittmann/tint, with JSON and logfmt output for production.
  • Runtime-adjustable log level — goroutine-safe, no restart needed.
  • Fan-out to multiple handlers (e.g. colored console + JSON file) via samber/slog-multi.
  • Ready-made net/http middleware for structured request logging (works great with go-chi).
  • Full slog interoperability — levels are plain slog.Level values, every logger wraps a *slog.Logger, and any third-party slog.Handler can be plugged in.

Requires Go 1.26+.

Installation

go get github.com/altafino/logger

Quick start

package main

import "github.com/altafino/logger"

func main() {
	// The default logger writes pretty, colored output to stderr at LevelInfo.
	logger.Info("server started", "port", 8080, "tls", true)
	logger.Warn("connection pool nearly full", "used", 95, "max", 100)
	logger.Error("payment failed", logger.Err(err), "order", 1234)
}

Attributes are alternating key/value pairs (or slog.Attr values), exactly as in slog. logger.Err(err) renders the error in red under the pretty format and as a regular err attribute everywhere else.

Configuring the default logger

Call Init once at startup:

logger.Init(
	logger.WithLevel(logger.LevelDebug),
	logger.WithFormat(logger.FormatJSON), // FormatPretty (default), FormatJSON, FormatText
	logger.WithSource(),                  // add file:line of the call site
	logger.WithAttrs("service", "billing", "version", "1.4.2"),
)

Independent logger instances

log := logger.New(
	logger.WithWriter(logFile),
	logger.WithFormat(logger.FormatJSON),
	logger.WithLevel(logger.LevelHTTP),
)
log.Info("hello", "answer", 42)

reqLog := log.With("request_id", id) // attrs attached to every record
reqLog.Info("cart fetched", "items", 3)

Levels

Levels are ordinary slog.Level values. From most to least verbose:

Level Value Purpose
LevelDebug -4 Development diagnostics
LevelHTTP -2 HTTP request/access logs
LevelInfo 0 Normal operational messages (default minimum)
LevelWarn 4 Unexpected, but not an error
LevelError 8 Operation failed
LevelCritical 12 Failure needing immediate attention
LevelOnly 16 Temporary "print this no matter what" debugging
LevelDisabled max Suppresses all output (as a minimum level)

The rule is the same one slog uses, with no special cases: a record is emitted when its level is ≥ the logger's minimum level. So at the default LevelInfo, Debug and HTTP records are hidden and everything else is shown. Set the minimum to LevelHTTP or LevelDebug to see request logs.

Only — temporary debugging

logger.Only(...) logs at the highest level, so it is always printed (unless logging is disabled). Setting the minimum level to LevelOnly silences everything except Only calls — handy when you want to focus on one spot in a noisy codebase:

logger.SetLevel(logger.LevelOnly) // hush everything else
logger.Only("checkout state", "cart", cart)

Changing the level at runtime

The minimum level is held in a slog.LevelVar: changing it is goroutine-safe, takes effect immediately, and propagates to loggers derived with With/WithGroup.

logger.SetLevel(logger.LevelDebug)   // default logger
log.SetLevel(logger.LevelDisabled)   // a specific instance

Parsing levels from flags / env / config

level, err := logger.ParseLevel(os.Getenv("LOG_LEVEL")) // "debug", "http", "info", "warn",
if err != nil {                                         // "error", "critical", "only", "disabled"
	level = logger.LevelInfo
}
logger.SetLevel(level)

ParseFormat does the same for "pretty", "json" and "text".

Output formats

Format Handler Use case
FormatPretty tint (colored, human) Development terminals (default)
FormatJSON slog.JSONHandler Production log pipelines
FormatText slog.TextHandler logfmt-style key=value

Color is auto-detected (TTY check, honors NO_COLOR) and can be forced with WithColor(true/false). The timestamp layout is configurable with WithTimeFormat(layout).

HTTP middleware

import (
	"github.com/altafino/logger"
	"github.com/altafino/logger/middleware"
	"github.com/go-chi/chi/v5"
	chimw "github.com/go-chi/chi/v5/middleware"
)

r := chi.NewRouter()
r.Use(
	chimw.RequestID,
	middleware.Logger,           // logs via the default logger, or:
	// middleware.New(myLogger), // logs via a specific instance
	chimw.Recoverer,
)

Each request is logged at LevelHTTP with structured attributes: method, status, path, remote, bytes, duration and (when chi's RequestID middleware is installed) request_id. Remember that request logs only appear when the minimum level is LevelHTTP or lower.

Advanced

Multiple destinations (fan-out)

Send pretty output to the terminal and JSON to a file:

jsonFile := slog.NewJSONHandler(f, nil)

logger.Init(
	logger.WithFormat(logger.FormatPretty),
	logger.WithExtraHandlers(jsonFile),
)

The logger's minimum level gates all handlers; each extra handler may filter further on its own.

Bring your own handler

Any slog.Handler works — the logger's dynamic level still applies on top:

log := logger.New(logger.WithHandler(myHandler))

slog interoperability

slog.SetDefault(logger.Default().Slog()) // route plain slog calls through this logger
log.Slog().LogAttrs(ctx, slog.LevelInfo, "msg", slog.Int("n", 1))

Migrating from the pre-slog API

The old API (InitLogger, Settings, InfoLevel, …) still compiles via deprecated aliases, but the semantics were fixed:

  • Level ordering is now strict. Previously Debug (4) was numerically above Critical (3) and Error/Critical were printed regardless of the configured level. Now levels filter purely by severity: at LevelCritical you see only Critical and Only records.
  • Calls take a message first. logger.Info(value1, value2) becomes logger.Info("message", "key1", value1, "key2", value2).
  • logger.Http is deprecated in favor of logger.HTTP.
  • Settings/InitLogger/LoggerSettings are deprecated in favor of Init(...Option); writing to LoggerSettings directly never worked reliably and now has no effect.

License

See LICENSE.

About

Simple log to terminal

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages