logger

package
v0.19.0 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2025 License: MIT Imports: 14 Imported by: 0

README

Contributions Welcome Release

Logger Package

The logger package provides a structured, context-aware logging solution for Go applications. It is built on top of the logrus library and is designed to facilitate easy integration with your projects, offering features like:

  • JSON-formatted logs suitable for production environments.
  • Support for multiple log levels (DEBUG, INFO, WARN, ERROR, FATAL).
  • Context propagation to include tracing information (e.g., trace_id, span_id).
  • Customizable log formatters and output destinations.
  • Integration with web frameworks like Gin.

Installation

go get github.com/kittipat1413/go-common/framework/logger

Documentation

Go Reference

For detailed API documentation, examples, and usage patterns, visit the Go Package Documentation.

Features

  • Structured Logging: Outputs logs in JSON format, making them easy to parse and analyze.
  • Context-Aware: Supports logging with context.Context, allowing you to include tracing information automatically.
  • Customizable Formatter: Use the default StructuredJSONFormatter or provide your own formatter to customize the log output.
  • Environment and Service Name: Optionally include environment and service name in your logs for better traceability.
  • No-Op Logger: Provides a no-operation logger for testing purposes, which discards all log messages.

Usage

Creating a Logger

You can create a logger using the NewLogger function, providing a Config struct to customize its behavior:

package main

import (
    "context"
    "time"
    "github.com/kittipat1413/go-common/framework/logger"
)

func main() {
    logConfig := logger.Config{
        Level: logger.INFO,
        Formatter: &logger.StructuredJSONFormatter{
            TimestampFormat: time.RFC3339,
            PrettyPrint:     false,
        },
        Environment: "production",
        ServiceName: "my-service",
    }
    
    log, err := logger.NewLogger(logConfig)
    if err != nil {
        panic(err)
    }

    log.Info(context.Background(), "Logger initialized successfully", nil)
}

Alternatively, you can use the default logger:

log := logger.NewDefaultLogger()
  • The NewDefaultLogger returns a logger instance with the default or user-defined configuration.
  • If SetDefaultLoggerConfig has been called, it uses the user-defined configuration; otherwise, it uses the package's default configuration.
Updating the Default Logger Configuration

You can update the default logger configuration using SetDefaultLoggerConfig:

err := logger.SetDefaultLoggerConfig(logConfig)
if err != nil {
    // Handle error
    panic(err)
}

Configuration

The Config struct allows you to customize the logger:

type Config struct {
	// Level determines the minimum log level that will be processed by the logger.
	// Logs with a level lower than this will be ignored.
	Level LogLevel
	// Formatter is an optional field for specifying a custom logrus formatter.
	// If not provided, the logger will use the StructuredJSONFormatter by default.
	Formatter logrus.Formatter
	// Environment is an optional field for specifying the running environment (e.g., "production", "staging").
	// This field is used for adding environment-specific fields to logs.
	Environment string
	// ServiceName is an optional field for specifying the name of the service.
	// This field is used for adding service-specific fields to logs.
	ServiceName string
	// Output is an optional field for specifying the output destination for logs (e.g., os.Stdout, file).
	// If not provided, logs will be written to stdout by default.
	Output io.Writer
}

Logging Messages

The logger provides methods for different log levels:

type Logger interface {
    WithFields(fields Fields) Logger
	Debug(ctx context.Context, msg string, fields Fields)
	Info(ctx context.Context, msg string, fields Fields)
	Warn(ctx context.Context, msg string, fields Fields)
	Error(ctx context.Context, msg string, err error, fields Fields)
	Fatal(ctx context.Context, msg string, err error, fields Fields)
}

Example:

ctx := context.Background()
fields := logger.Fields{"user_id": 12345}

log.Info(ctx, "User logged in", fields)
Including Errors

For error and fatal logs, you can include an error object:

err := errors.New("something went wrong")
log.Error(ctx, "Failed to process request", err, fields)
Adding Persistent Fields

You can add persistent fields to the logger using WithFields, which returns a new logger instance:

