# ll - A Modern Structured Logging Library for Go `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. ## Key Features - **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 `slog` integration. - **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. ## Installation Install `ll` using Go modules: ```bash go get github.com/olekukonko/ll ``` Ensure you have Go 1.21 or later for optimal compatibility. ## Getting Started Here’s a quick example to start logging with `ll`: ```go 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 } ``` ```go 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) } ``` ## Core Features ### 1. Hierarchical Namespaces 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**: ```go 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 output ``` ### 2. Structured Logging Add key-value metadata to logs for machine-readable output, making it easier to query and analyze logs in tools like ELK or Grafana. **Example**: ```go 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] ``` ### 3. Middleware Pipeline 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**: ```go 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] ``` ### 4. Conditional Logging Optimize performance by skipping expensive log operations when conditions are false, ideal for production environments. **Example**: ```go 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 processing ``` ### 5. Multiple Output Formats `ll` supports various output formats, including human-readable text, colorized logs, JSON, and integration with Go’s `slog` package. **Example**: ```go 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=Text ``` ### 6. Debugging Utilities `ll` provides powerful tools for debugging, including variable inspection, binary data dumps, and stack traces. #### Core Debugging Methods 1. **Dbg - Contextual Inspection** Inspects variables with file and line context, preserving variable names and handling all Go types. ```go 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] ``` 2. **Dump - Binary Inspection** Displays a hex/ASCII view of data, optimized for strings, bytes, and complex types (with JSON fallback). ```go ll.Handler(lh.NewColorizedHandler(os.Stdout)) ll.Dump("hello\nworld") // Output: Hex/ASCII dump (see example/dump.png) ``` 3. **Stack - Stack Inspection** Logs a stack trace for debugging critical errors. ```go ll.Handler(lh.NewColorizedHandler(os.Stdout)) ll.Stack("Critical error") // Output: [app] ERROR: Critical error [stack=...] (see example/stack.png) ``` 4**General Output** Logs a output in structured way for inspection of public & private values. ```go ll.Handler(lh.NewColorizedHandler(os.Stdout)) ll.Output(&SomeStructWithPrivateValues{}) ``` #### Performance Tracking Measure execution time for performance analysis. ```go // 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**: - `Dbg` calls are disabled at compile-time when not enabled. - `Dump` optimizes for primitive types, strings, and bytes with zero-copy paths. - Stack traces are configurable via `StackSize`. ## Real-World Example: Web Server A practical example of using `ll` in a web server with structured logging, middleware, and `slog` integration: ```go 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)**: ```json {"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-..."} ``` ## Why Choose `ll`? - **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 `slog` support. - **Debugging Power**: Advanced tools like `Dbg`, `Dump`, and `Stack` for deep inspection. - **Thread-Safe**: Safe for concurrent use in high-throughput applications. ## Comparison with Other Libraries | 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 | ## Benchmarks `ll` is optimized for performance, particularly for disabled logs and structured logging: - **Disabled Logs**: 30% faster than `slog` due to efficient conditional checks. - **Structured Logging**: 2x faster than `log` with minimal allocations. - **Namespace Caching**: Reduces overhead for hierarchical lookups. See `ll_bench_test.go` for detailed benchmarks on namespace creation, cloning, and field building. ## Testing and Stability 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`, and `ns.go`. - Added `slog` compatibility via `lh.SlogHandler`. ## Contributing Contributions are welcome! To contribute: 1. Fork the repository: `github.com/olekukonko/ll`. 2. Create a feature branch: `git checkout -b feature/your-feature`. 3. Commit changes: `git commit -m "Add your feature"`. 4. Push to the branch: `git push origin feature/your-feature`. 5. 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. ## License `ll` is licensed under the MIT License. See [LICENSE](LICENSE) for details. ## Resources - **Source Code**: [github.com/olekukonko/ll](https://github.com/olekukonko/ll) - **Issue Tracker**: [github.com/olekukonko/ll/issues](https://github.com/olekukonko/ll/issues) - **GoDoc**: [pkg.go.dev/github.com/olekukonko/ll](https://pkg.go.dev/github.com/olekukonko/ll)