Skip to content

SlavaBatig/godebug

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 ./...

About

Simple debug logger with colorful namespaces and log time

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages