Skip to content

Latest commit

 

History

History
167 lines (128 loc) · 5.54 KB

File metadata and controls

167 lines (128 loc) · 5.54 KB

godebug

Tiny namespaced debug logger for Go. Inspired by the npm debug package.

  • Color-coded namespace, auto-derived from FNV hash of the name
  • Per-call millisecond diff since previous log
  • Production-safe: ANSI color auto-disabled when output is not a TTY, or forced off with NO_COLOR=1 — no escape sequences in logs piped to files
  • JSON output with DEBUG_FORMAT=json for Datadog / log shippers
  • Printf-style Logf / Errorf with syntax-highlighted JSON for struct / map / slice arguments
  • Namespace filter via GO_DEBUG=app:*,-app:audit (npm-debug style)
  • Safe for concurrent use

Install

go get github.com/SlavaBatig/godebug

Usage

package main

import debug "github.com/SlavaBatig/godebug"

func main() {
    log := debug.New("app:auth")

    log.Debug("session lookup starting")
    log.Info("server starting")
    log.Infof("user %s logged in from %s", "alice", "192.168.1.1")
    log.Warn("rate limit close to threshold")

    type session struct {
        UserID int    `json:"user_id"`
        Token  string `json:"token"`
    }
    log.Infof("session=%v", session{UserID: 42, Token: "abc"})

    log.Errorf("auth failed: %v", map[string]any{"reason": "expired"})
    // log.Fatal("db dead") // would write + os.Exit(1)
}

Environment variables

Var Effect
NO_COLOR Any non-empty value disables ANSI color codes
DEBUG_FORMAT json switches output to newline-delimited JSON
GO_DEBUG Namespace filter (npm-debug style). Unset = log everything.

Color is also automatically disabled when stdout / stderr is not a terminal (pipe, file redirect, captured CI output), so production logs stay clean.

Namespace filter (GO_DEBUG)

Filter loggers by namespace at process start. Patterns are comma- or whitespace-separated. * is a wildcard. A - prefix turns a pattern into a skip.

GO_DEBUG='app:*'              # only app:* (including app:auth:retry)
GO_DEBUG='app:auth,db:*'      # exact app:auth plus everything under db
GO_DEBUG='*,-server:*'        # everything except server:*
GO_DEBUG='-app:audit'         # everything except app:audit
GO_DEBUG=                     # (unset / empty) log everything

Filtered-out loggers short-circuit on the first byte of every call: ~1 ns/op with zero allocations, so it's safe to leave verbose debug.Logf calls compiled into production code and toggle them via env. Use logger.Enabled() to guard expensive argument assembly:

if log.Enabled() {
    log.Logf("snapshot=%v", buildExpensiveSnapshot())
}

The filter can also be set explicitly via Options.Filter, which overrides GO_DEBUG (useful for tests and library configuration).

JSON output

DEBUG_FORMAT=json ./your-app

Each line is a single JSON object — exactly one newline per entry, all control characters and newlines inside values escaped by encoding/json. Safe for Datadog, Sumo, Splunk, Vector, Fluent Bit, and any other newline-delimited JSON consumer.

{
  "timestamp": "2026-05-20T12:00:00.123456789Z",
  "namespace": "app:auth",
  "level": "info",
  "message": "user alice logged in",
  "diff_ms": 12,
  "params": ["alice"]
}

params is populated for Logf / Errorf calls so structured fields are preserved for indexing.

Colored JSON (fancy local dev)

If you point a JSON-mode logger at a terminal, output is syntax-highlighted (keys cyan, strings green, numbers yellow, bools/null magenta, punctuation dim). The same TTY heuristic that disables colored text mode disables this when output is piped or redirected, so your-app | tee app.log still emits plain JSON suitable for ingestion. NO_COLOR=1 forces it off either way.

API

type Logger struct { /* ... */ }

func New(namespace string) *Logger
func NewWith(namespace string, opts Options) *Logger

// Plain.
func (l *Logger) Debug(messages ...string)
func (l *Logger) Info(messages ...string)
func (l *Logger) Warn(messages ...string)
func (l *Logger) Error(messages ...string)
func (l *Logger) Fatal(messages ...string) // writes then os.Exit(1)

// Printf-style.
func (l *Logger) Debugf(format string, args ...any)
func (l *Logger) Infof(format string, args ...any)
func (l *Logger) Warnf(format string, args ...any)
func (l *Logger) Errorf(format string, args ...any)
func (l *Logger) Fatalf(format string, args ...any) // writes then os.Exit(1)

func (l *Logger) Enabled() bool

Levels

Level Stream Text suffix Notes
debug stdout :debug
info stdout (none) Default — namespace shown clean
warn stderr :warn
error stderr :error
fatal stderr :fatal Always calls os.Exit(1) after writing

Fatal / Fatalf exit unconditionally — even when the namespace is filtered out via GO_DEBUG, the exit still happens. The filter controls verbosity, not whether the process dies.

Options lets callers override the writers, format, color, filter, and clock — see debug.go for details.

Tests & benchmarks

go test -race ./...
go test -bench=. -benchmem ./...