golog

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Aug 4, 2025 License: MIT Imports: 8 Imported by: 0

README

golog

Go Reference Go Report Card

A simple, structured logging library for Go applications with a focus on ease of use and flexibility.

Features

  • Leveled logging: Debug, Info, Warn, Error, Fatal
  • Structured logging: Add key-value pairs to your logs
  • JSON output format: Machine-readable logs
  • Context fields: Include context in all log messages
  • Hooks system: Send logs to multiple destinations
  • NATS integration: Built-in support for NATS messaging system
  • Simple and intuitive API: Inspired by zerolog's fluent API

Installation

go get github.com/pdat-cz/golog

Basic Usage

package main

import (
    "errors"
    "github.com/pdat-cz/golog"
)

func main() {
    // Create a new logger
    log := golog.NewConsoleLogger()
    
    // Set log level
    log.SetLevel(golog.DebugLevel)
    
    // Basic logging
    log.Info().Msg("Application started")
    
    // Structured logging
    log.Info().
        Str("service", "api").
        Int("port", 8080).
        Msg("Server listening")
    
    // With context
    contextLogger := log.With("requestID", "12345")
    contextLogger.Debug().Str("path", "/users").Msg("Request received")
    
    // Error logging
    err := errors.New("database connection failed")
    log.Error().
        Err(err).
        Str("component", "database").
        Msg("Failed to connect to database")
}

Advanced Usage

Custom Configuration

You can configure the logger with custom options:

opts := golog.Options{
    Writer:     os.Stdout,
    Level:      golog.InfoLevel,
    TimeFormat: time.RFC3339,
}
log := golog.New(opts)
Using Hooks

Hooks allow you to send log entries to multiple destinations.

Custom File Hook Example
// Create a custom hook that writes to a file
type FileHook struct {
    file   *os.File
    levels []golog.Level
}

func NewFileHook(filename string, levels ...golog.Level) (*FileHook, error) {
    if len(levels) == 0 {
        levels = []golog.Level{golog.InfoLevel, golog.WarnLevel, golog.ErrorLevel, golog.FatalLevel}
    }

    file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    if err != nil {
        return nil, err
    }

    return &FileHook{
        file:   file,
        levels: levels,
    }, nil
}

func (h *FileHook) Fire(entry map[string]interface{}) error {
    line := fmt.Sprintf("[%s] %s: %s\n",
        entry["time"],
        entry["level"],
        entry["message"],
    )

    _, err := h.file.WriteString(line)
    return err
}

func (h *FileHook) Levels() []golog.Level {
    return h.levels
}

// Usage
log := golog.NewConsoleLogger()
fileHook, err := NewFileHook("logs.txt", golog.InfoLevel, golog.ErrorLevel)
if err != nil {
    log.Fatal().Err(err).Msg("Failed to create file hook")
}
log.AddHook(fileHook)
NATS Hook

The library includes a built-in hook for sending logs to a NATS server. Here's a comprehensive example of how to use golog with NATS:

Basic NATS Integration

Here's a basic example of integrating golog with NATS:

package main

import (
    "github.com/nats-io/nats.go"
    "github.com/pdat-cz/golog"
    "os"
    "time"
)

func main() {
    // Create a logger
    log := golog.NewConsoleLogger()
    
    // Connect to NATS
    nc, err := nats.Connect(nats.DefaultURL)
    if err != nil {
        log.Fatal().Err(err).Msg("Failed to connect to NATS")
    }
    defer nc.Close()
    
    // Create a NATS hook with template-based subject
    // The subject will be constructed as "logs.{level}.{component}"
    natsHook := golog.NewNatsHook(nc, "logs.{level}.{component}")
    log.AddHook(natsHook)
    
    // Log messages with different components
    log.Info().Str("component", "api").Msg("API server started")
    log.Error().Str("component", "database").Msg("Database connection failed")
    
    // Give time for messages to be sent
    time.Sleep(100 * time.Millisecond)
}
Advanced NATS Integration with Subscribers

