- 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")
}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:
SetupDefaultSlogpicks 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.Contextas 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.logStripWriterColorremoves 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)
}MustSetupOpenTelemetryreturns a shutdown function — alwaysdeferit so traces and metrics flush before exit.glog.Spanis 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")
}NewSlogCaptureJSONHandlercaptures log records into an in-memory buffer.- Install it as the default handler so
lg.Info/lg.Errorcalls land in the capture. h.Log(i)returns the i-th record with parsedMessage,Level, andAttrs.
- 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.ContextWithAttrsto enrich a context instead of repeating attributes at every log site. - In production, add file outputs with
AddDefaultOutputplusStripWriterColor. - In tests, swap in
NewSlogCaptureJSONHandlerrather than parsing stdout.