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")
}

💻 Run locally

Copy the code above and run it on your machine

© 2026 ByteLearn.dev. Free courses for developers. · Privacy