logWithFields := log.WithFields(logger.Fields{
    "component": "authentication",
})
logWithFields.Info(ctx, "Authentication successful", nil)

You can find a complete working example in the repository under framework/logger/example.


StructuredJSONFormatter

The StructuredJSONFormatter is a custom logrus.Formatter designed to include contextual information in logs. It outputs logs in JSON format with a standardized structure, making it suitable for log aggregation and analysis tools.

Features
  • Timestamp: Includes a timestamp formatted according to TimestampFormat.
  • Severity: The log level (debug, info, warning, error, fatal).
  • Message: The log message.
  • Error Handling: Automatically includes error messages if an error is provided.
  • Tracing Information: Extracts trace_id and span_id from the context if available (e.g., when using OpenTelemetry).
  • Caller Information: Adds information about the function, file, and line number where the log was generated.
  • Stack Trace: Includes a stack trace for logs at the error level or higher.
  • Custom Fields: Supports additional fields provided via logger.Fields.
  • Field Key Customization: Allows custom formatting of field keys via FieldKeyFormatter.
Configuration

You can customize the StructuredJSONFormatter when initializing the logger:

import (
    "github.com/kittipat1413/go-common/framework/logger"
    "time"
)

formatter := &logger.StructuredJSONFormatter{
    TimestampFormat: time.RFC3339, // Customize timestamp format
    PrettyPrint:     true,         // Indent JSON output
    FieldKeyFormatter: func(key string) string {
        // Customize field keys
        switch key {
        case logger.DefaultEnvironmentKey:
            return "env"
        case logger.DefaultServiceNameKey:
            return "service"
        case logger.DefaultSJsonFmtSeverityKey:
            return "level"
        case logger.DefaultSJsonFmtMessageKey:
            return "msg"
        default:
            return key
        }
    },
}

logConfig := logger.Config{
    Level:     logger.INFO,
    Formatter: formatter,
}

Example Log Entry (default FieldKeyFormatter)

{
  "caller": {
    "file": "/go-common/framework/logger/example/gin_with_logger/main.go:99",
    "function": "main.handlerWithLogger"
  },
  "environment": "development",
  "message": "Handled HTTP request",
  "request": {
    "method": "GET",
    "url": "/log"
  },
  "request_id": "afa241ba-cb59-4053-a1f5-d82e6193790c",
  "response_time": 0.000026542,
  "service_name": "logger-example",
  "severity": "info",
  "span_id": "94e92f0e1b8532e6",
  "status": 200,
  "timestamp": "2024-10-20T02:01:57+07:00",
  "trace_id": "f891fd44c417fc7efa297e6a18ddf0cf"
}
{
  "caller": {
    "file": "/go-common/framework/logger/example/gin_with_logger/main.go:78",
    "function": "main.logMessages"
  },
  "environment": "development",
  "error": "example error",
  "example_field": "error_value",
  "message": "This is an error message",
  "service_name": "logger-example",
  "severity": "error",
  "stack_trace": "goroutine 1 [running]:\ngithub.com/kittipat1413/go-common/framework/logger.getStackTrace()\n\t/Users/kittipat/go-github-repo/go-common/framework/logger/structured_json_formatter.go:181 .....\n",
  "timestamp": "2024-10-20T02:01:55+07:00"
}
Tracing Integration

The StructuredJSONFormatter can extract tracing information (trace_id and span_id) from the context.Context if you are using a tracing system like OpenTelemetry. Ensure that spans are started and the context is propagated correctly.

Caller and Stack Trace
  • Caller Information: The formatter includes the function name, file, and line number where the log was generated, aiding in debugging.
  • Stack Trace: For logs at the error level or higher, a stack trace is included. This can be useful for diagnosing issues in production.

Custom Formatter

If you need a different format or additional customization, you can implement your own formatter by satisfying the logrus.Formatter interface and providing it to the logger configuration.

type MyCustomFormatter struct {
    // Custom fields...
}

func (f *MyCustomFormatter) Format(entry *logrus.Entry) ([]byte, error) {
    // Custom formatting logic...
}

Usage:

logConfig := logger.Config{
    Formatter: &MyCustomFormatter{},
}

