glog is a lightweight and powerful logging library for Go designed for simplicity, flexibility, and readability — built for humans and structured for machines.
Part of the Quick Framework ecosystem, glog supports colorized terminal output, slog-style key=value logs, JSON format, dynamic fields, custom patterns, and intelligent defaults.
- 🔥 Lightweight and zero-dependency
- 🎨 Supports
text,slog, andjsonformats - 🧠 Dynamic separator detection (
|,--,:… based on your pattern) - 📋 Fluent log builder API with dynamic fields:
.Str(),.Int(),.Bool(),.Any(),.Msg() - 🎯 Built-in caller tracing: add
file:linewith.Caller() - 🧵 Global
CustomFields+ per-logFields(contextual) - 🎯 Precise caller tracing with
${file}(file:line) support - 🎛️ Built-in log levels:
DEBUG,INFO,WARN,ERROR - 🧪 Fully tested with 100% coverage of critical paths
- 🌈 Terminal colors for
levelfield (intextandslog) - 🧠 Built-in fluent context support: create and extract TraceID, X-User-ID, etc
- ✅ Simple API:
Info(),Debugf(),Error(), etc.
Hardware: Apple M3 Max Command: go test -bench=. -benchtime=7s -benchmem
| Logger | Format | ops | ns/op | B/op | allocs/op |
|---|---|---|---|---|---|
glog |
text | 54,624,878 | 154.4 | 189 | 2 |
glog |
slog | 41,001,594 | 196.1 | 346 | 2 |
glog |
json | 32,973,249 | 255.4 | 324 | 3 |
zerolog |
text | 57,916,555 | 176.6 | 296 | 0 |
zerolog |
json | 52,844,396 | 162.7 | 325 | 0 |
zap |
text | 18,958,725 | 450.0 | 192 | 4 |
zap |
json | 30,100,347 | 276.9 | 128 | 1 |
logrus |
text | 8,296,321 | 1037.0 | 1139 | 16 |
logrus |
json | 5,424,778 | 1558.0 | 1758 | 28 |
$ go get github.com/jeffotoni/quick/glogNeed to propagate request metadata like TraceID, X-User-ID, Session-ID or any other key through Go's context.Context?
Use glog's fluent context builder to create and retrieve values safely and efficiently.
package main
import (
"context"
"github.com/jeffotoni/quick/glog"
)
ctx, cancel := glog.CreateCtx().
Set("X-Trace-ID", "abc-123").
Set("X-User-ID", "user-42").
Set("X-Session-ID", "sess-999").
Timeout(10 * time.Second).
Build()
defer cancel()
traceID := glog.GetCtx(ctx, "X-Trace-ID")
// abc-123
userID := glog.GetCtx(ctx, "X-User-ID")
// user-42
fields := glog.GetCtxAll(ctx)
// map[string]string{ "X-Trace-ID": "abc-123", "X-User-ID": "user-42", ... }package main
import (
"context"
"fmt"
"log"
"time"
"github.com/jeffotoni/quick/glog"
)
func main() {
logger := glog.New(glog.Config{
Format: "json",
Level: glog.DEBUG,
})
q := quick.New()
q.Post("/v1/user", func(c *quick.Ctx) error {
// creating a trace
traceID := c.Get(KeyName)
if traceID == "" {
traceID = rand.TraceID()
}
userID:= "user3039"
spanID:= "span39393"
ctx, cancel := glog.CreateCtx().
Set("X-Trace-ID", traceID).
Set("X-User-ID", userID).
Set("X-Span-ID", spanID).
Timeout(10 * time.Second).
Build()
defer cancel()
c.Set("X-Trace-ID", traceID)
c.Set("X-User-ID", userID)
c.Set("X-Span-ID", spanID)
c.Set("Content-type", "application/json")
var d any
err := c.BodyParser(&d)
if err != nil {
logger.Error().
Time().
Level().
Str(KeyName, traceID).
Str("error", err.Error()).
Send()
return c.Status(500).JSON(quick.M{"msg": err.Error()})
}
logger.Debug().
Time().
Level().
Str(KeyName, traceID).
Str("func", "BodyParser").
Str("status", "success").
// Caller().
Send()
// call metodh
b, err := SaveSomeWhere(ctx, logger, d)
if err != nil {
logger.Error().
Time().
Level().
Str(KeyName, traceID).
Str("Error", err.Error()).
Send()
return c.Status(500).JSON(quick.M{"msg": err.Error()})
}
logger.Debug().
Time().
Level().
Str(KeyName, traceID).
Str("func", "SaveSomeWhere").
Int("code", quick.StatusOK).
Msg("api-post-fluent").
Send()
all := glog.GetCtxAll(ctx)
fmt.Printf("X-Trace-ID:%s X-User-ID:%s X-Span-ID:%s\n", all["X-Trace-ID"], all["X-User-ID"], all["X-Span-ID"])
return c.Status(quick.StatusOK).Send(b)
})
q.Listen("0.0.0.0:8080")
}Output:
██████╗ ██╗ ██╗██╗ ██████╗██╗ ██╗
██╔═══██╗██║ ██║██║██╔═══ ██║ ██╔╝
██║ ██║██║ ██║██║██║ █████╔╝
██║▄▄ ██║██║ ██║██║██║ ██╔═██╗
╚██████╔╝╚██████╔╝██║╚██████╔ ██║ ██╗
╚══▀▀═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
Quick v0.0.1 🚀 Fast & Minimal Web Framework
─────────────────── ───────────────────────────────
🌎 Host : http://0.0.0.0
📌 Port : 8080
🔀 Routes: 1
─────────────────── ───────────────────────────────
time=2025-03-31T22:49:25-03:00 level=DEBUG X-Trace-ID=21OGKpAUzL4yQnPk func=BodyParser status=success
time=time=03-31T22:49:25-03:00 level=DEBUG X-Trace-ID=21OGKpAUzL4yQnPk func=SendSQS status=success
time=time=03-31T22:49:25-03:00 level=DEBUG X-Trace-ID=21OGKpAUzL4yQnPk func=Marshal status=success
time=time=03-31T22:49:25-03:00 level=DEBUG X-Trace-ID=21OGKpAUzL4yQnPk func=SaveSomeWhere code=200 msg=api-post-fluent
-- or
2025-03-31T22:50:00-03:00 DEBUG JHCbFzOY2su9Wf9v BodyParser success
2025-03-31T22:50:00-03:00 DEBUG JHCbFzOY2su9Wf9v SendSQS success
2025-03-31T22:50:00-03:00 DEBUG JHCbFzOY2su9Wf9v Marshal success
2025-03-31T22:50:00-03:00 DEBUG JHCbFzOY2su9Wf9v SaveSomeWhere 200 api-post-fluent
-- or
{"X-Trace-ID":"f5C5fIPqt285cEEZ","func":"BodyParser","status":"success","time":"2025-03-31T22:50:23-03:00","level":"DEBUG"}
{"X-Trace-ID":"f5C5fIPqt285cEEZ","func":"SendSQS","status":"success","time":"2025-03-31T22:50:24-03:00","level":"DEBUG"}
{"X-Trace-ID":"f5C5fIPqt285cEEZ","func":"Marshal","status":"success","time":"2025-03-31T22:50:24-03:00","level":"DEBUG"}
{"X-Trace-ID":"f5C5fIPqt285cEEZ","func":"SaveSomeWhere","code":200,"time":"2025-03-31T22:50:24-03:00","level":"DEBUG","msg":"api-post-fluent"}
package main
import (
"github.com/jeffotoni/quick/glog"
)
func main() {
logger := glog.New(glog.Config{
Format: "json",
Level: glog.DEBUG,
})
logger.Debug().
Int("TraceID", 123475).
Str("func", "BodyParser").
Str("status", "success").
Msg("api-fluent-example").
Send()
logger.Info().
Int("TraceID", 123475).
Bool("error", false).
Msg("api-fluent-example")
Send()
errTest := errors.New("something went wrong")
ts := time.Now()
dur := 1500 * time.Millisecond
logger.Warn().
Str("user", "jeff").
Int("retries", 3).
Bool("authenticated", true).
Float64("load", 87.4).
Duration("elapsed", dur).
AddTime("timestamp", ts).
Err("error", errTest).
Any("data", map[string]int{"a": 1}).
// Func("trace_id", func() any {
// return "abc123"
// }). // soon
Msg("Fluent log test").
Send()
}🖨️ Sample Output (text):
🟢 INFO
🔵 DEBUG
🟡 WARN
🔴 ERROR
{"TraceID":123475,"func":"BodyParser","status":"success","msg":"api-fluent-example"}
{"TraceID":123475,"error":false,"msg":"api-fluent-example"}
{"user":"jeff","retries":3,"authenticated":true,"load":87.4,"elapsed":"1.5s","timestamp":"2025-03-31T23:07:32-03:00","error":"something went wrong","data":null,"msg":"Fluent log test"}
We implemented unit tests for:
- All log levels (
Info,Debug,Error,Warn) with both fluent and legacy*Tsyntax - Pattern replacement and extra field rendering
- Separator auto-detection logic (via pattern) and fallback to
" "if not defined - Ordered field rendering in fluent logs (preserves insertion order)
- Caller trace injection via
${file}whenIncludeCalleris enabled - JSON and slog output structure (key/value format with coloring for
slog) - Writer redirection for test capture and output validation
- Contextual fallback logic for
SeparatorwhenPatternis empty - Edge case tests: missing keys, nil context, deadline timeout, custom key names
- Compatibility support for
Fieldsmaps via generic wrapper in legacy*Tmethods
Run tests with:
$ go test -v -coverIn addition to tests, we included rich Example_*() functions following Go’s documentation pattern.
Explore them with:
$ go doc github.com/jeffotoni/quick/glogIn pkg.go.dev quick glog
A long way to go, a long way to go, but at least after that the first step. Soon.
-
.Func()Method
Automatically capture the caller function/method name.
go logger.Info().Func().Msg("called").Send() // Output: func=main.HandleUser
-
File Output with Rotation Support
Allow rotating logs by file size or date.
go glog.Set(glog.Config{ Format: "text", Writer: glog.File("app.log").RotateDaily(), }) -
Export to Logfmt Format
Support output inlogfmtstyle (popular in Prometheus/Grafana ecosystems).
go glog.Set(glog.Config{ Format: "logfmt", })
-
Hooks for Pre/Post Processing
Inject logic before or after sending logs (e.g., to Sentry, metrics).
go glog.AddHook(func(e *glog.Entry) { if e.Level == glog.ERROR { reportToSentry(e) } }) -
Custom Field-Based Filtering
Filter logs dynamically based on custom fields.
go glog.Set(glog.Config{ Filter: func(e *glog.Entry) bool { return e.HasField("X-Trace-ID") }, })
-
Multiple Logger Instances
Instantiate logger per service/module.
go logger := glog.NewLogger(glog.Config{...}) logger.Info().Msg("auth log").Send() -
Enhanced Context Builder
Set multiple keys dynamically using.Set()
go ctx, cancel := glog.CreateCtx(). Set("X-Trace-ID", "abc123"). Set("X-User-ID", "user42"). Timeout(5 * time.Second). Build() -
GetCtxAllFunction
Retrieve all context values as a map from a context.
go all := glog.GetCtxAll(ctx) fmt.Println(all["X-Trace-ID"])
-
Async Logging Mode
Enable background writing using buffered channels.
go glog.Set(glog.Config{ Async: true, BufferSize: 1024, }) -
Benchmark Report in README
Include detailed comparisons: - Ops/sec - Allocations - Memory usage - Comparison withzerolog
- Pluggable Writers for Elasticsearch, Loki, Fluentd
Enable remote logging targets with ease.
go glog.ToElastic("http://localhost:9200") glog.ToLoki("http://localhost:3100")
-
Real-World Logging Examples
Include auth, API, retry policies, error handling, tracing. -
Showcase
.Caller(),.Func(),.Time(),.Duration()
Provide usage in JSON, text and slog formats. -
Context Guide
Document propagation ofTraceID,UserID, and contextual IDs across services.
If you like this project, give it a ⭐ star and feel free to open issues or PRs!
Made with 💚 by @jeffotoni
Part of the Quick Framework ecosystem

