slogr — Pretty Logger Package
A reusable logging package with three modes: colorized pretty output for development, text, and JSON for production.
prettyhandler.go
package slogr
import (
"context"
"fmt"
"log/slog"
"os"
"runtime"
"strings"
)
const (
reset = "\033[0m"
red = "\033[31m"
yellow = "\033[33m"
green = "\033[32m"
cyan = "\033[36m"
)
// PrettyHandler prints colorized, human-friendly log lines.
// Use it during development only — not for production.
type PrettyHandler struct {
level slog.Level
}
func colorLevel(level slog.Level) string {
switch level {
case slog.LevelError:
return red + level.String() + reset
case slog.LevelWarn:
return yellow + level.String() + reset
case slog.LevelInfo:
return green + level.String() + reset
case slog.LevelDebug:
return cyan + level.String() + reset
default:
return level.String()
}
}
func (h *PrettyHandler) Enabled(_ context.Context, level slog.Level) bool {
return level >= h.level
}
func (h *PrettyHandler) Handle(_ context.Context, r slog.Record) error {
ts := r.Time.Format("2006-01-02 15:04:05")
level := colorLevel(r.Level)
var attrs string
r.Attrs(func(a slog.Attr) bool {
attrs += fmt.Sprintf(" %s=%v", a.Key, a.Value)
return true
})
var source string
pc := make([]uintptr, 1)
n := runtime.Callers(6, pc)
if n > 0 {
frames := runtime.CallersFrames(pc)
if frame, _ := frames.Next(); frame.File != "" {
parts := strings.Split(frame.File, "/")
if len(parts) >= 2 {
source = fmt.Sprintf(" %s/%s:%d", parts[len(parts)-2], parts[len(parts)-1], frame.Line)
}
}
}
fmt.Fprintf(os.Stdout, "%s %s%s %s%s\n", level, ts, source, r.Message, attrs)
return nil
}
// WithAttrs and WithGroup are no-ops — this handler is for simple dev output.
// Attributes passed via slog.With() won't appear. Use text or JSON for that.
func (h *PrettyHandler) WithAttrs(attrs []slog.Attr) slog.Handler { return h }
func (h *PrettyHandler) WithGroup(name string) slog.Handler { return h }slogr.go
package slogr
import (
"context"
"fmt"
"log/slog"
"os"
"runtime"
)
type LoggerType = string
type Level = slog.Level
const (
LevelDebug = slog.LevelDebug
LevelInfo = slog.LevelInfo
LevelWarn = slog.LevelWarn
LevelError = slog.LevelError
)
const (
TypeText LoggerType = "TEXT"
TypeJSON LoggerType = "JSON"
TypePretty LoggerType = "PRETTY"
)
// callerCorrectingHandler adjusts the source location so log lines
// point to the actual call site, not the wrapper function.
type callerCorrectingHandler struct {
handler slog.Handler
skip int
}
func (h *callerCorrectingHandler) Enabled(ctx context.Context, level slog.Level) bool {
return h.handler.Enabled(ctx, level)
}
func (h *callerCorrectingHandler) Handle(ctx context.Context, r slog.Record) error {
if r.PC == 0 {
var pcs [1]uintptr
runtime.Callers(4+h.skip, pcs[:])
r.PC = pcs[0]
}
return h.handler.Handle(ctx, r)
}
func (h *callerCorrectingHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
return &callerCorrectingHandler{handler: h.handler.WithAttrs(attrs), skip: h.skip}
}
func (h *callerCorrectingHandler) WithGroup(name string) slog.Handler {
return &callerCorrectingHandler{handler: h.handler.WithGroup(name), skip: h.skip}
}
// SetupLog configures the default slog logger.
// Use TypePretty for development, TypeJSON for production.
func SetupLog(level Level, loggerType LoggerType) {
var baseHandler slog.Handler
switch loggerType {
case TypeJSON:
baseHandler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
Level: level,
})
case TypePretty:
baseHandler = &PrettyHandler{level: level}
default:
baseHandler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true,
Level: level,
})
}
handler := &callerCorrectingHandler{handler: baseHandler, skip: 1}
slog.SetDefault(slog.New(handler))
}
// Convenience wrappers — structured and formatted variants.
func Info(msg string, args ...any) { slog.Info(msg, args...) }
func Debug(msg string, args ...any) { slog.Debug(msg, args...) }
func Warn(msg string, args ...any) { slog.Warn(msg, args...) }
func Error(msg string, args ...any) { slog.Error(msg, args...) }
func Infof(format string, a ...any) { slog.Info(fmt.Sprintf(format, a...)) }
func Debugf(format string, a ...any) { slog.Debug(fmt.Sprintf(format, a...)) }
func Warnf(format string, a ...any) { slog.Warn(fmt.Sprintf(format, a...)) }
func Errorf(format string, a ...any) { slog.Error(fmt.Sprintf(format, a...)) }
func Fatal(msg string, args ...any) {
slog.Error(msg, args...)
os.Exit(1)
}
func Fatalf(format string, a ...any) {
slog.Error(fmt.Sprintf(format, a...))
os.Exit(1)
}
func Panic(msg string, args ...any) {
slog.Error(msg, args...)
panic(msg)
}
func Panicf(format string, a ...any) {
msg := fmt.Sprintf(format, a...)
slog.Error(msg)
panic(msg)
}Usage
package main
import "myproject/pkg/slogr"
func main() {
// Development — colorized, human-friendly
slogr.SetupLog(slogr.LevelDebug, slogr.TypePretty)
slogr.Info("server starting", "port", 8080)
slogr.Debug("loading config")
// Production — structured JSON
slogr.SetupLog(slogr.LevelInfo, slogr.TypeJSON)
slogr.Info("server starting", "port", 8080)
slogr.Fatal("database unreachable", "error", "connection refused")
}