ulog

package module
v1.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 22, 2025 License: MIT Imports: 13 Imported by: 0

README

ULOG - Advanced Go Logging Library

Go Version License

ULOG is a powerful and flexible Go logging library built on top of the standard log/slog package. It provides structured logging with Symfony Monolog-compatible format, multiple output formats, file rotation, and advanced handler capabilities.

Features

  • 🚀 Built on Go's standard log/slog - Full compatibility with slog ecosystem
  • 📝 Multiple Output Formats - JSON and human-readable text formats
  • 🔄 File Rotation - Automatic log rotation with size, backup count, and age limits
  • 🎯 Symfony Monolog Compatible - Follows PHP Symfony Monolog structure
  • 🔗 Context Support - Pass structured data through Go contexts
  • 📡 Multi-Handler - Send logs to multiple destinations simultaneously
  • Thread-Safe - Concurrent logging support with mutex protection
  • 🛠️ Highly Configurable - Flexible configuration options

Installation

go get github.com/vitiok78/ulog

Quick Start

Basic Usage
package main

import (
    "log/slog"
    "os"
    "github.com/vitiok78/ulog"
)

func main() {
    // Create a text logger
    handler := ulog.NewTextLogger(os.Stdout, "app")
    logger := slog.New(handler)
    
    logger.Info("Application started", "version", "1.0.0")
    logger.Error("Something went wrong", "error", "connection failed")
}
JSON Logging
// Create a JSON logger
handler := ulog.NewJSONLogger(os.Stdout, "api")
logger := slog.New(handler)

logger.Info("User login", "user_id", 123, "ip", "192.168.1.1")
// Output: {"message":"User login","context":{"user_id":123,"ip":"192.168.1.1"},"level":200,"level_name":"INFO","channel":"api","datetime":"2024-01-15T10:30:45.123456789Z","extra":{}}
File Logging with Rotation
// Create a file logger with rotation
handler, err := ulog.NewFileLogger(
    "/var/log/app.log",  // file path
    "json",              // format: "json" or "text"
    "app",               // channel name
    10*1024*1024,        // max size: 10MB
    5,                   // max backups: keep 5 old files
    30,                  // max age: 30 days
)
if err != nil {
    panic(err)
}

logger := slog.New(handler)
logger.Info("File logging enabled")

Advanced Usage

Custom Configuration
config := ulog.Config{
    Formatter:  ulog.NewJSONFormatter(),
    Writer:     os.Stderr,
    Channel:    "custom",
    CtxKeyName: "context",
    AddSource:  true,          // Add source file information
    Level:      slog.LevelInfo, // Minimum log level
}

handler := ulog.New(config)
logger := slog.New(handler)
Multi-Handler Setup
// Send logs to multiple destinations
fileHandler, _ := ulog.NewFileLogger("/var/log/app.log", "json", "app", 10*1024*1024, 5, 30)
consoleHandler := ulog.NewTextLogger(os.Stdout, "app")

multiHandler := ulog.NewMultiHandler(fileHandler, consoleHandler)
logger := slog.New(multiHandler)

logger.Info("This will be logged to both file and console")
Context-Based Logging
import (
    "context"
    "log/slog"
    "github.com/vitiok78/ulog"
)

// Add attributes to context
ctx := ulog.WithAttrsContext(context.Background(),
    slog.String("request_id", "req-123"),
    slog.String("user_id", "user-456"),
)

// These attributes will be included in all logs using this context
handler := ulog.NewJSONLogger(os.Stdout, "api")
logger := slog.New(handler)

logger.InfoContext(ctx, "Processing request")
// The request_id and user_id will be automatically included
Custom Formatters
// JSON formatter with custom time format
jsonFormatter := ulog.NewJSONFormatter()
jsonFormatter.TimeFormat = "2006-01-02 15:04:05"

// Text formatter with custom time format
textFormatter := ulog.NewTextFormatter()
textFormatter.TimeFormat = "2006-01-02 15:04:05"

config := ulog.Config{
    Formatter: jsonFormatter,
    Writer:    os.Stdout,
    Channel:   "app",
}

Log Levels

ULOG maps Go's slog levels to Symfony Monolog levels:

slog Level Monolog Level Monolog Name
DEBUG 100 DEBUG
INFO 200 INFO
WARN 300 WARNING
ERROR 400 ERROR
Higher 500 CRITICAL
Setting Minimum Log Level

Control which logs are output by setting the minimum level in the configuration:

config := ulog.Config{
    Formatter: ulog.NewJSONFormatter(),
    Writer:    os.Stdout,
    Channel:   "app",
    Level:     slog.LevelWarn, // Only WARN and above will be logged
}