This example shows how to set up both a publisher (logger) and subscribers to listen for log messages:

package main

import (
    "fmt"
    "github.com/nats-io/nats.go"
    "github.com/pdat-cz/golog"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    // Create a logger
    log := golog.NewConsoleLogger()
    
    // Connect to NATS
    nc, err := nats.Connect(nats.DefaultURL)
    if err != nil {
        log.Fatal().Err(err).Msg("Failed to connect to NATS")
    }
    defer nc.Close()
    
    // Set up subscribers for different log types
    
    // Subscribe to all logs
    nc.Subscribe("logs.>", func(msg *nats.Msg) {
        fmt.Printf("Received log: %s\n", string(msg.Data))
    })
    
    // Subscribe only to error logs
    nc.Subscribe("logs.error.>", func(msg *nats.Msg) {
        fmt.Printf("ERROR LOG: %s\n", string(msg.Data))
    })
    
    // Create a NATS hook with template-based subject
    natsHook := golog.NewNatsHook(nc, "logs.{level}.{component}")
    log.AddHook(natsHook)
    
    // Start logging in a separate goroutine
    go func() {
        for {
            log.Info().Str("component", "api").Msg("API request processed")
            log.Error().Str("component", "database").Msg("Database timeout")
            time.Sleep(2 * time.Second)
        }
    }()
    
    // Wait for interrupt signal
    sigCh := make(chan os.Signal, 1)
    signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
    <-sigCh
    
    fmt.Println("Shutting down...")
}
Subject Patterns and Variable Substitution

The NATS hook supports variable substitution in the subject. Any field in your log entry can be used in the subject pattern:

// Basic subject
natsHook := golog.NewNatsHook(nc, "logs")

// Level-based subject
natsHook := golog.NewNatsHook(nc, "logs.{level}")

// Service and level-based subject
natsHook := golog.NewNatsHook(nc, "logs.{service}.{level}")

// Using with structured logging
log.Info().
    Str("service", "auth").
    Str("user_id", "12345").
    Msg("User authenticated")  // Published to "logs.auth.info"
Filtering Log Levels

You can specify which log levels should trigger the NATS hook:

// Only send error and fatal logs to NATS
natsHook := golog.NewNatsHook(nc, "logs.{level}", golog.ErrorLevel, golog.FatalLevel)
log.AddHook(natsHook)

Log Levels

The following log levels are available, in order of increasing severity:

  • DebugLevel: Debug information for developers
  • InfoLevel: General information about application progress
  • WarnLevel: Warning events that might cause issues
  • ErrorLevel: Error events that might still allow the application to continue
  • FatalLevel: Fatal events that cause the application to exit

Contributing

Contributions are welcome! Here's how you can contribute:

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Commit your changes: git commit -am 'Add some feature'
  4. Push to the branch: git push origin feature/my-feature
  5. Submit a pull request

Please make sure your code follows the Go coding standards and includes appropriate tests.

License

MIT License - see LICENSE file for details.

Acknowledgments

This library is inspired by zerolog and aims to provide a simple, yet powerful logging solution for Go applications.

Documentation

Overview

Example

Example demonstrates basic usage of the logger

package main

import (
	"bytes"
	"fmt"
	"github.com/pdat-cz/golog"
	"strings"
)

func main() {
	// Create a buffer to capture output
	var buf bytes.Buffer

	// Create a logger with the buffer as output
	opts := golog.Options{
		Writer: &buf,
		Level:  golog.InfoLevel,
	}
	log := golog.New(opts)

	// Log messages at different levels
	log.Info().Msg("This is an info message")
	log.Warn().Msg("This is a warning message")
	log.Error().Msg("This is an error message")

	// Verify that messages were logged
	output := buf.String()
	fmt.Println("Info message logged:", strings.Contains(output, "info"))
	fmt.Println("Warn message logged:", strings.Contains(output, "warn"))
	fmt.Println("Error message logged:", strings.Contains(output, "error"))

}
Output:
Info message logged: true
Warn message logged: true
Error message logged: true

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Event

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

Event represents a log event

