Skip to content

ysmood/glog

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Overview

  • Just slog with sane defaults and helpers, use it with your existing code.
  • Best practice defaults for both development and production usage.
  • Use code to enforce the best practice, not doc.
  • Support opentelemetry traces and metrics.
  • Helpers for testing.
  • Environment variable configuration with sane defaults.
package main

import (
	"log/slog"
	"github.com/ysmood/glog"
)

func main() {
	glog.SetupDefaultSlog()

	slog.Info("hello world", "key", "value")
}

Tutorial

A walk-through of glog built around the runnable examples under examples/. Each section maps to one example.

1. Basic logging — examples/01_basic

The smallest useful program: call glog.SetupDefaultSlog() once at startup, then log through glog/pkg/lg, which is a context-aware wrapper over slog.

ctx := context.TODO()
glog.SetupDefaultSlog()

lg.Info(ctx, "test", "key", "value")
// 4:11PM INF basic/main.go:13 test key=value

lg.Error(ctx, "msg", "err", io.ErrClosedPipe)
// 4:11PM ERR basic/main.go:16 msg err="io: read/write on closed pipe"

Key points:

  • SetupDefaultSlog picks a human-friendly format in development and a structured format in production (chosen via environment variables and OS settings, such as GCP or docker container).
  • Always pass a context.Context as the first argument — it carries trace IDs and extra attributes (see later sections).
  • The source file and line are added automatically.

2. Additional outputs — examples/02_additional_output

By default logs go to stdout. Add rotating file outputs with glog.AddDefaultOutput:

glog.SetupDefaultSlog()

glog.AddDefaultOutput(glog.StripWriterColor(
    &timberjack.Logger{Filename: "tmp/a/b/c.log"},
))

lg.Info(context.TODO(), "test", "key", "value")
// Writes to stdout, tmp/a/b/c.log, and tmp/a/b/c/app.log
  • StripWriterColor removes ANSI color codes so files stay clean.
  • Bring your own io.Writer (e.g. timberjack.Logger) when you want custom rotation rules.

3. Context-linked logs — examples/03_logs_linkage

When many goroutines work on different items concurrently, correlating their logs is painful if you have to pass identifiers to every call. glog.ContextWithAttrs attaches attributes to a context so every log using that context inherits them.

This is especially useful if you don't want to use heavyweight tracing service like OpenTelemetry. Most of the time this kind of plain textual logging is sufficient for debugging and monitoring.

func process(ctx context.Context, task Task) {
    ctx = glog.ContextWithAttrs(ctx, slog.Int("id", task.ID))

    start := time.Now()
    processContent(ctx, task.Content)

    lg.Info(ctx, "done", "duration", time.Since(start).String())
}

func processContent(ctx context.Context, content string) {
    lg.Info(ctx, "processing content", "content", content)
}

Every log emitted with this ctx — including ones from called functions — carries id=<task.ID> automatically. This is the recommended pattern for request-scoped or task-scoped logging.

4. OpenTelemetry traces and metrics — examples/04_opentelemetry

glog integrates with OpenTelemetry so logs, spans, and metrics share the same context.

glog.SetupDefaultSlog()
defer glog.MustSetupOpenTelemetry(ctx, "test-service")()

ctx, span := lg.Span(ctx, "test-span")
defer span.End()

lg.Info(ctx, "test", "key", "value") // includes trace_id / span_id

counter := lg.MustNewCounter("test-counter")
for range 100 {
    counter.Add(ctx, 1)
}
  • MustSetupOpenTelemetry returns a shutdown function — always defer it so traces and metrics flush before exit.
  • glog.Span is a thin wrapper to start a span from the current context.
  • Logs made with the span's context are automatically linked to the span in your OTel backend.
  • NewCounter (and friends) create metric instruments that also benefit from the context.

Recommendation: always log with a context so trace info is attached whenever tracing is enabled.

5. Testing logs — examples/05_testing

glog ships a capture handler for asserting on log output in tests.

func Test(t *testing.T) {
    g := got.T(t)

    h := glog.NewSlogCaptureJSONHandler()
    slog.SetDefault(slog.New(h))

    lg.Info(g.Context(), "test", "key", "value")

    g.Equal(h.Log(0).Message, "test")
    g.Equal(h.Log(0).Level, slog.LevelInfo)
    g.Equal(h.Log(0).Attrs["key"], "value")
}
  • NewSlogCaptureJSONHandler captures log records into an in-memory buffer.
  • Install it as the default handler so lg.Info/lg.Error calls land in the capture.
  • h.Log(i) returns the i-th record with parsed Message, Level, and Attrs.

Recommended conventions

  • Call glog.SetupDefaultSlog() exactly once at program start.
  • Always log with a context.Context, even for seemingly local code — it keeps tracing and attribute linkage free.
  • Use glog.ContextWithAttrs to enrich a context instead of repeating attributes at every log site.
  • In production, add file outputs with AddDefaultOutput plus StripWriterColor.
  • In tests, swap in NewSlogCaptureJSONHandler rather than parsing stdout.

About

Golang slog for production best practices

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages