Beautiful, fast, structured logging for Go.
loghq is a developer-first logging library that produces gorgeous console output while being faster than zap and matching zerolog in benchmarks — with a simpler API. Zero allocations on every hot path, sync.Pool recycling, and hand-rolled encoders — no reflection, no fmt.Sprintf, no encoding/json.
go get github.com/Bhavyyadav25/loghqpackage main
import "github.com/Bhavyyadav25/loghq"
func main() {
loghq.Info("server started", "port", 8080)
loghq.Success("database connected", "host", "localhost")
loghq.Warn("cache miss rate high", "rate", 0.45)
loghq.Error("request failed", "status", 500, "path", "/api/users")
}Console output:
2025-01-30 14:32:01 ● INFO server started port=8080
2025-01-30 14:32:01 ✓ OK database connected host=localhost
2025-01-30 14:32:01 ▲ WARN cache miss rate high rate=0.45
2025-01-30 14:32:01 ✗ ERROR request failed status=500 path=/api/users
- 7 log levels — Trace, Debug, Info, Success, Warn, Error, Fatal
- Beautiful console output — Color-coded levels with icons (●, ◇, ✓, ▲, ✗)
- 3 encoders — Console (colored), JSON, Logfmt
- Structured logging — slog-style key-value pairs or typed fields
- Zero-allocation hot path — 0 allocs/op across every benchmark
- Faster than zap, slog, and logrus — Matches zerolog. See benchmarks
- File rotation — Built-in size/age-based rotation with gzip compression
- Context support — Propagate request IDs and fields via
context.Context - Caller info — Automatic file:line on every log entry
- Stack traces — Full traces on Error and Fatal levels
- Multi-handler — Route logs to multiple destinations simultaneously
// slog-style alternating key-value pairs
loghq.Info("request", "method", "GET", "status", 200, "elapsed", "12ms")
// Pre-bound fields
logger := loghq.WithFields(loghq.Fields{"service": "api", "version": "1.0"})
logger.Info("request handled")
// Typed fields (zero-alloc)
loghq.With(loghq.String("user", "ali"), loghq.Int("id", 42)).Info("logged in")logger := loghq.New(
loghq.WithHandler(loghq.NewJSONHandler(loghq.Stdout)),
)
logger.Info("request", "method", "GET", "status", 200)
// {"time":"2025-01-30T14:32:01Z","level":"INFO","msg":"request","method":"GET","status":200}logger := loghq.New(
loghq.WithHandler(loghq.NewLogfmtHandler(loghq.Stdout)),
)
logger.Info("request", "method", "GET", "status", 200)
// time=2025-01-30T14:32:01Z level=info msg=request method=GET status=200ctx := loghq.ContextWithFields(ctx, loghq.String("request_id", "abc-123"))
loghq.WithContext(ctx).Info("processing order", "order_id", 42)fw, _ := loghq.NewFileWriter(loghq.FileConfig{
Path: "/var/log/app.log",
MaxSize: 100 * 1024 * 1024, // 100MB
MaxAge: 7 * 24 * time.Hour,
MaxBackups: 5,
Compress: true,
})
logger := loghq.New(loghq.WithHandler(loghq.NewJSONHandler(fw)))logger := loghq.New(loghq.WithHandler(loghq.NewMultiHandler(
loghq.NewConsoleHandler(), // beautiful console
loghq.NewJSONHandler(fw), // structured file
)))logger := loghq.New(
loghq.WithLevel(loghq.DebugLevel),
loghq.WithHandler(loghq.NewConsoleHandler()),
loghq.WithCaller(true),
loghq.WithStackLevel(loghq.ErrorLevel),
)Benchmarked against every major Go logging library. JSON encoding to io.Discard, 10 iterations at 5 seconds each for statistical reliability.
Environment: Intel Core i7-1370P (20 threads), Linux amd64, Go 1.25.6
| Library | ns/op | B/op | allocs/op |
|---|---|---|---|
| loghq | 2.6 | 0 | 0 |
| zerolog | 4.2 | 0 | 0 |
| zap | 11.2 | 0 | 0 |
| slog | 11.3 | 0 | 0 |
loghq's atomic level check is 4.3x faster than zap and slog.
| Library | ns/op | B/op | allocs/op |
|---|---|---|---|
| loghq | 320 | 0 | 0 |
| zerolog | 380 | 0 | 0 |
| zap | 1,012 | 0 | 0 |
| slog | 1,027 | 0 | 0 |
| logrus | 3,478 | 872 | 19 |
loghq is faster than zerolog, 3.2x faster than zap, 3.2x faster than slog.
| Library | ns/op | B/op | allocs/op |
|---|---|---|---|
| zerolog | 665 | 0 | 0 |
| loghq | 828 | 0 | 0 |
| zap | 1,489 | 320 | 1 |
| slog | 2,014 | 0 | 0 |
| logrus | 7,688 | 2,287 | 35 |
loghq is 1.8x faster than zap with zero allocations vs zap's 320 B/op.
| Library | ns/op | B/op | allocs/op |
|---|---|---|---|
| zerolog | 957 | 0 | 0 |
| loghq | 1,288 | 0 | 0 |
| zap | 2,090 | 705 | 1 |
| slog | 3,241 | 208 | 1 |
| logrus | 13,147 | 3,981 | 52 |
loghq is 1.6x faster than zap, 2.5x faster than slog, 10.2x faster than logrus -- all with zero allocations.
| Library | ns/op | B/op | allocs/op |
|---|---|---|---|
| zerolog | 56 | 0 | 0 |
| loghq | 69 | 0 | 0 |
| zap | 214 | 64 | 1 |
| slog | 668 | 0 | 0 |
| logrus | 6,697 | 1,673 | 25 |
loghq matches zerolog under contention and is 3.1x faster than zap, 9.7x faster than slog.
loghq zerolog zap slog logrus
───── ─────── ─── ──── ──────
Disabled 2.6 ns 4.2 ns 11.2 ns 11.3 ns 2.5 ns
No Fields 320 ns 380 ns 1,012 ns 1,027 ns 3,478 ns
5 Fields 828 ns 665 ns 1,489 ns 2,014 ns 7,688 ns
10 Fields 1,288 ns 957 ns 2,090 ns 3,241 ns 13,147 ns
Parallel 69 ns 56 ns 214 ns 668 ns 6,697 ns
Allocs/op 0 0 0-1 0-1 19-52
Bytes/op 0 0 0-705 0-208 872-3,981
zerolog uses a typed chained-builder API (l.Info().Str("k","v").Int("n",1).Msg("m")) which avoids interface{} dispatch entirely. This gives it a raw speed edge in field-heavy benchmarks. loghq uses the simpler slog-style API (l.Info("msg", "key", "val")) that is more familiar and requires less boilerplate -- while still achieving zero allocations and beating zerolog in no-fields and disabled-level benchmarks.
- Zero allocations -- the entire hot path is allocation-free, from field parsing to JSON encoding
- Inline
[16]Fieldarray -- Record stores up to 16 fields without heap allocation - sync.Pool recycling -- Records and Buffers are pooled and reused
- Inlined KV parsing -- variadic
[]interface{}never escapes to heap - Direct field encoding -- no interface dispatch, no closures, no heap escape
- Hand-rolled encoders --
strconv.Append*andtime.AppendFormatdirectly into pooled buffers - Atomic level check -- disabled levels cost ~3ns via
atomic.Int32 - Lock-free handlers -- no mutexes in the encoding path
cd benchmarks
go test -bench=. -benchmem -count=10 -benchtime=5s -timeout=60mMIT