func (*Event) Any

func (e *Event) Any(key string, val interface{}) *Event

Any adds a field with any value to the event

func (*Event) Bool

func (e *Event) Bool(key string, val bool) *Event

Bool adds a boolean field to the event

Example

ExampleEvent_Bool demonstrates adding boolean fields to log events

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/pdat-cz/golog"
)

func main() {
	// Create a buffer to capture output
	var buf bytes.Buffer

	// Create a logger with the buffer as output
	opts := golog.Options{
		Writer: &buf,
		Level:  golog.InfoLevel,
	}
	log := golog.New(opts)

	// Add boolean fields to the log event
	log.Info().
		Bool("cache_hit", true).
		Bool("authenticated", false).
		Msg("Cache status")

	// Parse the JSON to verify fields
	var entry map[string]interface{}
	json.Unmarshal(buf.Bytes(), &entry)

	// Print the relevant fields
	fmt.Println("Message:", entry["message"])
	fmt.Println("Cache hit:", entry["cache_hit"])
	fmt.Println("Authenticated:", entry["authenticated"])

}
Output:
Message: Cache status
Cache hit: true
Authenticated: false

func (*Event) Duration added in v0.2.0

func (e *Event) Duration(key string, val time.Duration) *Event

Duration adds a duration field to the event

func (*Event) Err

func (e *Event) Err(err error) *Event

Err adds an error field to the event

Example

ExampleEvent_Err demonstrates adding error fields to log events

package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/pdat-cz/golog"
)

func main() {
	// Create a buffer to capture output
	var buf bytes.Buffer

	// Create a logger with the buffer as output
	opts := golog.Options{
		Writer: &buf,
		Level:  golog.InfoLevel,
	}
	log := golog.New(opts)

	// Create an error
	err := errors.New("connection refused")

	// Add the error to the log event
	log.Error().
		Err(err).
		Str("server", "db-01").
		Msg("Database connection failed")

	// Parse the JSON to verify fields
	var entry map[string]interface{}
	json.Unmarshal(buf.Bytes(), &entry)

	// Print the relevant fields
	fmt.Println("Message:", entry["message"])
	fmt.Println("Error:", entry["error"])
	fmt.Println("Server:", entry["server"])

}
Output:
Message: Database connection failed
Error: connection refused
Server: db-01

func (*Event) Hex added in v0.2.0

func (e *Event) Hex(key string, val []byte) *Event

Hex adds a hex-encoded byte slice field to the event

func (*Event) Int

func (e *Event) Int(key string, val int) *Event

Int adds an integer field to the event

Example

ExampleEvent_Int demonstrates adding integer fields to log events

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/pdat-cz/golog"
)

func main() {
	// Create a buffer to capture output
	var buf bytes.Buffer

	// Create a logger with the buffer as output
	opts := golog.Options{
		Writer: &buf,
		Level:  golog.InfoLevel,
	}
	log := golog.New(opts)

	// Add integer fields to the log event
	log.Info().
		Int("status_code", 200).
		Int("response_time_ms", 45).
		Msg("Request completed")

	// Parse the JSON to verify fields
	var entry map[string]interface{}
	json.Unmarshal(buf.Bytes(), &entry)

	// Print the relevant fields
	fmt.Println("Message:", entry["message"])
	fmt.Println("Status code:", int(entry["status_code"].(float64)))
	fmt.Println("Response time (ms):", int(entry["response_time_ms"].(float64)))

}
Output:
Message: Request completed
Status code: 200
Response time (ms): 45

func (*Event) Msg

func (e *Event) Msg(msg string)

Msg sends the event with the given message

func (*Event) Str

func (e *Event) Str(key, val string) *Event

Str adds a string field to the event

Example

ExampleEvent_Str demonstrates adding string fields to log events

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/pdat-cz/golog"
)