handler := ulog.New(config)
logger := slog.New(handler)

logger.Debug("This won't be logged")
logger.Info("This won't be logged either")
logger.Warn("This will be logged")
logger.Error("This will be logged too")

The default minimum level is slog.LevelInfo, which means DEBUG logs are filtered out by default.

Output Formats

Text Format
[2024-01-15T10:30:45.123456789Z] app.INFO: User logged in {"user_id":123,"action":"login"} []
JSON Format
{
  "message": "User logged in",
  "context": {"user_id": 123, "action": "login"},
  "level": 200,
  "level_name": "INFO",
  "channel": "app",
  "datetime": "2024-01-15T10:30:45.123456789Z",
  "extra": {}
}

File Rotation

The file writer supports automatic rotation based on:

  • Size: Rotate when file reaches maximum size
  • Age: Rotate files older than specified days
  • Backup Count: Keep only specified number of backup files
Size Parsing

The ParseByteSize function supports various size formats:

size, err := ulog.ParseByteSize("10MB")  // 10 megabytes
size, err := ulog.ParseByteSize("1GB")   // 1 gigabyte
size, err := ulog.ParseByteSize("500KB") // 500 kilobytes

Configuration Options

Config Struct
type Config struct {
    Formatter  Formatter    // Log formatter (JSON or Text)
    Writer     io.Writer    // Output destination
    Channel    string       // Channel name for logs
    CtxKeyName string       // Context key name for attributes
    AddSource  bool         // Include source file information
    Level      slog.Level   // Minimum log level to output
}
Default Configuration
config := ulog.DefaultConfig()
// Returns:
// - TextFormatter
// - os.Stderr output
// - "app" channel
// - "ctx" context key name
// - AddSource: false
// - Level: slog.LevelInfo

Thread Safety

All handlers are thread-safe and can be used concurrently across multiple goroutines. Internal mutex protection ensures data integrity during concurrent logging operations.

Integration with Standard Library

ULOG handlers implement the standard slog.Handler interface, making them fully compatible with Go's logging ecosystem:

// Use with standard slog
logger := slog.New(ulog.NewJSONLogger(os.Stdout, "app"))

// Use with slog default logger
slog.SetDefault(logger)
slog.Info("This uses ulog handler")

Error Handling

// File logger creation with error handling
handler, err := ulog.NewFileLogger("/var/log/app.log", "json", "app", 10*1024*1024, 5, 30)
if err != nil {
    // Handle error (e.g., permission issues, invalid path)
    log.Fatalf("Failed to create file logger: %v", err)
}

// Always close file writers when done
if closer, ok := handler.(*ulog.Handler); ok {
    defer closer.Close() // If the writer implements io.Closer
}

Best Practices

  1. Use structured logging: Pass key-value pairs instead of formatting strings
  2. Set appropriate log levels: Use DEBUG for development, INFO+ for production
  3. Configure rotation: Set reasonable file size and backup limits
  4. Use contexts: Pass request-scoped data through contexts
  5. Handle errors: Always check errors when creating file loggers
  6. Close resources: Ensure file writers are properly closed

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Documentation

Overview

Package ulog provides a configurable logging system based on Go's slog package. It follows the log structure from PHP Symfony monolog package.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(config Config) slog.Handler

New creates a new slog.Handler using the provided configuration

func NewFileLogger

func NewFileLogger(path string, format string, channel string, maxSize int64, maxBackups int, maxAge int) (slog.Handler, error)

NewFileLogger creates a new logger that writes to a file

func NewJSONLogger

func NewJSONLogger(w io.Writer, channel string) slog.Handler

NewJSONLogger creates a new logger with JSON formatting

func NewTextLogger

func NewTextLogger(w io.Writer, channel string) slog.Handler

NewTextLogger creates a new logger with text formatting

func ParseByteSize

func ParseByteSize(size string) (int64, error)

func WithAttrsContext

func WithAttrsContext(ctx context.Context, attrs ...slog.Attr) context.Context

WithAttrsContext adds slog.Attr values to the context

Types

type Config

type Config struct {
	Formatter  Formatter
	Writer     io.Writer
	Channel    string
	CtxKeyName string
	AddSource  bool
	Level      slog.Level
}

Config holds the configuration for the ulog handler

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a default configuration with text formatter and os.Stderr output

type FileWriter

type FileWriter struct {
	// contains filtered or unexported fields
}

FileWriter is a writer that writes to a file with optional rotation

func NewFileWriter

func NewFileWriter(path string, maxSize int64, maxBackups int, maxAge int) (*FileWriter, error)