No-Op Logger

For testing purposes, you can use the no-operation logger, which implements the Logger interface but discards all log messages:

log := logger.NewNoopLogger()

This can be useful to avoid cluttering test output with logs or when you need a logger that does nothing.

Example

You can find a complete working example in the repository under framework/logger/example.

Documentation

Index

Constants

View Source
const (
	DefaultEnvironmentKey = "environment"  // Default key for the environment field in logs (e.g., production, staging)
	DefaultServiceNameKey = "service_name" // Default key for the service name field in logs
	DefaultErrorKey       = "error"        // Default key for the error field in logs
)

Default field key constants for structured logging. These keys ensure consistent field naming across all log entries and enable proper log parsing and filtering in log aggregation systems.

View Source
const (
	DefaultSJsonFmtTimestampKey  = "timestamp"   // Default key for the timestamp field in logs
	DefaultSJsonFmtSeverityKey   = "severity"    // Default key for the severity field in logs
	DefaultSJsonFmtMessageKey    = "message"     // Default key for the message field in logs
	DefaultSJsonFmtErrorKey      = "error"       // Default key for the error field in logs
	DefaultSJsonFmtTraceIDKey    = "trace_id"    // Default key for the trace_id field in logs
	DefaultSJsonFmtSpanIDKey     = "span_id"     // Default key for the span_id field in logs
	DefaultSJsonFmtCallerKey     = "caller"      // Default key for the caller field in logs
	DefaultSJsonFmtCallerFuncKey = "function"    // Default key for the function field in logs
	DefaultSJsonFmtCallerFileKey = "file"        // Default key for the file field in logs
	DefaultSJsonFmtStackTraceKey = "stack_trace" // Default key for the stack_trace field in logs
)

Default JSON field keys for structured logging output. These constants ensure consistent field naming across all log entries.

Variables

View Source
var (
	// ErrInvalidLogLevel is returned when an invalid log level is specified.
	ErrInvalidLogLevel = errors.New("invalid log level")
)

Package-level errors

Functions

func NewContext

func NewContext(ctx context.Context, logger Logger) context.Context

NewContext returns a new context that carries the provided logger. The logger can later be retrieved using FromContext() for consistent logging throughout the request lifecycle.

Parameters:

  • ctx: Parent context
  • logger: Logger instance to store in the new context

Returns:

  • context.Context: New context containing the logger

Example:

logger := logger.NewLoggerWithConfig(config)
ctx = logger.NewContext(ctx, logger)

func NewRequest

func NewRequest(r *http.Request, logger Logger) *http.Request

NewRequest returns a new HTTP request that carries the provided logger in its context. Useful for middleware that wants to inject a configured logger into incoming requests for downstream handlers to use.

Parameters:

  • r: Original HTTP request
  • logger: Logger instance to attach to the request

Returns:

  • *http.Request: New request with logger attached to its context

Example:

// In middleware
func LoggerMiddleware(logger Logger) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            r = logger.NewRequest(r, logger)
            next.ServeHTTP(w, r)
        })
    }
}

func NoopFieldKeyFormatter

func NoopFieldKeyFormatter(defaultKey string) string

NoopFieldKeyFormatter is the default field key formatter that performs no transformation. Returns the original key unchanged, suitable when no key customization is needed.

func SetDefaultLoggerConfig

func SetDefaultLoggerConfig(config Config) error

SetDefaultLoggerConfig sets a custom configuration for the default logger. Validates the configuration by attempting to create a logger with it. If validation fails, the existing default configuration remains unchanged.

Parameters:

  • config: New default configuration to apply

Returns:

  • error: ErrInvalidLogLevel or other configuration errors

Example:

config := Config{Level: DEBUG, Output: os.Stderr}
err := logger.SetDefaultLoggerConfig(config)

Types

type Config