func main() {
	// Create a buffer to capture output
	var buf bytes.Buffer

	// Create a logger with the buffer as output
	opts := golog.Options{
		Writer: &buf,
		Level:  golog.InfoLevel,
	}
	log := golog.New(opts)

	// Add string fields to the log event
	log.Info().
		Str("service", "user-service").
		Str("method", "GetUser").
		Msg("User retrieved successfully")

	// Parse the JSON to verify fields
	var entry map[string]interface{}
	json.Unmarshal(buf.Bytes(), &entry)

	// Print the relevant fields
	fmt.Println("Message:", entry["message"])
	fmt.Println("Service:", entry["service"])
	fmt.Println("Method:", entry["method"])

}
Output:
Message: User retrieved successfully
Service: user-service
Method: GetUser

func (*Event) Time added in v0.2.0

func (e *Event) Time(key string, val time.Time) *Event

Time adds a time.Time field to the event

type Hook

type Hook interface {
	// Fire is called when a log event occurs
	Fire(entry map[string]interface{}) error
	// Levels returns the log levels this hook should be triggered for
	Levels() []Level
}

Hook represents a log hook that processes log entries

type Level

type Level int8

Level represents logging level

const (
	// DebugLevel defines debug log level
	DebugLevel Level = iota
	// InfoLevel defines info log level
	InfoLevel
	// WarnLevel defines warn log level
	WarnLevel
	// ErrorLevel defines error log level
	ErrorLevel
	// FatalLevel defines fatal log level
	FatalLevel
)

func ParseLevel

func ParseLevel(levelStr string) Level

ParseLevel parses a level string into a Level value

Example

ExampleParseLevel demonstrates parsing a level string into a Level value

package main

import (
	"fmt"
	"github.com/pdat-cz/golog"
)

func main() {
	// Parse level strings
	debugLevel := golog.ParseLevel("debug")
	infoLevel := golog.ParseLevel("info")
	warnLevel := golog.ParseLevel("warn")
	errorLevel := golog.ParseLevel("error")
	fatalLevel := golog.ParseLevel("fatal")
	unknownLevel := golog.ParseLevel("unknown")

	// Print the levels
	fmt.Println("Debug level:", debugLevel)
	fmt.Println("Info level:", infoLevel)
	fmt.Println("Warn level:", warnLevel)
	fmt.Println("Error level:", errorLevel)
	fmt.Println("Fatal level:", fatalLevel)
	fmt.Println("Unknown level (defaults to info):", unknownLevel)

}
Output:
Debug level: debug
Info level: info
Warn level: warn
Error level: error
Fatal level: fatal
Unknown level (defaults to info): info

func (Level) String

func (l Level) String() string

String returns the string representation of the log level

type Logger

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

Logger represents the core logger structure

func New

func New(opts Options) *Logger

New creates a new logger with the given options

Example

ExampleNew demonstrates creating a logger with custom options

package main

import (
	"bytes"
	"fmt"
	"github.com/pdat-cz/golog"
	"strings"
)

func main() {
	// Create a buffer to capture output
	var buf bytes.Buffer

	// Create custom options
	opts := golog.Options{
		Writer: &buf,
		Level:  golog.InfoLevel,
	}

	// Create a new logger with custom options
	log := golog.New(opts)

	// Log messages
	log.Debug().Msg("This debug message won't be logged due to level setting")
	log.Info().Msg("This info message will be logged")

	// Verify the debug message was filtered out and info was logged
	output := buf.String()
	fmt.Println("Debug message logged:", strings.Contains(output, "debug"))
	fmt.Println("Info message logged:", strings.Contains(output, "info"))

}
Output:
Debug message logged: false
Info message logged: true

func NewConsoleLogger

func NewConsoleLogger() *Logger

NewConsoleLogger creates a new logger with console output

func (*Logger) AddHook

func (l *Logger) AddHook(hook Hook) *Logger

AddHook adds a hook to the logger

Example

ExampleLogger_AddHook demonstrates adding a hook to the logger

package main

import (
	"bytes"
	"fmt"
	"github.com/pdat-cz/golog"
)

// PrintHook is a simple hook that captures log entries
type PrintHook struct {
	levels    []golog.Level
	lastEntry map[string]interface{}
}