NewFileWriter creates a new FileWriter

func (*FileWriter) Close

func (w *FileWriter) Close() error

Close implements the io.Closer interface

func (*FileWriter) Write

func (w *FileWriter) Write(p []byte) (n int, err error)

Write implements the io.Writer interface

type Formatter

type Formatter interface {
	Format(record LogRecord) ([]byte, error)
}

Formatter defines the interface for log formatters

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler implements slog.Handler interface

func NewHandler

func NewHandler(config Config) *Handler

NewHandler creates a new ulog Handler

func (*Handler) Enabled

func (h *Handler) Enabled(ctx context.Context, level slog.Level) bool

Enabled implements slog.Handler interface

func (*Handler) Handle

func (h *Handler) Handle(ctx context.Context, record slog.Record) error

Handle implements slog.Handler interface

func (*Handler) WithAttrs

func (h *Handler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs implements slog.Handler interface

func (*Handler) WithGroup

func (h *Handler) WithGroup(name string) slog.Handler

WithGroup implements slog.Handler interface

type JSONFormatter

type JSONFormatter struct {
	TimeFormat string
}

JSONFormatter formats logs as JSON following Symfony monolog format

func NewJSONFormatter

func NewJSONFormatter() *JSONFormatter

NewJSONFormatter creates a new JSONFormatter

func (*JSONFormatter) Format

func (f *JSONFormatter) Format(record LogRecord) ([]byte, error)

Format implements the Formatter interface

type LogRecord

type LogRecord struct {
	Message   string                 `json:"message"`
	Channel   string                 `json:"channel"`
	Level     int                    `json:"level"`
	LevelName string                 `json:"level_name"`
	DateTime  time.Time              `json:"datetime"`
	Context   map[string]interface{} `json:"context"`
	Extra     map[string]interface{} `json:"extra"`
}

LogRecord represents a log entry following the Symfony monolog structure

type MultiHandler

type MultiHandler struct {
	// contains filtered or unexported fields
}

MultiHandler sends log entries to multiple handlers

func NewMultiHandler

func NewMultiHandler(handlers ...slog.Handler) *MultiHandler

func (*MultiHandler) Enabled

func (h *MultiHandler) Enabled(ctx context.Context, level slog.Level) bool

Enabled reports whether the handler handles records at the given level.

func (*MultiHandler) Handle

func (h *MultiHandler) Handle(ctx context.Context, record slog.Record) error

Handle dispatches the record to all handlers.

func (*MultiHandler) WithAttrs

func (h *MultiHandler) WithAttrs(attrs []slog.Attr) slog.Handler

WithAttrs returns a new Handler whose attributes consist of both the receiver's attributes and the arguments.

func (*MultiHandler) WithGroup

func (h *MultiHandler) WithGroup(name string) slog.Handler

WithGroup returns a new Handler with the given group name.

type MultiWriter

type MultiWriter struct {
	// contains filtered or unexported fields
}

MultiWriter wraps multiple io.Writers into a single Writer

func NewMultiWriter

func NewMultiWriter(writers ...io.Writer) *MultiWriter

NewMultiWriter creates a new MultiWriter with the provided writers

func (*MultiWriter) AddWriter

func (mw *MultiWriter) AddWriter(w io.Writer)

AddWriter adds a new writer to the MultiWriter

func (*MultiWriter) Write

func (mw *MultiWriter) Write(p []byte) (n int, err error)

Write implements the io.Writer interface It writes the same data to all writers and returns the number of bytes written and the first error encountered (if any)

type TextFormatter

type TextFormatter struct {
	TimeFormat string
}

TextFormatter formats logs as human-readable text

func NewTextFormatter

func NewTextFormatter() *TextFormatter

NewTextFormatter creates a new TextFormatter

func (*TextFormatter) Format

func (f *TextFormatter) Format(record LogRecord) ([]byte, error)

Format implements the Formatter interface

type Writer

type Writer interface {
	io.Writer
	Close() error
}

Writer is an interface that wraps the basic Write method.

type WriterCloser

type WriterCloser struct {
	// contains filtered or unexported fields
}

WriterCloser is a simple implementation of Writer that wraps an io.Writer

func NewWriterCloser

func NewWriterCloser(w io.Writer) *WriterCloser

NewWriterCloser creates a new WriterCloser

func (*WriterCloser) Close

func (w *WriterCloser) Close() error

Close implements the io.Closer interface

func (*WriterCloser) Write

func (w *WriterCloser) Write(p []byte) (n int, err error)

Write implements the io.Writer interface

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL