ll is a high-performance, production-ready logging library for Go, designed to provide hierarchical namespaces, structured logging, middleware pipelines, conditional logging, and support for multiple output formats, including text, JSON, colorized logs, and compatibility with Go’s slog. It’s ideal for applications requiring fine-grained log control, extensibility, and scalability.
- Hierarchical Namespaces: Organize logs with fine-grained control over subsystems (e.g., "app/db").
- Structured Logging: Add key-value metadata for machine-readable logs.
- Middleware Pipeline: Customize log processing with error-based rejection.
- Conditional Logging: Optimize performance by skipping unnecessary log operations.
- Multiple Output Formats: Support for text, JSON, colorized logs, and
slogintegration. - Debugging Utilities: Inspect variables (
Dbg), binary data (Dump), and stack traces (Stack). - Thread-Safe: Built for concurrent use with mutex-protected state.
- Performance Optimized: Minimal allocations and efficient namespace caching.
Install ll using Go modules:
go get github.com/olekukonko/llEnsure you have Go 1.21 or later for optimal compatibility.
Here’s a quick example to start logging with ll:
package main
import (
"github.com/olekukonko/ll"
)
func main() {
// Create a logger with namespace "app"
logger := ll.New("")
// enable output
logger.Enable()
// Basic log
logger.Info("Welcome") // Output: [app] INFO: Application started
logger = logger.Namespace("app")
// Basic log
logger.Info("start at :8080") // Output: [app] INFO: Application started
//Output
//INFO: Welcome
//[app] INFO: start at :8080
}package main
import (
"github.com/olekukonko/ll"
"github.com/olekukonko/ll/lh"
"os"
)
func main() {
// Chaining
logger := ll.New("app").Enable().Handler(lh.NewTextHandler(os.Stdout))
// Basic log
logger.Info("Application started") // Output: [app] INFO: Application started
// Structured log with fields
logger.Fields("user", "alice", "status", 200).Info("User logged in")
// Output: [app] INFO: User logged in [user=alice status=200]
// Conditional log
debugMode := false
logger.If(debugMode).Debug("Debug info") // No output (debugMode is false)
}Namespaces allow you to organize logs hierarchically, enabling precise control over logging for different parts of your application. This is especially useful for large systems with multiple components.
Benefits:
- Granular Control: Enable/disable logs for specific subsystems (e.g., "app/db" vs. "app/api").
- Scalability: Manage log volume in complex applications.
- Readability: Clear namespace paths improve traceability.
Example:
logger := ll.New("app").Enable().Handler(lh.NewTextHandler(os.Stdout))
// Child loggers
dbLogger := logger.Namespace("db")
apiLogger := logger.Namespace("api").Style(lx.NestedPath)
// Namespace control
logger.NamespaceEnable("app/db") // Enable DB logs
logger.NamespaceDisable("app/api") // Disable API logs
dbLogger.Info("Query executed") // Output: [app/db] INFO: Query executed
apiLogger.Info("Request received") // No outputAdd key-value metadata to logs for machine-readable output, making it easier to query and analyze logs in tools like ELK or Grafana.
Example:
logger := ll.New("app").Enable().Handler(lh.NewTextHandler(os.Stdout))
// Variadic fields
logger.Fields("user", "bob", "status", 200).Info("Request completed")
// Output: [app] INFO: Request completed [user=bob status=200]
// Map-based fields
logger.Field(map[string]interface{}{"method": "GET"}).Info("Request")
// Output: [app] INFO: Request [method=GET]Customize log processing with a middleware pipeline. Middleware functions can enrich, filter, or transform logs, using an error-based rejection mechanism (non-nil errors stop logging).
Example:
logger := ll.New("app").Enable().Handler(lh.NewTextHandler(os.Stdout))
// Enrich logs with app metadata
logger.Use(ll.FuncMiddleware(func(e *lx.Entry) error {
if e.Fields == nil {
e.Fields = make(map[string]interface{})
}
e.Fields["app"] = "myapp"
return nil
}))
// Filter low-level logs
logger.Use(ll.FuncMiddleware(func(e *lx.Entry) error {
if e.Level < lx.LevelWarn {
return fmt.Errorf("level too low")
}
return nil
}))
logger.Info("Ignored") // No output (filtered)
logger.Warn("Warning") // Output: [app] WARN: Warning [app=myapp]Optimize performance by skipping expensive log operations when conditions are false, ideal for production environments.
Example:
logger := ll.New("app").Enable().Handler(lh.NewTextHandler(os.Stdout))
featureEnabled := true
logger.If(featureEnabled).Fields("action", "update").Info("Feature used")
// Output: [app] INFO: Feature used [action=update]
logger.If(false).Info("Ignored") // No output, no processingll supports various output formats, including human-readable text, colorized logs, JSON, and integration with Go’s slog package.
Example:
logger := ll.New("app").Enable()
// Text output
logger.Handler(lh.NewTextHandler(os.Stdout))
logger.Info("Text log") // Output: [app] INFO: Text log
// JSON output
logger.Handler(lh.NewJSONHandler(os.Stdout, time.RFC3339Nano))
logger.Info("JSON log") // Output: {"timestamp":"...","level":"INFO","message":"JSON log","namespace":"app"}
// Slog integration
slogText := slog.NewTextHandler(os.Stdout, nil)
logger.Handler(lh.NewSlogHandler(slogText))
logger.Info("Slog log") // Output: level=INFO msg="Slog log" namespace=app class=Textll provides powerful tools for debugging, including variable inspection, binary data dumps, and stack traces.
-
Dbg - Contextual Inspection Inspects variables with file and line context, preserving variable names and handling all Go types.
x := 42 user := struct{ Name string }{"Alice"} ll.Dbg(x) // Output: [file.go:123] x = 42 ll.Dbg(user) // Output: [file.go:124] user = [Name:Alice]
-
Dump - Binary Inspection Displays a hex/ASCII view of data, optimized for strings, bytes, and complex types (with JSON fallback).
ll.Handler(lh.NewColorizedHandler(os.Stdout)) ll.Dump("hello\nworld") // Output: Hex/ASCII dump (see example/dump.png)
-
Stack - Stack Inspection Logs a stack trace for debugging critical errors.
ll.Handler(lh.NewColorizedHandler(os.Stdout)) ll.Stack("Critical error") // Output: [app] ERROR: Critical error [stack=...] (see example/stack.png)
4General Output Logs a output in structured way for inspection of public & private values.
ll.Handler(lh.NewColorizedHandler(os.Stdout))
ll.Output(&SomeStructWithPrivateValues{}) Measure execution time for performance analysis.
// Automatic measurement
defer ll.Measure(func() { time.Sleep(time.Millisecond) })()
// Output: [app] INFO: function executed [duration=~1ms]
// Explicit benchmarking
start := time.Now()
time.Sleep(time.Millisecond)
ll.Benchmark(start) // Output: [app] INFO: benchmark [start=... end=... duration=...]Performance Notes:
Dbgcalls are disabled at compile-time when not enabled.Dumpoptimizes for primitive types, strings, and bytes with zero-copy paths.- Stack traces are configurable via
StackSize.
A practical example of using ll in a web server with structured logging, middleware, and slog integration:
package main
import (
"github.com/olekukonko/ll"
"github.com/olekukonko/ll/lh"
"log/slog"
"net/http"
"os"
"time"
)
func main() {
// Initialize logger with slog handler
slogHandler := slog.NewJSONHandler(os.Stdout, nil)
logger := ll.New("server").Enable().Handler(lh.NewSlogHandler(slogHandler))
// HTTP child logger
httpLogger := logger.Namespace("http").Style(lx.NestedPath)
// Middleware for request ID
httpLogger.Use(ll.FuncMiddleware(func(e *lx.Entry) error {
if e.Fields == nil {
e.Fields = make(map[string]interface{})
}
e.Fields["request_id"] = "req-" + time.Now().String()
return nil
}))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
httpLogger.Fields("method", r.Method, "path", r.URL.Path).Info("Request received")
w.Write([]byte("Hello, world!"))
httpLogger.Fields("duration_ms", time.Since(start).Milliseconds()).Info("Request completed")
})
logger.Info("Starting server on :8080")
http.ListenAndServe(":8080", nil)
}Sample Output (JSON via slog):
{"level":"INFO","msg":"Starting server on :8080","namespace":"server"}
{"level":"INFO","msg":"Request received","namespace":"server/http","class":"Text","method":"GET","path":"/","request_id":"req-..."}
{"level":"INFO","msg":"Request completed","namespace":"server/http","class":"Text","duration_ms":1,"request_id":"req-..."}- Granular Control: Hierarchical namespaces for precise log management.
- Performance: Conditional logging and optimized concatenation reduce overhead.
- Extensibility: Middleware pipeline for custom log processing.
- Structured Output: Machine-readable logs with key-value metadata.
- Flexible Formats: Text, JSON, colorized, and
slogsupport. - Debugging Power: Advanced tools like
Dbg,Dump, andStackfor deep inspection. - Thread-Safe: Safe for concurrent use in high-throughput applications.
| Feature | ll |
log (stdlib) |
slog (stdlib) |
zap |
|---|---|---|---|---|
| Hierarchical Namespaces | ✅ | ❌ | ❌ | ❌ |
| Structured Logging | ✅ (Fields, Context) | ❌ | ✅ | ✅ |
| Middleware Pipeline | ✅ | ❌ | ❌ | ✅ (limited) |
| Conditional Logging | ✅ (If, IfOne, IfAny) | ❌ | ❌ | ❌ |
| Slog Compatibility | ✅ | ❌ | ✅ (native) | ❌ |
| Debugging (Dbg, Dump) | ✅ | ❌ | ❌ | ❌ |
| Performance (disabled logs) | High (conditional) | Low | Medium | High |
| Output Formats | Text, JSON, Color, Slog | Text | Text, JSON | JSON, Text |
ll is optimized for performance, particularly for disabled logs and structured logging:
- Disabled Logs: 30% faster than
slogdue to efficient conditional checks. - Structured Logging: 2x faster than
logwith minimal allocations. - Namespace Caching: Reduces overhead for hierarchical lookups.
See ll_bench_test.go for detailed benchmarks on namespace creation, cloning, and field building.
The ll library includes a comprehensive test suite (ll_test.go) covering:
- Logger configuration, namespaces, and conditional logging.
- Middleware, rate limiting, and sampling.
- Handler output formats (text, JSON, slog).
- Debugging utilities (
Dbg,Dump,Stack).
Recent improvements:
- Fixed sampling middleware for reliable behavior at edge cases (0.0 and 1.0 rates).
- Enhanced documentation across
conditional.go,field.go,global.go,ll.go,lx.go, andns.go. - Added
slogcompatibility vialh.SlogHandler.
Contributions are welcome! To contribute:
- Fork the repository:
github.com/olekukonko/ll. - Create a feature branch:
git checkout -b feature/your-feature. - Commit changes:
git commit -m "Add your feature". - Push to the branch:
git push origin feature/your-feature. - Open a pull request with a clear description.
Please include tests in ll_test.go and update documentation as needed. Follow the Go coding style and run go test ./... before submitting.
ll is licensed under the MIT License. See LICENSE for details.
- Source Code: github.com/olekukonko/ll
- Issue Tracker: github.com/olekukonko/ll/issues
- GoDoc: pkg.go.dev/github.com/olekukonko/ll