// Fire is called when a log event occurs
func (h *PrintHook) Fire(entry map[string]interface{}) error {
	h.lastEntry = entry
	return nil
}

// Levels returns the log levels this hook should be triggered for
func (h *PrintHook) Levels() []golog.Level {
	return h.levels
}

func main() {
	// Create a buffer to capture output
	var buf bytes.Buffer

	// Create a logger with the buffer as output
	opts := golog.Options{
		Writer: &buf,
		Level:  golog.InfoLevel,
	}
	log := golog.New(opts)

	// Create and add a hook that only fires for error and fatal levels
	hook := &PrintHook{
		levels: []golog.Level{golog.ErrorLevel, golog.FatalLevel},
	}
	log.AddHook(hook)

	// This won't trigger the hook
	log.Info().Msg("This is an info message")

	// This will trigger the hook
	log.Error().Msg("This is an error message")

	// Verify the hook was triggered for error but not info
	fmt.Println("Hook triggered for error level:", hook.lastEntry != nil)
	if hook.lastEntry != nil {
		fmt.Println("Hook received message:", hook.lastEntry["message"])
	}

}
Output:
Hook triggered for error level: true
Hook received message: This is an error message

func (*Logger) Debug

func (l *Logger) Debug() *Event

Debug returns a debug level event logger

func (*Logger) Error

func (l *Logger) Error() *Event

Error returns an error level event logger

func (*Logger) Fatal

func (l *Logger) Fatal() *Event

Fatal returns a fatal level event logger

func (*Logger) GetLevel

func (l *Logger) GetLevel() Level

GetLevel returns the current logger level

func (*Logger) Info

func (l *Logger) Info() *Event

Info returns an info level event logger

func (*Logger) RemoveHook

func (l *Logger) RemoveHook(hook Hook) *Logger

RemoveHook removes a hook from the logger

func (*Logger) SetLevel

func (l *Logger) SetLevel(level Level)

SetLevel sets the logger's minimum level

func (*Logger) Warn

func (l *Logger) Warn() *Event

Warn returns a warn level event logger

func (*Logger) With

func (l *Logger) With(key string, value interface{}) *Logger

With returns a new logger with the given field added to its context

Example

ExampleLogger_With demonstrates using contextual logging

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/pdat-cz/golog"
)

func main() {
	// Create a buffer to capture output
	var buf bytes.Buffer

	// Create a logger with the buffer as output
	opts := golog.Options{
		Writer: &buf,
		Level:  golog.InfoLevel,
	}
	log := golog.New(opts)

	// Create a logger with context fields
	requestLogger := log.With("request_id", "req-123456").With("user_id", "user-789")

	// Log a message with the context fields
	requestLogger.Info().Msg("Processing request")

	// Parse the JSON to verify fields
	var entry map[string]interface{}
	json.Unmarshal(buf.Bytes(), &entry)

	// Print the relevant fields
	fmt.Println("Message:", entry["message"])
	fmt.Println("Request ID:", entry["request_id"])
	fmt.Println("User ID:", entry["user_id"])

}
Output:
Message: Processing request
Request ID: req-123456
User ID: user-789

type NatsConn

type NatsConn interface {
	Publish(subject string, data []byte) error
}

NatsConn is an interface that defines the methods needed from a NATS connection

type NatsHook

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

NatsHook sends log entries to NATS

func NewNatsHook

func NewNatsHook(conn NatsConn, subject string, levels ...Level) *NatsHook

NewNatsHook creates a new NATS hook.

func (*NatsHook) Fire

func (h *NatsHook) Fire(entry map[string]interface{}) error

Fire sends the log entry to NATS

func (*NatsHook) Levels

func (h *NatsHook) Levels() []Level

Levels returns the log levels this hook should be triggered for

type Options

type Options struct {
	Writer     io.Writer
	Level      Level
	TimeFormat string
}

Options for configuring a new logger

func DefaultOptions

func DefaultOptions() Options

DefaultOptions returns the default logger options

Jump to

Keyboard shortcuts

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