type Config struct {
	// Level determines the minimum log level that will be processed by the logger.
	// Logs with a level lower than this will be ignored.
	Level LogLevel

	// Formatter is an optional field for specifying a custom logrus formatter.
	// If not provided, the logger will use the StructuredJSONFormatter by default.
	Formatter logrus.Formatter

	// Environment is an optional field for specifying the running environment (e.g., "production", "staging").
	// This field is used for adding environment-specific fields to logs.
	Environment string

	// ServiceName is an optional field for specifying the name of the service.
	// This field is used for adding service-specific fields to logs.
	ServiceName string

	// Output is an optional field for specifying the output destination for logs (e.g., os.Stdout, file).
	// If not provided, logs will be written to stdout by default.
	Output io.Writer
}

Config holds configuration parameters for logger creation. Provides options for level, formatting, output destination, and metadata.

type FieldKeyFormatter

type FieldKeyFormatter func(key string) string

FieldKeyFormatter defines a function type for customizing JSON field keys. Enables consistent field naming conventions across different logging contexts.

Parameters:

  • key: Original field key

Returns:

  • string: Transformed field key

Example:

customFieldKeyFormatter := func(key string) string {
	switch key {
	case DefaultEnvironmentKey:
		return "env"
	case DefaultServiceNameKey:
		return "service"
	case DefaultSJsonFmtSeverityKey:
		return "level"
	default:
		return key
	}
}

type Fields

type Fields map[string]interface{}

Fields represents structured key-value pairs for rich logging context. Used to add metadata, request IDs, user information, or other contextual data.

type LogLevel

type LogLevel string

LogLevel represents the logging level as a string type for easy configuration.

const (
	DEBUG LogLevel = "debug" // Verbose output for debugging (lowest priority)
	INFO  LogLevel = "info"  // General informational messages
	WARN  LogLevel = "warn"  // Warning messages for potentially harmful situations
	ERROR LogLevel = "error" // Error messages for failure conditions
	FATAL LogLevel = "fatal" // Critical errors that may cause application termination
)

Log level constants define the available logging levels. These levels determine which messages are logged based on severity.

func (LogLevel) IsValid

func (l LogLevel) IsValid() bool

IsValid checks if the LogLevel is one of the defined valid levels. Useful for configuration validation and input sanitization.

func (LogLevel) ToLogrusLevel

func (l LogLevel) ToLogrusLevel() logrus.Level

ToLogrusLevel converts the custom LogLevel to the corresponding logrus.Level. Returns logrus.InfoLevel as a safe default for unknown/invalid levels.

type Logger

type Logger interface {
	// WithFields returns a new logger instance with additional structured fields.
	// Fields are merged with existing fields, with new fields taking precedence.
	WithFields(fields Fields) Logger

	// Debug logs debug-level messages for detailed troubleshooting.
	Debug(ctx context.Context, msg string, fields Fields)

	// Info logs informational messages about normal application flow.
	Info(ctx context.Context, msg string, fields Fields)

	// Warn logs warning messages for potentially harmful situations.
	Warn(ctx context.Context, msg string, fields Fields)

	// Error logs error messages for failure conditions.
	Error(ctx context.Context, msg string, err error, fields Fields)

	// Fatal logs critical errors and terminates the application.
	Fatal(ctx context.Context, msg string, err error, fields Fields)
}

Logger defines the interface for structured logging with context support.

func FromContext

func FromContext(ctx context.Context) Logger

FromContext retrieves a Logger from the context. Returns a default logger if no logger is found in the context, ensuring that logging is always available even without explicit setup.

Parameters:

  • ctx: Context that may contain a logger instance

Returns:

  • Logger: Logger from context or default logger if not found

Example:

logger := logger.FromContext(ctx)
logger.Info(ctx, "Processing request", nil)

func FromRequest

func FromRequest(r *http.Request) Logger

FromRequest retrieves a Logger from the HTTP request's context. Convenience function for HTTP handlers that need access to the logger without manually extracting it from the request context.

Parameters:

  • r: HTTP request containing context with potential logger

Returns:

  • Logger: Logger from request context or default logger if not found

Example:

func handleRequest(w http.ResponseWriter, r *http.Request) {
    logger := logger.FromRequest(r)
    logger.Info(r.Context(), "Handling request", map[string]interface{}{
        "method": r.Method,
        "path":   r.URL.Path,
    })
}

func NewDefaultLogger

func NewDefaultLogger() Logger

NewDefaultLogger returns a logger instance with the current default configuration. Uses user-defined configuration if SetDefaultLoggerConfig was called, otherwise uses package defaults.

Default JSON output includes:

  • timestamp: RFC3339 formatted time
  • severity: log level (debug, info, warn, error, fatal)
  • message: log message text
  • error: error details for Error/Fatal levels
  • trace_id: distributed trace correlation (if available in context)
  • span_id: span correlation within traces (if available)
  • caller: source location (function, file, line)
  • stack_trace: call stack for Error/Fatal levels

Returns:

  • Logger: Configured logger instance ready for use

Example:

logger := logger.NewDefaultLogger()
logger.Info(ctx, "Application started", nil)

func NewLogger

func NewLogger(config Config) (Logger, error)

NewLogger creates a new logger instance with the provided configuration. Validates configuration and sets up logrus with specified formatting and output.

Parameters:

  • config: Logger configuration including level, formatter, and metadata

Returns:

  • Logger: Configured logger instance
  • error: ErrInvalidLogLevel if log level is invalid

Example:

config := Config{
    Level:       DEBUG,
    Environment: "development",
    ServiceName: "user-service",
}
logger, err := logger.NewLogger(config)

func NewNoopLogger

func NewNoopLogger() Logger

NewNoopLogger returns a no-operation logger that silently discards all messages. Useful for testing scenarios or when logging needs to be disabled entirely.

Returns:

  • Logger: No-op logger that ignores all logging calls

Example:

logger := logger.NewNoopLogger()
logger.Info(ctx, "This message is discarded", nil) // No output

type StructuredJSONFormatter

type StructuredJSONFormatter struct {
	// TimestampFormat specifies the time format for log timestamps.
	// Defaults to time.RFC3339 if not specified.
	TimestampFormat string

	// PrettyPrint enables JSON indentation for human-readable output.
	PrettyPrint bool

	// SkipPackages contains additional package prefixes to exclude from caller detection.
	// Combined with defaultSJsonFmtSkipPackages for comprehensive filtering.
	SkipPackages []string

	// FieldKeyFormatter allows customization of JSON field keys.
	// If nil, NoopFieldKeyFormatter is used (no transformation).
	FieldKeyFormatter FieldKeyFormatter
}

StructuredJSONFormatter is a custom logrus formatter for structured JSON logs. Produces consistent JSON output with trace correlation, caller information, and configurable field key formatting suitable for log aggregation systems.

JSON output includes:

  • timestamp: RFC3339 formatted time
  • severity: log level (debug, info, warn, error, fatal)
  • message: log message text
  • error: error details for Error/Fatal levels
  • trace_id: distributed trace correlation (if available in context)
  • span_id: span correlation within traces (if available)
  • caller: source location (function, file, line)
  • stack_trace: call stack for Error/Fatal levels

Example JSON output:

{
  "timestamp": "2023-01-01T12:00:00Z",
  "severity": "info",
  "message": "User logged in",
  "user_id": 123,
  "trace_id": "abc123...",
  "span_id": "def456...",
  "caller": {
    "function": "main.handleLogin",
    "file": "/app/handlers.go:45"
  },
  "stack_trace": "..." // Only for error/fatal levels
}

func (*StructuredJSONFormatter) Format

func (f *StructuredJSONFormatter) Format(entry *logrus.Entry) ([]byte, error)

Format implements the logrus.Formatter interface for JSON log formatting. Produces structured JSON output with all configured fields including timestamps, trace IDs, caller information, and custom fields.

Parameters:

  • entry: logrus.Entry containing log data and context

Returns:

  • []byte: Formatted JSON log entry with newline
  • error: JSON marshaling error if formatting fails

Example Output:

{"timestamp":"2023-01-01T12:00:00Z","severity":"info","message":"test","trace_id":"abc123"}

Directories

Path Synopsis
example
gin_with_logger command
Package logger_mocks is a generated GoMock package.
Package logger_mocks is a generated GoMock package.

Jump to

Keyboard shortcuts

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